Using Decorators in Python for Automatic Registration

True Story Follows

Sometimes, I find myself in a situation where I have an idea for a piece of architecture, and the idea is that the system is extensible and any developer should be able to come along and add to it by creating a class the implements a few methods, and the rest of the system will magically take care of everything. Basically, in order to maintain the system, an onboarder should not have to invest time in understanding the entire system in order to make changes.

The Problem

The particular case I’m talking about is one in which an abstract class is created, and that class should be able to be extended any number of times. Imagine the typical object oriented example of extending animals; if a developer wants to integrate a new animal into the environment, it would be luxurious to subclass an abstract class, implement the required methods, and that’s it. No need to understand the rest of the system. No need to go and created references to your new concrete class elsewhere.

So here’s an example of what I’m talking about:

from .abstract_animal import AbstractAnimal


class Dog(AbstractAnimal):

    def do_something_important(self):
        print "Do some dog specific logic"


class Cat(AbstractAnimal):

    def do_something_important(self):
        print "Do some cat specific logic"


ALL_ANIMAL_CLASSES = (
    Dog,
    Cat,
)

for animal_cls in ALL_ANIMAL_CLASSES:
    animal_cls().do_something_important()

Notice how in this simple example, a developer must create a new subclass AND add it to the list of existing animal classes. I want to get rid of that second step. In a real world example, the 2nd step would likely be a bit more complex with more “wiring” involved.

A Solution

One thing that’s magical about Flask is how you can register URL’s with just a decorator. Add the decorator to a function and BAM! There’s a new URL:

@app.route('/hello')
def hello():
    return 'Hello World'

I wanted to implement the same sort of concept. So you can create a class decorator to decorate any concrete class:

REGISTERED_CLASSES = []


def registered_class(cls):
    REGISTERED_CLASSES.append(cls)
    return cls

And we can use in our example like so:

from .abstract_animal import AbstractAnimal
from .class_registry import register_class, REGISTERED_CLASSES


@register_class
class Dog(AbstractAnimal):

    def do_something_important(self):
        print "Do some dog specific logic"

@register_class
class Cat(AbstractAnimal):

    def do_something_important(self):
        print "Do some cat specific logic"


for animal_cls in REGISTERED_CLASSES:
    animal_cls().do_something_important()

The remaining problem is that the registration decorator is only called once the class is imported. So we still haven’t solved our problem yet because classes aren’t auto registered. This can be resolved in our __init__.py files which will handle imports. I found one of several solutions on StackOverflow to automatically import all classes in a module. So we can do something like:

from .class_registry import REGISTERED_CLASSES

# Import all classes in this directory so that classes with @register_class are registered. 

from os.path import basename, dirname, join
from glob import glob
pwd = dirname(__file__)
for x in glob(join(pwd, '*.py')):
    if not x.startswith('__'):
        __import__(basename(x)[:-3], globals(), locals())


__all__ = [
    'REGISTERED_CLASSES'
]

Now for any new developers that come along, they can simply do something like:

from .abstract_animal import AbstractAnimal
from .class_registry import register_class


@register_class
class LizardMan(AbstractAnimal):

    def do_something_important(self):
        print "Do some LizardMan specific logic"

The new class will now be part of the rest of the ecosystem without any additional work. This can allow for systems that delegate and decompose responsibilities to developers that don’t need to waste time understanding the rest of the system.

  • It’s been more than 2 years and no comments.. This is just what I was looking for, but why isn’t anybody interested? is there a ‘most common way’ of solving this? I cannot find it in forums.. I have tried a package called class-registry, is good but you can register instances and not only classes