171: MagicDict now subclasses from DictProxy; updated unit tests

This commit is contained in:
Jason Gerard DeRose 2008-08-15 01:24:51 +00:00
parent f6c2181eeb
commit e43a5c642e
2 changed files with 87 additions and 113 deletions

View File

@ -123,6 +123,13 @@ def lock(readonly):
class SetProxy(ReadOnly):
"""
A read-only proxy to an underlying set.
Although the underlying set cannot be changed through the SetProxy,
the set can change and is expected to (unless the underlying set is a
frozen set).
"""
def __init__(self, s):
allowed = (set, frozenset, dict)
if type(s) not in allowed:
@ -142,6 +149,9 @@ class SetProxy(ReadOnly):
class DictProxy(SetProxy):
"""
A read-only proxy to an underlying dict.
"""
def __init__(self, d):
if type(d) is not dict:
raise TypeError('%r is not %r' % (type(d), dict))
@ -150,11 +160,31 @@ class DictProxy(SetProxy):
def __getitem__(self, key):
"""
Returns the value
Returns the value corresponding to ``key``.
"""
return self.__d[key]
class MagicDict(DictProxy):
"""
A read-only dict whose items can also be accessed as attributes.
Although a MagicDict is read-only, the underlying dict can change (and is
assumed to).
One of these is created for each allowed base in a `Registrar` instance.
"""
def __getattr__(self, name):
"""
Returns the value corresponding to ``name``.
"""
try:
return self[name]
except KeyError:
raise AttributeError('no attribute %r' % name)
class Plugin(ReadOnly):
"""
Base class for all plugins.
@ -525,63 +555,7 @@ class NameSpace(ReadOnly):
return '%s(<%d members>)' % (self.__class__.__name__, len(self))
class MagicDict(ReadOnly):
"""
A read-only dict whose items can also be accessed as attributes.
Although a MagicDict is read-only, the underlying dict can change (and is
assumed to).
One of these is created for each allowed base in a `Registrar` instance.
"""
def __init__(self, d):
"""
:param d: The ``dict`` instance to proxy.
"""
assert type(d) is dict, '`d` must be %r, got %r' % (dict, type(d))
self.__d = d
lock(self)
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):

View File

@ -205,6 +205,62 @@ class test_DictProxy(ClassChecker):
raises(TypeError, delitem, proxy, key)
class test_MagicDict(ClassChecker):
"""
Tests the `plugable.MagicDict` class.
"""
_cls = plugable.MagicDict
def test_class(self):
assert self.cls.__bases__ == (plugable.DictProxy,)
for non_dict in ('hello', 69, object):
raises(TypeError, self.cls, non_dict)
def test_MagicDict(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)
class test_Plugin(ClassChecker):
"""
Tests the `plugable.Plugin` class.
@ -570,62 +626,6 @@ class test_NameSpace(ClassChecker):
no_set(ns, name)
class test_MagicDict(ClassChecker):
"""
Tests the `plugable.MagicDict` class.
"""
_cls = plugable.MagicDict
def test_class(self):
assert self.cls.__bases__ == (plugable.ReadOnly,)
for non_dict in ('hello', 69, object):
raises(AssertionError, self.cls, non_dict)
def test_MagicDict(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