diff options
author | Jason Gerard DeRose <jderose@redhat.com> | 2008-08-01 06:44:30 +0000 |
---|---|---|
committer | Jason Gerard DeRose <jderose@redhat.com> | 2008-08-01 06:44:30 +0000 |
commit | 8881e4a543e9f1f1edda2d1cc935c020950214e6 (patch) | |
tree | 2c2fa9a4d9e5ca18728ea75a87fafcb10ab85b2a | |
parent | 5eac2ea15fbef4fcd2f0a182e41bd4f6f5725d2a (diff) | |
download | freeipa.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.py | 102 | ||||
-rw-r--r-- | ipalib/tests/test_plugable.py | 37 |
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) |