summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ipalib/plugable.py251
-rw-r--r--tests/test_ipalib/test_plugable.py191
2 files changed, 2 insertions, 440 deletions
diff --git a/ipalib/plugable.py b/ipalib/plugable.py
index 0120f9729..923b72ad6 100644
--- a/ipalib/plugable.py
+++ b/ipalib/plugable.py
@@ -36,104 +36,9 @@ import subprocess
import errors
from errors import check_type, check_isinstance
from config import Env
-from constants import DEFAULT_CONFIG
import util
-
-
-
-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 to 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:
-
- >>> ro = ReadOnly() # Initially unlocked, can setattr, delattr
- >>> ro.name = 'John Doe'
- >>> ro.message = 'Hello, world!'
- >>> del ro.message
- >>> ro.__lock__() # Now locked, cannot setattr, delattr
- >>> ro.message = 'How are you?'
- Traceback (most recent call last):
- File "<stdin>", line 1, in <module>
- File ".../ipalib/plugable.py", line 93, in __setattr__
- (self.__class__.__name__, name)
- AttributeError: read-only: cannot set ReadOnly.message
- >>> del ro.name
- Traceback (most recent call last):
- File "<stdin>", line 1, in <module>
- File "/home/jderose/projects/freeipa2/ipalib/plugable.py", line 104, in __delattr__
- (self.__class__.__name__, name)
- AttributeError: read-only: cannot del ReadOnly.name
- """
-
- __locked = False
-
- def __lock__(self):
- """
- Put this instance into a read-only state.
-
- After the instance has been locked, attempting to set or delete an
- attribute will raise AttributeError.
- """
- assert self.__locked is False, '__lock__() can only be called once'
- self.__locked = True
-
- def __islocked__(self):
- """
- Return True if instance is locked, otherwise False.
- """
- return self.__locked
-
- def __setattr__(self, name, value):
- """
- If unlocked, set attribute named ``name`` to ``value``.
-
- If this instance is locked, AttributeError will be raised.
- """
- if self.__locked:
- raise AttributeError('read-only: cannot set %s.%s' %
- (self.__class__.__name__, name)
- )
- return object.__setattr__(self, name, value)
-
- def __delattr__(self, name):
- """
- If unlocked, delete attribute named ``name``.
-
- If this instance is locked, AttributeError will be raised.
- """
- if self.__locked:
- raise AttributeError('read-only: cannot del %s.%s' %
- (self.__class__.__name__, name)
- )
- return object.__delattr__(self, name)
-
-
-def lock(readonly):
- """
- Lock a `ReadOnly` instance.
-
- This is mostly a convenience function to call `ReadOnly.__lock__()`. It
- also verifies that the locking worked using `ReadOnly.__islocked__()`
-
- :param readonly: An instance of the `ReadOnly` class.
- """
- if not isinstance(readonly, ReadOnly):
- raise ValueError('not a ReadOnly instance: %r' % readonly)
- readonly.__lock__()
- assert readonly.__islocked__(), 'Ouch! The locking failed?'
- return readonly
+from base import ReadOnly, NameSpace, lock, islocked, check_name
+from constants import DEFAULT_CONFIG
class SetProxy(ReadOnly):
@@ -512,158 +417,6 @@ class PluginProxy(SetProxy):
)
-def check_name(name):
- """
- Verify that ``name`` is suitable for a `NameSpace` member name.
-
- Raises `errors.NameSpaceError` if ``name`` is not a valid Python
- identifier suitable for use as the name of `NameSpace` member.
-
- :param name: Identifier to test.
- """
- check_type(name, str, 'name')
- regex = r'^[a-z][_a-z0-9]*[a-z0-9]$'
- if re.match(regex, name) is None:
- raise errors.NameSpaceError(name, regex)
- return name
-
-
-class NameSpace(ReadOnly):
- """
- A read-only namespace with handy container behaviours.
-
- Each member of a NameSpace instance must have a ``name`` attribute whose
- value:
-
- 1. Is unique among the members
- 2. Passes the `check_name()` function
-
- Beyond that, no restrictions are placed on the members: they can be
- classes or instances, and of any type.
-
- The members can be accessed as attributes on the NameSpace instance or
- through a dictionary interface. For example:
-
- >>> class obj(object):
- ... name = 'my_obj'
- ...
- >>> namespace = NameSpace([obj])
- >>> obj is getattr(namespace, 'my_obj') # As attribute
- True
- >>> obj is namespace['my_obj'] # As dictionary item
- True
-
- Here is a more detailed example:
-
- >>> class Member(object):
- ... def __init__(self, i):
- ... self.i = i
- ... self.name = 'member_%d' % i
- ... def __repr__(self):
- ... return 'Member(%d)' % self.i
- ...
- >>> namespace = NameSpace(Member(i) for i in xrange(3))
- >>> namespace.member_0 is namespace['member_0']
- True
- >>> len(namespace) # Returns the number of members in namespace
- 3
- >>> list(namespace) # As iterable, iterates through the member names
- ['member_0', 'member_1', 'member_2']
- >>> list(namespace()) # Calling a NameSpace iterates through the members
- [Member(0), Member(1), Member(2)]
- >>> 'member_1' in namespace # Does namespace contain 'member_1'?
- True
- """
-
- def __init__(self, members, sort=True):
- """
- :param members: An iterable providing the members.
- :param sort: Whether to sort the members by member name.
- """
- self.__sort = check_type(sort, bool, 'sort')
- if self.__sort:
- self.__members = tuple(sorted(members, key=lambda m: m.name))
- else:
- self.__members = tuple(members)
- self.__names = tuple(m.name for m in self.__members)
- self.__map = dict()
- for member in self.__members:
- name = check_name(member.name)
- assert name not in self.__map, 'already has key %r' % name
- self.__map[name] = member
- assert not hasattr(self, name), 'already has attribute %r' % name
- setattr(self, name, member)
- lock(self)
-
- def __len__(self):
- """
- Return the number of members.
- """
- return len(self.__members)
-
- def __iter__(self):
- """
- Iterate through the member names.
-
- If this instance was created with ``sort=True``, the names will be in
- alphabetical order; otherwise the names will be in the same order as
- the members were passed to the constructor.
-
- This method is like an ordered version of dict.iterkeys().
- """
- for name in self.__names:
- yield name
-
- def __call__(self):
- """
- Iterate through the members.
-
- If this instance was created with ``sort=True``, the members will be
- in alphabetical order by name; otherwise the members will be in the
- same order as they were passed to the constructor.
-
- This method is like an ordered version of dict.itervalues().
- """
- for member in self.__members:
- yield member
-
- def __contains__(self, name):
- """
- Return True if namespace has a member named ``name``.
- """
- return name in self.__map
-
- def __getitem__(self, spec):
- """
- Return a member by name or index, or returns a slice of members.
-
- :param spec: The name or index of a member, or a slice object.
- """
- if type(spec) is str:
- return self.__map[spec]
- if type(spec) in (int, slice):
- return self.__members[spec]
- raise TypeError(
- 'spec: must be %r, %r, or %r; got %r' % (str, int, slice, spec)
- )
-
- def __repr__(self):
- """
- Return a pseudo-valid expression that could create this instance.
- """
- return '%s(<%d members>, sort=%r)' % (
- self.__class__.__name__,
- len(self),
- self.__sort,
- )
-
- def __todict__(self):
- """
- Return a copy of the private dict mapping name to member.
- """
- return dict(self.__map)
-
-
class Registrar(DictProxy):
"""
Collects plugin classes as they are registered.
diff --git a/tests/test_ipalib/test_plugable.py b/tests/test_ipalib/test_plugable.py
index b05943235..9eb102ff4 100644
--- a/tests/test_ipalib/test_plugable.py
+++ b/tests/test_ipalib/test_plugable.py
@@ -28,100 +28,6 @@ from tests.util import ClassChecker, create_test_api
from ipalib import plugable, errors
-class test_ReadOnly(ClassChecker):
- """
- Test the `ipalib.plugable.ReadOnly` class
- """
- _cls = plugable.ReadOnly
-
- def test_class(self):
- """
- Test the `ipalib.plugable.ReadOnly` class
- """
- assert self.cls.__bases__ == (object,)
- assert callable(self.cls.__lock__)
- assert callable(self.cls.__islocked__)
-
- def test_lock(self):
- """
- Test the `ipalib.plugable.ReadOnly.__lock__` method.
- """
- o = self.cls()
- assert o._ReadOnly__locked is False
- o.__lock__()
- assert o._ReadOnly__locked is True
- e = raises(AssertionError, o.__lock__) # Can only be locked once
- assert str(e) == '__lock__() can only be called once'
- assert o._ReadOnly__locked is True # This should still be True
-
- def test_lock(self):
- """
- Test the `ipalib.plugable.ReadOnly.__islocked__` method.
- """
- o = self.cls()
- assert o.__islocked__() is False
- o.__lock__()
- assert o.__islocked__() is True
-
- def test_setattr(self):
- """
- Test the `ipalib.plugable.ReadOnly.__setattr__` method.
- """
- o = self.cls()
- o.attr1 = 'Hello, world!'
- assert o.attr1 == 'Hello, world!'
- o.__lock__()
- for name in ('attr1', 'attr2'):
- e = raises(AttributeError, setattr, o, name, 'whatever')
- assert str(e) == 'read-only: cannot set ReadOnly.%s' % name
- assert o.attr1 == 'Hello, world!'
-
- def test_delattr(self):
- """
- Test the `ipalib.plugable.ReadOnly.__delattr__` method.
- """
- o = self.cls()
- o.attr1 = 'Hello, world!'
- o.attr2 = 'How are you?'
- assert o.attr1 == 'Hello, world!'
- assert o.attr2 == 'How are you?'
- del o.attr1
- assert not hasattr(o, 'attr1')
- o.__lock__()
- e = raises(AttributeError, delattr, o, 'attr2')
- assert str(e) == 'read-only: cannot del ReadOnly.attr2'
- assert o.attr2 == 'How are you?'
-
-
-def test_lock():
- """
- Test the `ipalib.plugable.lock` function.
- """
- f = plugable.lock
-
- # Test on a ReadOnly instance:
- o = plugable.ReadOnly()
- assert not o.__islocked__()
- assert f(o) is o
- assert o.__islocked__()
-
- # Test on something not subclassed from ReadOnly:
- class not_subclass(object):
- def __lock__(self):
- pass
- def __islocked__(self):
- return True
- o = not_subclass()
- raises(ValueError, f, o)
-
- # Test that it checks __islocked__():
- class subclass(plugable.ReadOnly):
- def __islocked__(self):
- return False
- o = subclass()
- raises(AssertionError, f, o)
-
-
class test_SetProxy(ClassChecker):
"""
Test the `ipalib.plugable.SetProxy` class.
@@ -472,7 +378,6 @@ class test_Plugin(ClassChecker):
assert e.argv == ('/bin/false',)
-
class test_PluginProxy(ClassChecker):
"""
Test the `ipalib.plugable.PluginProxy` class.
@@ -595,102 +500,6 @@ class test_PluginProxy(ClassChecker):
assert read_only(c, 'name') == 'another_name'
-def test_check_name():
- """
- Test the `ipalib.plugable.check_name` function.
- """
- f = plugable.check_name
- okay = [
- 'user_add',
- 'stuff2junk',
- 'sixty9',
- ]
- nope = [
- '_user_add',
- '__user_add',
- 'user_add_',
- 'user_add__',
- '_user_add_',
- '__user_add__',
- '60nine',
- ]
- for name in okay:
- assert name is f(name)
- e = raises(TypeError, f, unicode(name))
- assert str(e) == errors.TYPE_FORMAT % ('name', str, unicode(name))
- for name in nope:
- raises(errors.NameSpaceError, f, name)
- for name in okay:
- raises(errors.NameSpaceError, f, name.upper())
-
-class DummyMember(object):
- def __init__(self, i):
- assert type(i) is int
- self.name = 'member_%02d' % i
-
-
-class test_NameSpace(ClassChecker):
- """
- Test the `ipalib.plugable.NameSpace` class.
- """
- _cls = plugable.NameSpace
-
- def test_class(self):
- """
- Test the `ipalib.plugable.NameSpace` class.
- """
- assert self.cls.__bases__ == (plugable.ReadOnly,)
-
- def test_init(self):
- """
- Test the `ipalib.plugable.NameSpace.__init__` method.
- """
- o = self.cls(tuple())
- assert list(o) == []
- assert list(o()) == []
- for cnt in (10, 25):
- members = tuple(DummyMember(cnt - i) for i in xrange(cnt))
- for sort in (True, False):
- o = self.cls(members, sort=sort)
- if sort:
- ordered = tuple(sorted(members, key=lambda m: m.name))
- else:
- ordered = members
- names = tuple(m.name for m in ordered)
- assert o.__todict__() == dict((o.name, o) for o in ordered)
-
- # Test __len__:
- assert len(o) == cnt
-
- # Test __contains__:
- for name in names:
- assert name in o
- assert ('member_00') not in o
-
- # Test __iter__, __call__:
- assert tuple(o) == names
- assert tuple(o()) == ordered
-
- # Test __getitem__, getattr:
- for (i, member) in enumerate(ordered):
- assert o[i] is member
- name = member.name
- assert o[name] is member
- assert read_only(o, name) is member
-
- # Test negative indexes:
- for i in xrange(1, cnt + 1):
- assert o[-i] is ordered[-i]
-
- # Test slices:
- assert o[2:cnt-5] == ordered[2:cnt-5]
- assert o[::3] == ordered[::3]
-
- # Test __repr__:
- assert repr(o) == \
- 'NameSpace(<%d members>, sort=%r)' % (cnt, sort)
-
-
def test_Registrar():
"""
Test the `ipalib.plugable.Registrar` class