diff options
Diffstat (limited to 'ipalib')
-rw-r--r-- | ipalib/base.py | 78 | ||||
-rw-r--r-- | ipalib/exceptions.py | 2 | ||||
-rw-r--r-- | ipalib/tests/test_base.py | 88 |
3 files changed, 150 insertions, 18 deletions
diff --git a/ipalib/base.py b/ipalib/base.py index 70cfe5673..eb84dd12a 100644 --- a/ipalib/base.py +++ b/ipalib/base.py @@ -18,10 +18,10 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ -Base classes in for plug-in architecture and generative API. +Base classes for plug-in architecture and generative API. """ -from exceptions import NameSpaceError +from exceptions import SetAttributeError class Command(object): @@ -47,26 +47,90 @@ class Argument(object): class NameSpace(object): + """ + A read-only namespace of (key, value) pairs that can be accessed + both as instance attributes and as dictionary items. For example: + + >>> ns = NameSpace(dict(my_message='Hello world!')) + >>> ns.my_message + 'Hello world!' + >>> ns['my_message'] + 'Hello world!' + + Keep in mind that Python doesn't offer true ready-only attributes. A + NameSpace is read-only in that it prevents programmers from + *accidentally* setting its attributes, but a motivated programmer can + still set them. + + For example, setting an attribute the normal way will raise an exception: + + >>> ns.my_message = 'some new value' + (raises ipalib.exceptions.SetAttributeError) + + But a programmer could still set the attribute like this: + + >>> ns.__dict__['my_message'] = 'some new value' + + You should especially not implement a security feature that relies upon + NameSpace being strictly read-only. + """ + + __locked = False # Whether __setattr__ has been locked + def __init__(self, kw): + """ + The single constructor argument `kw` is a dict of the (key, value) + pairs to be in this NameSpace instance. + """ assert isinstance(kw, dict) self.__kw = dict(kw) for (key, value) in self.__kw.items(): assert not key.startswith('_') setattr(self, key, value) self.__keys = sorted(self.__kw) + self.__locked = True + + def __setattr__(self, name, value): + """ + Raises an exception if trying to set an attribute after the + NameSpace has been locked; otherwise calls object.__setattr__(). + """ + if self.__locked: + raise SetAttributeError(name) + super(NameSpace, self).__setattr__(name, value) def __getitem__(self, key): + """ + Returns item from namespace named `key`. + """ return self.__kw[key] - def __iter__(self): - for key in self.__keys: - yield key - - + def __hasitem__(self, key): + """ + Returns True if namespace has an item named `key`. + """ + return key in self.__kw + def __iter__(self): + """ + Yields the names in this NameSpace in ascending order. + For example: + >>> ns = NameSpace(dict(attr_b='world', attr_a='hello')) + >>> list(ns) + ['attr_a', 'attr_b'] + >>> [ns[k] for k in ns] + ['hello', 'world'] + """ + for key in self.__keys: + yield key + def __len__(self): + """ + Returns number of items in this NameSpace. + """ + return len(self.__keys) class API(object): diff --git a/ipalib/exceptions.py b/ipalib/exceptions.py index d1f3ab9e0..2c1e5a557 100644 --- a/ipalib/exceptions.py +++ b/ipalib/exceptions.py @@ -45,5 +45,5 @@ class IPAError(Exception): return self.msg % self.kw -class NameSpaceError(IPAError): +class SetAttributeError(IPAError): msg = 'Cannot set %r: NameSpace does not allow attribute setting' diff --git a/ipalib/tests/test_base.py b/ipalib/tests/test_base.py index 432b9120e..42cb89a1e 100644 --- a/ipalib/tests/test_base.py +++ b/ipalib/tests/test_base.py @@ -56,28 +56,96 @@ class test_NameSpace(): def test_public(self): """ - Test that NameSpace instance created with empty dict has no public - attributes. + Tests that a NameSpace instance created with empty dict has no public + attributes (that would then conflict with names we want to assign to + the NameSpace). Also tests that a NameSpace instance created with a + non-empty dict has no unexpected public methods. """ ns = self.ns({}) assert list(ns) == [] + assert len(ns) == 0 for name in dir(ns): - assert name.startswith('_') or name.startswith('_NameSpace__') + assert name.startswith('__') or name.startswith('_NameSpace__') + (kw, ns) = self.std() + keys = set(kw) + for name in dir(ns): + assert ( + name.startswith('__') or + name.startswith('_NameSpace__') or + name in keys + ) + + def test_dict_vs_attr(self): + """ + Tests that NameSpace.__getitem__() and NameSpace.__getattr__() return + the same values. + """ + (kw, ns) = self.std() + assert len(kw) > 0 + assert len(kw) == len(list(ns)) + for (key, val) in kw.items(): + assert ns[key] is val + assert getattr(ns, key) is val + + def test_setattr(self): + """ + Tests that attributes cannot be set on NameSpace instance. + """ + (kw, ns) = self.std() + value = 'new value' + for key in kw: + raised = False + try: + setattr(ns, key, value) + except exceptions.SetAttributeError: + raised = True + assert raised + assert getattr(ns, key, None) != value + assert ns[key] != value + + def test_setitem(self): + """ + Tests that attributes cannot be set via NameSpace dict interface. + """ + (kw, ns) = self.std() + value = 'new value' + for key in kw: + raised = False + try: + ns[key] = value + except TypeError: + raised = True + assert raised + assert getattr(ns, key, None) != value + assert ns[key] != value + + def test_hasitem(self): + """ + Test __hasitem__() membership method. + """ + (kw, ns) = self.std() + nope = [ + 'attr_d', + 'attr_e', + 'whatever', + ] + for key in kw: + assert key in ns + for key in nope: + assert key not in kw + assert key not in ns def test_iter(self): """ - Test that __iter__() method returns sorted list of attribute names. + Tests that __iter__() method returns sorted list of attribute names. """ (kw, ns) = self.std() assert list(ns) == sorted(kw) assert [ns[k] for k in ns] == ['Hello', 'all', 'yall!'] - def test_dict_vs_attr(self): + def test_len(self): """ - Tests NameSpace.__getitem__() and NameSpace.__getattr__() return the - same values. + Test __len__() method. """ (kw, ns) = self.std() - for (key, val) in kw.items(): - assert ns[key] is val - assert getattr(ns, key) is val + assert len(kw) == len(ns) == 3 |