summaryrefslogtreecommitdiffstats
path: root/ipalib
diff options
context:
space:
mode:
Diffstat (limited to 'ipalib')
-rw-r--r--ipalib/base.py78
-rw-r--r--ipalib/exceptions.py2
-rw-r--r--ipalib/tests/test_base.py88
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