Exemple #1
0
    def project_initialization(self, instances):
        """ Custom initialization of your project

        Please define your class Initializer in
        vanilla/project/initialization.py
        """

        try:
            meta = Meta()
            module_path = "%s.%s.%s" % \
                (CUSTOM_PACKAGE, 'initialization', 'initialization')
            module = meta.get_module_from_string(
                module_path,
                debug_on_fail=False,
            )
            Initializer = meta.get_class_from_string('Initializer',
                                                     module,
                                                     skip_error=True)
            if Initializer is None:
                log.debug("No custom init available")
            else:
                try:
                    Initializer(instances)
                except BaseException as e:
                    log.error("Errors during custom initialization: %s", e)
                else:
                    log.info("Vanilla project has been initialized")

        except BaseException:
            log.debug("No custom init available")
    def get(self, test_num, task_id=None):
        celery = self.get_service_instance('celery')

        meta = Meta()
        methods = meta.get_methods_inside_instance(self)
        method_name = "test_%s" % test_num
        if method_name not in methods:
            raise RestApiException("Test %d not found" % test_num)
        method = methods[method_name]
        out = method(celery, task_id)
        return self.force_response(out)
Exemple #3
0
    def __init__(self, config_file_name='services'):

        self.authentication_service = None
        self.authentication_name = 'authentication'
        self.task_service_name = 'celery'
        self.modules = []
        self.services_configuration = []
        self.services = {}
        self.services_classes = {}
        self.extensions_instances = {}
        self.available_services = {}
        self.meta = Meta()
        self.check_configuration(config_file_name)
        self.load_classes()
Exemple #4
0
    def custom_connection(self, **kwargs):

        if len(kwargs) > 0:
            print("TODO: use args for connection?", kwargs)

        uri = 'postgresql://%s:%s@%s:%s/%s' % (
            self.variables.get('user'), self.variables.get('password'),
            self.variables.get('host'), self.variables.get('port'),
            self.variables.get('db'))

        log.very_verbose("URI IS %s" % re_obscure_pattern(uri))

        # TODO: in case we need different connection binds
        # (multiple connections with sql) then:
        # SQLALCHEMY_BINDS = {
        #     'users':        'mysqldb://localhost/users',
        #     'appmeta':      'sqlite:////path/to/appmeta.db'
        # }

        self.app.config['SQLALCHEMY_POOL_TIMEOUT'] = 3
        self.app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
        self.app.config['SQLALCHEMY_DATABASE_URI'] = uri

        pool_size = self.variables.get('poolsize')
        if pool_size is not None:
            # sqlalchemy docs: http://j.mp/2xT0GOc
            # defaults: overflow=10, pool_size=5
            self.app.config['SQLALCHEMY_MAX_OVERFLOW'] = 0
            self.app.config['SQLALCHEMY_POOL_SIZE'] = int(pool_size)

        obj_name = 'db'
        m = Meta()
        # search the original sqlalchemy object into models
        db = m.obj_from_models(obj_name, self.name, CUSTOM_PACKAGE)
        if db is None:
            log.warning("No sqlalchemy db imported in custom package")
            db = m.obj_from_models(obj_name, self.name, BACKEND_PACKAGE)
        if db is None:
            log.critical_exit("Could not get %s within %s models" %
                              (obj_name, self.name))

        return db
Exemple #5
0
    def custom_post_handle_user_input(self, user_node, input_data):
        meta = Meta()
        module_path = "%s.%s.%s" % \
            (CUSTOM_PACKAGE, 'initialization', 'initialization')
        module = meta.get_module_from_string(
            module_path,
            debug_on_fail=False,
        )

        Customizer = meta.get_class_from_string('Customizer',
                                                module,
                                                skip_error=True)
        if Customizer is None:
            log.debug("No user properties customizer available")
        else:
            try:
                Customizer().custom_post_handle_user_input(
                    self, user_node, input_data)
            except BaseException as e:
                log.error("Unable to customize user properties: %s", e)
Exemple #6
0
    def __init__(self, testing=False, production=False, init=False):

        # Input
        self._testing = testing
        self._production = production
        self._initiliazing = init

        # Some initialization
        self._endpoints = []
        self._definitions = {}
        self._configurations = {}
        self._query_params = {}
        self._schemas_map = {}
        self._meta = Meta()

        # Do things
        self.read_configuration()

        if not self._initiliazing:
            self.do_schema()
            self.find_endpoints()
            self.do_swagger()
Exemple #7
0
    def custom_user_properties(self, userdata):
        meta = Meta()
        module_path = "%s.%s.%s" % \
            (CUSTOM_PACKAGE, 'initialization', 'initialization')
        module = meta.get_module_from_string(
            module_path,
            debug_on_fail=False,
        )

        Customizer = meta.get_class_from_string('Customizer',
                                                module,
                                                skip_error=True)
        if Customizer is None:
            log.debug("No user properties customizer available")
        else:
            try:
                userdata = Customizer().custom_user_properties(userdata)
            except BaseException as e:
                log.error("Unable to customize user properties: %s", e)

        if "email" in userdata:
            userdata["email"] = userdata["email"].lower()

        return userdata
        def decorated(*args, **kwargs):

            # Recover the auth object
            auth_type, token = self.get_auth_from_header()
            # Base header for errors
            headers = {HTTPAUTH_AUTH_HEADER: self.authenticate_header()}
            # Internal API 'self' reference
            decorated_self = Meta.get_self_reference_from_args(*args)

            if auth_type is None or auth_type.lower() != self._scheme.lower():
                # Wrong authentication string
                msg = "Valid credentials have to be provided " + \
                      "inside Headers, e.g. %s: '%s %s'" % \
                      (HTTPAUTH_AUTH_FIELD, HTTPAUTH_DEFAULT_SCHEME, 'TOKEN')
                #
                return decorated_self.send_errors(
                    # label="No authentication schema",
                    message=msg,
                    headers=headers,
                    code=hcodes.HTTP_BAD_UNAUTHORIZED)

            # Handling OPTIONS forwarded to our application:
            # ignore headers and let go, avoid unwanted interactions with CORS
            if request.method != 'OPTIONS':

                # Check authentication
                token_fn = decorated_self.auth.verify_token
                if not self.authenticate(token_fn, token):
                    # Clear TCP receive buffer of any pending data
                    request.data
                    # Mimic the response from a normal endpoint
                    # To use the same standards
                    return decorated_self.send_errors(
                        message="Invalid token received '%s'" % token,
                        headers=headers,
                        code=hcodes.HTTP_BAD_UNAUTHORIZED)

            # Check roles
            if len(roles) > 0:
                roles_fn = decorated_self.auth.verify_roles
                if not self.authenticate_roles(roles_fn, roles,
                                               required_roles):
                    return decorated_self.send_errors(
                        message="You are not authorized: missing privileges",
                        code=hcodes.HTTP_BAD_UNAUTHORIZED)

            return f(*args, **kwargs)
Exemple #9
0
class Customizer(object):
    """
    Customize your BACKEND:
    Read all of available configurations and definitions.
    """

    def __init__(self, testing=False, production=False, init=False):

        # Input
        self._testing = testing
        self._production = production
        self._initiliazing = init

        # Some initialization
        self._endpoints = []
        self._definitions = {}
        self._configurations = {}
        self._query_params = {}
        self._schemas_map = {}
        self._meta = Meta()

        # Do things
        self.read_configuration()

        if not self._initiliazing:
            self.do_schema()
            self.find_endpoints()
            self.do_swagger()

    def read_configuration(self):
        ##################
        # Reading configuration

        default_file_path = helpers.current_dir(CONF_PATH)
        project_file_path = helpers.current_dir(CONF_PATH)
        self._configurations = configuration.read(
            default_file_path,
            project_path=project_file_path,
        )

    def do_schema(self):
        """ Schemas exposing, if requested """

        name = '%s.%s.%s' % (BACKEND_PACKAGE, 'rest', 'schema')
        module = self._meta.get_module_from_string(
            name,
            exit_if_not_found=True
        )
        schema_class = getattr(module, 'RecoverSchema')

        self._schema_endpoint = EndpointElements(
            cls=schema_class,
            exists=True,
            custom={
                'methods': {
                    'get': ExtraAttributes(auth=None),
                    # WHY DOES POST REQUEST AUTHENTICATION
                    # 'post': ExtraAttributes(auth=None)
                }
            },
            methods={}
        )

        # TODO: find a way to map authentication
        # as in the original endpoint for the schema 'get' method

        # TODO: find a way to publish on swagger the schema
        # if endpoint is enabled to publish and the developer asks for it

    def find_endpoints(self):

        ##################
        # Walk swagger directories looking for endpoints

        # FIXME: how to do this?
        # from utilities import helpers
        # custom_dir = helpers.current_dir(CUSTOM_PACKAGE)
        # base_dir = helpers.script_abspath(__file__)

        base_swagger_confdir = helpers.script_abspath(__file__)
        custom_swagger_confdir = helpers.current_dir(CUSTOM_PACKAGE)

        # for base_dir in [BACKEND_PACKAGE, CUSTOM_PACKAGE]:
        for base_dir in [base_swagger_confdir, custom_swagger_confdir]:

            swagger_dir = os.path.join(base_dir, 'swagger')
            log.verbose("Swagger dir: %s" % swagger_dir)

            for ep in os.listdir(swagger_dir):

                swagger_endpoint_dir = os.path.join(swagger_dir, ep)

                if os.path.isfile(swagger_endpoint_dir):
                    exception = '%s.yaml' % SWAGGER_MODELS_FILE
                    if not swagger_endpoint_dir.endswith('/' + exception):
                        log.debug(
                            "Found a file instead of a folder: %s",
                            swagger_endpoint_dir
                        )
                    continue

                # isbase = base_dir == BACKEND_PACKAGE
                isbase = base_dir.startswith('/usr/local')
                base_module = helpers.last_dir(base_dir)
                from utilities import ENDPOINTS_CODE_DIR
                if isbase:
                    apiclass_module = '%s.%s' % (base_module, 'resources')
                else:
                    apiclass_module = '%s.%s' % (
                        base_module, ENDPOINTS_CODE_DIR)

                current = self.lookup(
                    ep, apiclass_module, swagger_endpoint_dir, isbase)
                if current is not None and current.exists:
                    # Add endpoint to REST mapping
                    self._endpoints.append(current)

    def do_swagger(self):

        # SWAGGER read endpoints definition
        swag = BeSwagger(self._endpoints, self)
        swag_dict = swag.swaggerish()

        # TODO: update internal endpoints from swagger
        self._endpoints = swag._endpoints[:]

        # SWAGGER validation
        if not swag.validation(swag_dict):
            log.critical_exit("Current swagger definition is invalid")

        self._definitions = swag_dict

    def read_frameworks(self):

        file = os.path.join("config", "frameworks.yaml")
        self._frameworks = load_yaml_file(file)

    def lookup(self, endpoint, apiclass_module, swagger_endpoint_dir, isbase):

        log.verbose("Found endpoint dir: '%s'" % endpoint)

        if os.path.exists(os.path.join(swagger_endpoint_dir, 'SKIP')):
            log.info("Skipping: %s", endpoint)
            return None

        # Find yaml files
        conf = None
        yaml_files = {}
        yaml_listing = os.path.join(swagger_endpoint_dir, "*.%s" % YAML_EXT)

        for file in glob.glob(yaml_listing):
            if file.endswith('specs.%s' % YAML_EXT):
                # load configuration and find file and class
                conf = load_yaml_file(file)
            else:
                # add file to be loaded from swagger extension
                p = re.compile(r'\/([^\.\/]+)\.' + YAML_EXT + '$')
                match = p.search(file)
                method = match.groups()[0]
                yaml_files[method] = file

        if len(yaml_files) < 1:
            raise Exception("%s: no methods defined in any YAML" % endpoint)
        if conf is None or 'class' not in conf:
            raise ValueError("No 'class' defined for '%s'" % endpoint)

        current = self.load_endpoint(endpoint, apiclass_module, conf, isbase)
        current.methods = yaml_files
        return current

    # def read_complex_config(self, configfile):
    #     """ A more complex configuration is available in JSON format """
    #     content = {}
    #     with open(configfile) as fp:
    #         content = json.load(fp)
    #     return content

    def load_endpoint(self, default_uri, apiclass_module, conf, isbase):

        endpoint = EndpointElements(custom={})

        # Load the endpoint class defined in the YAML file
        file_name = conf.pop('file', default_uri)
        class_name = conf.pop('class')
        name = '%s.%s' % (apiclass_module, file_name)
        module = self._meta.get_module_from_string(name, exit_on_fail=False)

        # Error if unable to find the module in python
        if module is None:
            log.critical_exit(
                "Could not find module %s (in %s)" % (name, file_name))

        # Check for dependecies and skip if missing
        from restapi.services.detect import detector

        for var in conf.pop('depends_on', []):

            negate = ''
            pieces = var.strip().split(' ')
            pieces_num = len(pieces)
            if pieces_num == 1:
                dependency = pieces.pop()
            elif pieces_num == 2:
                negate, dependency = pieces
            else:
                log.exit('Wrong parameter: %s', var)

            check = detector.get_bool_from_os(dependency)
            # Enable the possibility to depend on not having a variable
            if negate.lower() == 'not':
                check = not check

            # Skip if not meeting the requirements of the dependency
            if not check:
                if not self._testing:
                    log.warning("Skip '%s': unmet %s", default_uri, dependency)
                return endpoint

        # Get the class from the module
        endpoint.cls = self._meta.get_class_from_string(class_name, module)
        if endpoint.cls is None:
            log.critical("Could not extract python class '%s'", class_name)
            return endpoint
        else:
            endpoint.exists = True

        # Is this a base or a custom class?
        endpoint.isbase = isbase

        # DEPRECATED
        # endpoint.instance = endpoint.cls()

        # Global tags
        # to be applied to all methods
        endpoint.tags = conf.pop('labels', [])

        # base URI
        base = conf.pop('baseuri', API_URL)
        if base not in BASE_URLS:
            log.warning("Invalid base %s", base)
            base = API_URL
        base = base.strip('/')

        #####################
        # MAPPING
        schema = conf.pop('schema', {})
        mappings = conf.pop('mapping', [])
        if len(mappings) < 1:
            raise KeyError("Missing 'mapping' section")

        endpoint.uris = {}  # attrs python lib bug?
        endpoint.custom['schema'] = {
            'expose': schema.get('expose', False),
            'publish': {}
        }
        for label, uri in mappings.items():

            # BUILD URI
            total_uri = '/%s%s' % (base, uri)
            endpoint.uris[label] = total_uri

            # If SCHEMA requested create
            if endpoint.custom['schema']['expose']:

                schema_uri = '%s%s%s' % (API_URL, '/schemas', uri)

                p = hex(id(endpoint.cls))
                self._schema_endpoint.uris[label + p] = schema_uri

                endpoint.custom['schema']['publish'][label] = \
                    schema.get('publish', False)

                self._schemas_map[schema_uri] = total_uri

        # Description for path parameters
        endpoint.ids = conf.pop('ids', {})

        # Check if something strange is still in configuration
        if len(conf) > 0:
            raise KeyError("Unwanted keys: %s" % list(conf.keys()))

        return endpoint
def test_failures():
    meta = Meta()

    try:
        meta.import_submodules_from_package("utilities_bla")
    except AttributeError:
        pass
    else:
        pytest.fail("This call should fail and raise and AttributeError")

    try:
        meta.import_submodules_from_package("utilities_bla",
                                            exit_if_not_found=True)
    except AttributeError:
        pass
    else:
        pytest.fail("This call should fail and raise and AttributeError")

    try:
        meta.import_submodules_from_package("utilities_bla",
                                            exit_if_not_found=True,
                                            exit_on_fail=True)
    except AttributeError:
        pass
    else:
        pytest.fail("This call should fail and raise and AttributeError")

    try:
        meta.import_submodules_from_package("utilities_bla", exit_on_fail=True)
    except AttributeError:
        pass
    else:
        pytest.fail("This call should fail and raise and AttributeError")

    module = meta.get_module_from_string("utilities.metabla")
    assert module is None

    try:
        module = meta.get_module_from_string("utilities.metabla",
                                             exit_if_not_found=True)
    except SystemExit:
        pass
    else:
        pytest.fail("This call should fail and exit")

    try:
        module = meta.get_module_from_string("utilities.metabla",
                                             exit_if_not_found=True,
                                             exit_on_fail=True)
    except SystemExit:
        pass
    else:
        pytest.fail("This call should fail and exit")

    try:
        module = meta.get_module_from_string("utilities.metabla",
                                             exit_if_not_found=True,
                                             exit_on_fail=True,
                                             debug_on_fail=True)
    except SystemExit:
        pass
    else:
        pytest.fail("This call should fail and exit")

    # FIXME: unable to test exit_on_fail... we need a moule with import errors?
    module = meta.get_module_from_string("utilities.metabla",
                                         exit_on_fail=True)
    assert module is None

    module = meta.get_module_from_string("utilities.metabla",
                                         debug_on_fail=True)
    assert module is None
def test():
    meta = Meta()
    cls_name = "Meta"

    import utilities
    modules = meta.get_submodules_from_package(utilities)
    assert "meta" in modules

    sub_modules = meta.import_submodules_from_package("utilities")
    assert sub_modules is not None
    assert isinstance(sub_modules, list)
    assert len(sub_modules) > 0

    module = meta.get_module_from_string("utilities.meta")
    assert module is not None
    assert hasattr(module, cls_name)

    classes = meta.get_classes_from_module(module)
    assert classes is not None
    assert isinstance(classes, dict)
    assert cls_name in classes
    assert classes[cls_name].__name__ == cls_name

    new_classes = meta.get_new_classes_from_module(module)
    assert new_classes is not None
    assert isinstance(new_classes, dict)
    assert cls_name in new_classes
    assert new_classes[cls_name].__name__ == cls_name

    meta_class = meta.get_class_from_string(cls_name, module)
    assert meta_class is not None
    assert meta_class.__name__ == cls_name

    methods = meta.get_methods_inside_instance(meta, private_methods=True)
    assert methods is not None
    assert isinstance(methods, dict)
    # it is a static method
    assert "get_methods_inside_instance" not in methods
    assert "get_authentication_module" in methods

    metacls = meta.metaclassing(Meta, "Test")
    assert metacls is not None
    assert metacls.__name__ == "Test"

    self_ref = meta.get_self_reference_from_args(meta, "test", 1)
    assert self_ref == meta
Exemple #12
0
class Detector(object):
    def __init__(self, config_file_name='services'):

        self.authentication_service = None
        self.authentication_name = 'authentication'
        self.task_service_name = 'celery'
        self.modules = []
        self.services_configuration = []
        self.services = {}
        self.services_classes = {}
        self.extensions_instances = {}
        self.available_services = {}
        self.meta = Meta()
        self.check_configuration(config_file_name)
        self.load_classes()

    @staticmethod
    def get_global_var(key, default=None):
        return os.environ.get(key, default)

    @staticmethod
    @lru_cache(maxsize=None)  # avoid calling it twice for the same var
    def get_bool_from_os(name):

        bool_var = os.environ.get(name, False)
        if isinstance(bool_var, bool):
            return bool_var

        # if not directly a bool, try an interpretation
        # INTEGERS
        try:
            tmp = int(bool_var)
            return bool(tmp)
        except ValueError:
            pass

        # STRINGS
        # any non empty string with a least one char
        # has to be considered True
        if isinstance(bool_var, str) and len(bool_var) > 0:
            return True

        return False

    @staticmethod
    # @lru_cache(maxsize=None)
    def prefix_name(service):
        return \
            service.get('name'), \
            service.get('prefix').lower() + '_'

    def check_configuration(self, config_file_name):

        self.services_configuration = load_yaml_file(
            file=config_file_name,
            path=os.path.join(helpers.script_abspath(__file__), '..', '..',
                              CORE_CONFIG_PATH),
            logger=True)

        for service in self.services_configuration:

            name, prefix = self.prefix_name(service)

            # Was this service enabled from the developer?
            enable_var = str(prefix + 'enable').upper()
            self.available_services[name] = self.get_bool_from_os(enable_var)

            if self.available_services[name]:

                # read variables
                variables = self.load_variables(service, enable_var, prefix)
                service['variables'] = variables

                # set auth service
                if name == self.authentication_name:
                    self.authentication_service = variables.get('service')

        # log.pp(self.services_configuration)

        if self.authentication_service is None:
            raise AttributeError("no service defined behind authentication")
        else:
            log.info("Authentication based on '%s' service" %
                     self.authentication_service)

    def load_group(self, label):

        variables = {}
        for var, value in os.environ.items():
            var = var.lower()
            if var.startswith(label):
                key = var[len(label):].strip('_')
                value = value.strip('"').strip("'")
                variables[key] = value
        return variables

    def output_service_variables(self, service_name):
        service_class = self.services_classes.get(service_name, {})
        try:
            return service_class.variables
        except BaseException:
            return {}

    @staticmethod
    def load_group(label):
        from utilities.basher import detect_vargroup
        return detect_vargroup(label)

    def output_service_variables(self, service_name):
        service_class = self.services_classes.get(service_name, {})
        try:
            return service_class.variables
        except BaseException:
            return {}

    @staticmethod
    def load_variables(service, enable_var=None, prefix=None):

        variables = {}
        host = None

        if prefix is None:
            _, prefix = Detector.prefix_name(service)

        for var, value in os.environ.items():
            if enable_var is not None and var == enable_var:
                continue
            var = var.lower()

            # This is the case when a variable belongs to a service 'prefix'
            if var.startswith(prefix):

                # Fix key and value before saving
                key = var[len(prefix):]
                # One thing that we must avoid is any quote around our value
                value = value.strip('"').strip("'")
                # save
                variables[key] = value

                if key == 'host':
                    host = value

        # Verify if service is EXTERNAL
        variables['external'] = False
        if isinstance(host, str):  # and host.count('.') > 2:
            if not host.endswith('dockerized.io'):
                variables['external'] = True
                log.very_verbose("Service %s detected as external:\n%s" %
                                 (service, host))

        return variables

    def load_class_from_module(self, classname='BaseInjector', service=None):

        if service is None:
            flaskext = ''
        else:
            flaskext = '.' + service.get('extension')

        # Try inside our extensions
        module = self.meta.get_module_from_string(
            modulestring=BACKEND_PACKAGE + '.flask_ext' + flaskext,
            exit_on_fail=True)
        if module is None:
            log.critical_exit("Missing %s for %s" % (flaskext, service))

        return getattr(module, classname)

    def load_classes(self):

        for service in self.services_configuration:

            name, _ = self.prefix_name(service)

            if not self.available_services.get(name):
                continue
            log.very_verbose("Looking for class %s" % name)

            variables = service.get('variables')
            ext_name = service.get('class')

            # Get the existing class
            MyClass = self.load_class_from_module(ext_name, service=service)

            try:
                # Passing variables
                MyClass.set_variables(variables)

                # Passing models
                if service.get('load_models'):
                    MyClass.set_models(
                        self.meta.import_models(name, custom=False),
                        self.meta.import_models(name,
                                                custom=True,
                                                exit_on_fail=False))
                else:
                    log.very_verbose("Skipping models")

            except AttributeError:
                log.critical_exit('Extension class %s ' % ext_name +
                                  'not compliant: missing method(s)' +
                                  'Did you extend "%s"?' % 'BaseExtension')

            # Save
            self.services_classes[name] = MyClass
            log.debug("Got class definition for %s", MyClass)

        if len(self.services_classes) < 1:
            raise KeyError("No classes were recovered!")

        return self.services_classes

    def init_services(self,
                      app,
                      worker_mode=False,
                      project_init=False,
                      project_clean=False):

        instances = {}
        auth_backend = None

        for service in self.services_configuration:

            name, _ = self.prefix_name(service)

            if not self.available_services.get(name):
                continue

            if name == self.authentication_name and auth_backend is None:
                if self.authentication_service is None:
                    log.warning("No authentication")
                    continue
                else:
                    log.exit("Auth service '%s' seems unreachable" %
                             self.authentication_service)

            args = {}
            if name == self.task_service_name:
                args['worker_mode'] = worker_mode

            # Get extension class and build the extension object
            ExtClass = self.services_classes.get(name)
            try:
                ext_instance = ExtClass(app, **args)
            except TypeError as e:
                log.critical_exit('Your class %s is not compliant:\n%s' %
                                  (name, e))
            else:
                self.extensions_instances[name] = ext_instance

            # Initialize the real service getting the first service object
            log.debug("Initializing %s", name)
            service_instance = ext_instance.custom_init(pinit=project_init,
                                                        pdestroy=project_clean,
                                                        abackend=auth_backend)
            instances[name] = service_instance

            if name == self.authentication_service:
                auth_backend = service_instance

            self.extensions_instances[name] = ext_instance

            # Injecting into the Celery Extension Class
            # all celery tasks found in *vanilla_package/tasks*
            if name == self.task_service_name:

                task_package = "%s.tasks" % CUSTOM_PACKAGE

                submodules = self.meta.import_submodules_from_package(
                    task_package, exit_on_fail=True)
                for submodule in submodules:
                    tasks = self.meta.get_celery_tasks_from_module(submodule)

                    for func_name, funct in tasks.items():
                        setattr(ExtClass, func_name, funct)

        if len(self.extensions_instances) < 1:
            raise KeyError("No instances available for modules")

        # Only once in a lifetime
        if project_init:
            self.project_initialization(instances)

        return self.extensions_instances

    def load_injector_modules(self):

        for service in self.services_configuration:

            name, _ = self.prefix_name(service)
            if not self.available_services.get(name):
                continue

            # Module for injection
            ModuleBaseClass = self.load_class_from_module()
            # Create modules programmatically 8)
            MyModule = self.meta.metaclassing(ModuleBaseClass,
                                              service.get('injector'))

            # Recover class
            MyClass = self.services_classes.get(name)
            if MyClass is None:
                raise AttributeError("No class found for %s" % name)
            MyModule.set_extension_class(MyClass)
            self.modules.append(MyModule)

        return self.modules

    def check_availability(self, name):

        if '.' in name:
            # In this case we are receiving a module name
            # e.g. restapi.services.mongodb
            name = name.split('.')[::-1][0]

        return self.available_services.get(name)

    @classmethod
    def project_initialization(self, instances):
        """ Custom initialization of your project

        Please define your class Initializer in
        vanilla/project/initialization.py
        """

        try:
            meta = Meta()
            module_path = "%s.%s.%s" % \
                (CUSTOM_PACKAGE, 'initialization', 'initialization')
            module = meta.get_module_from_string(
                module_path,
                debug_on_fail=False,
            )
            Initializer = meta.get_class_from_string('Initializer',
                                                     module,
                                                     skip_error=True)
            if Initializer is None:
                log.debug("No custom init available")
            else:
                try:
                    Initializer(instances)
                except BaseException as e:
                    log.error("Errors during custom initialization: %s", e)
                else:
                    log.info("Vanilla project has been initialized")

        except BaseException:
            log.debug("No custom init available")
Exemple #13
0
from utilities.logs import get_logger

log = get_logger(__name__)

################################################
# Reload Flask app code also for the worker
# This is necessary to have the app context available
app = create_app(worker_mode=True)

celery_app = app.extensions.get('celery').celery_app
celery_app.app = app


def get_service(service):
    return celery_app.app.extensions.get(service).get_instance()


celery_app.get_service = get_service

################################################
# Import tasks modules to make sure all tasks are available

meta = Meta()
# main_package = "commons.tasks."
# # Base tasks
# submodules = meta.import_submodules_from_package(main_package + "base")
# # Custom tasks
submodules = meta.import_submodules_from_package("%s.tasks" % CUSTOM_PACKAGE)

log.debug("Celery worker is ready %s", celery_app)
class BaseExtension(metaclass=abc.ABCMeta):

    models = {}  # I get models on a cls level, instead of instances
    meta = Meta()

    def __init__(self, app=None, **kwargs):

        self.objs = {}
        self.set_name()
        self.args = kwargs

        self.app = app
        if app is not None:
            self.init_app(app)

    def set_name(self):
        """ a different name for each extended object """
        self.name = self.__class__.__name__.lower()
        log.very_verbose("Opening service instance of %s" % self.name)

    @classmethod
    def set_models(cls, base_models, custom_models):

        # Join models as described by issue #16
        cls.models = base_models
        for key, model in custom_models.items():

            # Verify if overriding
            if key in base_models.keys():
                original_model = base_models[key]
                # Override
                if issubclass(model, original_model):
                    log.very_verbose("Overriding model %s" % key)
                    cls.models[key] = model
                    continue

            # Otherwise just append
            cls.models[key] = model

        if len(cls.models) > 0:
            log.verbose("Loaded models")

    @classmethod
    def set_variables(cls, envvars):
        cls.variables = envvars

    def init_app(self, app):
        app.teardown_appcontext(self.teardown)

    def pre_object(self, ref, key):
        """ Make sure reference and key are strings """

        if ref is None:
            ref = self.__class__.__name__
        elif isinstance(ref, object):
            ref = ref.__class__.__name__
        elif not isinstance(ref, str):
            ref = str(ref)

        if not isinstance(key, str):
            key = str(key)

        return ref + key

    def set_object(self, obj, key='[]', ref=None):
        """ set object into internal array """

        h = self.pre_object(ref, key)
        self.objs[h] = obj
        return obj

    def get_object(self, key='[]', ref=None):
        """ recover object if any """

        h = self.pre_object(ref, key)
        obj = self.objs.get(h, None)
        return obj

    def connect(self, **kwargs):

        obj = None

        # BEFORE
        ok = self.pre_connection(**kwargs)

        if not ok:
            log.critical("Unable to make preconnection for %s", self.name)
            return obj

        # Try until it's connected
        if len(kwargs) > 0:
            obj = self.custom_connection(**kwargs)
        else:
            obj = self.retry()
            log.info("Connected! %s", self.name)

        # AFTER
        self.post_connection(obj, **kwargs)

        obj.connection_time = datetime.now()
        return obj

    def set_models_to_service(self, obj):

        if len(self.models) < 1 and self.__class__.__name__ == 'NeoModel':
            raise Exception()

        for name, model in self.models.items():
            # Save attribute inside class with the same name
            log.very_verbose("Injecting model '%s'" % name)
            setattr(obj, name, model)
            obj.models = self.models

        return obj

    def set_connection_exception(self):
        return None

    def retry(self, retry_interval=3, max_retries=-1):
        retry_count = 0

        # Get the exception which will signal a missing connection
        exceptions = self.set_connection_exception()
        if exceptions is None:
            exceptions = (BaseException, )

        while max_retries != 0 or retry_count < max_retries:

            retry_count += 1
            if retry_count > 1:
                log.verbose("testing again in %s secs", retry_interval)

            try:
                obj = self.custom_connection()
            except exceptions as e:
                log.error("Catched: %s(%s)", e.__class__.__name__, e)
                # NOTE: if you critical_exit uwsgi will not show this line
                log.critical("Service '%s' not available", self.name)
                log.exit()
                # raise e
            else:
                break

            # Increment sleeps time if doing a lot of retries
            if retry_count % 3 == 0:
                log.debug("Incrementing interval")
                retry_interval += retry_interval

        return obj

    def teardown(self, exception):
        ctx = stack.top
        if self.get_object(ref=ctx) is not None:
            self.close_connection(ctx)

    def get_instance(self, **kwargs):

        # Parameters
        global_instance = kwargs.pop('global_instance', False)
        isauth = kwargs.pop('authenticator', False)
        cache_expiration = kwargs.pop('cache_expiration', None)
        # pinit = kwargs('project_initialization', False)

        # Variables
        obj = None
        ctx = stack.top
        ref = self
        unique_hash = str(sorted(kwargs.items()))
        log.very_verbose("instance hash: %s" % unique_hash)

        # When not using the context, this is the first connection
        if ctx is None:
            # First connection, before any request
            obj = self.connect()
            if obj is None:
                return None
            # self.initialization(obj=obj)
            self.set_object(obj=obj, ref=ref)

            log.verbose("First connection for %s" % self.name)

        else:
            # isauth = 'Authenticator' == self.__class__.__name__

            if not isauth:
                if not global_instance:
                    ref = ctx

                obj = self.get_object(ref=ref, key=unique_hash)

            if obj is not None and cache_expiration is not None:
                now = datetime.now()
                exp = timedelta(seconds=cache_expiration)

                if now < obj.connection_time + exp:
                    log.very_verbose("Cache still fresh for %s" % (self))
                else:
                    log.warning("Cache expired for %s", (self))
                    obj = None

            if obj is None:
                obj = self.connect(**kwargs)
                if obj is None:
                    return None
                self.set_object(obj=obj, ref=ref, key=unique_hash)
            else:
                pass

            log.very_verbose("Instance %s(%s)" % (ref.__class__.__name__, obj))

        obj = self.set_models_to_service(obj)

        return obj

    ############################
    # OPTIONALLY
    # to be executed only at init time?

    def pre_connection(self, **kwargs):
        return True

    def post_connection(self, obj=None, **kwargs):
        return True

    def close_connection(self, ctx):
        """ override this method if you must close
        your connection after each request"""

        # obj = self.get_object(ref=ctx)
        # obj.close()
        self.set_object(obj=None, ref=ctx)  # it could be overidden

    ############################
    # To be overridden
    @abc.abstractmethod
    def custom_connection(self, **kwargs):
        return

    ############################
    # Already has default
    def custom_init(self, **kwargs):
        """
        The real signature:
        def custom_init(self, pinit=False, pdestroy=False, abackend=None):

            - A backend is needed for non-standalone services
                e.g. authentication module
            - Project initialization/removal could be used here
                or carried on to low levels;
                they get activated by specific flask cli commands

        """
        return self.get_instance()
Exemple #15
0
"""
Take care of authenticatin with External Service with Oauth2 protocol.

Testend against GitHub, then worked off B2ACCESS (EUDAT oauth service)
"""

import os
from base64 import b64encode
from restapi.protocols.oauth import oauth
# from restapi.confs import PRODUCTION
from utilities.globals import mem
from utilities.meta import Meta
from utilities.logs import get_logger

log = get_logger(__name__)
meta = Meta()

B2ACCESS_MAIN_PORT = 8443
B2ACCESS_CA_PORT = 8445
B2ACCESS_URLS = {
    'development': 'unity.eudat-aai.fz-juelich.de',
    'staging': 'b2access-integration.fz-juelich.de',
    'production': 'b2access.eudat.eu',
}


class ExternalLogins(object):

    _available_services = {}

    def __init__(self):