diff options
author | Jan Cholasta <jcholast@redhat.com> | 2016-06-14 13:02:30 +0200 |
---|---|---|
committer | Jan Cholasta <jcholast@redhat.com> | 2016-06-15 14:03:51 +0200 |
commit | bebdce89b6075f77beb36ce194b36ad4d7104ca3 (patch) | |
tree | 8ea216e2ed72e70b5007ae7047a0dabd4c6a5e47 /ipalib | |
parent | 3e6af238bb695572e462ff49a3096ab0e2e85bc5 (diff) | |
download | freeipa-bebdce89b6075f77beb36ce194b36ad4d7104ca3.tar.gz freeipa-bebdce89b6075f77beb36ce194b36ad4d7104ca3.tar.xz freeipa-bebdce89b6075f77beb36ce194b36ad4d7104ca3.zip |
plugable: allow plugins to be non-classes
Allow registering any object that is callable and has `name` and `bases`
attributes as a plugin.
https://fedorahosted.org/freeipa/ticket/4739
Reviewed-By: David Kupka <dkupka@redhat.com>
Diffstat (limited to 'ipalib')
-rw-r--r-- | ipalib/plugable.py | 45 | ||||
-rw-r--r-- | ipalib/util.py | 26 |
2 files changed, 53 insertions, 18 deletions
diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 497b5450e..d8ff5c186 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -26,7 +26,6 @@ http://docs.python.org/ref/sequence-types.html """ import sys -import inspect import threading import os from os import path @@ -40,6 +39,7 @@ import six from ipalib import errors from ipalib.config import Env from ipalib.text import _ +from ipalib.util import classproperty from ipalib.base import ReadOnly, NameSpace, lock, islocked from ipalib.constants import DEFAULT_CONFIG from ipapython.ipa_log_manager import ( @@ -101,8 +101,8 @@ class Registry(object): :param klass: A subclass of `Plugin` to attempt to register. """ - if not inspect.isclass(klass): - raise TypeError('plugin must be a class; got %r' % klass) + if not callable(klass): + raise TypeError('plugin must be callable; got %r' % klass) # Raise DuplicateError if this exact class was already registered: if klass in self.__registry: @@ -134,9 +134,18 @@ class Plugin(ReadOnly): self.__finalize_lock = threading.RLock() log_mgr.get_logger(self, True) - @property - def name(self): - return type(self).__name__ + @classmethod + def __name_getter(cls): + return cls.__name__ + + # you know nothing, pylint + name = classproperty(__name_getter) + + @classmethod + def __bases_getter(cls): + return cls.__bases__ + + bases = classproperty(__bases_getter) @property def doc(self): @@ -571,12 +580,12 @@ class API(ReadOnly): :param klass: A subclass of `Plugin` to attempt to add. :param override: If true, override an already added plugin. """ - if not inspect.isclass(klass): - raise TypeError('plugin must be a class; got %r' % klass) + if not callable(klass): + raise TypeError('plugin must be callable; got %r' % klass) # Find the base class or raise SubclassError: - for base in self.bases: - if issubclass(klass, self.bases): + for base in klass.bases: + if issubclass(base, self.bases): break else: raise errors.PluginSubclassError( @@ -585,13 +594,13 @@ class API(ReadOnly): ) # Check override: - prev = self.__plugins.get(klass.__name__) + prev = self.__plugins.get(klass.name) if prev: if not override: # Must use override=True to override: raise errors.PluginOverrideError( base=base.__name__, - name=klass.__name__, + name=klass.name, plugin=klass, ) @@ -601,12 +610,12 @@ class API(ReadOnly): # There was nothing already registered to override: raise errors.PluginMissingOverrideError( base=base.__name__, - name=klass.__name__, + name=klass.name, plugin=klass, ) # The plugin is okay, add to sub_d: - self.__plugins[klass.__name__] = klass + self.__plugins[klass.name] = klass def finalize(self): """ @@ -627,7 +636,7 @@ class API(ReadOnly): members = [] for klass in self.__plugins.values(): - if not issubclass(klass, base): + if not any(issubclass(b, base) for b in klass.bases): continue try: instance = plugins[klass] @@ -635,7 +644,7 @@ class API(ReadOnly): instance = plugins[klass] = klass(self) members.append(instance) plugin_info.setdefault( - '%s.%s' % (klass.__module__, klass.__name__), + '%s.%s' % (klass.__module__, klass.name), []).append(name) if not production_mode: @@ -657,8 +666,8 @@ class API(ReadOnly): lock(self) def get_plugin_next(self, klass): - if not inspect.isclass(klass): - raise TypeError('plugin must be a class; got %r' % klass) + if not callable(klass): + raise TypeError('plugin must be callable; got %r' % klass) return self.__next[klass] diff --git a/ipalib/util.py b/ipalib/util.py index 2c8772e52..4b5f11509 100644 --- a/ipalib/util.py +++ b/ipalib/util.py @@ -872,3 +872,29 @@ def detect_dns_zone_realm_type(api, domain): def has_managed_topology(api): domainlevel = api.Command['domainlevel_get']().get('result', DOMAIN_LEVEL_0) return domainlevel > DOMAIN_LEVEL_0 + + +class classproperty(object): + __slots__ = ('__doc__', 'fget') + + def __init__(self, fget=None, doc=None): + if doc is None and fget is not None: + doc = fget.__doc__ + + self.fget = fget + self.__doc__ = doc + + def __get__(self, obj, obj_type): + if self.fget is not None: + return self.fget.__get__(obj, obj_type)() + raise AttributeError("unreadable attribute") + + def __set__(self, obj, value): + raise AttributeError("can't set attribute") + + def __delete__(self, obj): + raise AttributeError("can't delete attribute") + + def getter(self, fget): + self.fget = fget + return self |