Source code for fairways.decorators.entities
"Special marks for module executables"
from ..funcflow import FuncFlow as ff
import logging
log = logging.getLogger(__name__)
import functools
from enum import Enum
DECORATORS = dict()
import sys
[docs]def register_decorator(cls):
"""Decorator to register plug-ins"""
name = cls.mark_name
DECORATORS[name] = cls
source_module = cls.__module__
# setattr(sys.modules[__name__], name, cls)
setattr(sys.modules[source_module], name, cls)
return cls
class RegistryItem:
def __init__(self, **attrs):
self.subject = attrs["subject"]
self.meta = attrs["meta"]
self.mark_name = attrs["mark_name"]
self.module = attrs["module"]
self.doc = attrs["doc"]
def __str__(self):
return f"Registry Item: '{self.module}:{self.mark_name}' / object: '{self.subject.__name__}'"
[docs]class Mark:
"""Base class for decorators.
These decorators are registered in a registry and can be invoked via convinient "mark_name" later.
Each class of decorator "knows" about all functions decorated with it.
It is possible to iterate over these functions, filter them by decorator mark, origin module.
New decorators can be defined outside this module by new derived class and by special decorator \@register_decorator
"""
#: Alias to call decorator
mark_name = "mark"
#: Data model to keep info about wrapped function with user-defined metadata
registry_item_class = RegistryItem
#: Keyword arguments of decorator
decorator_kwargs = []
#: Required keyword arguments of decorator
decorator_required_kwargs = []
#: Should be used no more than once per module
once_per_module = True
_registry = []
def __init__(self, **options):
"""Constructor method
"""
options = ff.pick(options, *(self.decorator_kwargs))
missed = [k for k in self.decorator_required_kwargs if k not in options.keys()]
if len(missed) > 0:
raise TypeError("Decorator {} - required args missed: {}".format(self.__class__.__name__, ",".join(missed)))
self.options = options
# log.debug('Decorator: %s', self)
def __str__(self):
return self.mark_name
def __call__(self, subject):
if self.once_per_module and self.__class__.__name__ != Mark.__name__:
if self.find_module_entity(subject.__module__):
raise Exception(f"Mark '{self.mark_name}' alredy defined in module '{subject.__module__}'")
self._registry += [self.registry_item_class(
subject=subject,
meta=ff.extend({}, self.options),
mark_name=self.mark_name,
module=subject.__module__,
doc=subject.__doc__,
)]
log.debug('Decorator called: %s', self.mark_name)
return subject
[docs] @classmethod
def items(cls):
"""List of records per each decorated function.
Each record has type which defined in class attribute "registry_item_class"
Note that invocation of Mark.items() returns records for all subclasses of Mark while invocation of same method for subclass returns records for this class only.
:return: Records list
:rtype: list
"""
if cls.__name__ == Mark.__name__:
return iter(cls._registry)
return ff.filter(cls._registry, lambda v: v.mark_name == cls.mark_name)
[docs] @classmethod
def find_module_entity(cls, module_name):
"""Find record by module where decorator used.
:param module_name: Name of a module.
:type module_name: str
:raises TypeError: Do not use this method for base class Mark.
:return: Registry record. Note that the returned type depends on "registry_item_class".
:rtype: RegistryItem
"""
if cls.__name__ == Mark.__name__:
raise TypeError("find_module_entity applicable to Mark descendants only")
return ff.find(
cls.items(),
lambda r: r.module == module_name)
[docs] @classmethod
def chain(cls):
"""Return items as a FuncFlow chain suitable for "chained" processing (filtering, mapping, etc...).
:return: Same as for .items() but wrapped in funcflow.Chain.
:rtype: funcflow.Chain
"""
return ff.chain(cls.items())
[docs] @classmethod
def reset_registry(cls):
"""Clean registry from records which belongs for class.
"""
own_items = cls.items()
new_registry = []
for item in cls._registry:
if item in own_items:
continue
new_registry.append(item)
cls._registry = new_registry