mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
Removed depreciated code in ipalib.plugable that has been moving into ipalib.base
This commit is contained in:
parent
ea7f9594df
commit
b4dc333ee2
@ -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.
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user