Source code for omnifig.decorators


# from omnibelt import monkey_patch

from .top import register_script, register_component, register_modifier
from .util import autofill_args

[docs]def Script(name, description=None, use_config=True): ''' Decorator to register a script :param name: name of script :param description: a short description of what the script does :param use_config: :code:`True` if the config should be passed as only arg when calling the script function, otherise it will automatically pull all arguments in the script function signature :return: decorator function expecting a callable ''' def _reg_script_decorator(fn): nonlocal name, use_config register_script(name, fn, use_config=use_config, description=description) return fn # monkey_patch(_reg_script_decorator) return _reg_script_decorator
[docs]def AutoScript(name, description=None): ''' Convienence decorator to register scripts that automatically extract relevant arguments from the config object :param name: name of the script :param description: a short description of what the script does :return: decorator function expecting a callable that does not expect the config as argument (otherwise use :func:`Script`) ''' return Script(name, use_config=False, description=description)
[docs]def Component(name=None): ''' Decorator to register a component NOTE: components should usually be types/classes to allow modifications :param name: if not provided, will use the __name__ attribute. :return: decorator function ''' def _register_cmp_decorator(cmp): nonlocal name if name is None: name = cmp.__name__ register_component(name, cmp) return cmp # monkey_patch(_register_cmp_decorator) return _register_cmp_decorator
[docs]def AutoComponent(name=None, aliases=None, auto_name=True): ''' Instead of directly passing the config to an AutoComponent, the necessary args are auto filled and passed in. This means AutoComponents are somewhat limited in that they cannot modify the config object and they cannot be modified with AutoModifiers. Note: AutoComponents are usually components that are created with functions (rather than classes) since they can't be automodified. When registering classes as components, you should probably use `Component` instead, and pull from the config directly. :param name: name to use when registering the auto component :param aliases: optional aliases for arguments used when autofilling (should be a dict[name,list[aliases]]) :return: decorator function ''' def _auto_cmp_decorator(cmp): nonlocal name, aliases if type(cmp) == type: # to allow AutoModifiers cls = type(f'Auto_{cmp.__name__}' if auto_name else cmp.__name__, (cmp,), {}) # monkey_patch(cls) def _cmp_init_fn(self, info): args, kwargs = autofill_args(cmp, info, aliases=aliases, run=False) super(cls, self).__init__(*args, **kwargs) # monkey_patch(_cmp_init_fn) cls.__init__ = _cmp_init_fn _auto_create_fn = cls else: def _auto_create_fn(config): nonlocal cmp, aliases return autofill_args(cmp, config, aliases) # monkey_patch(_auto_create_fn) Component(name)(_auto_create_fn) return cmp # monkey_patch(_auto_cmp_decorator) return _auto_cmp_decorator
[docs]def Modifier(name=None, expects_config=False): ''' Decorator to register a modifier NOTE: a :class:`Modifier` is usually not a type/class, but rather a function (except :class:`AutoModifiers`, see below) :param name: if not provided, will use the __name__ attribute. :param expects_config: True iff this modifier expects to be given the config as second arg :return: decorator function ''' def _mod_decorator_fn(mod): nonlocal name, expects_config if name is None: name = mod.__name__ register_modifier(name, mod, expects_config=expects_config) return mod # monkey_patch(_mod_decorator_fn) return _mod_decorator_fn
[docs]def AutoModifier(name=None): ''' Can be used to automatically register modifiers that combine types To keep component creation as clean as possible, modifier types should allow arguments to their __init__ (other than the Config object) and only call pull on arguments not provided, that way child classes of the modifier types can specify defaults for the modifications without calling pull() multiple times on the same arg. Note: in a way, this converts components to modifiers (but think before using). This turns the modified component into a child class of this modifier and its previous type. In short, Modifiers are used for wrapping of components, AutoModifiers are used for subclassing components :param name: if not provided, will use the __name__ attribute. :return: decorator to decorate a class ''' def _auto_mod_decorator(mod_type): nonlocal name def _the_mod_creation_fn(cmpn_type): # awesome python feature -> dynamic type declaration! cls = cmpn_type if issubclass(cmpn_type, mod_type) \ else type('{}_{}'.format(mod_type.__name__, cmpn_type.__name__), (mod_type, cmpn_type), {}) # monkey_patch(cls) return cls # monkey_patch(_the_mod_creation_fn) Modifier(name=mod_type.__name__ if name is None else name)(_the_mod_creation_fn) return mod_type # monkey_patch(_auto_mod_decorator) return _auto_mod_decorator
def _make_post_mod(mod): def _make_cmpn_decorator(cmpn): def _modification_fn(info): return mod(cmpn(info), info) # monkey_patch(_modification_fn) return _modification_fn # monkey_patch(_make_cmpn_decorator) return _make_cmpn_decorator
[docs]def Modification(name=None): ''' A kind of Modifier that modifies the component after it is created, and then returns the modified component expects a callable with input (component, config) Modifications should almost always be applied after all other modifiers, so they should appear at the end of _mod list :param name: name to register :return: a decorator expecting the modification function ''' def _reg_modification(mod): nonlocal name Modifier(name)(_make_post_mod(mod)) return mod # monkey_patch(_reg_modification) return _reg_modification