diff options
author | Jan Cholasta <jcholast@redhat.com> | 2015-02-16 13:11:38 +0000 |
---|---|---|
committer | Tomas Babej <tbabej@redhat.com> | 2015-03-05 11:01:36 +0100 |
commit | 2db741e847c60d712dbc8ee1cd65a978a78eb312 (patch) | |
tree | 12d5c372ef63cc2044c5436b4d657f4044de8fb0 /ipalib/plugable.py | |
parent | aa745b31d3762121bb0df1432cb2a48d1d15fd2a (diff) | |
download | freeipa-2db741e847c60d712dbc8ee1cd65a978a78eb312.tar.gz freeipa-2db741e847c60d712dbc8ee1cd65a978a78eb312.tar.xz freeipa-2db741e847c60d712dbc8ee1cd65a978a78eb312.zip |
ipalib: Allow multiple API instances
Merged the Registrar class into the Registry class. Plugins are now
registered globally instead of in ipalib.api and are instantiated per-API
instance. Different set of plugin base classes can be used in each API
instance.
https://fedorahosted.org/freeipa/ticket/3090
Reviewed-By: Tomas Babej <tbabej@redhat.com>
Diffstat (limited to 'ipalib/plugable.py')
-rw-r--r-- | ipalib/plugable.py | 204 |
1 files changed, 98 insertions, 106 deletions
diff --git a/ipalib/plugable.py b/ipalib/plugable.py index a6504d162..aae762649 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -74,18 +74,94 @@ class Registry(object): For forward compatibility, make sure that the module-level instance of this object is named "register". """ - # TODO: Instead of auto-loading when plugin modules are imported, - # plugins should be stored in this object. - # The API should examine it and load plugins explicitly. - def __call__(self): - from ipalib import api - def decorator(cls): - api.register(cls) - return cls + __allowed = {} + __registered = set() + + def base(self): + def decorator(base): + if not inspect.isclass(base): + raise TypeError('plugin base must be a class; got %r' % base) + + if base in self.__allowed: + raise errors.PluginDuplicateError(plugin=base) + + self.__allowed[base] = {} + + return base + + return decorator + + def __findbases(self, klass): + """ + Iterates through allowed bases that ``klass`` is a subclass of. + + Raises `errors.PluginSubclassError` if ``klass`` is not a subclass of + any allowed base. + + :param klass: The plugin class to find bases for. + """ + found = False + for (base, sub_d) in self.__allowed.iteritems(): + if issubclass(klass, base): + found = True + yield (base, sub_d) + if not found: + raise errors.PluginSubclassError( + plugin=klass, bases=self.__allowed.keys() + ) + + def __call__(self, override=False): + def decorator(klass): + if not inspect.isclass(klass): + raise TypeError('plugin must be a class; got %r' % klass) + + # Raise DuplicateError if this exact class was already registered: + if klass in self.__registered: + raise errors.PluginDuplicateError(plugin=klass) + + # Find the base class or raise SubclassError: + for (base, sub_d) in self.__findbases(klass): + # Check override: + if klass.__name__ in sub_d: + if not override: + # Must use override=True to override: + raise errors.PluginOverrideError( + base=base.__name__, + name=klass.__name__, + plugin=klass, + ) + else: + if override: + # There was nothing already registered to override: + raise errors.PluginMissingOverrideError( + base=base.__name__, + name=klass.__name__, + plugin=klass, + ) + + # The plugin is okay, add to sub_d: + sub_d[klass.__name__] = klass + + # The plugin is okay, add to __registered: + self.__registered.add(klass) + + return klass return decorator + def __base_iter(self, *allowed): + for base in allowed: + sub_d = self.__allowed[base] + subclasses = set(sub_d.itervalues()) + yield (base, subclasses) + + def iter(self, *allowed): + for base in allowed: + if base not in self.__allowed: + raise TypeError("unknown plugin base %r" % base) + return self.__base_iter(*allowed) + class SetProxy(ReadOnly): """ @@ -365,111 +441,28 @@ class Plugin(ReadOnly): ) -class Registrar(DictProxy): - """ - Collects plugin classes as they are registered. - - The Registrar does not instantiate plugins... it only implements the - override logic and stores the plugins in a namespace per allowed base - class. - - The plugins are instantiated when `API.finalize()` is called. - """ - def __init__(self, *allowed): - """ - :param allowed: Base classes from which plugins accepted by this - Registrar must subclass. - """ - self.__allowed = dict((base, {}) for base in allowed) - self.__registered = set() - super(Registrar, self).__init__( - dict(self.__base_iter()) - ) - - def __base_iter(self): - for (base, sub_d) in self.__allowed.iteritems(): - if not is_production_mode(self): - assert inspect.isclass(base) - name = base.__name__ - if not is_production_mode(self): - assert not hasattr(self, name) - setattr(self, name, MagicDict(sub_d)) - yield (name, base) - - def __findbases(self, klass): - """ - Iterates through allowed bases that ``klass`` is a subclass of. - - Raises `errors.PluginSubclassError` if ``klass`` is not a subclass of - any allowed base. - - :param klass: The plugin class to find bases for. - """ - if not is_production_mode(self): - assert inspect.isclass(klass) - found = False - for (base, sub_d) in self.__allowed.iteritems(): - if issubclass(klass, base): - found = True - yield (base, sub_d) - if not found: - raise errors.PluginSubclassError( - plugin=klass, bases=self.__allowed.keys() - ) - - def __call__(self, klass, override=False): - """ - Register the plugin ``klass``. - - :param klass: A subclass of `Plugin` to attempt to register. - :param override: If true, override an already registered plugin. - """ - if not inspect.isclass(klass): - raise TypeError('plugin must be a class; got %r' % klass) - - # Raise DuplicateError if this exact class was already registered: - if klass in self.__registered: - raise errors.PluginDuplicateError(plugin=klass) - - # Find the base class or raise SubclassError: - for (base, sub_d) in self.__findbases(klass): - # Check override: - if klass.__name__ in sub_d: - if not override: - # Must use override=True to override: - raise errors.PluginOverrideError( - base=base.__name__, - name=klass.__name__, - plugin=klass, - ) - else: - if override: - # There was nothing already registered to override: - raise errors.PluginMissingOverrideError( - base=base.__name__, - name=klass.__name__, - plugin=klass, - ) - - # The plugin is okay, add to sub_d: - sub_d[klass.__name__] = klass - - # The plugin is okay, add to __registered: - self.__registered.add(klass) - - class API(DictProxy): """ Dynamic API object through which `Plugin` instances are accessed. """ def __init__(self, *allowed): + self.__allowed = allowed self.__d = dict() self.__done = set() - self.register = Registrar(*allowed) + self.__registry = Registry() self.env = Env() super(API, self).__init__(self.__d) + def register(self, klass, override=False): + """ + Register the plugin ``klass``. + + :param klass: A subclass of `Plugin` to attempt to register. + :param override: If true, override an already registered plugin. + """ + self.__registry(override)(klass) + def __doing(self, name): if name in self.__done: raise StandardError( @@ -752,11 +745,10 @@ class API(DictProxy): yield p.instance production_mode = is_production_mode(self) - for name in self.register: - base = self.register[name] - magic = getattr(self.register, name) + for base, subclasses in self.__registry.iter(*self.__allowed): + name = base.__name__ namespace = NameSpace( - plugin_iter(base, (magic[k] for k in magic)) + plugin_iter(base, subclasses) ) if not production_mode: assert not ( |