summaryrefslogtreecommitdiffstats
path: root/ipalib
diff options
context:
space:
mode:
authorJason Gerard DeRose <jderose@redhat.com>2008-08-14 07:10:07 +0000
committerJason Gerard DeRose <jderose@redhat.com>2008-08-14 07:10:07 +0000
commit00f4272662e56a98fe498cc8f5761cc15bcd3825 (patch)
tree6cdb1bf0d782cd2fb70a281d5b96b0e6d76b0ba4 /ipalib
parenta59d6698d2a13792210bcaeac1ee79e255fd8f1c (diff)
downloadfreeipa-00f4272662e56a98fe498cc8f5761cc15bcd3825.tar.gz
freeipa-00f4272662e56a98fe498cc8f5761cc15bcd3825.tar.xz
freeipa-00f4272662e56a98fe498cc8f5761cc15bcd3825.zip
154: Merged ProxyTarget functionality into Plugin to make things a bit clearer
Diffstat (limited to 'ipalib')
-rw-r--r--ipalib/plugable.py245
-rw-r--r--ipalib/tests/test_plugable.py255
2 files changed, 245 insertions, 255 deletions
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 <type 'str'> 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 <type 'frozenset'> 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
+ <type 'frozenset'>, 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 <type 'str'> 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 <type 'frozenset'> 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
- <type 'frozenset'>, 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.