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 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:

View File

@ -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)

View File

@ -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()

View File

@ -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):
""" """

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): class Registrar(DictProxy):
""" """
Collects plugin classes as they are registered. Collects plugin classes as they are registered.
@ -734,9 +622,6 @@ 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 PluginProxy(base, p.instance)
else:
yield p.instance yield p.instance
for name in self.register: for name in self.register:

View File

@ -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()

View File

@ -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.