From 00f4272662e56a98fe498cc8f5761cc15bcd3825 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Thu, 14 Aug 2008 07:10:07 +0000 Subject: 154: Merged ProxyTarget functionality into Plugin to make things a bit clearer --- ipalib/plugable.py | 245 ++++++++++++++++++++-------------------- ipalib/tests/test_plugable.py | 255 ++++++++++++++++++++---------------------- 2 files changed, 245 insertions(+), 255 deletions(-) (limited to 'ipalib') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 8ab5e2491..645b2d16c 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -102,9 +102,130 @@ class ReadOnly(object): return object.__delattr__(self, name) +class Plugin(ReadOnly): + """ + Base class for all plugins. + """ + __public__ = frozenset() + __api = None + + def __get_name(self): + """ + Convenience property to return the class name. + """ + return self.__class__.__name__ + name = property(__get_name) + + def __get_doc(self): + """ + Convenience property to return the class docstring. + """ + return self.__class__.__doc__ + doc = property(__get_doc) + + def __get_api(self): + """ + Returns the `API` instance passed to `finalize`, or + or returns None if `finalize` has not yet been called. + """ + return self.__api + api = property(__get_api) + + @classmethod + def implements(cls, arg): + """ + Returns True if this cls.__public__ frozenset contains `arg`; + returns False otherwise. + + There are three different ways this can be called: + + 1. With a argument, e.g.: + + >>> class base(ProxyTarget): + >>> __public__ = frozenset(['some_attr', 'another_attr']) + >>> base.implements('some_attr') + True + >>> base.implements('an_unknown_attribute') + False + + 2. With a argument, e.g.: + + >>> base.implements(frozenset(['some_attr'])) + True + >>> base.implements(frozenset(['some_attr', 'an_unknown_attribute'])) + False + + 3. With any object that has a `__public__` attribute that is + , e.g.: + + >>> class whatever(object): + >>> __public__ = frozenset(['another_attr']) + >>> base.implements(whatever) + True + + Unlike ProxyTarget.implemented_by(), this returns an abstract answer + because only the __public__ frozenset is checked... a ProxyTarget + need not itself have attributes for all names in __public__ + (subclasses might provide them). + """ + assert type(cls.__public__) is frozenset + if isinstance(arg, str): + return arg in cls.__public__ + if type(getattr(arg, '__public__', None)) is frozenset: + return cls.__public__.issuperset(arg.__public__) + if type(arg) is frozenset: + return cls.__public__.issuperset(arg) + raise TypeError( + "must be str, frozenset, or have frozenset '__public__' attribute" + ) + + @classmethod + def implemented_by(cls, arg): + """ + Returns True if (1) `arg` is an instance of or subclass of this class, + and (2) `arg` (or `arg.__class__` if instance) has an attribute for + each name in this class's __public__ frozenset; returns False + otherwise. + + Unlike ProxyTarget.implements(), this returns a concrete answer + because the attributes of the subclass are checked. + """ + if inspect.isclass(arg): + subclass = arg + else: + subclass = arg.__class__ + assert issubclass(subclass, cls), 'must be subclass of %r' % cls + for name in cls.__public__: + if not hasattr(subclass, name): + return False + return True + + def finalize(self, api): + """ + After all the plugins are instantiated, `API` calls this method, + passing itself as the only argument. This is where plugins should + check that other plugins they depend upon have actually been loaded. + + :param api: An `API` instance. + """ + assert self.__api is None, 'finalize() can only be called once' + assert api is not None, 'finalize() argument cannot be None' + self.__api = api + + def __repr__(self): + """ + Returns a fully qualified module_name.class_name() representation that + could be used to construct this Plugin instance. + """ + return '%s.%s()' % ( + self.__class__.__module__, + self.__class__.__name__ + ) + + class Proxy(ReadOnly): """ - Allows access to only certain attributes on its target object. + Allows 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, @@ -208,128 +329,6 @@ class Proxy(ReadOnly): ) -class ProxyTarget(ReadOnly): - __public__ = frozenset() - - def __get_name(self): - """ - Convenience property to return the class name. - """ - return self.__class__.__name__ - name = property(__get_name) - - def __get_doc(self): - """ - Convenience property to return the class docstring. - """ - return self.__class__.__doc__ - doc = property(__get_doc) - - @classmethod - def implements(cls, arg): - """ - Returns True if this cls.__public__ frozenset contains `arg`; - returns False otherwise. - - There are three different ways this can be called: - - 1. With a argument, e.g.: - - >>> class base(ProxyTarget): - >>> __public__ = frozenset(['some_attr', 'another_attr']) - >>> base.implements('some_attr') - True - >>> base.implements('an_unknown_attribute') - False - - 2. With a argument, e.g.: - - >>> base.implements(frozenset(['some_attr'])) - True - >>> base.implements(frozenset(['some_attr', 'an_unknown_attribute'])) - False - - 3. With any object that has a `__public__` attribute that is - , e.g.: - - >>> class whatever(object): - >>> __public__ = frozenset(['another_attr']) - >>> base.implements(whatever) - True - - Unlike ProxyTarget.implemented_by(), this returns an abstract answer - because only the __public__ frozenset is checked... a ProxyTarget - need not itself have attributes for all names in __public__ - (subclasses might provide them). - """ - assert type(cls.__public__) is frozenset - if isinstance(arg, str): - return arg in cls.__public__ - if type(getattr(arg, '__public__', None)) is frozenset: - return cls.__public__.issuperset(arg.__public__) - if type(arg) is frozenset: - return cls.__public__.issuperset(arg) - raise TypeError( - "must be str, frozenset, or have frozenset '__public__' attribute" - ) - - @classmethod - def implemented_by(cls, arg): - """ - Returns True if (1) `arg` is an instance of or subclass of this class, - and (2) `arg` (or `arg.__class__` if instance) has an attribute for - each name in this class's __public__ frozenset; returns False - otherwise. - - Unlike ProxyTarget.implements(), this returns a concrete answer - because the attributes of the subclass are checked. - """ - if inspect.isclass(arg): - subclass = arg - else: - subclass = arg.__class__ - assert issubclass(subclass, cls), 'must be subclass of %r' % cls - for name in cls.__public__: - if not hasattr(subclass, name): - return False - return True - - -class Plugin(ProxyTarget): - """ - Base class for all plugins. - """ - - __api = None - - def __get_api(self): - """ - Returns the plugable.API instance passed to Plugin.finalize(), or - or returns None if finalize() has not yet been called. - """ - return self.__api - api = property(__get_api) - - def finalize(self, api): - """ - After all the plugins are instantiated, the plugable.API calls this - method, passing itself as the only argument. This is where plugins - should check that other plugins they depend upon have actually been - loaded. - """ - assert self.__api is None, 'finalize() can only be called once' - assert api is not None, 'finalize() argument cannot be None' - self.__api = api - - def __repr__(self): - """ - Returns a fully qualified module_name.class_name() representation that - could be used to construct this Plugin instance. - """ - return '%s.%s()' % ( - self.__class__.__module__, - self.__class__.__name__ - ) def check_name(name): diff --git a/ipalib/tests/test_plugable.py b/ipalib/tests/test_plugable.py index ddb8fed07..aa449b718 100644 --- a/ipalib/tests/test_plugable.py +++ b/ipalib/tests/test_plugable.py @@ -86,138 +86,24 @@ class test_ReadOnly(ClassChecker): assert read_only(obj, 'an_attribute') == 'Hello world!' -class test_Proxy(ClassChecker): - """ - Tests the `Proxy` class. - """ - _cls = plugable.Proxy - - def test_class(self): - assert self.cls.__bases__ == (plugable.ReadOnly,) - - def test_proxy(self): - # 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): - """ - Tests the `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): - """ - Tests the `__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' - - -class test_ProxyTarget(ClassChecker): +class test_Plugin(ClassChecker): """ - Test the `ProxyTarget` class. + Tests the `Plugin` class. """ - _cls = plugable.ProxyTarget + _cls = plugable.Plugin def test_class(self): assert self.cls.__bases__ == (plugable.ReadOnly,) + assert self.cls.__public__ == frozenset() assert type(self.cls.name) is property - assert self.cls.implements(frozenset()) + assert type(self.cls.doc) is property + assert type(self.cls.api) is property def test_name(self): """ Tests the `name` property. """ - assert read_only(self.cls(), 'name') == 'ProxyTarget' + assert read_only(self.cls(), 'name') == 'Plugin' class some_subclass(self.cls): pass @@ -323,17 +209,6 @@ class test_ProxyTarget(ClassChecker): assert base.implemented_by(fail) is False assert base.implemented_by(fail()) is False - -class test_Plugin(ClassChecker): - """ - Tests the `Plugin` class. - """ - _cls = plugable.Plugin - - def test_class(self): - assert self.cls.__bases__ == (plugable.ProxyTarget,) - assert type(self.cls.api) is property - def test_finalize(self): """ Tests the `finalize` method. @@ -360,6 +235,122 @@ class test_Plugin(ClassChecker): raises(AssertionError, sub.finalize, api) +class test_Proxy(ClassChecker): + """ + Tests the `Proxy` class. + """ + _cls = plugable.Proxy + + def test_class(self): + assert self.cls.__bases__ == (plugable.ReadOnly,) + + def test_proxy(self): + # 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): + """ + Tests the `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): + """ + Tests the `__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_check_name(): """ Tests the `check_name` function. -- cgit