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

      

+3


source to share


2 answers


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.

0


source


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)

+1


source







All Articles