def test_meta(self) -> None: # This is a valid package containing other packages... but no task will be found tasks = Meta.get_celery_tasks("restapi.utilities") assert isinstance(tasks, list) assert len(tasks) == 0 tasks = Meta.get_celery_tasks("this-should-not-exist") assert isinstance(tasks, list) assert len(tasks) == 0 mcls = Meta.get_classes_from_module( "this-should-not-exist") # type: ignore assert isinstance(mcls, dict) assert len(mcls) == 0 assert not Meta.get_module_from_string("this-should-not-exist") try: Meta.get_module_from_string( "this-should-not-exist", exit_on_fail=True, ) pytest.fail("ModuleNotFoundError not raised") # pragma: no cover except ModuleNotFoundError: pass # This method is not very robust... but... let's test the current implementation # It basicaly return the first args if it is an instance of some classes assert not Meta.get_self_reference_from_args() selfref = Meta.get_self_reference_from_args("test") assert selfref == "test" models = Meta.import_models("this-should", "not-exist", mandatory=False) assert isinstance(models, dict) assert len(models) == 0 try: Meta.import_models("this-should", "not-exist", mandatory=True) pytest.fail("SystemExit not raised") # pragma: no cover except SystemExit: pass # Check exit_on_fail default value models = Meta.import_models("this-should", "not-exist") assert isinstance(models, dict) assert len(models) == 0 assert Meta.get_instance("invalid.path", "InvalidClass") is None assert Meta.get_instance("customization", "InvalidClass") is None assert Meta.get_instance("customization", "Customizer") is not None
def project_initialization(self, instances, app=None): """ Custom initialization of your project Please define your class Initializer in project/YOURPROJECT/backend/initialization/initialization.py """ try: # NOTE: this might be a pattern # see in meta.py:get_customizer_class module_path = "{}.{}.{}".format( CUSTOM_PACKAGE, 'initialization', 'initialization', ) module = Meta.get_module_from_string(module_path) meta = Meta() 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, app=app) except BaseException as e: log.error("Errors during custom initialization: {}", e) else: log.info("Vanilla project has been initialized") except BaseException: log.debug("No custom init available")
def extract_endpoints(self, base_dir: Path) -> List[Type[EndpointResource]]: endpoints_classes: List[Type[EndpointResource]] = [] # get last item of the path # normpath is required to strip final / if any base_module = base_dir.name apis_dir = base_dir.joinpath("endpoints") apiclass_module = f"{base_module}.endpoints" for epfile in apis_dir.glob("*.py"): # get module name (es: endpoints.filename) module_name = f"{apiclass_module}.{epfile.stem}" # Convert module name into a module log.debug("Importing {}", module_name) module = Meta.get_module_from_string( module_name, exit_on_fail=True, ) # Extract classes from the module # module can't be none because of exit_on_fail=True... # but my-py can't understand this classes = Meta.get_new_classes_from_module(module) # type: ignore for class_name, epclss in classes.items(): # Filtering out classes without expected data if ( not hasattr(epclss, "methods") or epclss.methods is None ): # pragma: no cover continue log.debug("Importing {} from {}", class_name, module_name) skip, dependency = self.skip_endpoint(epclss.depends_on) if skip: log.debug( "Skipping '{} {}' due to unmet dependency: {}", module_name, class_name, dependency, ) continue endpoints_classes.append(epclss) return endpoints_classes
def custom_post_handle_user_input(self, user_node, input_data): module_path = "{}.initialization.initialization".format(CUSTOM_PACKAGE) module = Meta.get_module_from_string(module_path) meta = Meta() 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: {}", e)
def load_class_from_module(self, classname, service=None): if service is None: flaskext = '' else: flaskext = '.' + service.get('extension') # Try inside our extensions module = Meta.get_module_from_string( modulestring=BACKEND_PACKAGE + '.flask_ext' + flaskext, exit_on_fail=True ) if module is None: log.exit("Missing {} for {}", flaskext, service) return getattr(module, classname)
def extract_endpoints(self, base_dir): endpoints_classes = [] # get last item of the path # normpath is required to strip final / if any base_module = os.path.basename(os.path.normpath(base_dir)) apis_dir = os.path.join(base_dir, "endpoints") apiclass_module = f"{base_module}.endpoints" for epfiles in glob.glob(f"{apis_dir}/*.py"): # get module name (es: endpoints.filename) module_file = os.path.basename(os.path.splitext(epfiles)[0]) module_name = f"{apiclass_module}.{module_file}" # Convert module name into a module log.debug("Importing {}", module_name) module = Meta.get_module_from_string( module_name, exit_on_fail=True, ) # Extract classes from the module # module can't be none because of exit_on_fail=True... # but my-py can't understand this classes = Meta.get_new_classes_from_module(module) # type: ignore for class_name, epclss in classes.items(): # Filtering out classes without expected data if not hasattr(epclss, "methods") or epclss.methods is None: continue log.debug("Importing {} from {}.{}", class_name, apis_dir, module_file) skip, dependency = self.skip_endpoint(epclss.depends_on) if skip: log.debug( "Skipping '{} {}' due to unmet dependency: {}", module_name, class_name, dependency, ) continue endpoints_classes.append(epclss) return endpoints_classes
def load_commands(self): Meta.get_module_from_string("restapi.services.bot") if EXTENDED_PACKAGE != EXTENDED_PROJECT_DISABLED: Meta.get_module_from_string(f"{EXTENDED_PACKAGE}.bot") Meta.get_module_from_string(f"{CUSTOM_PACKAGE}.bot") # Handle the rest as normal messages # NOTE: this has to be the last handler to be attached self.updater.dispatcher.add_handler( MessageHandler(Filters.text, self.invalid_message))
def custom_user_properties(self, userdata): module_path = "{}.initialization.initialization".format(CUSTOM_PACKAGE) module = Meta.get_module_from_string(module_path) meta = Meta() 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: {}", e) if "email" in userdata: userdata["email"] = userdata["email"].lower() return userdata
def do_schema(self): """ Schemas exposing, if requested """ name = '{}.rest.schema'.format(BACKEND_PACKAGE) module = Meta.get_module_from_string(name, exit_if_not_found=True, exit_on_fail=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={}, )
def get_module(connector: str, module: str) -> Optional[ModuleType]: return Meta.get_module_from_string( ".".join((module, CONNECTORS_FOLDER, connector)) )
def find_endpoints(self): ################## # Walk folders looking for endpoints endpoints_folders = [] # base swagger dir (rapydo/http-ap) endpoints_folders.append({'path': ABS_RESTAPI_PATH, 'iscore': True}) # swagger dir from extended project, if any if self._extended_project is not None: endpoints_folders.append({ 'path': os.path.join(os.curdir, self._extended_project), 'iscore': False }) # custom swagger dir endpoints_folders.append({ 'path': os.path.join(os.curdir, CUSTOM_PACKAGE), 'iscore': False }) # already_loaded = {} for folder in endpoints_folders: base_dir = folder.get('path') iscore = folder.get('iscore') # get last item of the path # normapath is required to strip final / is any base_module = os.path.basename(os.path.normpath(base_dir)) if iscore: apis_dir = os.path.join(base_dir, 'resources') apiclass_module = '{}.resources'.format(base_module) else: apis_dir = os.path.join(base_dir, 'apis') apiclass_module = '{}.apis'.format(base_module) # Looking for all file in apis folder for epfiles in os.listdir(apis_dir): # get module name (es: apis.filename) module_file = os.path.splitext(epfiles)[0] module_name = "{}.{}".format(apiclass_module, module_file) # Convert module name into a module log.debug("Importing {}", module_name) try: module = Meta.get_module_from_string( module_name, exit_on_fail=True, exit_if_not_found=True) except BaseException as e: log.exit("Cannot import {}\nError: {}", module_name, e) # Extract classes from the module # classes = meta.get_classes_from_module(module) classes = meta.get_new_classes_from_module(module) for class_name in classes: ep_class = classes.get(class_name) # Filtering out classes without required data if not hasattr(ep_class, "methods"): continue if ep_class.methods is None: continue # if class_name in already_loaded: # log.warning( # "Skipping import of {} from {}.{}, already loded from {}", # class_name, # apis_dir, # module_file, # already_loaded[class_name], # ) # continue # already_loaded[class_name] = "{}.{}".format(apis_dir, module_file) log.debug("Importing {} from {}.{}", class_name, apis_dir, module_file) if not self._testing: skip = False for var in ep_class.depends_on: pieces = var.strip().split(' ') pieces_num = len(pieces) if pieces_num == 1: dependency = pieces.pop() negate = False elif pieces_num == 2: negate, dependency = pieces negate = negate.lower() == 'not' else: log.exit('Wrong parameter: {}', var) check = detector.get_bool_from_os(dependency) if negate: check = not check # Skip if not meeting the requirements of the dependency if not check: skip = True break if skip: log.debug( "Skipping '{} {}' due to unmet dependency: {}", module_name, class_name, dependency) continue # Building endpoint endpoint = EndpointElements(custom={}) endpoint.cls = ep_class endpoint.exists = True endpoint.iscore = iscore # Global tags to be applied to all methods endpoint.tags = ep_class.labels # base URI base = ep_class.baseuri if base not in BASE_URLS: log.warning("Invalid base {}", base) base = API_URL base = base.strip('/') endpoint.base_uri = base endpoint.uris = {} # attrs python lib bug? endpoint.custom['schema'] = { 'expose': ep_class.expose_schema, 'publish': {}, } endpoint.methods = {} mapping_lists = [] for m in ep_class.methods: method_name = "_{}".format(m) if not hasattr(ep_class, method_name): method_name = m if not hasattr(ep_class, method_name): log.warning("{} configuration not found in {}", m, class_name) continue # Enable this warning to start conversions GET -> _GET # Find other warning like this by searching: # **FASTAPI** # else: # log.warning( # "Obsolete dict {} in {}", m, class_name # ) conf = getattr(ep_class, method_name) kk = conf.keys() mapping_lists.extend(kk) endpoint.methods[m.lower()] = copy.deepcopy(conf) if endpoint.custom['schema']['expose']: for uri in mapping_lists: total_uri = '/{}{}'.format(endpoint.base_uri, uri) schema_uri = '{}/schemas{}'.format(API_URL, uri) p = hex(id(endpoint.cls)) self._schema_endpoint.uris[uri + p] = schema_uri self._schemas_map[schema_uri] = total_uri self._endpoints.append(endpoint)