From c0f558d98b46df6131b221b746e8dc54787225e7 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Tue, 4 Aug 2009 02:41:11 -0600 Subject: [PATCH] Removed PluginProxy and all its uses --- ipalib/__init__.py | 4 +- ipalib/base.py | 8 +- ipalib/cli.py | 15 +-- ipalib/frontend.py | 12 +-- ipalib/plugable.py | 117 +------------------- tests/test_ipalib/test_frontend.py | 2 +- tests/test_ipalib/test_plugable.py | 164 +++-------------------------- 7 files changed, 35 insertions(+), 287 deletions(-) diff --git a/ipalib/__init__.py b/ipalib/__init__.py index 4ad58d6e9..b21c30384 100644 --- a/ipalib/__init__.py +++ b/ipalib/__init__.py @@ -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: diff --git a/ipalib/base.py b/ipalib/base.py index e0951e41e..38b1e8f32 100644 --- a/ipalib/base.py +++ b/ipalib/base.py @@ -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) diff --git a/ipalib/cli.py b/ipalib/cli.py index 3258556b9..5f7822357 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -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() diff --git a/ipalib/frontend.py b/ipalib/frontend.py index 2a5c5311e..5ee703812 100644 --- a/ipalib/frontend.py +++ b/ipalib/frontend.py @@ -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): """ diff --git a/ipalib/plugable.py b/ipalib/plugable.py index ba7ac711b..d94ff7524 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -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] diff --git a/tests/test_ipalib/test_frontend.py b/tests/test_ipalib/test_frontend.py index 170891eea..385219d04 100644 --- a/tests/test_ipalib/test_frontend.py +++ b/tests/test_ipalib/test_frontend.py @@ -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() diff --git a/tests/test_ipalib/test_plugable.py b/tests/test_ipalib/test_plugable.py index 6f0cc297d..ec1ef3e00 100644 --- a/tests/test_ipalib/test_plugable.py +++ b/tests/test_ipalib/test_plugable.py @@ -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.