summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ipalib/plugable.py39
-rw-r--r--ipalib/tests/test_plugable.py12
2 files changed, 51 insertions, 0 deletions
diff --git a/ipalib/plugable.py b/ipalib/plugable.py
index c5ec08ec..4ce4a9ba 100644
--- a/ipalib/plugable.py
+++ b/ipalib/plugable.py
@@ -39,6 +39,39 @@ def check_identifier(name):
class ReadOnly(object):
"""
Base class for classes with read-only attributes.
+
+ Be forewarned that Python does not offer true read-only user defined
+ classes. In particular, do not rely upon the read-only-ness of this
+ class for security purposes.
+
+ The point of this class is not to make it impossible to set or delete
+ attributes, but do make it impossible to accidentally do so. The plugins
+ are not thread-safe: in the server, they are loaded once and the same
+ instances will be used to process many requests. Therefore, it is
+ imperative that they not set any instance attributes after they have
+ been initialized. This base class enforces that policy.
+
+ For example:
+
+ >>> class givenName(ReadOnly):
+ >>> def __init__(self):
+ >>> self.whatever = 'some value' # Hasn't been locked yet
+ >>> self.__lock__()
+ >>>
+ >>> def finalize(self, api):
+ >>> # After the instance has been locked, attributes can still be
+ >>> # set, but only in a round-about, unconventional way:
+ >>> object.__setattr__(self, 'api', api)
+ >>>
+ >>> def normalize(self, value):
+ >>> # After the instance has been locked, trying to set an
+ >>> # attribute in the normal way will raise AttributeError.
+ >>> self.value = value # Not thread safe!
+ >>> return self.actually_normalize()
+ >>>
+ >>> def actually_normalize(self):
+ >>> # Again, this is not thread safe:
+ >>> return unicode(self.value).strip()
"""
__locked = False
@@ -50,6 +83,12 @@ class ReadOnly(object):
assert self.__locked is False, '__lock__() can only be called once'
self.__locked = True
+ def __islocked__(self):
+ """
+ Returns True if this instance is locked, False otherwise.
+ """
+ return self.__locked
+
def __setattr__(self, name, value):
"""
Raises an AttributeError if ReadOnly.__lock__() has already been called;
diff --git a/ipalib/tests/test_plugable.py b/ipalib/tests/test_plugable.py
index 605debe5..42453ed5 100644
--- a/ipalib/tests/test_plugable.py
+++ b/ipalib/tests/test_plugable.py
@@ -62,6 +62,18 @@ class test_ReadOnly(ClassChecker):
def test_class(self):
assert self.cls.__bases__ == (object,)
assert callable(self.cls.__lock__)
+ assert callable(self.cls.__islocked__)
+
+ def test_lock(self):
+ """
+ Tests the `__lock__` and `__islocked__` methods.
+ """
+ o = self.cls()
+ assert o.__islocked__() is False
+ o.__lock__()
+ assert o.__islocked__() is True
+ raises(AssertionError, o.__lock__) # Can only be locked once
+ assert o.__islocked__() is True # This should still be True
def test_when_unlocked(self):
"""