mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2024-12-23 07:33:27 -06:00
Removed PluginProxy and all its uses
This commit is contained in:
parent
b7b9f9b6a6
commit
c0f558d98b
@ -239,9 +239,7 @@ plugin you implement.
|
|||||||
|
|
||||||
Backend plugins are much more free-form than command plugins. Aside from a
|
Backend plugins are much more free-form than command plugins. Aside from a
|
||||||
few reserved attribute names, you can define arbitrary public methods on your
|
few reserved attribute names, you can define arbitrary public methods on your
|
||||||
backend plugin (in contrast, frontend plugins get wrapped in a
|
backend plugin.
|
||||||
`plugable.PluginProxy`, which allow access to only specific attributes on the
|
|
||||||
frontend plugin).
|
|
||||||
|
|
||||||
Here is a simple example:
|
Here is a simple example:
|
||||||
|
|
||||||
|
@ -382,7 +382,7 @@ class NameSpace(ReadOnly):
|
|||||||
examples, see the `plugable.API` and the `frontend.Command` classes.
|
examples, see the `plugable.API` and the `frontend.Command` classes.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, members, sort=True):
|
def __init__(self, members, sort=True, name_attr='name'):
|
||||||
"""
|
"""
|
||||||
:param members: An iterable providing the members.
|
:param members: An iterable providing the members.
|
||||||
:param sort: Whether to sort the members by member name.
|
:param sort: Whether to sort the members by member name.
|
||||||
@ -394,14 +394,14 @@ class NameSpace(ReadOnly):
|
|||||||
self.__sort = sort
|
self.__sort = sort
|
||||||
if sort:
|
if sort:
|
||||||
self.__members = tuple(
|
self.__members = tuple(
|
||||||
sorted(members, key=lambda m: m.name)
|
sorted(members, key=lambda m: getattr(m, name_attr))
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.__members = tuple(members)
|
self.__members = tuple(members)
|
||||||
self.__names = tuple(m.name for m in self.__members)
|
self.__names = tuple(getattr(m, name_attr) for m in self.__members)
|
||||||
self.__map = dict()
|
self.__map = dict()
|
||||||
for member in self.__members:
|
for member in self.__members:
|
||||||
name = check_name(member.name)
|
name = check_name(getattr(member, name_attr))
|
||||||
if name in self.__map:
|
if name in self.__map:
|
||||||
raise AttributeError(OVERRIDE_ERROR %
|
raise AttributeError(OVERRIDE_ERROR %
|
||||||
(self.__class__.__name__, name, self.__map[name], member)
|
(self.__class__.__name__, name, self.__map[name], member)
|
||||||
|
@ -491,12 +491,15 @@ class help(frontend.Command):
|
|||||||
|
|
||||||
_PLUGIN_BASE_MODULE = 'ipalib.plugins'
|
_PLUGIN_BASE_MODULE = 'ipalib.plugins'
|
||||||
|
|
||||||
def _get_command_module(self, cmd_plugin_proxy):
|
def _get_command_module(self, module):
|
||||||
"""
|
"""
|
||||||
Return bare module name where command represented by PluginProxy
|
Return last part of ``module`` name, or ``None`` if module is this file.
|
||||||
instance is defined. Return None if command isn't defined in
|
|
||||||
a plugin module (we consider these commands to be builtins).
|
For example:
|
||||||
"""
|
"""
|
||||||
|
if module == __name__:
|
||||||
|
return
|
||||||
|
return module.split('.')[-1]
|
||||||
# get representation in the form of 'base_module.bare_module.command()'
|
# get representation in the form of 'base_module.bare_module.command()'
|
||||||
r = repr(cmd_plugin_proxy)
|
r = repr(cmd_plugin_proxy)
|
||||||
# skip base module part and the following dot
|
# skip base module part and the following dot
|
||||||
@ -517,7 +520,7 @@ class help(frontend.Command):
|
|||||||
|
|
||||||
# build help topics
|
# build help topics
|
||||||
for c in self.Command():
|
for c in self.Command():
|
||||||
topic = self._get_command_module(c)
|
topic = self._get_command_module(c.module)
|
||||||
if topic:
|
if topic:
|
||||||
self._topics.setdefault(topic, [0]).append(c)
|
self._topics.setdefault(topic, [0]).append(c)
|
||||||
# compute maximum length of command in topic
|
# compute maximum length of command in topic
|
||||||
@ -528,7 +531,7 @@ class help(frontend.Command):
|
|||||||
|
|
||||||
# compute maximum topic length
|
# compute maximum topic length
|
||||||
self._mtl = max(
|
self._mtl = max(
|
||||||
len(s) for s in (self._topics.keys() + self._builtins)
|
len(s) for s in (self._topics.keys() + [c.name for c in self._builtins])
|
||||||
)
|
)
|
||||||
|
|
||||||
super(help, self).finalize()
|
super(help, self).finalize()
|
||||||
|
@ -352,7 +352,7 @@ class Command(HasParam):
|
|||||||
>>> list(api.Command)
|
>>> list(api.Command)
|
||||||
['my_command']
|
['my_command']
|
||||||
>>> api.Command.my_command # doctest:+ELLIPSIS
|
>>> api.Command.my_command # doctest:+ELLIPSIS
|
||||||
PluginProxy(Command, ...my_command())
|
ipalib.frontend.my_command()
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__public__ = frozenset((
|
__public__ = frozenset((
|
||||||
@ -752,10 +752,10 @@ class Object(HasParam):
|
|||||||
def set_api(self, api):
|
def set_api(self, api):
|
||||||
super(Object, self).set_api(api)
|
super(Object, self).set_api(api)
|
||||||
self.methods = NameSpace(
|
self.methods = NameSpace(
|
||||||
self.__get_attrs('Method'), sort=False
|
self.__get_attrs('Method'), sort=False, name_attr='attr_name'
|
||||||
)
|
)
|
||||||
self.properties = NameSpace(
|
self.properties = NameSpace(
|
||||||
self.__get_attrs('Property'), sort=False
|
self.__get_attrs('Property'), sort=False, name_attr='attr_name'
|
||||||
)
|
)
|
||||||
self._create_param_namespace('params')
|
self._create_param_namespace('params')
|
||||||
pkeys = filter(lambda p: p.primary_key, self.params())
|
pkeys = filter(lambda p: p.primary_key, self.params())
|
||||||
@ -798,9 +798,9 @@ class Object(HasParam):
|
|||||||
return
|
return
|
||||||
namespace = self.api[name]
|
namespace = self.api[name]
|
||||||
assert type(namespace) is NameSpace
|
assert type(namespace) is NameSpace
|
||||||
for proxy in namespace(): # Equivalent to dict.itervalues()
|
for plugin in namespace(): # Equivalent to dict.itervalues()
|
||||||
if proxy.obj_name == self.name:
|
if plugin.obj_name == self.name:
|
||||||
yield proxy.__clone__('attr_name')
|
yield plugin
|
||||||
|
|
||||||
def get_params(self):
|
def get_params(self):
|
||||||
"""
|
"""
|
||||||
|
@ -308,118 +308,6 @@ class Plugin(ReadOnly):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class PluginProxy(SetProxy):
|
|
||||||
"""
|
|
||||||
Allow access to only certain attributes on a `Plugin`.
|
|
||||||
|
|
||||||
Think of a proxy as an agreement that "I will have at most these
|
|
||||||
attributes". This is different from (although similar to) an interface,
|
|
||||||
which can be thought of as an agreement that "I will have at least these
|
|
||||||
attributes".
|
|
||||||
"""
|
|
||||||
|
|
||||||
__slots__ = (
|
|
||||||
'__base',
|
|
||||||
'__target',
|
|
||||||
'__name_attr',
|
|
||||||
'__public__',
|
|
||||||
'name',
|
|
||||||
'doc',
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(self, base, target, name_attr='name'):
|
|
||||||
"""
|
|
||||||
:param base: A subclass of `Plugin`.
|
|
||||||
:param target: An instance ``base`` or a subclass of ``base``.
|
|
||||||
:param name_attr: The name of the attribute on ``target`` from which
|
|
||||||
to derive ``self.name``.
|
|
||||||
"""
|
|
||||||
if not inspect.isclass(base):
|
|
||||||
raise TypeError(
|
|
||||||
'`base` must be a class, got %r' % base
|
|
||||||
)
|
|
||||||
if not isinstance(target, base):
|
|
||||||
raise ValueError(
|
|
||||||
'`target` must be an instance of `base`, got %r' % target
|
|
||||||
)
|
|
||||||
self.__base = base
|
|
||||||
self.__target = target
|
|
||||||
self.__name_attr = name_attr
|
|
||||||
if hasattr(type(target), '__public__'):
|
|
||||||
self.__public__ = type(target).__public__
|
|
||||||
else:
|
|
||||||
self.__public__ = base.__public__
|
|
||||||
self.name = getattr(target, name_attr)
|
|
||||||
self.doc = target.doc
|
|
||||||
assert type(self.__public__) is frozenset
|
|
||||||
super(PluginProxy, self).__init__(self.__public__)
|
|
||||||
|
|
||||||
def implements(self, arg):
|
|
||||||
"""
|
|
||||||
Return True if plugin being proxied implements ``arg``.
|
|
||||||
|
|
||||||
This method simply calls the corresponding `Plugin.implements`
|
|
||||||
classmethod.
|
|
||||||
|
|
||||||
Unlike `Plugin.implements`, this is not a classmethod as a
|
|
||||||
`PluginProxy` can only implement anything as an instance.
|
|
||||||
"""
|
|
||||||
return self.__base.implements(arg)
|
|
||||||
|
|
||||||
def __clone__(self, name_attr):
|
|
||||||
"""
|
|
||||||
Return a `PluginProxy` instance similar to this one.
|
|
||||||
|
|
||||||
The new `PluginProxy` returned will be identical to this one except
|
|
||||||
the proxy name might be derived from a different attribute on the
|
|
||||||
target `Plugin`. The same base and target will be used.
|
|
||||||
"""
|
|
||||||
return self.__class__(self.__base, self.__target, name_attr)
|
|
||||||
|
|
||||||
def __getitem__(self, key):
|
|
||||||
"""
|
|
||||||
Return attribute named ``key`` on target `Plugin`.
|
|
||||||
|
|
||||||
If this proxy allows access to an attribute named ``key``, that
|
|
||||||
attribute will be returned. If access is not allowed,
|
|
||||||
KeyError will be raised.
|
|
||||||
"""
|
|
||||||
if key in self.__public__:
|
|
||||||
return getattr(self.__target, key)
|
|
||||||
raise KeyError('no public attribute %s.%s' % (self.name, key))
|
|
||||||
|
|
||||||
def __getattr__(self, name):
|
|
||||||
"""
|
|
||||||
Return attribute named ``name`` on target `Plugin`.
|
|
||||||
|
|
||||||
If this proxy allows access to an attribute named ``name``, that
|
|
||||||
attribute will be returned. If access is not allowed,
|
|
||||||
AttributeError will be raised.
|
|
||||||
"""
|
|
||||||
if name in self.__public__:
|
|
||||||
return getattr(self.__target, name)
|
|
||||||
raise AttributeError('no public attribute %s.%s' % (self.name, name))
|
|
||||||
|
|
||||||
def __call__(self, *args, **kw):
|
|
||||||
"""
|
|
||||||
Call target `Plugin` and return its return value.
|
|
||||||
|
|
||||||
If `__call__` is not an attribute this proxy allows access to,
|
|
||||||
KeyError is raised.
|
|
||||||
"""
|
|
||||||
return self['__call__'](*args, **kw)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
"""
|
|
||||||
Return a Python expression that could create this instance.
|
|
||||||
"""
|
|
||||||
return '%s(%s, %r)' % (
|
|
||||||
self.__class__.__name__,
|
|
||||||
self.__base.__name__,
|
|
||||||
self.__target,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Registrar(DictProxy):
|
class Registrar(DictProxy):
|
||||||
"""
|
"""
|
||||||
Collects plugin classes as they are registered.
|
Collects plugin classes as they are registered.
|
||||||
@ -734,10 +622,7 @@ class API(DictProxy):
|
|||||||
p = plugins[klass]
|
p = plugins[klass]
|
||||||
assert base not in p.bases
|
assert base not in p.bases
|
||||||
p.bases.append(base)
|
p.bases.append(base)
|
||||||
if base.__proxy__:
|
yield p.instance
|
||||||
yield PluginProxy(base, p.instance)
|
|
||||||
else:
|
|
||||||
yield p.instance
|
|
||||||
|
|
||||||
for name in self.register:
|
for name in self.register:
|
||||||
base = self.register[name]
|
base = self.register[name]
|
||||||
|
@ -666,7 +666,7 @@ class test_Object(ClassChecker):
|
|||||||
assert attr is getattr(namespace, attr_name)
|
assert attr is getattr(namespace, attr_name)
|
||||||
assert attr.obj_name == 'user'
|
assert attr.obj_name == 'user'
|
||||||
assert attr.attr_name == attr_name
|
assert attr.attr_name == attr_name
|
||||||
assert attr.name == attr_name
|
assert attr.name == '%s_%s' % ('user', attr_name)
|
||||||
|
|
||||||
# Test params instance attribute
|
# Test params instance attribute
|
||||||
o = self.cls()
|
o = self.cls()
|
||||||
|
@ -377,128 +377,6 @@ class test_Plugin(ClassChecker):
|
|||||||
assert e.argv == ('/bin/false',)
|
assert e.argv == ('/bin/false',)
|
||||||
|
|
||||||
|
|
||||||
class test_PluginProxy(ClassChecker):
|
|
||||||
"""
|
|
||||||
Test the `ipalib.plugable.PluginProxy` class.
|
|
||||||
"""
|
|
||||||
_cls = plugable.PluginProxy
|
|
||||||
|
|
||||||
def test_class(self):
|
|
||||||
"""
|
|
||||||
Test the `ipalib.plugable.PluginProxy` class.
|
|
||||||
"""
|
|
||||||
assert self.cls.__bases__ == (plugable.SetProxy,)
|
|
||||||
|
|
||||||
def test_proxy(self):
|
|
||||||
"""
|
|
||||||
Test proxy behaviour of `ipalib.plugable.PluginProxy` instance.
|
|
||||||
"""
|
|
||||||
# Setup:
|
|
||||||
class base(object):
|
|
||||||
__public__ = frozenset((
|
|
||||||
'public_0',
|
|
||||||
'public_1',
|
|
||||||
'__call__',
|
|
||||||
))
|
|
||||||
|
|
||||||
def public_0(self):
|
|
||||||
return 'public_0'
|
|
||||||
|
|
||||||
def public_1(self):
|
|
||||||
return 'public_1'
|
|
||||||
|
|
||||||
def __call__(self, caller):
|
|
||||||
return 'ya called it, %s.' % caller
|
|
||||||
|
|
||||||
def private_0(self):
|
|
||||||
return 'private_0'
|
|
||||||
|
|
||||||
def private_1(self):
|
|
||||||
return 'private_1'
|
|
||||||
|
|
||||||
class plugin(base):
|
|
||||||
name = 'user_add'
|
|
||||||
attr_name = 'add'
|
|
||||||
doc = 'add a new user'
|
|
||||||
|
|
||||||
# Test that TypeError is raised when base is not a class:
|
|
||||||
raises(TypeError, self.cls, base(), None)
|
|
||||||
|
|
||||||
# Test that ValueError is raised when target is not instance of base:
|
|
||||||
raises(ValueError, self.cls, base, object())
|
|
||||||
|
|
||||||
# Test with correct arguments:
|
|
||||||
i = plugin()
|
|
||||||
p = self.cls(base, i)
|
|
||||||
assert read_only(p, 'name') is plugin.name
|
|
||||||
assert read_only(p, 'doc') == plugin.doc
|
|
||||||
assert list(p) == sorted(base.__public__)
|
|
||||||
|
|
||||||
# Test normal methods:
|
|
||||||
for n in xrange(2):
|
|
||||||
pub = 'public_%d' % n
|
|
||||||
priv = 'private_%d' % n
|
|
||||||
assert getattr(i, pub)() == pub
|
|
||||||
assert getattr(p, pub)() == pub
|
|
||||||
assert hasattr(p, pub)
|
|
||||||
assert getattr(i, priv)() == priv
|
|
||||||
assert not hasattr(p, priv)
|
|
||||||
|
|
||||||
# Test __call__:
|
|
||||||
value = 'ya called it, dude.'
|
|
||||||
assert i('dude') == value
|
|
||||||
assert p('dude') == value
|
|
||||||
assert callable(p)
|
|
||||||
|
|
||||||
# Test name_attr='name' kw arg
|
|
||||||
i = plugin()
|
|
||||||
p = self.cls(base, i, 'attr_name')
|
|
||||||
assert read_only(p, 'name') == 'add'
|
|
||||||
|
|
||||||
def test_implements(self):
|
|
||||||
"""
|
|
||||||
Test the `ipalib.plugable.PluginProxy.implements` method.
|
|
||||||
"""
|
|
||||||
class base(object):
|
|
||||||
__public__ = frozenset()
|
|
||||||
name = 'base'
|
|
||||||
doc = 'doc'
|
|
||||||
@classmethod
|
|
||||||
def implements(cls, arg):
|
|
||||||
return arg + 7
|
|
||||||
|
|
||||||
class sub(base):
|
|
||||||
@classmethod
|
|
||||||
def implements(cls, arg):
|
|
||||||
"""
|
|
||||||
Defined to make sure base.implements() is called, not
|
|
||||||
target.implements()
|
|
||||||
"""
|
|
||||||
return arg
|
|
||||||
|
|
||||||
o = sub()
|
|
||||||
p = self.cls(base, o)
|
|
||||||
assert p.implements(3) == 10
|
|
||||||
|
|
||||||
def test_clone(self):
|
|
||||||
"""
|
|
||||||
Test the `ipalib.plugable.PluginProxy.__clone__` method.
|
|
||||||
"""
|
|
||||||
class base(object):
|
|
||||||
__public__ = frozenset()
|
|
||||||
class sub(base):
|
|
||||||
name = 'some_name'
|
|
||||||
doc = 'doc'
|
|
||||||
label = 'another_name'
|
|
||||||
|
|
||||||
p = self.cls(base, sub())
|
|
||||||
assert read_only(p, 'name') == 'some_name'
|
|
||||||
c = p.__clone__('label')
|
|
||||||
assert isinstance(c, self.cls)
|
|
||||||
assert c is not p
|
|
||||||
assert read_only(c, 'name') == 'another_name'
|
|
||||||
|
|
||||||
|
|
||||||
def test_Registrar():
|
def test_Registrar():
|
||||||
"""
|
"""
|
||||||
Test the `ipalib.plugable.Registrar` class
|
Test the `ipalib.plugable.Registrar` class
|
||||||
@ -682,50 +560,34 @@ class test_API(ClassChecker):
|
|||||||
assert api.isdone('bootstrap') is True
|
assert api.isdone('bootstrap') is True
|
||||||
assert api.isdone('finalize') is True
|
assert api.isdone('finalize') is True
|
||||||
|
|
||||||
def get_base(b):
|
def get_base_name(b):
|
||||||
return 'base%d' % b
|
return 'base%d' % b
|
||||||
|
|
||||||
def get_plugin(b, p):
|
|
||||||
|
def get_plugin_name(b, p):
|
||||||
return 'base%d_plugin%d' % (b, p)
|
return 'base%d_plugin%d' % (b, p)
|
||||||
|
|
||||||
for b in xrange(2):
|
for b in xrange(2):
|
||||||
base_name = get_base(b)
|
base_name = get_base_name(b)
|
||||||
|
base = locals()[base_name]
|
||||||
ns = getattr(api, base_name)
|
ns = getattr(api, base_name)
|
||||||
assert isinstance(ns, plugable.NameSpace)
|
assert isinstance(ns, plugable.NameSpace)
|
||||||
assert read_only(api, base_name) is ns
|
assert read_only(api, base_name) is ns
|
||||||
assert len(ns) == 3
|
assert len(ns) == 3
|
||||||
for p in xrange(3):
|
for p in xrange(3):
|
||||||
plugin_name = get_plugin(b, p)
|
plugin_name = get_plugin_name(b, p)
|
||||||
proxy = ns[plugin_name]
|
plugin = locals()[plugin_name]
|
||||||
assert isinstance(proxy, plugable.PluginProxy)
|
inst = ns[plugin_name]
|
||||||
assert proxy.name == plugin_name
|
assert isinstance(inst, base)
|
||||||
assert read_only(ns, plugin_name) is proxy
|
assert isinstance(inst, plugin)
|
||||||
assert read_only(proxy, 'method')(7) == 7 + b
|
assert inst.name == plugin_name
|
||||||
|
assert read_only(ns, plugin_name) is inst
|
||||||
|
assert inst.method(7) == 7 + b
|
||||||
|
|
||||||
# Test that calling finilize again raises AssertionError:
|
# Test that calling finilize again raises AssertionError:
|
||||||
e = raises(StandardError, api.finalize)
|
e = raises(StandardError, api.finalize)
|
||||||
assert str(e) == 'API.finalize() already called', str(e)
|
assert str(e) == 'API.finalize() already called', str(e)
|
||||||
|
|
||||||
# Test with base class that doesn't request a proxy
|
|
||||||
class NoProxy(plugable.Plugin):
|
|
||||||
__proxy__ = False
|
|
||||||
api = plugable.API(NoProxy)
|
|
||||||
api.env.mode = 'unit_test'
|
|
||||||
class plugin0(NoProxy):
|
|
||||||
pass
|
|
||||||
api.register(plugin0)
|
|
||||||
class plugin1(NoProxy):
|
|
||||||
pass
|
|
||||||
api.register(plugin1)
|
|
||||||
api.finalize()
|
|
||||||
names = ['plugin0', 'plugin1']
|
|
||||||
assert list(api.NoProxy) == names
|
|
||||||
for name in names:
|
|
||||||
plugin = api.NoProxy[name]
|
|
||||||
assert getattr(api.NoProxy, name) is plugin
|
|
||||||
assert isinstance(plugin, plugable.Plugin)
|
|
||||||
assert not isinstance(plugin, plugable.PluginProxy)
|
|
||||||
|
|
||||||
def test_bootstrap(self):
|
def test_bootstrap(self):
|
||||||
"""
|
"""
|
||||||
Test the `ipalib.plugable.API.bootstrap` method.
|
Test the `ipalib.plugable.API.bootstrap` method.
|
||||||
|
Loading…
Reference in New Issue
Block a user