Removed PluginProxy and all its uses

This commit is contained in:
Jason Gerard DeRose 2009-08-04 02:41:11 -06:00 committed by Rob Crittenden
parent b7b9f9b6a6
commit c0f558d98b
7 changed files with 35 additions and 287 deletions

View File

@ -239,9 +239,7 @@ plugin you implement.
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
backend plugin (in contrast, frontend plugins get wrapped in a
`plugable.PluginProxy`, which allow access to only specific attributes on the
frontend plugin).
backend plugin.
Here is a simple example:

View File

@ -382,7 +382,7 @@ class NameSpace(ReadOnly):
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 sort: Whether to sort the members by member name.
@ -394,14 +394,14 @@ class NameSpace(ReadOnly):
self.__sort = sort
if sort:
self.__members = tuple(
sorted(members, key=lambda m: m.name)
sorted(members, key=lambda m: getattr(m, name_attr))
)
else:
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()
for member in self.__members:
name = check_name(member.name)
name = check_name(getattr(member, name_attr))
if name in self.__map:
raise AttributeError(OVERRIDE_ERROR %
(self.__class__.__name__, name, self.__map[name], member)

View File

@ -491,12 +491,15 @@ class help(frontend.Command):
_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
instance is defined. Return None if command isn't defined in
a plugin module (we consider these commands to be builtins).
Return last part of ``module`` name, or ``None`` if module is this file.
For example:
"""
if module == __name__:
return
return module.split('.')[-1]
# get representation in the form of 'base_module.bare_module.command()'
r = repr(cmd_plugin_proxy)
# skip base module part and the following dot
@ -517,7 +520,7 @@ class help(frontend.Command):
# build help topics
for c in self.Command():
topic = self._get_command_module(c)
topic = self._get_command_module(c.module)
if topic:
self._topics.setdefault(topic, [0]).append(c)
# compute maximum length of command in topic
@ -528,7 +531,7 @@ class help(frontend.Command):
# compute maximum topic length
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()

View File

@ -352,7 +352,7 @@ class Command(HasParam):
>>> list(api.Command)
['my_command']
>>> api.Command.my_command # doctest:+ELLIPSIS
PluginProxy(Command, ...my_command())
ipalib.frontend.my_command()
"""
__public__ = frozenset((
@ -752,10 +752,10 @@ class Object(HasParam):
def set_api(self, api):
super(Object, self).set_api(api)
self.methods = NameSpace(
self.__get_attrs('Method'), sort=False
self.__get_attrs('Method'), sort=False, name_attr='attr_name'
)
self.properties = NameSpace(
self.__get_attrs('Property'), sort=False
self.__get_attrs('Property'), sort=False, name_attr='attr_name'
)
self._create_param_namespace('params')
pkeys = filter(lambda p: p.primary_key, self.params())
@ -798,9 +798,9 @@ class Object(HasParam):
return
namespace = self.api[name]
assert type(namespace) is NameSpace
for proxy in namespace(): # Equivalent to dict.itervalues()
if proxy.obj_name == self.name:
yield proxy.__clone__('attr_name')
for plugin in namespace(): # Equivalent to dict.itervalues()
if plugin.obj_name == self.name:
yield plugin
def get_params(self):
"""

View File

@ -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):
"""
Collects plugin classes as they are registered.
@ -734,10 +622,7 @@ class API(DictProxy):
p = plugins[klass]
assert base not in p.bases
p.bases.append(base)
if base.__proxy__:
yield PluginProxy(base, p.instance)
else:
yield p.instance
yield p.instance
for name in self.register:
base = self.register[name]

View File

@ -666,7 +666,7 @@ class test_Object(ClassChecker):
assert attr is getattr(namespace, attr_name)
assert attr.obj_name == 'user'
assert attr.attr_name == attr_name
assert attr.name == attr_name
assert attr.name == '%s_%s' % ('user', attr_name)
# Test params instance attribute
o = self.cls()

View File

@ -377,128 +377,6 @@ class test_Plugin(ClassChecker):
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():
"""
Test the `ipalib.plugable.Registrar` class
@ -682,50 +560,34 @@ class test_API(ClassChecker):
assert api.isdone('bootstrap') is True
assert api.isdone('finalize') is True
def get_base(b):
def get_base_name(b):
return 'base%d' % b
def get_plugin(b, p):
def get_plugin_name(b, p):
return 'base%d_plugin%d' % (b, p)
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)
assert isinstance(ns, plugable.NameSpace)
assert read_only(api, base_name) is ns
assert len(ns) == 3
for p in xrange(3):
plugin_name = get_plugin(b, p)
proxy = ns[plugin_name]
assert isinstance(proxy, plugable.PluginProxy)
assert proxy.name == plugin_name
assert read_only(ns, plugin_name) is proxy
assert read_only(proxy, 'method')(7) == 7 + b
plugin_name = get_plugin_name(b, p)
plugin = locals()[plugin_name]
inst = ns[plugin_name]
assert isinstance(inst, base)
assert isinstance(inst, plugin)
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:
e = raises(StandardError, api.finalize)
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):
"""
Test the `ipalib.plugable.API.bootstrap` method.