mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
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>
This commit is contained in:
parent
3e6af238bb
commit
bebdce89b6
@ -26,7 +26,6 @@ http://docs.python.org/ref/sequence-types.html
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import inspect
|
|
||||||
import threading
|
import threading
|
||||||
import os
|
import os
|
||||||
from os import path
|
from os import path
|
||||||
@ -40,6 +39,7 @@ import six
|
|||||||
from ipalib import errors
|
from ipalib import errors
|
||||||
from ipalib.config import Env
|
from ipalib.config import Env
|
||||||
from ipalib.text import _
|
from ipalib.text import _
|
||||||
|
from ipalib.util import classproperty
|
||||||
from ipalib.base import ReadOnly, NameSpace, lock, islocked
|
from ipalib.base import ReadOnly, NameSpace, lock, islocked
|
||||||
from ipalib.constants import DEFAULT_CONFIG
|
from ipalib.constants import DEFAULT_CONFIG
|
||||||
from ipapython.ipa_log_manager import (
|
from ipapython.ipa_log_manager import (
|
||||||
@ -101,8 +101,8 @@ class Registry(object):
|
|||||||
|
|
||||||
:param klass: A subclass of `Plugin` to attempt to register.
|
:param klass: A subclass of `Plugin` to attempt to register.
|
||||||
"""
|
"""
|
||||||
if not inspect.isclass(klass):
|
if not callable(klass):
|
||||||
raise TypeError('plugin must be a class; got %r' % klass)
|
raise TypeError('plugin must be callable; got %r' % klass)
|
||||||
|
|
||||||
# Raise DuplicateError if this exact class was already registered:
|
# Raise DuplicateError if this exact class was already registered:
|
||||||
if klass in self.__registry:
|
if klass in self.__registry:
|
||||||
@ -134,9 +134,18 @@ class Plugin(ReadOnly):
|
|||||||
self.__finalize_lock = threading.RLock()
|
self.__finalize_lock = threading.RLock()
|
||||||
log_mgr.get_logger(self, True)
|
log_mgr.get_logger(self, True)
|
||||||
|
|
||||||
@property
|
@classmethod
|
||||||
def name(self):
|
def __name_getter(cls):
|
||||||
return type(self).__name__
|
return cls.__name__
|
||||||
|
|
||||||
|
# you know nothing, pylint
|
||||||
|
name = classproperty(__name_getter)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def __bases_getter(cls):
|
||||||
|
return cls.__bases__
|
||||||
|
|
||||||
|
bases = classproperty(__bases_getter)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def doc(self):
|
def doc(self):
|
||||||
@ -571,12 +580,12 @@ class API(ReadOnly):
|
|||||||
:param klass: A subclass of `Plugin` to attempt to add.
|
:param klass: A subclass of `Plugin` to attempt to add.
|
||||||
:param override: If true, override an already added plugin.
|
:param override: If true, override an already added plugin.
|
||||||
"""
|
"""
|
||||||
if not inspect.isclass(klass):
|
if not callable(klass):
|
||||||
raise TypeError('plugin must be a class; got %r' % klass)
|
raise TypeError('plugin must be callable; got %r' % klass)
|
||||||
|
|
||||||
# Find the base class or raise SubclassError:
|
# Find the base class or raise SubclassError:
|
||||||
for base in self.bases:
|
for base in klass.bases:
|
||||||
if issubclass(klass, self.bases):
|
if issubclass(base, self.bases):
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
raise errors.PluginSubclassError(
|
raise errors.PluginSubclassError(
|
||||||
@ -585,13 +594,13 @@ class API(ReadOnly):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Check override:
|
# Check override:
|
||||||
prev = self.__plugins.get(klass.__name__)
|
prev = self.__plugins.get(klass.name)
|
||||||
if prev:
|
if prev:
|
||||||
if not override:
|
if not override:
|
||||||
# Must use override=True to override:
|
# Must use override=True to override:
|
||||||
raise errors.PluginOverrideError(
|
raise errors.PluginOverrideError(
|
||||||
base=base.__name__,
|
base=base.__name__,
|
||||||
name=klass.__name__,
|
name=klass.name,
|
||||||
plugin=klass,
|
plugin=klass,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -601,12 +610,12 @@ class API(ReadOnly):
|
|||||||
# There was nothing already registered to override:
|
# There was nothing already registered to override:
|
||||||
raise errors.PluginMissingOverrideError(
|
raise errors.PluginMissingOverrideError(
|
||||||
base=base.__name__,
|
base=base.__name__,
|
||||||
name=klass.__name__,
|
name=klass.name,
|
||||||
plugin=klass,
|
plugin=klass,
|
||||||
)
|
)
|
||||||
|
|
||||||
# The plugin is okay, add to sub_d:
|
# The plugin is okay, add to sub_d:
|
||||||
self.__plugins[klass.__name__] = klass
|
self.__plugins[klass.name] = klass
|
||||||
|
|
||||||
def finalize(self):
|
def finalize(self):
|
||||||
"""
|
"""
|
||||||
@ -627,7 +636,7 @@ class API(ReadOnly):
|
|||||||
members = []
|
members = []
|
||||||
|
|
||||||
for klass in self.__plugins.values():
|
for klass in self.__plugins.values():
|
||||||
if not issubclass(klass, base):
|
if not any(issubclass(b, base) for b in klass.bases):
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
instance = plugins[klass]
|
instance = plugins[klass]
|
||||||
@ -635,7 +644,7 @@ class API(ReadOnly):
|
|||||||
instance = plugins[klass] = klass(self)
|
instance = plugins[klass] = klass(self)
|
||||||
members.append(instance)
|
members.append(instance)
|
||||||
plugin_info.setdefault(
|
plugin_info.setdefault(
|
||||||
'%s.%s' % (klass.__module__, klass.__name__),
|
'%s.%s' % (klass.__module__, klass.name),
|
||||||
[]).append(name)
|
[]).append(name)
|
||||||
|
|
||||||
if not production_mode:
|
if not production_mode:
|
||||||
@ -657,8 +666,8 @@ class API(ReadOnly):
|
|||||||
lock(self)
|
lock(self)
|
||||||
|
|
||||||
def get_plugin_next(self, klass):
|
def get_plugin_next(self, klass):
|
||||||
if not inspect.isclass(klass):
|
if not callable(klass):
|
||||||
raise TypeError('plugin must be a class; got %r' % klass)
|
raise TypeError('plugin must be callable; got %r' % klass)
|
||||||
|
|
||||||
return self.__next[klass]
|
return self.__next[klass]
|
||||||
|
|
||||||
|
@ -872,3 +872,29 @@ def detect_dns_zone_realm_type(api, domain):
|
|||||||
def has_managed_topology(api):
|
def has_managed_topology(api):
|
||||||
domainlevel = api.Command['domainlevel_get']().get('result', DOMAIN_LEVEL_0)
|
domainlevel = api.Command['domainlevel_get']().get('result', DOMAIN_LEVEL_0)
|
||||||
return domainlevel > 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
|
||||||
|
Loading…
Reference in New Issue
Block a user