summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ipalib/plugable.py51
-rw-r--r--ipalib/tests/test_plugable.py55
2 files changed, 106 insertions, 0 deletions
diff --git a/ipalib/plugable.py b/ipalib/plugable.py
index bf0f52b4..1a186b61 100644
--- a/ipalib/plugable.py
+++ b/ipalib/plugable.py
@@ -207,6 +207,57 @@ class Proxy2(ReadOnly):
return self['__call__'](*args, **kw)
+class NameSpace2(ReadOnly):
+ """
+ A read-only namespace of (key, value) pairs that can be accessed
+ both as instance attributes and as dictionary items.
+ """
+
+ def __init__(self, proxies):
+ """
+ NameSpace2
+ """
+ object.__setattr__(self, '_NameSpace2__proxies', tuple(proxies))
+ object.__setattr__(self, '_NameSpace2__d', dict())
+ for proxy in self.__proxies:
+ assert isinstance(proxy, Proxy2)
+ assert proxy.name not in self.__d
+ self.__d[proxy.name] = proxy
+ assert not hasattr(self, proxy.name)
+ object.__setattr__(self, proxy.name, proxy)
+
+ def __iter__(self):
+ """
+ Iterates through the proxies in this NameSpace in the same order they
+ were passed in the contructor.
+ """
+ for proxy in self.__proxies:
+ yield proxy
+
+ def __len__(self):
+ """
+ Returns number of proxies in this NameSpace.
+ """
+ return len(self.__proxies)
+
+ def __contains__(self, key):
+ """
+ Returns True if a proxy named `key` is in this NameSpace.
+ """
+ return key in self.__d
+
+ def __getitem__(self, key):
+ """
+ Returns proxy named `key`; otherwise raises KeyError.
+ """
+ if key in self.__d:
+ return self.__d[key]
+ raise KeyError('NameSpace has no item for key %r' % key)
+
+ def __repr__(self):
+ return '%s(<%d proxies>)' % (self.__class__.__name__, len(self))
+
+
class NameSpace(ReadOnly):
"""
A read-only namespace of (key, value) pairs that can be accessed
diff --git a/ipalib/tests/test_plugable.py b/ipalib/tests/test_plugable.py
index 383e068e..8ad45864 100644
--- a/ipalib/tests/test_plugable.py
+++ b/ipalib/tests/test_plugable.py
@@ -233,7 +233,62 @@ def test_Proxy2():
p = cls(base, i)
+def test_NameSpace2():
+ cls = plugable.NameSpace2
+ assert issubclass(cls, plugable.ReadOnly)
+
+ class base(object):
+ public = frozenset((
+ 'plusplus',
+ ))
+
+ def plusplus(self, n):
+ return n + 1
+
+ class plugin(base):
+ def __init__(self, name):
+ self.name = name
+ def get_name(i):
+ return 'noun_verb%d' % i
+
+ def get_proxies(n):
+ for i in xrange(n):
+ yield plugable.Proxy2(base, plugin(get_name(i)))
+
+ cnt = 20
+ ns = cls(get_proxies(cnt))
+
+ # Test __len__
+ assert len(ns) == cnt
+
+ # Test __iter__
+ i = None
+ for (i, proxy) in enumerate(ns):
+ assert type(proxy) is plugable.Proxy2
+ assert proxy.name == get_name(i)
+ assert i == cnt - 1
+
+ # Test __contains__, __getitem__, getattr():
+ proxies = frozenset(ns)
+ for i in xrange(cnt):
+ name = get_name(i)
+ assert name in ns
+ proxy = ns[name]
+ assert proxy.name == name
+ assert type(proxy) is plugable.Proxy2
+ assert proxy in proxies
+ assert read_only(ns, name) is proxy
+
+ # Test dir():
+ assert set(get_name(i) for i in xrange(cnt)).issubset(set(dir(ns)))
+
+ # Test that KeyError, AttributeError is raised:
+ name = get_name(cnt)
+ assert name not in ns
+ raises(KeyError, getitem, ns, name)
+ raises(AttributeError, getattr, ns, name)
+ no_set(ns, name)