Source code for restapi.utilities.meta
"""
Meta thinking: python objects & introspection
usefull documentation:
http://python-3-patterns-idioms-test.readthedocs.org/en/latest/Metaprogramming.html
"""
import inspect
import pkgutil
from importlib import import_module
from types import ModuleType
from typing import Any, Callable, Optional
from restapi.config import BACKEND_PACKAGE, CUSTOM_PACKAGE
from restapi.utilities import print_and_exit
from restapi.utilities.logs import log
[docs]
class Meta:
"""Utilities with meta in mind"""
[docs]
@staticmethod
def get_classes_from_module(module: ModuleType) -> dict[str, type[Any]]:
"""
Find classes inside a python module file.
"""
try:
return {
name: cls
for name, cls in module.__dict__.items()
if isinstance(cls, type)
}
except AttributeError:
log.warning("Could not find any class in module {}", module)
return {}
[docs]
@staticmethod
def get_new_classes_from_module(module: ModuleType) -> dict[str, type[Any]]:
"""
Skip classes not originated inside the module.
"""
classes = {}
for name, value in Meta.get_classes_from_module(module).items():
if module.__name__ in value.__module__:
classes[name] = value
return classes
# Should return `from types import ModuleType` -> Optional[ModuleType]
[docs]
@staticmethod
def get_module_from_string(
modulestring: str, exit_on_fail: bool = False
) -> Optional[ModuleType]:
"""
Getting a module import
when your module is stored as a string in a variable
"""
try:
return import_module(modulestring)
except ModuleNotFoundError as e:
if exit_on_fail:
log.error(e)
raise e
return None
except Exception as e: # pragma: no cover
if exit_on_fail:
log.error(e)
raise e
log.error("Module {} not found.\nError: {}", modulestring, e)
return None
[docs]
@staticmethod
def get_self_reference_from_args(*args: Any) -> Optional[Any]:
"""
Useful in decorators:
being able to call the internal method by getting
the 'self' reference from the decorated method
(when it's there)
"""
if len(args) > 0:
candidate_as_self = args[0]
cls_attribute = getattr(candidate_as_self, "__class__", None)
if cls_attribute is not None and inspect.isclass(cls_attribute):
return args[0]
return None
[docs]
@staticmethod
def import_models(
name: str, package: str, mandatory: bool = False
) -> dict[str, type[Any]]:
if package == BACKEND_PACKAGE:
module_name = f"{package}.connectors.{name}.models"
else:
module_name = f"{package}.models.{name}"
try:
module = Meta.get_module_from_string(module_name, exit_on_fail=True)
except Exception as e:
module = None
if mandatory:
log.critical(e)
if not module:
if mandatory:
print_and_exit("Cannot load {} models from {}", name, module_name)
return {}
return Meta.get_new_classes_from_module(module)
[docs]
@staticmethod
def get_celery_tasks(package_name: str) -> list[Callable[..., Any]]:
"""
Extract all celery tasks from a module.
Celery tasks are functions decorated by @CeleryExt.celery_app.task(...)
This decorator transform the function into a class child of
celery.local.PromiseProxy
"""
tasks: list[Callable[..., Any]] = []
# package = tasks folder
package = Meta.get_module_from_string(package_name)
if package is None:
return tasks
# get all modules in package (i.e. py files)
path = package.__path__
for _, module_name, ispkg in pkgutil.iter_modules(path):
# skip modules (i.e. subfolders)
if ispkg: # pragma: no cover
continue
module_path = f"{package_name}.{module_name}"
log.debug("Loading module '{}'", module_path)
# convert file name in submodule, i.e.
# tasks.filename
submodule = Meta.get_module_from_string(
module_path,
exit_on_fail=True,
)
# get all functions in py file
functions = inspect.getmembers(submodule)
for func in functions:
obj_type = type(func[1])
if obj_type.__module__ != "celery.local":
continue
# This was a dict name => func
# tasks[func[0]] = func[1]
# Now it is a list
tasks.append(func[1])
return tasks
[docs]
@staticmethod
def get_class(module_relpath: str, class_name: str) -> Optional[Any]:
abspath = f"{CUSTOM_PACKAGE}.{module_relpath}"
module = Meta.get_module_from_string(abspath)
if module is None:
log.debug("{} path does not exist", abspath)
return None
if not hasattr(module, class_name):
return None
return getattr(module, class_name)