summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJason Gerard DeRose <jderose@redhat.com>2008-08-01 06:44:30 +0000
committerJason Gerard DeRose <jderose@redhat.com>2008-08-01 06:44:30 +0000
commit8881e4a543e9f1f1edda2d1cc935c020950214e6 (patch)
tree2c2fa9a4d9e5ca18728ea75a87fafcb10ab85b2a
parent5eac2ea15fbef4fcd2f0a182e41bd4f6f5725d2a (diff)
downloadfreeipa.git-8881e4a543e9f1f1edda2d1cc935c020950214e6.tar.gz
freeipa.git-8881e4a543e9f1f1edda2d1cc935c020950214e6.tar.xz
freeipa.git-8881e4a543e9f1f1edda2d1cc935c020950214e6.zip
38: dict interface of Registrar now works with both classes and strings as the key
-rw-r--r--ipalib/plugable.py102
-rw-r--r--ipalib/tests/test_plugable.py37
2 files changed, 120 insertions, 19 deletions
diff --git a/ipalib/plugable.py b/ipalib/plugable.py
index e74809cd..9e94e96b 100644
--- a/ipalib/plugable.py
+++ b/ipalib/plugable.py
@@ -124,6 +124,83 @@ class Proxy(ReadOnly):
return to_cli(self.name)
+class NameSpace(ReadOnly):
+ """
+ A read-only namespace of (key, value) pairs that can be accessed
+ both as instance attributes and as dictionary items.
+ """
+
+ def __init__(self, kw):
+ """
+ The `kw` argument is a dict of the (key, value) pairs to be in this
+ NameSpace instance. The optional `order` keyword argument specifies
+ the order of the keys in this namespace; if omitted, the default is
+ to sort the keys in ascending order.
+ """
+ assert isinstance(kw, dict)
+ self.__kw = dict(kw)
+ for (key, value) in self.__kw.items():
+ assert not key.startswith('_')
+ setattr(self, key, value)
+ if order is None:
+ self.__keys = sorted(self.__kw)
+ else:
+ self.__keys = list(order)
+ assert set(self.__keys) == set(self.__kw)
+ self.__locked = True
+
+ def __setattr__(self, name, value):
+ """
+ Raises an exception if trying to set an attribute after the
+ NameSpace has been locked; otherwise calls object.__setattr__().
+ """
+ if self.__locked:
+ raise errors.SetError(name)
+ super(NameSpace, self).__setattr__(name, value)
+
+ def __getitem__(self, key):
+ """
+ Returns item from namespace named `key`.
+ """
+ return self.__kw[key]
+
+ def __hasitem__(self, key):
+ """
+ Returns True if namespace has an item named `key`.
+ """
+ return bool(key in self.__kw)
+
+ def __iter__(self):
+ """
+ Yields the names in this NameSpace in ascending order, or in the
+ the order specified in `order` kw arg.
+
+ For example:
+
+ >>> ns = NameSpace(dict(attr_b='world', attr_a='hello'))
+ >>> list(ns)
+ ['attr_a', 'attr_b']
+ >>> [ns[k] for k in ns]
+ ['hello', 'world']
+ """
+ for key in self.__keys:
+ yield key
+
+ def __call__(self):
+ """
+ Iterates through the values in this NameSpace in the same order as
+ the keys.
+ """
+ for key in self.__keys:
+ yield self.__kw[key]
+
+ def __len__(self):
+ """
+ Returns number of items in this NameSpace.
+ """
+ return len(self.__keys)
+
+
class Registrar(object):
def __init__(self, *allowed):
"""
@@ -179,15 +256,30 @@ class Registrar(object):
self.__registered.add(cls)
sub_d[cls.__name__] = cls
- def __getitem__(self, name):
+ def __getitem__(self, item):
"""
Returns a copy of the namespace dict of the base class named `name`.
"""
- return dict(self.__d[name])
+ if inspect.isclass(item):
+ if item not in self.__allowed:
+ raise KeyError(repr(item))
+ key = item.__name__
+ else:
+ key = item
+ return dict(self.__d[key])
+
+ def __contains__(self, item):
+ """
+ Returns True if a base class named `name` is in this Registrar.
+ """
+ if inspect.isclass(item):
+ return item in self.__allowed
+ return item in self.__d
def __iter__(self):
"""
- Iterates through the names of the allowed base classes.
+ Iterates through a (base, registered_plugins) tuple for each allowed
+ base.
"""
- for key in self.__d:
- yield key
+ for base in self.__allowed:
+ yield (base, self.__d[base.__name__].values())
diff --git a/ipalib/tests/test_plugable.py b/ipalib/tests/test_plugable.py
index fc2e9a67..a24bddfd 100644
--- a/ipalib/tests/test_plugable.py
+++ b/ipalib/tests/test_plugable.py
@@ -21,7 +21,7 @@
Unit tests for `ipalib.plugable` module.
"""
-import tstutil
+from tstutil import raises, no_set, no_del, read_only
from ipalib import plugable, errors
@@ -57,20 +57,22 @@ def test_ReadOnly():
obj = plugable.ReadOnly()
names = ['not_an_attribute', 'an_attribute']
for name in names:
- tstutil.no_set(obj, name)
- tstutil.no_del(obj, name)
+ no_set(obj, name)
+ no_del(obj, name)
class some_ro_class(plugable.ReadOnly):
def __init__(self):
object.__setattr__(self, 'an_attribute', 'Hello world!')
obj = some_ro_class()
for name in names:
- tstutil.no_set(obj, name)
- tstutil.no_del(obj, name)
- assert tstutil.read_only(obj, 'an_attribute') == 'Hello world!'
+ no_set(obj, name)
+ no_del(obj, name)
+ assert read_only(obj, 'an_attribute') == 'Hello world!'
def test_Proxy():
+ assert issubclass(plugable.Proxy, plugable.ReadOnly)
+
class CommandProxy(plugable.Proxy):
__slots__ = (
'validate',
@@ -115,14 +117,14 @@ def test_Proxy():
i = do_something()
p = CommandProxy(i)
assert getattr(i, name)(1) == 4
- tstutil.raises(AttributeError, getattr, p, name)
+ raises(AttributeError, getattr, p, name)
# Test that attributes are read-only:
name = 'validate'
i = do_something()
p = CommandProxy(i)
assert getattr(p, name)(1) == 3
- assert tstutil.read_only(p, name)(1) == 3
+ assert read_only(p, name)(1) == 3
def test_Registrar():
@@ -141,15 +143,22 @@ def test_Registrar():
# Test creation of Registrar:
r = plugable.Registrar(Base1, Base2)
- assert sorted(r) == ['Base1', 'Base2']
+
+ # Test __hasitem__, __getitem__:
+ for base in [Base1, Base2]:
+ assert base in r
+ assert base.__name__ in r
+ assert r[base] == {}
+ assert r[base.__name__] == {}
+
# Check that TypeError is raised trying to register something that isn't
# a class:
- tstutil.raises(TypeError, r, plugin1())
+ raises(TypeError, r, plugin1())
# Check that SubclassError is raised trying to register a class that is
# not a subclass of an allowed base:
- tstutil.raises(errors.SubclassError, r, plugin3)
+ raises(errors.SubclassError, r, plugin3)
# Check that registration works
r(plugin1)
@@ -162,7 +171,7 @@ def test_Registrar():
# Check that DuplicateError is raised trying to register exact class
# again:
- tstutil.raises(errors.DuplicateError, r, plugin1)
+ raises(errors.DuplicateError, r, plugin1)
# Check that OverrideError is raised trying to register class with same
# name and same base:
@@ -171,7 +180,7 @@ def test_Registrar():
pass
class plugin1(base1_extended):
pass
- tstutil.raises(errors.OverrideError, r, plugin1)
+ raises(errors.OverrideError, r, plugin1)
# Check that overriding works
r(plugin1, override=True)
@@ -182,7 +191,7 @@ def test_Registrar():
# Check that MissingOverrideError is raised trying to override a name
# not yet registerd:
- tstutil.raises(errors.MissingOverrideError, r, plugin2, override=True)
+ raises(errors.MissingOverrideError, r, plugin2, override=True)
# Check that additional plugin can be registered:
r(plugin2)