159: Added plugable.DictProxy class; added corresponding unit tests; added setitem(), delitem() functions to tstutil

This commit is contained in:
Jason Gerard DeRose 2008-08-14 17:21:21 +00:00
parent ca53615ddd
commit b403fd822b
3 changed files with 138 additions and 5 deletions

View File

@ -426,8 +426,8 @@ class NameSpace(ReadOnly):
def __contains__(self, name):
"""
Returns True if this NameSpace contains a member named ``name``; returns
False otherwise.
Returns True if instance contains a member named ``name``, otherwise
False.
:param name: The name of a potential member
"""
@ -435,8 +435,10 @@ class NameSpace(ReadOnly):
def __getitem__(self, name):
"""
If this NameSpace contains a member named ``name``, returns that member;
otherwise raises KeyError.
Returns the member named ``name``.
Raises KeyError if this NameSpace does not contain a member named
``name``.
:param name: The name of member to retrieve
"""
@ -468,6 +470,66 @@ class NameSpace(ReadOnly):
return '%s(<%d members>)' % (self.__class__.__name__, len(self))
class DictProxy(ReadOnly):
"""
A read-only dict whose items can also be accessed as attributes.
Although a DictProxy is read-only, the underlying dict can change (and is
assumed to).
One of these is created for each allowed base class in a `Registrar`
instance.
"""
def __init__(self, d):
"""
:param d: The ``dict`` instance to proxy.
"""
self.__d = d
self.__lock__()
assert self.__islocked__()
def __len__(self):
"""
Returns number of items in underlying ``dict``.
"""
return len(self.__d)
def __iter__(self):
"""
Iterates through keys of underlying ``dict`` in ascending order.
"""
for name in sorted(self.__d):
yield name
def __contains__(self, key):
"""
Returns True if underlying dict contains ``key``, False otherwise.
:param key: The key to query upon.
"""
return key in self.__d
def __getitem__(self, key):
"""
Returns value from underlying dict corresponding to ``key``.
:param key: The key of the value to retrieve.
"""
if key in self.__d:
return self.__d[key]
raise KeyError('no item at key %r' % key)
def __getattr__(self, name):
"""
Returns value from underlying dict corresponding to ``name``.
:param name: The name of the attribute to retrieve.
"""
if name in self.__d:
return self.__d[name]
raise AttributeError('no attribute %r' % name)
class Registrar(ReadOnly):
def __init__(self, *allowed):
"""

View File

@ -21,7 +21,8 @@
Unit tests for `ipalib.plugable` module.
"""
from tstutil import raises, getitem, no_set, no_del, read_only
from tstutil import raises, no_set, no_del, read_only
from tstutil import getitem, setitem, delitem
from tstutil import ClassChecker
from ipalib import plugable, errors
@ -452,6 +453,60 @@ class test_NameSpace(ClassChecker):
no_set(ns, name)
class test_DictProxy(ClassChecker):
"""
Tests the `plugable.DictProxy` class.
"""
_cls = plugable.DictProxy
def test_class(self):
assert self.cls.__bases__ == (plugable.ReadOnly,)
def test_DictProxy(self):
cnt = 10
keys = []
d = dict()
dictproxy = self.cls(d)
for i in xrange(cnt):
key = 'key_%d' % i
val = 'val_%d' % i
keys.append(key)
# Test thet key does not yet exist
assert len(dictproxy) == i
assert key not in dictproxy
assert not hasattr(dictproxy, key)
raises(KeyError, getitem, dictproxy, key)
raises(AttributeError, getattr, dictproxy, key)
# Test that items/attributes cannot be set on dictproxy:
raises(TypeError, setitem, dictproxy, key, val)
raises(AttributeError, setattr, dictproxy, key, val)
# Test that additions in d are reflected in dictproxy:
d[key] = val
assert len(dictproxy) == i + 1
assert key in dictproxy
assert hasattr(dictproxy, key)
assert dictproxy[key] is val
assert read_only(dictproxy, key) is val
# Test __iter__
assert list(dictproxy) == keys
for key in keys:
# Test that items cannot be deleted through dictproxy:
raises(TypeError, delitem, dictproxy, key)
raises(AttributeError, delattr, dictproxy, key)
# Test that deletions in d are reflected in dictproxy
del d[key]
assert len(dictproxy) == len(d)
assert key not in dictproxy
raises(KeyError, getitem, dictproxy, key)
raises(AttributeError, getattr, dictproxy, key)
def test_Registrar():
class Base1(object):
pass

View File

@ -60,6 +60,22 @@ def getitem(obj, key):
return obj[key]
def setitem(obj, key, value):
"""
Works like setattr but for dictionary interface. Uses this in combination
with raises() to test that, for example, TypeError is raised.
"""
obj[key] = value
def delitem(obj, key):
"""
Works like delattr but for dictionary interface. Uses this in combination
with raises() to test that, for example, TypeError is raised.
"""
del obj[key]
def no_set(obj, name, value='some_new_obj'):
"""
Tests that attribute cannot be set.