Python decoder - registering a class in the database
I searched the internet for a solution to this problem, but I couldn't find anything elegant.
Let's say I have a base class with a registry that derived classes can use as a decorator to register their methods:
from abc import ABCMeta
class Base(object):
__metaclass__ = ABCMeta
def __init__(self, name):
self._name = name
self._content = {}
def run_them_all(self):
for key, content in self._content.items():
print(key, content)
# This should be the registery function
def register(old_method, key):
self._content[key] = old_method()
class Derived(Base):
@Base.register("some other content")
def do_something(self):
return {"name": "yes"}
@Base.register("some_content")
def do_something_else(self):
return {"hi": "ho"}
def this_should_not_be_registered(self):
return "yooo"
derived = Derived("John")
derived.run_them_all()
Can this be achieved? Having a normal decorator expects an explicit function call to create a detour. But I just want to register these calls for later use, or at least register their return values ββto be used later, without directly calling these methods.
This should result in:
{"some other content": {"name": "yes"}}
{"some content": {"hi": "ho"}}
I just want to avoid overriding run_them_all like this:
class Derived(Base):
...
def run_them_all(self):
self._content["some_content"] = self.do_something_else()
...
return self._content
source to share
Well, I found a way to solve this, but it's not graceful.
I created a metaclass that will add an instance variable when the derived class is created:
import types
class MetaTemplateData(type):
def __new__(mcs, name, base, dct):
orig_init = dct.get("__init__")
decorators = []
for _, value in dct.items():
if isinstance(value, types.FunctionType):
if value.__name__ == "_content_wrapper":
decorators.append(value)
elif isinstance(value, staticmethod):
function = value.__func__
if function.__name__ == "_content_wrapper":
decorators.append(function)
def init_wrapper(self, *args, **kwargs):
if orig_init:
orig_init(self, *args, **kwargs)
# pylint: disable=protected-access
self._callbacks = getattr(self, "_callbacks", [])
self._callbacks.extend(decorators)
dct["__init__"] = init_wrapper
return type.__new__(mcs, name, base, dct)
This is our decorator, this will call the function and check if it is static or not, and save its result in a new dict:
def add_content(key):
def register_decorator(function):
def _content_wrapper(self=None, *args, **kwargs):
num_args = function.__code__.co_argcount
if isinstance(function, types.FunctionType):
if self and num_args != 0:
data = function(self, *args, **kwargs)
else:
data = function(*args, **kwargs)
else:
data = function.__func__()
return {key: data}
return _content_wrapper
return register_decorator
How can we create a base class like this that takes care of naming all decorated elements for us and keeping its result inside a dict:
class Base(object, metaclass=MetaTemplateData):
def __init__(self, email):
self._email = email
self._callbacks = []
def parse(self):
content = {}
for func in self._callbacks:
content.update(func(self))
return content
Finally, we'll select a derived class that takes care of adding content:
class Derived(Base):
def __init__(self, name, email):
super().__init__(email)
self._name = name
@add_content("key_number_one")
def something(self):
return {
"email": self._email
}
@add_content("key_two")
def something_else(self):
return {
"email": "DAEMAIL" + self._email
}
@add_content("key_for_static")
@staticmethod
def _do_private_things():
return {
"oh lord": "hoho"
}
Now, when we instantiate the derived class, the magic happens:
derived = Derived("John", "some@email.com")
content = derived.parse()
print(content)
Result:
{'key_for_static': {'oh lord': 'hoho'}, 'key_number_one': {'email': ' some@email.com '}, 'key_two': {'email': 'DAEMAILsome @ email.com' }}
You can also call all participants from outside, and everything works as expected.
source to share
Why not just define the decorator as an external function (this can be done inside the class, but it's cumbersome):
def register(key):
def register_decorator(function):
def fn(self, *args, **kw):
out = function(self, *args, **kw)
self.register(key, out)
return fn
return register_decorator
And use it like this:
class BlaBla(Base):
...
@register
def do_something(self):
return {"name": "yes"}
Also, your function Base.register
is to receive 3 arguments self
, key
and output
that is the result of start-up method (instead of the method)
source to share