registries.py 4.57 KB
Newer Older
Eliot Berriot's avatar
Eliot Berriot committed
1 2 3
from collections import OrderedDict
import inspect

Eliot Berriot's avatar
Eliot Berriot committed
4 5 6 7 8 9 10 11
try:
    # use Python3 reload
    from imp import reload

except:
    # we are on Python2
    pass

Eliot Berriot's avatar
Eliot Berriot committed
12 13 14 15 16 17 18 19 20 21 22 23 24 25
class Registry(OrderedDict):

    def register_decorator_factory(self, **kwargs):
        """ 
            Return an actual decorator for registering objects into registry
        """ 
        name = kwargs.get('name')
        def decorator(decorated):
            self.register_func(obj=decorated, name=name)
            return decorated
        return decorator

    def register(self, obj=None, name=None, **kwargs):
        """
26
            Use this method as a decorator on class/function you want to register:
Eliot Berriot's avatar
Eliot Berriot committed
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55

            @registry.register(name="test")
            class Test:
                pass

            :param:obj: An object to register in the registry
            :param:name: The name of the object to register. If none, the obj class name will be used


        """
        if obj is None:
            return self.register_decorator_factory(obj=obj, name=name, **kwargs)
        else:
            self.register_func(obj=obj, name=name, **kwargs)
            return obj

    def get_object_name(self, obj):
        """
            Return a name from an element (object, class, function...)
        """
        if callable(obj):
            return obj.__name__

        elif inspect.isclass(obj):
            return obj.__class__.__name__

        else:
            raise ValueError("Cannot deduce name from given object ({0}). Please user registry.register() with a 'name' argument.".format(obj))

56 57 58 59 60 61 62
    def validate(self, obj):
        """
            Called before registering a new value into the registry
            Override this method if you want to restrict what type of data cna be registered
        """
        return True

63 64 65 66 67
    def prepare_name(self, obj, name=None):
        if name is None:
            return self.get_object_name(obj)
        return name

Eliot Berriot's avatar
Eliot Berriot committed
68 69 70 71
    def register_func(self, obj, name=None, **kwargs):
        """
            Register an object, class, function... into the registry
        """
72
        if self.validate(obj):
73 74 75 76
            o = self.prepare_data(obj)
            n = self.prepare_name(obj, name)            
            self[n] = o            

77 78
        else:
            raise ValueError("{0} (type: {0.__class__}) is not a valid value for {1} registry".format(obj, self.__class__))
Eliot Berriot's avatar
Eliot Berriot committed
79

80 81 82
    def prepare_data(self, obj):
        """
            Override this methode if you want to manipulate data before registering it
83
            You MUST return a value to register
84 85 86
        """
        return obj

87

Eliot Berriot's avatar
Eliot Berriot committed
88 89 90 91 92 93
    def autodiscover(self, apps, force_reload=True):
        """
            Iterate throught every installed apps, trying to import `look_into` package
            :param apps: an iterable of string, refering to python modules the registry will try to import via autodiscover
        """
        for app in apps:
94
            app_package = __import__(app)
Eliot Berriot's avatar
Eliot Berriot committed
95
            try:
96

Eliot Berriot's avatar
Eliot Berriot committed
97 98 99 100 101
                package = '{0}.{1}'.format(app, self.look_into) # try to import self.package inside current app
                #print(package)
                module = __import__(package)
                if force_reload:
                    reload(module)
102 103 104 105 106 107 108 109 110 111 112 113 114 115
            except ImportError as exc:
                # From django's syncdb
                # This is slightly hackish. We want to ignore ImportErrors
                # if the module itself is missing -- but we don't
                # want to ignore the exception if the module exists
                # but raises an ImportError for some reason. The only way we
                # can do this is to check the text of the exception. Note that
                # we're a bit broad in how we check the text, because different
                # Python implementations may not use the same text.
                # CPython uses the text "No module named"
                # PyPy uses "No module named myproject.myapp"
                msg = exc.args[0]
                if not msg.startswith('No module named') or self.look_into not in msg:
                    raise
Eliot Berriot's avatar
Eliot Berriot committed
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132


class MetaRegistry(Registry):
    """
        Keep a reference to all registries
    """
    look_into = "registries"

    def autodiscover(self, apps, cascade=True, **kwargs):
        """
            :param cascade: If true, will trigger autodiscover on discovered registries
        """
        super(MetaRegistry, self).autodiscover(apps, **kwargs)
        if cascade:
            self.autodiscover_registries(apps)

    def autodiscover_registries(self, apps):
Eliot Berriot's avatar
Eliot Berriot committed
133
        for key, registry in self.items():
Eliot Berriot's avatar
Eliot Berriot committed
134 135 136
            registry.autodiscover(apps)
            
meta_registry = MetaRegistry()