diff --git a/ipalib/plugable.py b/ipalib/plugable.py index bf0f52b46..1a186b61d 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 383e068e2..8ad458642 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)