mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
154: Merged ProxyTarget functionality into Plugin to make things a bit clearer
This commit is contained in:
@@ -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):
|
||||
|
||||
@@ -86,6 +86,155 @@ class test_ReadOnly(ClassChecker):
|
||||
assert read_only(obj, 'an_attribute') == 'Hello world!'
|
||||
|
||||
|
||||
class test_Plugin(ClassChecker):
|
||||
"""
|
||||
Tests the `Plugin` class.
|
||||
"""
|
||||
_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 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') == 'Plugin'
|
||||
|
||||
class some_subclass(self.cls):
|
||||
pass
|
||||
assert read_only(some_subclass(), 'name') == 'some_subclass'
|
||||
|
||||
def test_doc(self):
|
||||
"""
|
||||
Tests the `doc` property.
|
||||
"""
|
||||
class some_subclass(self.cls):
|
||||
'here is the doc string'
|
||||
assert read_only(some_subclass(), 'doc') == 'here is the doc string'
|
||||
|
||||
def test_implements(self):
|
||||
"""
|
||||
Tests the `implements` classmethod.
|
||||
"""
|
||||
class example(self.cls):
|
||||
__public__ = frozenset((
|
||||
'some_method',
|
||||
'some_property',
|
||||
))
|
||||
class superset(self.cls):
|
||||
__public__ = frozenset((
|
||||
'some_method',
|
||||
'some_property',
|
||||
'another_property',
|
||||
))
|
||||
class subset(self.cls):
|
||||
__public__ = frozenset((
|
||||
'some_property',
|
||||
))
|
||||
class any_object(object):
|
||||
__public__ = frozenset((
|
||||
'some_method',
|
||||
'some_property',
|
||||
))
|
||||
|
||||
for ex in (example, example()):
|
||||
# Test using str:
|
||||
assert ex.implements('some_method')
|
||||
assert not ex.implements('another_method')
|
||||
|
||||
# Test using frozenset:
|
||||
assert ex.implements(frozenset(['some_method']))
|
||||
assert not ex.implements(
|
||||
frozenset(['some_method', 'another_method'])
|
||||
)
|
||||
|
||||
# Test using another object/class with __public__ frozenset:
|
||||
assert ex.implements(example)
|
||||
assert ex.implements(example())
|
||||
|
||||
assert ex.implements(subset)
|
||||
assert not subset.implements(ex)
|
||||
|
||||
assert not ex.implements(superset)
|
||||
assert superset.implements(ex)
|
||||
|
||||
assert ex.implements(any_object)
|
||||
assert ex.implements(any_object())
|
||||
|
||||
def test_implemented_by(self):
|
||||
"""
|
||||
Tests the `implemented_by` classmethod.
|
||||
"""
|
||||
class base(self.cls):
|
||||
__public__ = frozenset((
|
||||
'attr0',
|
||||
'attr1',
|
||||
'attr2',
|
||||
))
|
||||
|
||||
class okay(base):
|
||||
def attr0(self):
|
||||
pass
|
||||
def __get_attr1(self):
|
||||
assert False # Make sure property isn't accesed on instance
|
||||
attr1 = property(__get_attr1)
|
||||
attr2 = 'hello world'
|
||||
another_attr = 'whatever'
|
||||
|
||||
class fail(base):
|
||||
def __init__(self):
|
||||
# Check that class, not instance is inspected:
|
||||
self.attr2 = 'hello world'
|
||||
def attr0(self):
|
||||
pass
|
||||
def __get_attr1(self):
|
||||
assert False # Make sure property isn't accesed on instance
|
||||
attr1 = property(__get_attr1)
|
||||
another_attr = 'whatever'
|
||||
|
||||
# Test that AssertionError is raised trying to pass something not
|
||||
# subclass nor instance of base:
|
||||
raises(AssertionError, base.implemented_by, object)
|
||||
|
||||
# Test on subclass with needed attributes:
|
||||
assert base.implemented_by(okay) is True
|
||||
assert base.implemented_by(okay()) is True
|
||||
|
||||
# Test on subclass *without* needed attributes:
|
||||
assert base.implemented_by(fail) is False
|
||||
assert base.implemented_by(fail()) is False
|
||||
|
||||
def test_finalize(self):
|
||||
"""
|
||||
Tests the `finalize` method.
|
||||
"""
|
||||
api = 'the api instance'
|
||||
o = self.cls()
|
||||
assert read_only(o, 'name') == 'Plugin'
|
||||
assert repr(o) == '%s.Plugin()' % plugable.__name__
|
||||
assert read_only(o, 'api') is None
|
||||
raises(AssertionError, o.finalize, None)
|
||||
o.finalize(api)
|
||||
assert read_only(o, 'api') is api
|
||||
raises(AssertionError, o.finalize, api)
|
||||
|
||||
class some_plugin(self.cls):
|
||||
pass
|
||||
sub = some_plugin()
|
||||
assert read_only(sub, 'name') == 'some_plugin'
|
||||
assert repr(sub) == '%s.some_plugin()' % __name__
|
||||
assert read_only(sub, 'api') is None
|
||||
raises(AssertionError, sub.finalize, None)
|
||||
sub.finalize(api)
|
||||
assert read_only(sub, 'api') is api
|
||||
raises(AssertionError, sub.finalize, api)
|
||||
|
||||
|
||||
class test_Proxy(ClassChecker):
|
||||
"""
|
||||
Tests the `Proxy` class.
|
||||
@@ -202,164 +351,6 @@ class test_Proxy(ClassChecker):
|
||||
assert read_only(c, 'name') == 'another_name'
|
||||
|
||||
|
||||
class test_ProxyTarget(ClassChecker):
|
||||
"""
|
||||
Test the `ProxyTarget` class.
|
||||
"""
|
||||
_cls = plugable.ProxyTarget
|
||||
|
||||
def test_class(self):
|
||||
assert self.cls.__bases__ == (plugable.ReadOnly,)
|
||||
assert type(self.cls.name) is property
|
||||
assert self.cls.implements(frozenset())
|
||||
|
||||
def test_name(self):
|
||||
"""
|
||||
Tests the `name` property.
|
||||
"""
|
||||
assert read_only(self.cls(), 'name') == 'ProxyTarget'
|
||||
|
||||
class some_subclass(self.cls):
|
||||
pass
|
||||
assert read_only(some_subclass(), 'name') == 'some_subclass'
|
||||
|
||||
def test_doc(self):
|
||||
"""
|
||||
Tests the `doc` property.
|
||||
"""
|
||||
class some_subclass(self.cls):
|
||||
'here is the doc string'
|
||||
assert read_only(some_subclass(), 'doc') == 'here is the doc string'
|
||||
|
||||
def test_implements(self):
|
||||
"""
|
||||
Tests the `implements` classmethod.
|
||||
"""
|
||||
class example(self.cls):
|
||||
__public__ = frozenset((
|
||||
'some_method',
|
||||
'some_property',
|
||||
))
|
||||
class superset(self.cls):
|
||||
__public__ = frozenset((
|
||||
'some_method',
|
||||
'some_property',
|
||||
'another_property',
|
||||
))
|
||||
class subset(self.cls):
|
||||
__public__ = frozenset((
|
||||
'some_property',
|
||||
))
|
||||
class any_object(object):
|
||||
__public__ = frozenset((
|
||||
'some_method',
|
||||
'some_property',
|
||||
))
|
||||
|
||||
for ex in (example, example()):
|
||||
# Test using str:
|
||||
assert ex.implements('some_method')
|
||||
assert not ex.implements('another_method')
|
||||
|
||||
# Test using frozenset:
|
||||
assert ex.implements(frozenset(['some_method']))
|
||||
assert not ex.implements(
|
||||
frozenset(['some_method', 'another_method'])
|
||||
)
|
||||
|
||||
# Test using another object/class with __public__ frozenset:
|
||||
assert ex.implements(example)
|
||||
assert ex.implements(example())
|
||||
|
||||
assert ex.implements(subset)
|
||||
assert not subset.implements(ex)
|
||||
|
||||
assert not ex.implements(superset)
|
||||
assert superset.implements(ex)
|
||||
|
||||
assert ex.implements(any_object)
|
||||
assert ex.implements(any_object())
|
||||
|
||||
def test_implemented_by(self):
|
||||
"""
|
||||
Tests the `implemented_by` classmethod.
|
||||
"""
|
||||
class base(self.cls):
|
||||
__public__ = frozenset((
|
||||
'attr0',
|
||||
'attr1',
|
||||
'attr2',
|
||||
))
|
||||
|
||||
class okay(base):
|
||||
def attr0(self):
|
||||
pass
|
||||
def __get_attr1(self):
|
||||
assert False # Make sure property isn't accesed on instance
|
||||
attr1 = property(__get_attr1)
|
||||
attr2 = 'hello world'
|
||||
another_attr = 'whatever'
|
||||
|
||||
class fail(base):
|
||||
def __init__(self):
|
||||
# Check that class, not instance is inspected:
|
||||
self.attr2 = 'hello world'
|
||||
def attr0(self):
|
||||
pass
|
||||
def __get_attr1(self):
|
||||
assert False # Make sure property isn't accesed on instance
|
||||
attr1 = property(__get_attr1)
|
||||
another_attr = 'whatever'
|
||||
|
||||
# Test that AssertionError is raised trying to pass something not
|
||||
# subclass nor instance of base:
|
||||
raises(AssertionError, base.implemented_by, object)
|
||||
|
||||
# Test on subclass with needed attributes:
|
||||
assert base.implemented_by(okay) is True
|
||||
assert base.implemented_by(okay()) is True
|
||||
|
||||
# Test on subclass *without* needed attributes:
|
||||
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.
|
||||
"""
|
||||
api = 'the api instance'
|
||||
o = self.cls()
|
||||
assert read_only(o, 'name') == 'Plugin'
|
||||
assert repr(o) == '%s.Plugin()' % plugable.__name__
|
||||
assert read_only(o, 'api') is None
|
||||
raises(AssertionError, o.finalize, None)
|
||||
o.finalize(api)
|
||||
assert read_only(o, 'api') is api
|
||||
raises(AssertionError, o.finalize, api)
|
||||
|
||||
class some_plugin(self.cls):
|
||||
pass
|
||||
sub = some_plugin()
|
||||
assert read_only(sub, 'name') == 'some_plugin'
|
||||
assert repr(sub) == '%s.some_plugin()' % __name__
|
||||
assert read_only(sub, 'api') is None
|
||||
raises(AssertionError, sub.finalize, None)
|
||||
sub.finalize(api)
|
||||
assert read_only(sub, 'api') is api
|
||||
raises(AssertionError, sub.finalize, api)
|
||||
|
||||
|
||||
def test_check_name():
|
||||
"""
|
||||
Tests the `check_name` function.
|
||||
|
||||
Reference in New Issue
Block a user