Created
August 28, 2017 23:39
-
-
Save tempusfrangit/0b6000f71c7898ed9de12605873bf959 to your computer and use it in GitHub Desktop.
new dependency code (meta)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class _NoDependencyFound(Exception): | |
"""Internal-use only exception for the registry.""" | |
class DuplicateProviderError(Exception): | |
"""A duplicate provider API is attempting to be registered.""" | |
class DependencyRegistry(object): | |
__registry = {} | |
__instances = {} | |
__iter__ = __registry.__iter__ | |
def __init__(self): | |
raise RuntimeError('Dependency Registry may not be instantiated ' | |
'directly') | |
@classmethod | |
def get(cls, name): | |
# TODO(morgan) Check if keystone is configured and error if not. | |
registry = cls.__registry | |
instances = cls.__instances | |
try: | |
if name.endswith('_api'): | |
if name in registry: | |
return instances.setdefault(name, registry[name]()) | |
except KeyError: | |
raise _NoDependencyFound() | |
@classmethod | |
def register_class_as_provider(cls, name, provider_cls): | |
if name in cls.__registry: | |
raise DuplicateProviderError( | |
'`%s` is already registered as a provider.' % name) | |
cls.__registry[name] = provider_cls | |
@classmethod | |
def _clear_registry(cls): | |
"""Used for testing purposes only. | |
Clears both the registry *and* the instantiations.""" | |
# NOTE(morgan): This should *never* be used | |
# TODO(morgan): Delete this method once it is certain it is not needed | |
cls.__registry.clear() | |
cls.__instances.clear() | |
@classmethod | |
def _clear_registry_instances(cls): | |
"""Clears the provider instances and allows re-instantiation. | |
This is usually only used in the case of testing. | |
""" | |
cls.__instances.clear() | |
@classmethod | |
def instantiate_all_providers(cls): | |
for name, prov in cls.__registry.items(): | |
cls.__instances[name] = prov() | |
class _DependencyRegistryMeta(type): | |
def __new__(meta, name, bases, class_dict): | |
setattr_method = None | |
parts = [p.lower for p in re.split(r'([A-Z][a-z]*)', name) if p] | |
registry_name = '_'.join(parts) | |
if not name.endswith('_api'): | |
raise ValueError('Class must have "API" as the last three ' | |
'letters of it\'s name to be a provider api.') | |
if '__setattr__' in class_dict: | |
setattr_method = class_dict['setattr'] | |
class_dict['_original_setattr'] = class_dict.pop('__setattr__') | |
def __replacement_setattr__(self, key, value): | |
if key in DependencyRegistry: | |
raise ValueError('Cannot set the name of a registered ' | |
'provider api on a consumer.') | |
if setattr_method is not None: | |
self._original_setattr(key, value) | |
else: | |
super(self.__class__, self).__setattr__(key, value) | |
class_dict['__setattr__'] = __replacement_setattr__ | |
# TODO(morgan): replace __init__ and error if the class is | |
# instantiated more than once. | |
cls = type.__new__(meta, name, bases, class_dict) | |
DependencyRegistry.register_class_as_provider(registry_name, cls) | |
return cls | |
def requires_api(*dependencies): | |
def wrapped(cls): | |
cls._dependencies = dependencies | |
__getattribute_meth = getattr(cls, '__getattribute__', None) | |
if __getattribute_meth is None or not callable(__getattribute_meth): | |
__getattribute_meth = None | |
def getattribute_method(self, item): | |
try: | |
return DependencyRegistry.get(item) | |
except _NoDependencyFound: | |
# NOTE(morgan): if it's not an API that is found just move on | |
# and do the normal thing | |
pass | |
if __getattribute_meth is not None: | |
return __getattribute_meth(item) | |
else: | |
super(self.__class__, self).__getattribute(item) | |
def init_method(self, *args, **kwargs): | |
for dep in self._dependencies: | |
if dep not in DependencyRegistry: | |
raise UnresolvableDependencyException(dep, []) | |
self._wrapped_init(*args, **kwargs) | |
cls._wrapped_getattribute = cls.__getattribute__ | |
cls._wrapped_init = cls.__init__ | |
cls.__getattribute__ = getattribute_method | |
cls.__init__ = init_method | |
return cls | |
return wrapped |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment