Opened 7 years ago
Last modified 6 years ago
#29443 closed Cleanup/optimization
Unify registries in Django — at Version 2
Reported by: | Johannes Maron | Owned by: | nobody |
---|---|---|---|
Component: | Utilities | Version: | dev |
Severity: | Normal | Keywords: | plugin, registry, pattern, utils |
Cc: | info@… | Triage Stage: | Accepted |
Has patch: | no | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description (last modified by )
Django utilizes registries in various places thought the code, such as the admin, models, template-tags and the check framework.
At the moment all those places provide their own implementation. Considering that this is a recurring pattern I would suggest to unify the implementation. More specifically to provide a common super class and override or extend behavior if need be.
I would also suggest to add this to django.utils
including public documentation, because this pattern could be useful for Django projects and 3rd party libraries as it is to the Django project itself.
Luckily I have written one before. I don't know if it is any good or even compatible implementations in Django but I added a lot of documentation and research. I am happy to share this here:
class Registry: """ A registry to store and retrieve elements. This is an implementation of the "registry pattern" by Martin Fowler (ISBN: 0321127420). A registry can be used interact with a list of objects, without knowing them. It allows a package to define a common interface and to interact with its implementations without holding a reference to them. A typical use case are plugins where a package provides a feature and allows other packages to extend it. In this case the super package might need to interact with its extensions, without knowing them. This concept is used in places like Django's admin or template tags. Typing a registry can help to define a common interface, that needs to be implemented by all extensions. Usage example: ``mainapp/feature.py``:: from common.utils import Registry class BasePlugin: def get_url(self): raise NotImplementedError() plugins = Registry(entry_type=BasePlugin) class MyFeature: def get_plugin_urls(self): return (plugin.get_url() for plugin in plugins) ``featureapp/feature.py``:: from mainapp.feature import BasePlugin class MyPlugin(BasePlugin): def get_url(self): return "https://example.com" ``featureapp/app.py``:: from django.apps import AppConfig from mainapp.feature import plugins class FeatureApp(AppConfig): name = 'featureapp' def ready(self): plugins.register(MyPlugin) In order for a plugin to be registered, the ``register`` call needs to be executed. A good place to do that in Django apps is in the app's `AppConfig.ready` method. Args: entry_type (``type``): The registry will only allow subclasses or instances of this type to be registered. unique (bool): Whether or not elements can be registered multiple times. Default: ``True`` """ def __init__(self, entry_type=None, unique=True): if entry_type is not None and not isinstance(entry_type, type): raise TypeError('"entry_type" expects a class, but got an instance.') self.entry_type = entry_type self.unique = unique self._register = [] def __iter__(self): # The list is copied, to avoid mutation of the registry while iterating. return iter(list(self._register)) def __len__(self): return len(self._register) def clear(self): """Clear all entries from the registry.""" self._register.clear() def register(self, entry): """ Store entry in the registry. Args: entry: Entry that is to be added to the registry. Raises: TypeError: If the entry type does not match the type of the registry instance. ValueError: If the entry is already registered and the registry instance is set to be unique. """ entry_type = entry if isinstance(entry, type) else type(entry) if self.entry_type and not ( isinstance(entry, self.entry_type) or issubclass(entry_type, self.entry_type) ): raise TypeError('"entry" expects %s, but got %s' % (self.entry_type, entry)) if self.unique and entry in self._register: raise ValueError('"%s" is already registered.' % entry) self._register.append(entry) def unregister(self, entry): """ Remove all occurrences of ``entry`` from registry. Args: entry: Entry that is to be removed from the registry. Raises: ValueError: If entry is not registered. """ if entry not in self._register: raise ValueError('"%s" is not registered.' % entry) # If the registry is not unique, an element could be in the list more than once. while entry in self._register: self._register.remove(entry)
Change History (2)
comment:1 by , 7 years ago
Description: | modified (diff) |
---|
comment:2 by , 7 years ago
Description: | modified (diff) |
---|