<commit_before># -*- coding: utf-8 -*-
##############################################################################
#
#    OpenERP, Open Source Management Solution
#    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#    Copyright (C) 2012-2012 Camptocamp Austria (<http://www.camptocamp.at>)
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU Affero General Public License as
#    published by the Free Software Foundation, either version 3 of the
#    License, or (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU Affero General Public License for more details.
#
#    You should have received a copy of the GNU Affero General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from osv import fields, osv
from tools.translate import _
import time
import netsvc

class account_invoice(osv.osv):
    _inherit = "account.invoice"

#   def _get_state(self, cr, uid, ids, context=None):

#       res = list(super(account_invoice, self)._columns['state'].selection)
#       res.append(('draft_reset','Reset to draft'))

#       return res

#   _columns ={
# FIXME the _get_state raises error
#        'state': fields.selection(selection=_get_state, string='State', required=True),
#       'state': fields.selection([
#           ('draft','Draft'),
#           ('draft_reset','Reset to Draft'),
#           ('proforma','Pro-forma'),
#           ('proforma2','Pro-forma'),
#           ('open','Open'),
#           ('paid','Paid'),
#           ('cancel','Cancelled')
#           ],'State', select=True, readonly=True,
#           help=' * The \'Draft\' state is used when a user is encoding a new and unconfirmed Invoice. \
#           \n* The \'Pro-forma\' when invoice is in Pro-forma state,invoice does not have an invoice number. \
#           \n* The \'Open\' state is used when user create invoice,a invoice number is generated.Its in open state till user does not pay invoice. \
#           \n* The \'Paid\' state is set automatically when the invoice is paid. Its related journal entries may or may not be reconciled. \
#           \n* The \'Cancelled\' state is used when user cancel invoice.'),

#   }

#    def _auto_init(self, cr, context=None):
#           cr.execute("""update wkf_instance
#                         set state = 'active'
#                       where state = 'complete'
#                         and res_type = 'account.invoice'
#""")

    def action_reopen(self, cr, uid, ids, *args):
        context = {} # TODO: Use context from arguments
        attachment_obj = self.pool.get('ir.attachment')
        invoice_obj = self.pool.get('account.invoice')
        account_move_obj = self.pool.get('account.move')
        account_move_line_obj = self.pool.get('account.move.line')
        report_xml_obj = self.pool.get('ir.actions.report.xml')
        invoices = self.read(cr, uid, ids, ['move_id', 'payment_ids', 'id'])
        wf_service = netsvc.LocalService("workflow")

        move_ids = [] # ones that we will need to update
        now = ' ' + _('Invalid') + time.strftime(' [%Y%m%d %H%M%S]')
        for i in invoices:
            if i['move_id']:
                move_ids.append(i['move_id'][0])
                for move in account_move_obj.browse(cr, uid, move_ids):
                    if not move.journal_id.reopen_posted:
                        raise osv.except_osv(_('Error !'), _('You can not reopen invoice of this journal [%s]! You need to need to set "Allow Update Posted Entries" first')%(move.journal_id.name))

            if i['payment_ids']:
                pay_ids = account_move_line_obj.browse(cr, uid, i['payment_ids'])
                for move_line in pay_ids:
                    if move_line.reconcile_id or (move_line.reconcile_partial_id and move_line.reconcile_partial_id.line_partial_ids):
                        raise osv.except_osv(_('Error !'), _('You can not reopen an invoice which is partially paid! You need to unreconcile related payment entries first!'))
	    self.write(cr, uid, i['id'], {'state':'draft'})
            wf_service.trg_delete(uid, 'account.invoice', i['id'], cr)
            wf_service.trg_create(uid, 'account.invoice', i['id'], cr)

        # rename attachments (reports)
        # for some reason datas_fname has .pdf.pdf extension
        for inv in invoice_obj.browse(cr, uid, ids):
            report_ids = report_xml_obj.search(cr, uid, [('model','=', 'account.invoice'), ('attachment','!=', False)])
            for report in report_xml_obj.browse(cr, uid, report_ids):
              if report.attachment:
                aname = report.attachment.replace('object','inv')
                if eval(aname):
                  aname = eval(aname)+'.pdf'
                  attachment_ids = attachment_obj.search(cr, uid, [('res_model','=','account.invoice'),('datas_fname', '=', aname),('res_id','=',inv.id)])
                  for a in attachment_obj.browse(cr, uid, attachment_ids):
                    vals = {
                        'name': a.name.replace('.pdf', now+'.pdf'),
                        'datas_fname': a.datas_fname.replace('.pdf.pdf', now+'.pdf.pdf')
                           }
                    attachment_obj.write(cr, uid, a.id, vals)

        # unset set the invoices move_id
        self.write(cr, uid, ids, {'move_id': False})

        if move_ids:

            for move in account_move_obj.browse(cr , uid, move_ids):
                name = move.name + now
                account_move_obj.write(cr, uid, [move.id], {'name':name})
                move_copy_id = account_move_obj.copy(cr, uid, move.id)
                name = name + '*'
                cr.execute("""update account_move_line
                                 set debit=credit, credit=debit
                               where move_id = %s;""" % (move_copy_id))
                account_move_obj.write(cr, uid, [move_copy_id], {'name':name})
                account_move_obj.button_validate(cr, uid, [move_copy_id], context=None)
                # reconcile
                r_id = self.pool.get('account.move.reconcile').create(cr, uid, {'type': 'auto'})
                line_ids = account_move_line_obj.search(cr, uid, [('move_id','in',[move_copy_id, move.id])])
                lines_to_reconile = []
                for ltr in account_move_line_obj.browse(cr, uid, line_ids):
                    if ltr.account_id.id in (ltr.partner_id.property_account_payable.id, ltr.partner_id.property_account_receivable.id):
                         lines_to_reconile.append(ltr.id)
                account_move_line_obj.write(cr, uid, lines_to_reconile, {'reconcile_id':r_id})

        self._log_event(cr, uid, ids, -1.0, 'Reopened Invoice')

        return True

    #def action_move_create(self, cr, uid, ids, context=None):
    #    move_obj = self.pool.get('account.move')
    #    move_ids = [] # ones that we will need to update
    #    for inv in self.browse(cr, uid, ids, context=context):
    #        if inv.move_id:
    #            move_ids.append(inv.move_id.id)
    #        else:
    #            super(account_invoice, self).action_move_create(cr, uid, ids, context)
    #    if move_ids:
    #        move_obj.write(cr, uid, move_ids, {'state':'posted'})

    def action_number(self, cr, uid, ids, context=None):
        for inv in self.browse(cr, uid, ids, context=context):
            if not inv.internal_number:
                super(account_invoice, self).action_number(cr, uid, ids, context)
        return True

account_invoice()
<commit_msg>[FIX] reopen invoice just works if journal skip draft is not enabled<commit_after># -*- coding: utf-8 -*-
##############################################################################
#
#    OpenERP, Open Source Management Solution
#    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#    Copyright (C) 2012-2012 Camptocamp Austria (<http://www.camptocamp.at>)
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU Affero General Public License as
#    published by the Free Software Foundation, either version 3 of the
#    License, or (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU Affero General Public License for more details.
#
#    You should have received a copy of the GNU Affero General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from osv import fields, osv
from tools.translate import _
import time
import netsvc
import logging

class account_invoice(osv.osv):
    _inherit = "account.invoice"

#   def _get_state(self, cr, uid, ids, context=None):

#       res = list(super(account_invoice, self)._columns['state'].selection)
#       res.append(('draft_reset','Reset to draft'))

#       return res

#   _columns ={
# FIXME the _get_state raises error
#        'state': fields.selection(selection=_get_state, string='State', required=True),
#       'state': fields.selection([
#           ('draft','Draft'),
#           ('draft_reset','Reset to Draft'),
#           ('proforma','Pro-forma'),
#           ('proforma2','Pro-forma'),
#           ('open','Open'),
#           ('paid','Paid'),
#           ('cancel','Cancelled')
#           ],'State', select=True, readonly=True,
#           help=' * The \'Draft\' state is used when a user is encoding a new and unconfirmed Invoice. \
#           \n* The \'Pro-forma\' when invoice is in Pro-forma state,invoice does not have an invoice number. \
#           \n* The \'Open\' state is used when user create invoice,a invoice number is generated.Its in open state till user does not pay invoice. \
#           \n* The \'Paid\' state is set automatically when the invoice is paid. Its related journal entries may or may not be reconciled. \
#           \n* The \'Cancelled\' state is used when user cancel invoice.'),

#   }

#    def _auto_init(self, cr, context=None):
#           cr.execute("""update wkf_instance
#                         set state = 'active'
#                       where state = 'complete'
#                         and res_type = 'account.invoice'
#""")

    def action_reopen(self, cr, uid, ids, *args):
	_logger = logging.getLogger(__name__)

        context = {} # TODO: Use context from arguments
        attachment_obj = self.pool.get('ir.attachment')
        invoice_obj = self.pool.get('account.invoice')
        account_move_obj = self.pool.get('account.move')
        account_move_line_obj = self.pool.get('account.move.line')
        report_xml_obj = self.pool.get('ir.actions.report.xml')
        invoices = self.read(cr, uid, ids, ['move_id', 'payment_ids', 'id'])
	_logger.debug('FGF reopen invoices %s' % (invoices))

        wf_service = netsvc.LocalService("workflow")

        move_ids = [] # ones that we will need to update
        now = ' ' + _('Invalid') + time.strftime(' [%Y%m%d %H%M%S]')
        for i in invoices:
            if i['move_id']:
                move_ids.append(i['move_id'][0])
	        _logger.debug('FGF reopen move_ids %s' % (move_ids))
                for move in account_move_obj.browse(cr, uid, move_ids):
                    if not move.journal_id.reopen_posted:
                        raise osv.except_osv(_('Error !'), _('You can not reopen invoice of this journal [%s]! You need to need to set "Allow Update Posted Entries" first')%(move.journal_id.name))

            if i['payment_ids']:
                pay_ids = account_move_line_obj.browse(cr, uid, i['payment_ids'])
                for move_line in pay_ids:
                    if move_line.reconcile_id or (move_line.reconcile_partial_id and move_line.reconcile_partial_id.line_partial_ids):
                        raise osv.except_osv(_('Error !'), _('You can not reopen an invoice which is partially paid! You need to unreconcile related payment entries first!'))
	    self.write(cr, uid, i['id'], {'state':'draft'})
            wf_service.trg_delete(uid, 'account.invoice', i['id'], cr)
            wf_service.trg_create(uid, 'account.invoice', i['id'], cr)

        # rename attachments (reports)
        # for some reason datas_fname has .pdf.pdf extension
        for inv in invoice_obj.browse(cr, uid, ids):
            report_ids = report_xml_obj.search(cr, uid, [('model','=', 'account.invoice'), ('attachment','!=', False)])
            for report in report_xml_obj.browse(cr, uid, report_ids):
              if report.attachment:
                aname = report.attachment.replace('object','inv')
                if eval(aname):
                  aname = eval(aname)+'.pdf'
                  attachment_ids = attachment_obj.search(cr, uid, [('res_model','=','account.invoice'),('datas_fname', '=', aname),('res_id','=',inv.id)])
                  for a in attachment_obj.browse(cr, uid, attachment_ids):
                    vals = {
                        'name': a.name.replace('.pdf', now+'.pdf'),
                        'datas_fname': a.datas_fname.replace('.pdf.pdf', now+'.pdf.pdf')
                           }
                    attachment_obj.write(cr, uid, a.id, vals)

        # unset set the invoices move_id
        self.write(cr, uid, ids, {'move_id': False})

        if move_ids:

            for move in account_move_obj.browse(cr , uid, move_ids):
                name = move.name + now
                account_move_obj.write(cr, uid, [move.id], {'name':name})
	        _logger.debug('FGF reopen move_copy moveid %s' % (move.id) )
		if move.journal_id.entry_posted:
                     raise osv.except_osv(_('Error !'), _('You can not reopen an invoice if the journal is set to skip draft!'))
		move_copy_id = account_move_obj.copy(cr, uid, move.id)
	        _logger.debug('FGF reopen move_copy_id %s' % (move_copy_id))
                name = name + '*'
                cr.execute("""update account_move_line
                                 set debit=credit, credit=debit
                               where move_id = %s;""" % (move_copy_id))
                account_move_obj.write(cr, uid, [move_copy_id], {'name':name})
	        _logger.debug('FGF reopen move_copy_id validate' )
                account_move_obj.button_validate(cr, uid, [move_copy_id], context=None)
	        _logger.debug('FGF reopen move_copy_id validated' )
                # reconcile
                r_id = self.pool.get('account.move.reconcile').create(cr, uid, {'type': 'auto'})
	        _logger.debug('FGF reopen reconcile_id %s' % (r_id))
                line_ids = account_move_line_obj.search(cr, uid, [('move_id','in',[move_copy_id, move.id])])
	        _logger.debug('FGF reopen reconcile_line_ids %s' % (line_ids))
                lines_to_reconile = []
                for ltr in account_move_line_obj.browse(cr, uid, line_ids):
                    if ltr.account_id.id in (ltr.partner_id.property_account_payable.id, ltr.partner_id.property_account_receivable.id):
                         lines_to_reconile.append(ltr.id)
                account_move_line_obj.write(cr, uid, lines_to_reconile, {'reconcile_id':r_id})

        self._log_event(cr, uid, ids, -1.0, 'Reopened Invoice')

        return True

    #def action_move_create(self, cr, uid, ids, context=None):
    #    move_obj = self.pool.get('account.move')
    #    move_ids = [] # ones that we will need to update
    #    for inv in self.browse(cr, uid, ids, context=context):
    #        if inv.move_id:
    #            move_ids.append(inv.move_id.id)
    #        else:
    #            super(account_invoice, self).action_move_create(cr, uid, ids, context)
    #    if move_ids:
    #        move_obj.write(cr, uid, move_ids, {'state':'posted'})

    def action_number(self, cr, uid, ids, context=None):
        for inv in self.browse(cr, uid, ids, context=context):
            if not inv.internal_number:
                super(account_invoice, self).action_number(cr, uid, ids, context)
        return True

account_invoice()
<|endoftext|>


<commit_before>import logging
import threading
from typing import Any, Callable

from core.gui.errors import show_grpc_response_exceptions

class BackgroundTask:
    def __init__(self, master: Any, task: Callable, callback: Callable = None, args=()):
        self.master = master
        self.args = args
        self.task = task
        self.callback = callback
        self.thread = None

    def start(self):
        logging.info("starting task")
        self.thread = threading.Thread(target=self.run, daemon=True)
        self.thread.start()

    def run(self):
        result = self.task(*self.args)
        logging.info("task completed")
        # if start session fails, a response with Result: False and a list of exceptions is returned
        if hasattr(result, "result") and not result.result:
            if hasattr(result, "exceptions") and len(result.exceptions) > 0:
                self.master.after(
                    0,
                    show_grpc_response_exceptions,
                    *(
                        result.__class__.__name__,
                        result.exceptions,
                        self.master,
                        self.master,
                    )
                )
        if self.callback:
            if result is None:
                args = ()
            elif isinstance(result, (list, tuple)):
                args = result
            else:
                args = (result,)
            logging.info("calling callback: %s", args)
            self.master.after(0, self.callback, *args)
<commit_msg>replace hasattr with getattr for cleaner code<commit_after>import logging
import threading
from typing import Any, Callable

from core.gui.errors import show_grpc_response_exceptions

class BackgroundTask:
    def __init__(self, master: Any, task: Callable, callback: Callable = None, args=()):
        self.master = master
        self.args = args
        self.task = task
        self.callback = callback
        self.thread = None

    def start(self):
        logging.info("starting task")
        self.thread = threading.Thread(target=self.run, daemon=True)
        self.thread.start()

    def run(self):
        result = self.task(*self.args)
        logging.info("task completed")
        # if start session fails, a response with Result: False and a list of exceptions is returned
        if not getattr(result, "result", True):
            if len(getattr(result, "exceptions", [])) > 0:
                self.master.after(
                    0,
                    show_grpc_response_exceptions,
                    *(
                        result.__class__.__name__,
                        result.exceptions,
                        self.master,
                        self.master,
                    )
                )
        if self.callback:
            if result is None:
                args = ()
            elif isinstance(result, (list, tuple)):
                args = result
            else:
                args = (result,)
            logging.info("calling callback: %s", args)
            self.master.after(0, self.callback, *args)
<|endoftext|>


<commit_before># vim: tabstop=4 shiftwidth=4 softtabstop=4
#
#    Copyright 2010 OpenStack LLC.
#    Copyright 2010 United States Government as represented by the
#    Administrator of the National Aeronautics and Space Administration.
#    All Rights Reserved.
#
#    Copyright 2009 Facebook
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

"""
Implementation of an S3-like storage server based on local files.

Useful to test features that will eventually run on S3, or if you want to
run something locally that was once running on S3.

We don't support all the features of S3, but it does work with the
standard S3 client for the most basic semantics. To use the standard
S3 client with this module::

    c = S3.AWSAuthConnection("", "", server="localhost", port=8888,
                             is_secure=False)
    c.create_bucket("mybucket")
    c.put("mybucket", "mykey", "a value")
    print c.get("mybucket", "mykey").body

"""

import datetime
import json
import logging
import multiprocessing
import os
import urllib

from twisted.application import internet
from twisted.application import service
from twisted.web import error
from twisted.web import resource
from twisted.web import server
from twisted.web import static

from nova import context
from nova import exception
from nova import flags
from nova import utils
from nova.auth import manager
from nova.objectstore import bucket
from nova.objectstore import image

FLAGS = flags.FLAGS

def render_xml(request, value):
    """Writes value as XML string to request"""
    assert isinstance(value, dict) and len(value) == 1
    request.setHeader("Content-Type", "application/xml; charset=UTF-8")

    name = value.keys()[0]
    request.write('<?xml version="1.0" encoding="UTF-8"?>\n')
    request.write('<' + utils.utf8(name) +
                 ' xmlns="http://doc.s3.amazonaws.com/2006-03-01">')
    _render_parts(value.values()[0], request.write)
    request.write('</' + utils.utf8(name) + '>')
    request.finish()

def finish(request, content=None):
    """Finalizer method for request"""
    if content:
        request.write(content)
    request.finish()

def _render_parts(value, write_cb):
    """Helper method to render different Python objects to XML"""
    if isinstance(value, basestring):
        write_cb(utils.xhtml_escape(value))
    elif isinstance(value, int) or isinstance(value, long):
        write_cb(str(value))
    elif isinstance(value, datetime.datetime):
        write_cb(value.strftime("%Y-%m-%dT%H:%M:%S.000Z"))
    elif isinstance(value, dict):
        for name, subvalue in value.iteritems():
            if not isinstance(subvalue, list):
                subvalue = [subvalue]
            for subsubvalue in subvalue:
                write_cb('<' + utils.utf8(name) + '>')
                _render_parts(subsubvalue, write_cb)
                write_cb('</' + utils.utf8(name) + '>')
    else:
        raise Exception("Unknown S3 value type %r", value)

def get_argument(request, key, default_value):
    """Returns the request's value at key, or default_value
    if not found
    """
    if key in request.args:
        return request.args[key][0]
    return default_value

def get_context(request):
    """Returns the supplied request's context object"""
    try:
        # Authorization Header format: 'AWS <access>:<secret>'
        authorization_header = request.getHeader('Authorization')
        if not authorization_header:
            raise exception.NotAuthorized()
        auth_header_value = authorization_header.split(' ')[1]
        access, _ignored, secret = auth_header_value.rpartition(':')
        am = manager.AuthManager()
        (user, project) = am.authenticate(access,
                                          secret,
                                          {},
                                          request.method,
                                          request.getRequestHostname(),
                                          request.uri,
                                          headers=request.getAllHeaders(),
                                          check_type='s3')
        return context.RequestContext(user, project)
    except exception.Error as ex:
        logging.debug("Authentication Failure: %s", ex)
        raise exception.NotAuthorized()

class ErrorHandlingResource(resource.Resource):
    """Maps exceptions to 404 / 401 codes.  Won't work for
    exceptions thrown after NOT_DONE_YET is returned.
    """
    # TODO(unassigned) (calling-all-twisted-experts): This needs to be
    #                   plugged in to the right place in twisted...
    #                   This doesn't look like it's the right place
    #                   (consider exceptions in getChild; or after
    #                   NOT_DONE_YET is returned
    def render(self, request):
        """Renders the response as XML"""
        try:
            return resource.Resource.render(self, request)
        except exception.NotFound:
            request.setResponseCode(404)
            return ''
        except exception.NotAuthorized:
            request.setResponseCode(403)
            return ''

class S3(ErrorHandlingResource):
    """Implementation of an S3-like storage server based on local files."""
    def __init__(self):
        ErrorHandlingResource.__init__(self)

    def getChild(self, name, request):  # pylint: disable-msg=C0103
        """Returns either the image or bucket resource"""
        request.context = get_context(request)
        if name == '':
            return self
        elif name == '_images':
            return ImagesResource()
        else:
            return BucketResource(name)

    def render_GET(self, request):  # pylint: disable-msg=R0201
        """Renders the GET request for a list of buckets as XML"""
        logging.debug('List of buckets requested')
        buckets = [b for b in bucket.Bucket.all() \
                   if b.is_authorized(request.context)]

        render_xml(request, {"ListAllMyBucketsResult": {
            "Buckets": {"Bucket": [b.metadata for b in buckets]},
        }})
        return server.NOT_DONE_YET

class BucketResource(ErrorHandlingResource):
    """A web resource containing an S3-like bucket"""
    def __init__(self, name):
        resource.Resource.__init__(self)
        self.name = name

    def getChild(self, name, request):
        """Returns the bucket resource itself, or the object resource
        the bucket contains if a name is supplied
        """
        if name == '':
            return self
        else:
            return ObjectResource(bucket.Bucket(self.name), name)

    def render_GET(self, request):
        "Returns the keys for the bucket resource"""
        logging.debug("List keys for bucket %s", self.name)

        try:
            bucket_object = bucket.Bucket(self.name)
        except exception.NotFound:
            return error.NoResource(message="No such bucket").render(request)

        if not bucket_object.is_authorized(request.context):
            raise exception.NotAuthorized()

        prefix = get_argument(request, "prefix", u"")
        marker = get_argument(request, "marker", u"")
        max_keys = int(get_argument(request, "max-keys", 1000))
        terse = int(get_argument(request, "terse", 0))

        results = bucket_object.list_keys(prefix=prefix,
                                          marker=marker,
                                          max_keys=max_keys,
                                          terse=terse)
        render_xml(request, {"ListBucketResult": results})
        return server.NOT_DONE_YET

    def render_PUT(self, request):
        "Creates the bucket resource"""
        logging.debug("Creating bucket %s", self.name)
        logging.debug("calling bucket.Bucket.create(%r, %r)",
                      self.name,
                      request.context)
        bucket.Bucket.create(self.name, request.context)
        request.finish()
        return server.NOT_DONE_YET

    def render_DELETE(self, request):
        """Deletes the bucket resource"""
        logging.debug("Deleting bucket %s", self.name)
        bucket_object = bucket.Bucket(self.name)

        if not bucket_object.is_authorized(request.context):
            raise exception.NotAuthorized()

        bucket_object.delete()
        request.setResponseCode(204)
        return ''

class ObjectResource(ErrorHandlingResource):
    """The resource returned from a bucket"""
    def __init__(self, bucket, name):
        resource.Resource.__init__(self)
        self.bucket = bucket
        self.name = name

    def render_GET(self, request):
        """Returns the object

        Raises NotAuthorized if user in request context is not
        authorized to delete the object.
        """
        logging.debug("Getting object: %s / %s", self.bucket.name, self.name)

        if not self.bucket.is_authorized(request.context):
            raise exception.NotAuthorized()

        obj = self.bucket[urllib.unquote(self.name)]
        request.setHeader("Content-Type", "application/unknown")
        request.setHeader("Last-Modified",
                          datetime.datetime.utcfromtimestamp(obj.mtime))
        request.setHeader("Etag", '"' + obj.md5 + '"')
        return static.File(obj.path).render_GET(request)

    def render_PUT(self, request):
        """Modifies/inserts the object and returns a result code

        Raises NotAuthorized if user in request context is not
        authorized to delete the object.
        """
        logging.debug("Putting object: %s / %s", self.bucket.name, self.name)

        if not self.bucket.is_authorized(request.context):
            raise exception.NotAuthorized()

        key = urllib.unquote(self.name)
        request.content.seek(0, 0)
        self.bucket[key] = request.content.read()
        request.setHeader("Etag", '"' + self.bucket[key].md5 + '"')
        finish(request)
        return server.NOT_DONE_YET

    def render_DELETE(self, request):
        """Deletes the object and returns a result code

        Raises NotAuthorized if user in request context is not
        authorized to delete the object.
        """

        logging.debug("Deleting object: %s / %s",
                      self.bucket.name,
                      self.name)

        if not self.bucket.is_authorized(request.context):
            raise exception.NotAuthorized()

        del self.bucket[urllib.unquote(self.name)]
        request.setResponseCode(204)
        return ''

class ImageResource(ErrorHandlingResource):
    """A web resource representing a single image"""
    isLeaf = True

    def __init__(self, name):
        resource.Resource.__init__(self)
        self.img = image.Image(name)

    def render_GET(self, request):
        """Returns the image file"""
        if not self.img.is_authorized(request.context, True):
            raise exception.NotAuthorized()
        return static.File(self.img.image_path,
                           defaultType='application/octet-stream').\
                           render_GET(request)

class ImagesResource(resource.Resource):
    """A web resource representing a list of images"""

    def getChild(self, name, _request):
        """Returns itself or an ImageResource if no name given"""
        if name == '':
            return self
        else:
            return ImageResource(name)

    def render_GET(self, request):  # pylint: disable-msg=R0201
        """ returns a json listing of all images
            that a user has permissions to see """

        images = [i for i in image.Image.all() \
                  if i.is_authorized(request.context, readonly=True)]

        # Bug #617776:
        # We used to have 'type' in the image metadata, but this field
        # should be called 'imageType', as per the EC2 specification.
        # For compat with old metadata files we copy type to imageType if
        # imageType is not present.
        # For compat with euca2ools (and any other clients using the
        # incorrect name) we copy imageType to type.
        # imageType is primary if we end up with both in the metadata file
        # (which should never happen).
        def decorate(m):
            if 'imageType' not in m and 'type' in m:
                m[u'imageType'] = m['type']
            elif 'imageType' in m:
                m[u'type'] = m['imageType']
            if 'displayName' not in m:
                m[u'displayName'] = u''
            return m

        request.write(json.dumps([decorate(i.metadata) for i in images]))
        request.finish()
        return server.NOT_DONE_YET

    def render_PUT(self, request):  # pylint: disable-msg=R0201
        """ create a new registered image """

        image_id = get_argument(request, 'image_id', u'')
        image_location = get_argument(request, 'image_location', u'')

        image_path = os.path.join(FLAGS.images_path, image_id)
        if not image_path.startswith(FLAGS.images_path) or \
           os.path.exists(image_path):
            raise exception.NotAuthorized()

        bucket_object = bucket.Bucket(image_location.split("/")[0])

        if not bucket_object.is_authorized(request.context):
            raise exception.NotAuthorized()

        p = multiprocessing.Process(target=image.Image.register_aws_image,
                args=(image_id, image_location, request.context))
        p.start()
        return ''

    def render_POST(self, request):  # pylint: disable-msg=R0201
        """Update image attributes: public/private"""

        # image_id required for all requests
        image_id = get_argument(request, 'image_id', u'')
        image_object = image.Image(image_id)
        if not image_object.is_authorized(request.context):
            logging.debug("not authorized for render_POST in images")
            raise exception.NotAuthorized()

        operation = get_argument(request, 'operation', u'')
        if operation:
            # operation implies publicity toggle
            logging.debug("handling publicity toggle")
            image_object.set_public(operation == 'add')
        else:
            # other attributes imply update
            logging.debug("update user fields")
            clean_args = {}
            for arg in request.args.keys():
                clean_args[arg] = request.args[arg][0]
            image_object.update_user_editable_fields(clean_args)
        return ''

    def render_DELETE(self, request):  # pylint: disable-msg=R0201
        """Delete a registered image"""
        image_id = get_argument(request, "image_id", u"")
        image_object = image.Image(image_id)

        if not image_object.is_authorized(request.context):
            raise exception.NotAuthorized()

        image_object.delete()

        request.setResponseCode(204)
        return ''

def get_site():
    """Support for WSGI-like interfaces"""
    root = S3()
    site = server.Site(root)
    return site

def get_application():
    """Support WSGI-like interfaces"""
    factory = get_site()
    application = service.Application("objectstore")
    # Disabled because of lack of proper introspection in Twisted
    # or possibly different versions of twisted?
    # pylint: disable-msg=E1101
    objectStoreService = internet.TCPServer(FLAGS.s3_port, factory,
                                            interface=FLAGS.s3_host)
    objectStoreService.setServiceParent(application)
    return application
<commit_msg>Added an extra argument to the objectstore listen to separate out the listening host from the connecting host.<commit_after># vim: tabstop=4 shiftwidth=4 softtabstop=4
#
#    Copyright 2010 OpenStack LLC.
#    Copyright 2010 United States Government as represented by the
#    Administrator of the National Aeronautics and Space Administration.
#    All Rights Reserved.
#
#    Copyright 2009 Facebook
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

"""
Implementation of an S3-like storage server based on local files.

Useful to test features that will eventually run on S3, or if you want to
run something locally that was once running on S3.

We don't support all the features of S3, but it does work with the
standard S3 client for the most basic semantics. To use the standard
S3 client with this module::

    c = S3.AWSAuthConnection("", "", server="localhost", port=8888,
                             is_secure=False)
    c.create_bucket("mybucket")
    c.put("mybucket", "mykey", "a value")
    print c.get("mybucket", "mykey").body

"""

import datetime
import json
import logging
import multiprocessing
import os
import urllib

from twisted.application import internet
from twisted.application import service
from twisted.web import error
from twisted.web import resource
from twisted.web import server
from twisted.web import static

from nova import context
from nova import exception
from nova import flags
from nova import utils
from nova.auth import manager
from nova.objectstore import bucket
from nova.objectstore import image

FLAGS = flags.FLAGS
flags.DEFINE_string('s3_listen_host', '', 'Host to listen on.')

def render_xml(request, value):
    """Writes value as XML string to request"""
    assert isinstance(value, dict) and len(value) == 1
    request.setHeader("Content-Type", "application/xml; charset=UTF-8")

    name = value.keys()[0]
    request.write('<?xml version="1.0" encoding="UTF-8"?>\n')
    request.write('<' + utils.utf8(name) +
                 ' xmlns="http://doc.s3.amazonaws.com/2006-03-01">')
    _render_parts(value.values()[0], request.write)
    request.write('</' + utils.utf8(name) + '>')
    request.finish()

def finish(request, content=None):
    """Finalizer method for request"""
    if content:
        request.write(content)
    request.finish()

def _render_parts(value, write_cb):
    """Helper method to render different Python objects to XML"""
    if isinstance(value, basestring):
        write_cb(utils.xhtml_escape(value))
    elif isinstance(value, int) or isinstance(value, long):
        write_cb(str(value))
    elif isinstance(value, datetime.datetime):
        write_cb(value.strftime("%Y-%m-%dT%H:%M:%S.000Z"))
    elif isinstance(value, dict):
        for name, subvalue in value.iteritems():
            if not isinstance(subvalue, list):
                subvalue = [subvalue]
            for subsubvalue in subvalue:
                write_cb('<' + utils.utf8(name) + '>')
                _render_parts(subsubvalue, write_cb)
                write_cb('</' + utils.utf8(name) + '>')
    else:
        raise Exception("Unknown S3 value type %r", value)

def get_argument(request, key, default_value):
    """Returns the request's value at key, or default_value
    if not found
    """
    if key in request.args:
        return request.args[key][0]
    return default_value

def get_context(request):
    """Returns the supplied request's context object"""
    try:
        # Authorization Header format: 'AWS <access>:<secret>'
        authorization_header = request.getHeader('Authorization')
        if not authorization_header:
            raise exception.NotAuthorized()
        auth_header_value = authorization_header.split(' ')[1]
        access, _ignored, secret = auth_header_value.rpartition(':')
        am = manager.AuthManager()
        (user, project) = am.authenticate(access,
                                          secret,
                                          {},
                                          request.method,
                                          request.getRequestHostname(),
                                          request.uri,
                                          headers=request.getAllHeaders(),
                                          check_type='s3')
        return context.RequestContext(user, project)
    except exception.Error as ex:
        logging.debug("Authentication Failure: %s", ex)
        raise exception.NotAuthorized()

class ErrorHandlingResource(resource.Resource):
    """Maps exceptions to 404 / 401 codes.  Won't work for
    exceptions thrown after NOT_DONE_YET is returned.
    """
    # TODO(unassigned) (calling-all-twisted-experts): This needs to be
    #                   plugged in to the right place in twisted...
    #                   This doesn't look like it's the right place
    #                   (consider exceptions in getChild; or after
    #                   NOT_DONE_YET is returned
    def render(self, request):
        """Renders the response as XML"""
        try:
            return resource.Resource.render(self, request)
        except exception.NotFound:
            request.setResponseCode(404)
            return ''
        except exception.NotAuthorized:
            request.setResponseCode(403)
            return ''

class S3(ErrorHandlingResource):
    """Implementation of an S3-like storage server based on local files."""
    def __init__(self):
        ErrorHandlingResource.__init__(self)

    def getChild(self, name, request):  # pylint: disable-msg=C0103
        """Returns either the image or bucket resource"""
        request.context = get_context(request)
        if name == '':
            return self
        elif name == '_images':
            return ImagesResource()
        else:
            return BucketResource(name)

    def render_GET(self, request):  # pylint: disable-msg=R0201
        """Renders the GET request for a list of buckets as XML"""
        logging.debug('List of buckets requested')
        buckets = [b for b in bucket.Bucket.all() \
                   if b.is_authorized(request.context)]

        render_xml(request, {"ListAllMyBucketsResult": {
            "Buckets": {"Bucket": [b.metadata for b in buckets]},
        }})
        return server.NOT_DONE_YET

class BucketResource(ErrorHandlingResource):
    """A web resource containing an S3-like bucket"""
    def __init__(self, name):
        resource.Resource.__init__(self)
        self.name = name

    def getChild(self, name, request):
        """Returns the bucket resource itself, or the object resource
        the bucket contains if a name is supplied
        """
        if name == '':
            return self
        else:
            return ObjectResource(bucket.Bucket(self.name), name)

    def render_GET(self, request):
        "Returns the keys for the bucket resource"""
        logging.debug("List keys for bucket %s", self.name)

        try:
            bucket_object = bucket.Bucket(self.name)
        except exception.NotFound:
            return error.NoResource(message="No such bucket").render(request)

        if not bucket_object.is_authorized(request.context):
            raise exception.NotAuthorized()

        prefix = get_argument(request, "prefix", u"")
        marker = get_argument(request, "marker", u"")
        max_keys = int(get_argument(request, "max-keys", 1000))
        terse = int(get_argument(request, "terse", 0))

        results = bucket_object.list_keys(prefix=prefix,
                                          marker=marker,
                                          max_keys=max_keys,
                                          terse=terse)
        render_xml(request, {"ListBucketResult": results})
        return server.NOT_DONE_YET

    def render_PUT(self, request):
        "Creates the bucket resource"""
        logging.debug("Creating bucket %s", self.name)
        logging.debug("calling bucket.Bucket.create(%r, %r)",
                      self.name,
                      request.context)
        bucket.Bucket.create(self.name, request.context)
        request.finish()
        return server.NOT_DONE_YET

    def render_DELETE(self, request):
        """Deletes the bucket resource"""
        logging.debug("Deleting bucket %s", self.name)
        bucket_object = bucket.Bucket(self.name)

        if not bucket_object.is_authorized(request.context):
            raise exception.NotAuthorized()

        bucket_object.delete()
        request.setResponseCode(204)
        return ''

class ObjectResource(ErrorHandlingResource):
    """The resource returned from a bucket"""
    def __init__(self, bucket, name):
        resource.Resource.__init__(self)
        self.bucket = bucket
        self.name = name

    def render_GET(self, request):
        """Returns the object

        Raises NotAuthorized if user in request context is not
        authorized to delete the object.
        """
        logging.debug("Getting object: %s / %s", self.bucket.name, self.name)

        if not self.bucket.is_authorized(request.context):
            raise exception.NotAuthorized()

        obj = self.bucket[urllib.unquote(self.name)]
        request.setHeader("Content-Type", "application/unknown")
        request.setHeader("Last-Modified",
                          datetime.datetime.utcfromtimestamp(obj.mtime))
        request.setHeader("Etag", '"' + obj.md5 + '"')
        return static.File(obj.path).render_GET(request)

    def render_PUT(self, request):
        """Modifies/inserts the object and returns a result code

        Raises NotAuthorized if user in request context is not
        authorized to delete the object.
        """
        logging.debug("Putting object: %s / %s", self.bucket.name, self.name)

        if not self.bucket.is_authorized(request.context):
            raise exception.NotAuthorized()

        key = urllib.unquote(self.name)
        request.content.seek(0, 0)
        self.bucket[key] = request.content.read()
        request.setHeader("Etag", '"' + self.bucket[key].md5 + '"')
        finish(request)
        return server.NOT_DONE_YET

    def render_DELETE(self, request):
        """Deletes the object and returns a result code

        Raises NotAuthorized if user in request context is not
        authorized to delete the object.
        """

        logging.debug("Deleting object: %s / %s",
                      self.bucket.name,
                      self.name)

        if not self.bucket.is_authorized(request.context):
            raise exception.NotAuthorized()

        del self.bucket[urllib.unquote(self.name)]
        request.setResponseCode(204)
        return ''

class ImageResource(ErrorHandlingResource):
    """A web resource representing a single image"""
    isLeaf = True

    def __init__(self, name):
        resource.Resource.__init__(self)
        self.img = image.Image(name)

    def render_GET(self, request):
        """Returns the image file"""
        if not self.img.is_authorized(request.context, True):
            raise exception.NotAuthorized()
        return static.File(self.img.image_path,
                           defaultType='application/octet-stream').\
                           render_GET(request)

class ImagesResource(resource.Resource):
    """A web resource representing a list of images"""

    def getChild(self, name, _request):
        """Returns itself or an ImageResource if no name given"""
        if name == '':
            return self
        else:
            return ImageResource(name)

    def render_GET(self, request):  # pylint: disable-msg=R0201
        """ returns a json listing of all images
            that a user has permissions to see """

        images = [i for i in image.Image.all() \
                  if i.is_authorized(request.context, readonly=True)]

        # Bug #617776:
        # We used to have 'type' in the image metadata, but this field
        # should be called 'imageType', as per the EC2 specification.
        # For compat with old metadata files we copy type to imageType if
        # imageType is not present.
        # For compat with euca2ools (and any other clients using the
        # incorrect name) we copy imageType to type.
        # imageType is primary if we end up with both in the metadata file
        # (which should never happen).
        def decorate(m):
            if 'imageType' not in m and 'type' in m:
                m[u'imageType'] = m['type']
            elif 'imageType' in m:
                m[u'type'] = m['imageType']
            if 'displayName' not in m:
                m[u'displayName'] = u''
            return m

        request.write(json.dumps([decorate(i.metadata) for i in images]))
        request.finish()
        return server.NOT_DONE_YET

    def render_PUT(self, request):  # pylint: disable-msg=R0201
        """ create a new registered image """

        image_id = get_argument(request, 'image_id', u'')
        image_location = get_argument(request, 'image_location', u'')

        image_path = os.path.join(FLAGS.images_path, image_id)
        if not image_path.startswith(FLAGS.images_path) or \
           os.path.exists(image_path):
            raise exception.NotAuthorized()

        bucket_object = bucket.Bucket(image_location.split("/")[0])

        if not bucket_object.is_authorized(request.context):
            raise exception.NotAuthorized()

        p = multiprocessing.Process(target=image.Image.register_aws_image,
                args=(image_id, image_location, request.context))
        p.start()
        return ''

    def render_POST(self, request):  # pylint: disable-msg=R0201
        """Update image attributes: public/private"""

        # image_id required for all requests
        image_id = get_argument(request, 'image_id', u'')
        image_object = image.Image(image_id)
        if not image_object.is_authorized(request.context):
            logging.debug("not authorized for render_POST in images")
            raise exception.NotAuthorized()

        operation = get_argument(request, 'operation', u'')
        if operation:
            # operation implies publicity toggle
            logging.debug("handling publicity toggle")
            image_object.set_public(operation == 'add')
        else:
            # other attributes imply update
            logging.debug("update user fields")
            clean_args = {}
            for arg in request.args.keys():
                clean_args[arg] = request.args[arg][0]
            image_object.update_user_editable_fields(clean_args)
        return ''

    def render_DELETE(self, request):  # pylint: disable-msg=R0201
        """Delete a registered image"""
        image_id = get_argument(request, "image_id", u"")
        image_object = image.Image(image_id)

        if not image_object.is_authorized(request.context):
            raise exception.NotAuthorized()

        image_object.delete()

        request.setResponseCode(204)
        return ''

def get_site():
    """Support for WSGI-like interfaces"""
    root = S3()
    site = server.Site(root)
    return site

def get_application():
    """Support WSGI-like interfaces"""
    factory = get_site()
    application = service.Application("objectstore")
    # Disabled because of lack of proper introspection in Twisted
    # or possibly different versions of twisted?
    # pylint: disable-msg=E1101
    objectStoreService = internet.TCPServer(FLAGS.s3_port, factory,
                                            interface=FLAGS.s3_listen_host)
    objectStoreService.setServiceParent(application)
    return application
<|endoftext|>


<commit_before>#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright (c) 2017 Stanislav Ivochkin <isn@extrn.org>
# License: MIT (see LICENSE for details)

from jcppy._version import __version__, __revision__
from jcppy.header import header
from jcppy.source import source
<commit_msg>Work around missing jcppy._version for local development<commit_after>#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright (c) 2017 Stanislav Ivochkin <isn@extrn.org>
# License: MIT (see LICENSE for details)

try:
    from jcppy._version import __version__, __revision__
except ImportError:
    __version__ = 'latest'
    __revision__ = 'latest'
    __hash__ = 'unknown'
from jcppy.header import header
from jcppy.source import source
<|endoftext|>


<commit_before>
from django.test import TestCase
from django.contrib.auth.models import User

from model_mommy import mommy

from ahem.backends import BaseBackend
from ahem.notification import Notification
from ahem.scopes import QuerySetScope, ContextFilterScope

class TestBackend(BaseBackend):
    name = 'test_backend'
    required_settings = ['username', 'id']

class TestBackend(BaseBackend):
    name = 'other_backend'

class QuerySetNotification(Notification):
    name = 'query_set_notification'
    backends = ['test_backend', 'other_backend']

    scope = QuerySetScope()

    templates = {
        'default': 'ahem/tests/test_template.html'}

class CustomQuerySetNotification(Notification):
    name = 'custom_query_set_notification'
    backends = ['test_backend', 'other_backend']

    scope = QuerySetScope(User.objects.filter(is_staff=True))

    templates = {
        'default': 'ahem/tests/test_template.html'}

class QuerySetScopeTests(TestCase):

    def setUp(self):
        self.users = mommy.make('auth.User', _quantity=3)

    def test_gets_all_users_if_no_query_scope_is_set(self):
        users = self.users
        notification = QuerySetNotification()

        notification_users = notification.get_users({})
        self.assertEqual(len(users), len(notification_users))

    def test_a_queryset_can_be_passed_to_the_scope(self):
        staffs = mommy.make('auth.User', is_staff=True, _quantity=2)

        notification = CustomQuerySetNotification()

        notification_users = notification.get_users({})
        self.assertEqual(len(staffs), len(notification_users))
<commit_msg>adding tests to context filter scope<commit_after>
from django.test import TestCase
from django.contrib.auth.models import User

from model_mommy import mommy

from ahem.backends import BaseBackend
from ahem.notification import Notification
from ahem.scopes import QuerySetScope, ContextFilterScope

class TestBackend(BaseBackend):
    name = 'test_backend'
    required_settings = ['username', 'id']

class TestBackend(BaseBackend):
    name = 'other_backend'

class QuerySetNotification(Notification):
    name = 'query_set_notification'
    backends = ['test_backend', 'other_backend']

    scope = QuerySetScope()

    templates = {
        'default': 'ahem/tests/test_template.html'}

class CustomQuerySetNotification(Notification):
    name = 'custom_query_set_notification'
    backends = ['test_backend', 'other_backend']

    scope = QuerySetScope(User.objects.filter(is_staff=True))

    templates = {
        'default': 'ahem/tests/test_template.html'}

class QuerySetScopeTests(TestCase):

    def setUp(self):
        self.users = mommy.make('auth.User', _quantity=3)

    def test_gets_all_users_if_no_query_scope_is_set(self):
        users = self.users
        notification = QuerySetNotification()

        notification_users = notification.get_users({})
        self.assertEqual(len(users), len(notification_users))

    def test_a_queryset_can_be_passed_to_the_scope(self):
        staffs = mommy.make('auth.User', is_staff=True, _quantity=2)

        notification = CustomQuerySetNotification()

        notification_users = notification.get_users({})
        self.assertEqual(len(staffs), len(notification_users))

class ContextFilterScopeNotification(Notification):
    name = 'context_filter_scope_notification'

    backends = ['test_backend', 'other_backend']

    scope = ContextFilterScope(lookup_context_key='is_staff', lookup_field='is_staff')

    templates = {
        'default': 'ahem/tests/test_template.html'}

class ContextFilterScopeTests(TestCase):

    def setUp(self):
        self.users = mommy.make('auth.User', _quantity=3)
        self.staffs = mommy.make('auth.User', is_staff=True, _quantity=2)

        self.notification = ContextFilterScopeNotification()

    def test_returns_only_non_staffs(self):
        users = self.users

        notification_users = self.notification.get_users({'is_staff': False})
        self.assertEqual(len(users), len(notification_users))

    def test_returns_only_staffs(self):
        staffs = self.staffs

        notification_users = self.notification.get_users({'is_staff': True})
        self.assertEqual(len(staffs), len(notification_users))
<|endoftext|>


<commit_before>from __future__ import annotations

from contextlib import suppress
from uuid import UUID
import datetime
import ipaddress
import re
import typing

from jsonschema.exceptions import FormatError

class FormatChecker(object):
    """
    A ``format`` property checker.

    JSON Schema does not mandate that the ``format`` property actually do any
    validation. If validation is desired however, instances of this class can
    be hooked into validators to enable format validation.

    `FormatChecker` objects always return ``True`` when asked about
    formats that they do not know how to validate.

    To check a custom format using a function that takes an instance and
    returns a ``bool``, use the `FormatChecker.checks` or
    `FormatChecker.cls_checks` decorators.

    Arguments:

        formats (~collections.abc.Iterable):

            The known formats to validate. This argument can be used to
            limit which formats will be used during validation.
    """

    checkers: dict[
        str,
        tuple[
            typing.Callable[[typing.Any], bool],
            Exception | tuple[Exception, ...],
        ],
    ] = {}

    def __init__(self, formats=None):
        if formats is None:
            self.checkers = self.checkers.copy()
        else:
            self.checkers = dict((k, self.checkers[k]) for k in formats)

    def __repr__(self):
        return "<FormatChecker checkers={}>".format(sorted(self.checkers))

    def checks(self, format, raises=()):
        """
        Register a decorated function as validating a new format.

        Arguments:

            format (str):

                The format that the decorated function will check.

            raises (Exception):

                The exception(s) raised by the decorated function when an
                invalid instance is found.

                The exception object will be accessible as the
                `jsonschema.exceptions.ValidationError.cause` attribute of the
                resulting validation error.
        """

        def _checks(func):
            self.checkers[format] = (func, raises)
            return func
        return _checks

    cls_checks = classmethod(checks)

    def check(self, instance, format):
        """
        Check whether the instance conforms to the given format.

        Arguments:

            instance (*any primitive type*, i.e. str, number, bool):

                The instance to check

            format (str):

                The format that instance should conform to

        Raises:

            FormatError: if the instance does not conform to ``format``
        """

        if format not in self.checkers:
            return

        func, raises = self.checkers[format]
        result, cause = None, None
        try:
            result = func(instance)
        except raises as e:
            cause = e
        if not result:
            raise FormatError(f"{instance!r} is not a {format!r}", cause=cause)

    def conforms(self, instance, format):
        """
        Check whether the instance conforms to the given format.

        Arguments:

            instance (*any primitive type*, i.e. str, number, bool):

                The instance to check

            format (str):

                The format that instance should conform to

        Returns:

            bool: whether it conformed
        """

        try:
            self.check(instance, format)
        except FormatError:
            return False
        else:
            return True

draft3_format_checker = FormatChecker()
draft4_format_checker = FormatChecker()
draft6_format_checker = FormatChecker()
draft7_format_checker = FormatChecker()
draft201909_format_checker = FormatChecker()
draft202012_format_checker = FormatChecker()

_draft_checkers = dict(
    draft3=draft3_format_checker,
    draft4=draft4_format_checker,
    draft6=draft6_format_checker,
    draft7=draft7_format_checker,
    draft201909=draft201909_format_checker,
    draft202012=draft202012_format_checker,
)

def _checks_drafts(
    name=None,
    draft3=None,
    draft4=None,
    draft6=None,
    draft7=None,
    draft201909=None,
    draft202012=None,
    raises=(),
):
    draft3 = draft3 or name
    draft4 = draft4 or name
    draft6 = draft6 or name
    draft7 = draft7 or name
    draft201909 = draft201909 or name
    draft202012 = draft202012 or name

    def wrap(func):
        if draft3:
            func = _draft_checkers["draft3"].checks(draft3, raises)(func)
        if draft4:
            func = _draft_checkers["draft4"].checks(draft4, raises)(func)
        if draft6:
            func = _draft_checkers["draft6"].checks(draft6, raises)(func)
        if draft7:
            func = _draft_checkers["draft7"].checks(draft7, raises)(func)
        if draft201909:
            func = _draft_checkers["draft201909"].checks(draft201909, raises)(
                func,
            )
        if draft202012:
            func = _draft_checkers["draft202012"].checks(draft202012, raises)(
                func,
            )

        # Oy. This is bad global state, but relied upon for now, until
        # deprecation. See #519 and test_format_checkers_come_with_defaults
        FormatChecker.cls_checks(
            draft202012 or draft201909 or draft7 or draft6 or draft4 or draft3,
            raises,
        )(func)
        return func
    return wrap

@_checks_drafts(name="idn-email")
@_checks_drafts(name="email")
def is_email(instance):
    if not isinstance(instance, str):
        return True
    return "@" in instance

@_checks_drafts(
    draft3="ip-address",
    draft4="ipv4",
    draft6="ipv4",
    draft7="ipv4",
    draft201909="ipv4",
    draft202012="ipv4",
    raises=ipaddress.AddressValueError,
)
def is_ipv4(instance):
    if not isinstance(instance, str):
        return True
    return ipaddress.IPv4Address(instance)

@_checks_drafts(name="ipv6", raises=ipaddress.AddressValueError)
def is_ipv6(instance):
    if not isinstance(instance, str):
        return True
    address = ipaddress.IPv6Address(instance)
    return not getattr(address, "scope_id", "")

with suppress(ImportError):
    from fqdn import FQDN

    @_checks_drafts(
        draft3="host-name",
        draft4="hostname",
        draft6="hostname",
        draft7="hostname",
        draft201909="hostname",
        draft202012="hostname",
    )
    def is_host_name(instance):
        if not isinstance(instance, str):
            return True
        return FQDN(instance).is_valid

with suppress(ImportError):
    # The built-in `idna` codec only implements RFC 3890, so we go elsewhere.
    import idna

    @_checks_drafts(
        draft7="idn-hostname",
        draft201909="idn-hostname",
        draft202012="idn-hostname",
        raises=(idna.IDNAError, UnicodeError),
    )
    def is_idn_host_name(instance):
        if not isinstance(instance, str):
            return True
        idna.encode(instance)
        return True

try:
    import rfc3987
except ImportError:
    with suppress(ImportError):
        from rfc3986_validator import validate_rfc3986

        @_checks_drafts(name="uri")
        def is_uri(instance):
            if not isinstance(instance, str):
                return True
            return validate_rfc3986(instance, rule="URI")

        @_checks_drafts(
            draft6="uri-reference",
            draft7="uri-reference",
            draft201909="uri-reference",
            draft202012="uri-reference",
            raises=ValueError,
        )
        def is_uri_reference(instance):
            if not isinstance(instance, str):
                return True
            return validate_rfc3986(instance, rule="URI_reference")

else:
    @_checks_drafts(
        draft7="iri",
        draft201909="iri",
        draft202012="iri",
        raises=ValueError,
    )
    def is_iri(instance):
        if not isinstance(instance, str):
            return True
        return rfc3987.parse(instance, rule="IRI")

    @_checks_drafts(
        draft7="iri-reference",
        draft201909="iri-reference",
        draft202012="iri-reference",
        raises=ValueError,
    )
    def is_iri_reference(instance):
        if not isinstance(instance, str):
            return True
        return rfc3987.parse(instance, rule="IRI_reference")

    @_checks_drafts(name="uri", raises=ValueError)
    def is_uri(instance):
        if not isinstance(instance, str):
            return True
        return rfc3987.parse(instance, rule="URI")

    @_checks_drafts(
        draft6="uri-reference",
        draft7="uri-reference",
        draft201909="uri-reference",
        draft202012="uri-reference",
        raises=ValueError,
    )
    def is_uri_reference(instance):
        if not isinstance(instance, str):
            return True
        return rfc3987.parse(instance, rule="URI_reference")

with suppress(ImportError):
    from rfc3339_validator import validate_rfc3339

    @_checks_drafts(name="date-time")
    def is_datetime(instance):
        if not isinstance(instance, str):
            return True
        return validate_rfc3339(instance.upper())

    @_checks_drafts(
        draft7="time",
        draft201909="time",
        draft202012="time",
    )
    def is_time(instance):
        if not isinstance(instance, str):
            return True
        return is_datetime("1970-01-01T" + instance)

@_checks_drafts(name="regex", raises=re.error)
def is_regex(instance):
    if not isinstance(instance, str):
        return True
    return re.compile(instance)

@_checks_drafts(
    draft3="date",
    draft7="date",
    draft201909="date",
    draft202012="date",
    raises=ValueError,
)
def is_date(instance):
    if not isinstance(instance, str):
        return True
    return instance.isascii() and datetime.date.fromisoformat(instance)

@_checks_drafts(draft3="time", raises=ValueError)
def is_draft3_time(instance):
    if not isinstance(instance, str):
        return True
    return datetime.datetime.strptime(instance, "%H:%M:%S")

with suppress(ImportError):
    from webcolors import CSS21_NAMES_TO_HEX
    import webcolors

    def is_css_color_code(instance):
        return webcolors.normalize_hex(instance)

    @_checks_drafts(draft3="color", raises=(ValueError, TypeError))
    def is_css21_color(instance):
        if (
            not isinstance(instance, str)
            or instance.lower() in CSS21_NAMES_TO_HEX
        ):
            return True
        return is_css_color_code(instance)

with suppress(ImportError):
    import jsonpointer

    @_checks_drafts(
        draft6="json-pointer",
        draft7="json-pointer",
        draft201909="json-pointer",
        draft202012="json-pointer",
        raises=jsonpointer.JsonPointerException,
    )
    def is_json_pointer(instance):
        if not isinstance(instance, str):
            return True
        return jsonpointer.JsonPointer(instance)

    # TODO: I don't want to maintain this, so it
    #       needs to go either into jsonpointer (pending
    #       https://github.com/stefankoegl/python-json-pointer/issues/34) or
    #       into a new external library.
    @_checks_drafts(
        draft7="relative-json-pointer",
        draft201909="relative-json-pointer",
        draft202012="relative-json-pointer",
        raises=jsonpointer.JsonPointerException,
    )
    def is_relative_json_pointer(instance):
        # Definition taken from:
        # https://tools.ietf.org/html/draft-handrews-relative-json-pointer-01#section-3
        if not isinstance(instance, str):
            return True
        non_negative_integer, rest = [], ""
        for i, character in enumerate(instance):
            if character.isdigit():
                # digits with a leading "0" are not allowed
                if i > 0 and int(instance[i - 1]) == 0:
                    return False

                non_negative_integer.append(character)
                continue

            if not non_negative_integer:
                return False

            rest = instance[i:]
            break
        return (rest == "#") or jsonpointer.JsonPointer(rest)

with suppress(ImportError):
    import uri_template

    @_checks_drafts(
        draft6="uri-template",
        draft7="uri-template",
        draft201909="uri-template",
        draft202012="uri-template",
    )
    def is_uri_template(instance):
        if not isinstance(instance, str):
            return True
        return uri_template.validate(instance)

with suppress(ImportError):
    import isoduration

    @_checks_drafts(
        draft201909="duration",
        draft202012="duration",
        raises=isoduration.DurationParsingException,
    )
    def is_duration(instance):
        if not isinstance(instance, str):
            return True
        return isoduration.parse_duration(instance)

@_checks_drafts(
    draft201909="uuid",
    draft202012="uuid",
    raises=ValueError,
)
def is_uuid(instance):
    if not isinstance(instance, str):
        return True
    UUID(instance)
    return all(instance[position] == "-" for position in (8, 13, 18, 23))
<commit_msg>Type annotate format checker methods<commit_after>from __future__ import annotations

from contextlib import suppress
from uuid import UUID
import datetime
import ipaddress
import re
import typing

from jsonschema.exceptions import FormatError

_FormatCheckCallable = typing.Callable[[object], bool]
_F = typing.TypeVar("_F", bound=_FormatCheckCallable)
_RaisesType = typing.Union[
    typing.Type[Exception], typing.Tuple[typing.Type[Exception], ...],
]

class FormatChecker(object):
    """
    A ``format`` property checker.

    JSON Schema does not mandate that the ``format`` property actually do any
    validation. If validation is desired however, instances of this class can
    be hooked into validators to enable format validation.

    `FormatChecker` objects always return ``True`` when asked about
    formats that they do not know how to validate.

    To check a custom format using a function that takes an instance and
    returns a ``bool``, use the `FormatChecker.checks` or
    `FormatChecker.cls_checks` decorators.

    Arguments:

        formats (~collections.abc.Iterable):

            The known formats to validate. This argument can be used to
            limit which formats will be used during validation.
    """

    checkers: dict[
        str,
        tuple[_FormatCheckCallable, _RaisesType],
    ] = {}

    def __init__(self, formats: typing.Iterable[str] | None = None):
        if formats is None:
            self.checkers = self.checkers.copy()
        else:
            self.checkers = dict((k, self.checkers[k]) for k in formats)

    def __repr__(self):
        return "<FormatChecker checkers={}>".format(sorted(self.checkers))

    def checks(
        self, format: str, raises: _RaisesType = (),
    ) -> typing.Callable[[_F], _F]:
        """
        Register a decorated function as validating a new format.

        Arguments:

            format (str):

                The format that the decorated function will check.

            raises (Exception):

                The exception(s) raised by the decorated function when an
                invalid instance is found.

                The exception object will be accessible as the
                `jsonschema.exceptions.ValidationError.cause` attribute of the
                resulting validation error.
        """

        def _checks(func: _F) -> _F:
            self.checkers[format] = (func, raises)
            return func

        return _checks

    @classmethod
    def cls_checks(
        cls, format: str, raises: _RaisesType = (),
    ) -> typing.Callable[[_F], _F]:
        def _checks(func: _F) -> _F:
            cls.checkers[format] = (func, raises)
            return func

        return _checks

    def check(self, instance: object, format: str) -> None:
        """
        Check whether the instance conforms to the given format.

        Arguments:

            instance (*any primitive type*, i.e. str, number, bool):

                The instance to check

            format (str):

                The format that instance should conform to

        Raises:

            FormatError: if the instance does not conform to ``format``
        """

        if format not in self.checkers:
            return

        func, raises = self.checkers[format]
        result, cause = None, None
        try:
            result = func(instance)
        except raises as e:
            cause = e
        if not result:
            raise FormatError(f"{instance!r} is not a {format!r}", cause=cause)

    def conforms(self, instance: object, format: str) -> bool:
        """
        Check whether the instance conforms to the given format.

        Arguments:

            instance (*any primitive type*, i.e. str, number, bool):

                The instance to check

            format (str):

                The format that instance should conform to

        Returns:

            bool: whether it conformed
        """

        try:
            self.check(instance, format)
        except FormatError:
            return False
        else:
            return True

draft3_format_checker = FormatChecker()
draft4_format_checker = FormatChecker()
draft6_format_checker = FormatChecker()
draft7_format_checker = FormatChecker()
draft201909_format_checker = FormatChecker()
draft202012_format_checker = FormatChecker()

_draft_checkers: dict[str, FormatChecker] = dict(
    draft3=draft3_format_checker,
    draft4=draft4_format_checker,
    draft6=draft6_format_checker,
    draft7=draft7_format_checker,
    draft201909=draft201909_format_checker,
    draft202012=draft202012_format_checker,
)

def _checks_drafts(
    name=None,
    draft3=None,
    draft4=None,
    draft6=None,
    draft7=None,
    draft201909=None,
    draft202012=None,
    raises=(),
) -> typing.Callable[[_F], _F]:
    draft3 = draft3 or name
    draft4 = draft4 or name
    draft6 = draft6 or name
    draft7 = draft7 or name
    draft201909 = draft201909 or name
    draft202012 = draft202012 or name

    def wrap(func: _F) -> _F:
        if draft3:
            func = _draft_checkers["draft3"].checks(draft3, raises)(func)
        if draft4:
            func = _draft_checkers["draft4"].checks(draft4, raises)(func)
        if draft6:
            func = _draft_checkers["draft6"].checks(draft6, raises)(func)
        if draft7:
            func = _draft_checkers["draft7"].checks(draft7, raises)(func)
        if draft201909:
            func = _draft_checkers["draft201909"].checks(draft201909, raises)(
                func,
            )
        if draft202012:
            func = _draft_checkers["draft202012"].checks(draft202012, raises)(
                func,
            )

        # Oy. This is bad global state, but relied upon for now, until
        # deprecation. See #519 and test_format_checkers_come_with_defaults
        FormatChecker.cls_checks(
            draft202012 or draft201909 or draft7 or draft6 or draft4 or draft3,
            raises,
        )(func)
        return func

    return wrap

@_checks_drafts(name="idn-email")
@_checks_drafts(name="email")
def is_email(instance: object) -> bool:
    if not isinstance(instance, str):
        return True
    return "@" in instance

@_checks_drafts(
    draft3="ip-address",
    draft4="ipv4",
    draft6="ipv4",
    draft7="ipv4",
    draft201909="ipv4",
    draft202012="ipv4",
    raises=ipaddress.AddressValueError,
)
def is_ipv4(instance: object) -> bool:
    if not isinstance(instance, str):
        return True
    return bool(ipaddress.IPv4Address(instance))

@_checks_drafts(name="ipv6", raises=ipaddress.AddressValueError)
def is_ipv6(instance: object) -> bool:
    if not isinstance(instance, str):
        return True
    address = ipaddress.IPv6Address(instance)
    return not getattr(address, "scope_id", "")

with suppress(ImportError):
    from fqdn import FQDN

    @_checks_drafts(
        draft3="host-name",
        draft4="hostname",
        draft6="hostname",
        draft7="hostname",
        draft201909="hostname",
        draft202012="hostname",
    )
    def is_host_name(instance: object) -> bool:
        if not isinstance(instance, str):
            return True
        return FQDN(instance).is_valid

with suppress(ImportError):
    # The built-in `idna` codec only implements RFC 3890, so we go elsewhere.
    import idna

    @_checks_drafts(
        draft7="idn-hostname",
        draft201909="idn-hostname",
        draft202012="idn-hostname",
        raises=(idna.IDNAError, UnicodeError),
    )
    def is_idn_host_name(instance: object) -> bool:
        if not isinstance(instance, str):
            return True
        idna.encode(instance)
        return True

try:
    import rfc3987
except ImportError:
    with suppress(ImportError):
        from rfc3986_validator import validate_rfc3986

        @_checks_drafts(name="uri")
        def is_uri(instance: object) -> bool:
            if not isinstance(instance, str):
                return True
            return validate_rfc3986(instance, rule="URI")

        @_checks_drafts(
            draft6="uri-reference",
            draft7="uri-reference",
            draft201909="uri-reference",
            draft202012="uri-reference",
            raises=ValueError,
        )
        def is_uri_reference(instance: object) -> bool:
            if not isinstance(instance, str):
                return True
            return validate_rfc3986(instance, rule="URI_reference")

else:

    @_checks_drafts(
        draft7="iri",
        draft201909="iri",
        draft202012="iri",
        raises=ValueError,
    )
    def is_iri(instance: object) -> bool:
        if not isinstance(instance, str):
            return True
        return rfc3987.parse(instance, rule="IRI")

    @_checks_drafts(
        draft7="iri-reference",
        draft201909="iri-reference",
        draft202012="iri-reference",
        raises=ValueError,
    )
    def is_iri_reference(instance: object) -> bool:
        if not isinstance(instance, str):
            return True
        return rfc3987.parse(instance, rule="IRI_reference")

    @_checks_drafts(name="uri", raises=ValueError)
    def is_uri(instance: object) -> bool:
        if not isinstance(instance, str):
            return True
        return rfc3987.parse(instance, rule="URI")

    @_checks_drafts(
        draft6="uri-reference",
        draft7="uri-reference",
        draft201909="uri-reference",
        draft202012="uri-reference",
        raises=ValueError,
    )
    def is_uri_reference(instance: object) -> bool:
        if not isinstance(instance, str):
            return True
        return rfc3987.parse(instance, rule="URI_reference")

with suppress(ImportError):
    from rfc3339_validator import validate_rfc3339

    @_checks_drafts(name="date-time")
    def is_datetime(instance: object) -> bool:
        if not isinstance(instance, str):
            return True
        return validate_rfc3339(instance.upper())

    @_checks_drafts(
        draft7="time",
        draft201909="time",
        draft202012="time",
    )
    def is_time(instance: object) -> bool:
        if not isinstance(instance, str):
            return True
        return is_datetime("1970-01-01T" + instance)

@_checks_drafts(name="regex", raises=re.error)
def is_regex(instance: object) -> bool:
    if not isinstance(instance, str):
        return True
    return bool(re.compile(instance))

@_checks_drafts(
    draft3="date",
    draft7="date",
    draft201909="date",
    draft202012="date",
    raises=ValueError,
)
def is_date(instance: object) -> bool:
    if not isinstance(instance, str):
        return True
    return bool(instance.isascii() and datetime.date.fromisoformat(instance))

@_checks_drafts(draft3="time", raises=ValueError)
def is_draft3_time(instance: object) -> bool:
    if not isinstance(instance, str):
        return True
    return bool(datetime.datetime.strptime(instance, "%H:%M:%S"))

with suppress(ImportError):
    from webcolors import CSS21_NAMES_TO_HEX
    import webcolors

    def is_css_color_code(instance: object) -> bool:
        return webcolors.normalize_hex(instance)

    @_checks_drafts(draft3="color", raises=(ValueError, TypeError))
    def is_css21_color(instance: object) -> bool:
        if (
            not isinstance(instance, str)
            or instance.lower() in CSS21_NAMES_TO_HEX
        ):
            return True
        return is_css_color_code(instance)

with suppress(ImportError):
    import jsonpointer

    @_checks_drafts(
        draft6="json-pointer",
        draft7="json-pointer",
        draft201909="json-pointer",
        draft202012="json-pointer",
        raises=jsonpointer.JsonPointerException,
    )
    def is_json_pointer(instance: object) -> bool:
        if not isinstance(instance, str):
            return True
        return bool(jsonpointer.JsonPointer(instance))

    # TODO: I don't want to maintain this, so it
    #       needs to go either into jsonpointer (pending
    #       https://github.com/stefankoegl/python-json-pointer/issues/34) or
    #       into a new external library.
    @_checks_drafts(
        draft7="relative-json-pointer",
        draft201909="relative-json-pointer",
        draft202012="relative-json-pointer",
        raises=jsonpointer.JsonPointerException,
    )
    def is_relative_json_pointer(instance: object) -> bool:
        # Definition taken from:
        # https://tools.ietf.org/html/draft-handrews-relative-json-pointer-01#section-3
        if not isinstance(instance, str):
            return True
        non_negative_integer, rest = [], ""
        for i, character in enumerate(instance):
            if character.isdigit():
                # digits with a leading "0" are not allowed
                if i > 0 and int(instance[i - 1]) == 0:
                    return False

                non_negative_integer.append(character)
                continue

            if not non_negative_integer:
                return False

            rest = instance[i:]
            break
        return (rest == "#") or bool(jsonpointer.JsonPointer(rest))

with suppress(ImportError):
    import uri_template

    @_checks_drafts(
        draft6="uri-template",
        draft7="uri-template",
        draft201909="uri-template",
        draft202012="uri-template",
    )
    def is_uri_template(instance: object) -> bool:
        if not isinstance(instance, str):
            return True
        return uri_template.validate(instance)

with suppress(ImportError):
    import isoduration

    @_checks_drafts(
        draft201909="duration",
        draft202012="duration",
        raises=isoduration.DurationParsingException,
    )
    def is_duration(instance: object) -> bool:
        if not isinstance(instance, str):
            return True
        return bool(isoduration.parse_duration(instance))

@_checks_drafts(
    draft201909="uuid",
    draft202012="uuid",
    raises=ValueError,
)
def is_uuid(instance: object) -> bool:
    if not isinstance(instance, str):
        return True
    UUID(instance)
    return all(instance[position] == "-" for position in (8, 13, 18, 23))
<|endoftext|>


<commit_before>#!/usr/bin/env python
#
# name:             website.py
# author:           Harold Bradley III
# email:            harold@bradleystudio.net
# date:             11/11/2015
#
# description:      A class to manage websites
#

from ext_pylib.prompt import prompt, prompt_str, warn_prompt
import os
import re
import requests
import tarfile
import time
from vhost import Vhost
from website_domain import Website_Domain

# localhost(function)
#   a python decorator that creates a temporary host entry before
#   the function is called and removes it after it is completed.
def localhost(function):
    def function_wrapper(self, *args, **kwargs):
        print 'Adding temporary host entry.'
        os.system("echo '127.0.0.1 " + self.domain + "' | cat >> /etc/hosts")
        function(self, *args, **kwargs)
        print 'Removing temporary host entry.'
        os.system("sed -i '/^127\.0\.0\.1 " + self.domain + "$/d' /etc/hosts")
    return function_wrapper

# Website(website)
#   a class that describes a generic website with the following properties:
#       domain              Website domain
#       dirs['htdocs']      Website root directory
#       dirs['assets']      Website assets directory
#       dirs['logs']        Website log directory
#       files['vhost_conf'] Apache vhost config file.
#       files['htaccess']   Website root htaccess file.
#   primary methods:
#       install()
#       uninstall()
#       verify()
class Website(object):

    def __init__(self, domain, atts = { 'htdocs'     : None,
                                        'assets'     : None,
                                        'logs'       : None,
                                        'vhost_conf' : None,
                                        'htaccess'   : None,
                                       }):
        """Initializes a new Website instance."""

        print '[*] SSL not yet implemented in Website class.'

        self.domain = Website_Domain(domain)
        self.dirs = {}
        self.files = {}

        self.files['vhost_conf'] = WW_Vhost({
            'path'  : '/etc/apache2/sites-available/' + self.domain + '.conf',
            'perms' : 0644,
            'owner' : 'root',
            'group' : 'root',
        } if not atts['vhost_conf'] else atts['vhost_conf'])

        # If an apache vhost config already exists, try to parse it
        if self.files['vhost_conf'].exists():
            print self.files['vhost_conf'] + ' already exists. Parsing file...'
            parsed_atts = self.files['vhost_conf'].parse()
            if parsed_atts:
                print 'Using parsed data.'
                atts = parsed_atts

        # Convert...
        # or migrate?

        # Website htdocs directory
        self.dirs['htdocs'] = WW_Dir({
            'path'  : '/var/www/' + self.domain + '/htdocs/',
            'perms' : 0775,
            'owner' : 'www-data',
            'group' : 'www-data',
        } if not atts['htdocs'] else atts['htdocs'])

        # Website assets directory
        self.dirs['assets'] = WW_Dir({
            'path'  : '/var/www/' + self.domain + '/assets/',
            'perms' : 0775,
            'owner' : 'root',
            'group' : 'mm_admin', # TODO: Setup a user setting
        } if not atts['assets'] else atts['assets'])

        # Website Log Directory
        self.dirs['logs'] = WW_Dir({
            'path'  : '/var/www/' + self.domain + '/logs/',
            'perms' : 0775,
            'owner' : 'root',
            'group' : 'mm_admin',
        } if not atts['logs'] else atts['logs'])

        self.files['htaccess']  = WW_File({
            'path'  : self.dirs['htdocs'] + '.htaccess',
            'perms' : 0664,
            'owner' : 'www-data',
            'group' : 'www-data',
        } if not atts['htaccess'] else atts['htaccess'])

    def __str__(self):
        """Returns a string with relevant instance information."""
        string =  '\n  domain:           ' + self.domain
        string += '\n  vhost config:     ' + self.files['vhost_conf']
        string += '\n  htdocs directory: ' + self.dirs['htdocs']
        string += '\n  assets:           ' + self.dirs['assets']
        string += '\n  logs:             ' + self.dirs['logs']
        return string

    def __repr__(self):
        return self.__str__()

    ################
    # Public Methods

    def install(self):
        """Installs website to server"""
        # Check if domain is already installed
        if self.is_installed():
            print self.domain + ' is already installed.'

        # Create dirs
        for dir in self.dirs:
            dir.create()

        # Create Files
        for file in self.files:
            file.create()

        self.domain.set_ip()

    def uninstall(self):
        """Removes website from server"""
        # Remove Files
        for file in self.files:
            file.remove()

        # Remove dirs
        for dir in self.dirs:
            dir.remove()

        self.domain.set_ip()

    def verify(self, repair = False):
        """Verifies website's installation"""
        print self

        # Verify Domain
        result = self.domain.verify()

        # Verify Files
        for file in self.files:
            result = result and file.verify(repair)

        # Remove dirs
        for dir in self.dirs:
            result = result and dir.verify(repair)
        #todo: check if website is enabled
        #todo: check if apache is started
        # Check for index.html or php
        return result

    def repair(self):
        print 'Repairing ' + self.domain + '...'
        if self.verify(True):
            print 'Repair completed successfully. [OK]'
        else:
            print 'Repair resulted in errors. [ERROR]'

    def pack(self):
        pass

    def unpack(self):
        pass

    def migrate(self, old_website = None):
        if not old_website:
            old_website = self.existing
        # make sure there's nothing conflicting
        # install() new
        # this overwrites vhostconf...
        # for dirs and files in old copy
        # this includes vhost...

    def is_installed(self):
        apache_list = os.popen("apache2ctl -S | grep ' namevhost " + self.domain + " '").read()
        if apache_list == '':
            return false
        return true

    def enable(self, ask = True):
        if not ask or prompt('Enable ' + self.domain + ' in apache?'):
            self.files['vhost_conf'].enable()

    def disable(self, ask = True):
        if not ask or prompt('Disable ' + self.domain + ' in apache?'):
            self.files['vhost_conf'].disable()

    def restart(self, ask = True):
        if not ask or prompt('Restart Apache?'):
            os.system(RESTART_APACHE)
<commit_msg>Removes spurious reference to old classname.<commit_after>#!/usr/bin/env python
#
# name:             website.py
# author:           Harold Bradley III
# email:            harold@bradleystudio.net
# date:             11/11/2015
#
# description:      A class to manage websites
#

from ext_pylib.files import Dir, File
from ext_pylib.prompt import prompt, prompt_str, warn_prompt
import os
import re
import requests
import tarfile
import time
from vhost import Vhost
from website_domain import Website_Domain

# localhost(function)
#   a python decorator that creates a temporary host entry before
#   the function is called and removes it after it is completed.
def localhost(function):
    def function_wrapper(self, *args, **kwargs):
        print 'Adding temporary host entry.'
        os.system("echo '127.0.0.1 " + self.domain + "' | cat >> /etc/hosts")
        function(self, *args, **kwargs)
        print 'Removing temporary host entry.'
        os.system("sed -i '/^127\.0\.0\.1 " + self.domain + "$/d' /etc/hosts")
    return function_wrapper

# Website(website)
#   a class that describes a generic website with the following properties:
#       domain              Website domain
#       dirs['htdocs']      Website root directory
#       dirs['assets']      Website assets directory
#       dirs['logs']        Website log directory
#       files['vhost_conf'] Apache vhost config file.
#       files['htaccess']   Website root htaccess file.
#   primary methods:
#       install()
#       uninstall()
#       verify()
class Website(object):

    def __init__(self, domain, atts = { 'htdocs'     : None,
                                        'assets'     : None,
                                        'logs'       : None,
                                        'vhost_conf' : None,
                                        'htaccess'   : None,
                                       }):
        """Initializes a new Website instance."""

        print '[*] SSL not yet implemented in Website class.'

        self.domain = Website_Domain(domain)
        self.dirs = {}
        self.files = {}

        self.files['vhost_conf'] = Vhost({
            'path'  : '/etc/apache2/sites-available/' + self.domain + '.conf',
            'perms' : 0644,
            'owner' : 'root',
            'group' : 'root',
        } if not atts['vhost_conf'] else atts['vhost_conf'])

        # If an apache vhost config already exists, try to parse it
        if self.files['vhost_conf'].exists():
            print self.files['vhost_conf'] + ' already exists. Parsing file...'
            parsed_atts = self.files['vhost_conf'].parse()
            if parsed_atts:
                print 'Using parsed data.'
                atts = parsed_atts

        # Convert...
        # or migrate?

        # Website htdocs directory
        self.dirs['htdocs'] = Dir({
            'path'  : '/var/www/' + self.domain + '/htdocs/',
            'perms' : 0775,
            'owner' : 'www-data',
            'group' : 'www-data',
        } if not atts['htdocs'] else atts['htdocs'])

        # Website assets directory
        self.dirs['assets'] = Dir({
            'path'  : '/var/www/' + self.domain + '/assets/',
            'perms' : 0775,
            'owner' : 'root',
            'group' : 'mm_admin', # TODO: Setup a user setting
        } if not atts['assets'] else atts['assets'])

        # Website Log Directory
        self.dirs['logs'] = Dir({
            'path'  : '/var/www/' + self.domain + '/logs/',
            'perms' : 0775,
            'owner' : 'root',
            'group' : 'mm_admin',
        } if not atts['logs'] else atts['logs'])

        self.files['htaccess']  = File({
            'path'  : self.dirs['htdocs'] + '.htaccess',
            'perms' : 0664,
            'owner' : 'www-data',
            'group' : 'www-data',
        } if not atts['htaccess'] else atts['htaccess'])

    def __str__(self):
        """Returns a string with relevant instance information."""
        string =  '\n  domain:           ' + self.domain
        string += '\n  vhost config:     ' + self.files['vhost_conf']
        string += '\n  htdocs directory: ' + self.dirs['htdocs']
        string += '\n  assets:           ' + self.dirs['assets']
        string += '\n  logs:             ' + self.dirs['logs']
        return string

    def __repr__(self):
        return self.__str__()

    ################
    # Public Methods

    def install(self):
        """Installs website to server"""
        # Check if domain is already installed
        if self.is_installed():
            print self.domain + ' is already installed.'

        # Create dirs
        for dir in self.dirs:
            dir.create()

        # Create Files
        for file in self.files:
            file.create()

        self.domain.set_ip()

    def uninstall(self):
        """Removes website from server"""
        # Remove Files
        for file in self.files:
            file.remove()

        # Remove dirs
        for dir in self.dirs:
            dir.remove()

        self.domain.set_ip()

    def verify(self, repair = False):
        """Verifies website's installation"""
        print self

        # Verify Domain
        result = self.domain.verify()

        # Verify Files
        for file in self.files:
            result = result and file.verify(repair)

        # Remove dirs
        for dir in self.dirs:
            result = result and dir.verify(repair)
        #todo: check if website is enabled
        #todo: check if apache is started
        # Check for index.html or php
        return result

    def repair(self):
        print 'Repairing ' + self.domain + '...'
        if self.verify(True):
            print 'Repair completed successfully. [OK]'
        else:
            print 'Repair resulted in errors. [ERROR]'

    def pack(self):
        pass

    def unpack(self):
        pass

    def migrate(self, old_website = None):
        if not old_website:
            old_website = self.existing
        # make sure there's nothing conflicting
        # install() new
        # this overwrites vhostconf...
        # for dirs and files in old copy
        # this includes vhost...

    def is_installed(self):
        apache_list = os.popen("apache2ctl -S | grep ' namevhost " + self.domain + " '").read()
        if apache_list == '':
            return false
        return true

    def enable(self, ask = True):
        if not ask or prompt('Enable ' + self.domain + ' in apache?'):
            self.files['vhost_conf'].enable()

    def disable(self, ask = True):
        if not ask or prompt('Disable ' + self.domain + ' in apache?'):
            self.files['vhost_conf'].disable()

    def restart(self, ask = True):
        if not ask or prompt('Restart Apache?'):
            os.system(RESTART_APACHE)
<|endoftext|>