mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
171: MagicDict now subclasses from DictProxy; updated unit tests
This commit is contained in:
parent
f6c2181eeb
commit
e43a5c642e
@ -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):
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user