summaryrefslogtreecommitdiffstats
path: root/ipapython
diff options
context:
space:
mode:
authorPetr Viktorin <pviktori@redhat.com>2015-04-10 14:22:59 +0200
committerJan Cholasta <jcholast@redhat.com>2015-04-23 11:34:39 +0000
commit2cafb47ed7e4eba354355fa3aed1d8bcbac5fb68 (patch)
treeb84b570c0b20496e7a31e59fde910fca5827a23e /ipapython
parent5b3ee6842f464119d41066b20959b05922249558 (diff)
downloadfreeipa-2cafb47ed7e4eba354355fa3aed1d8bcbac5fb68.tar.gz
freeipa-2cafb47ed7e4eba354355fa3aed1d8bcbac5fb68.tar.xz
freeipa-2cafb47ed7e4eba354355fa3aed1d8bcbac5fb68.zip
Remove Editable DN and DN component classes
Make all DNs, RDNs and AVAs immutable. Immutability makes reasoning about DN-handling code easier, as value objects can't be changed once created. Instead of mutable DNs, one can use a list (or even a generator) of RDNs that's converted to a DN on output. Reviewed-By: Jan Cholasta <jcholast@redhat.com>
Diffstat (limited to 'ipapython')
-rw-r--r--ipapython/dn.py303
1 files changed, 16 insertions, 287 deletions
diff --git a/ipapython/dn.py b/ipapython/dn.py
index 5b6570770..59e9368ae 100644
--- a/ipapython/dn.py
+++ b/ipapython/dn.py
@@ -387,14 +387,6 @@ if container_dn in dn:
# the respective components of each are pair-wise compared until one
# is discovered to be non-equal. The comparison is case insensitive.
-Cloning (Object Copy):
-
-All the class types are capable of cloning by passing an object of the
-same type (or subclass) to the constructor. The new object is a copy
-of the object passed as input to the constructor. One place this is
-useful is when you want to coerce between immutable and mutable
-versions in order to modify an object.
-
Concatenation, In-Place Addition, Insertion:
# DN's and RDN's can be concatenated.
@@ -414,63 +406,15 @@ dn1.insert(0, RDN('cn', 'Bob'))
Finally see the unittest for a more complete set of ways you can
manipulate these objects.
-Mutability
-----------
-
-Python makes a clear distinction between mutable and immutable
-objects. Examples of immutable Python objects are strings, integers
-and floats. Examples of mutable Python objects are lists, dicts, and
-sets. Immutable objects cannot be modified, mutable objects can be
-modified. An object's mutability affects how the object behaves when
-passed to a function or method, this is because it's the object's
-reference which is always passed, thus immutable objects behave as if
-it were "call by value" and mutable objects behave as if it were "call
-by reference" (mutable objects can be modifed inside the
-function/method and that modification will be visible to the
-caller. On object's mutability also affects how an object will behave
-when used as a key in a dict or as a member of a set.
-
-The following discussion applies equally to AVA, RDN and DN object
-class variants.
-
-The AVA, RDN and DN classes have both immutable and mutable
-variants. The base classes (AVA, RDN, DN) are immutable. Each of the
-immutable base classes have a mutable subclass whose name begins with
-'Editable'. Thus the DN class is immutable, instances of that class
-cannot be modified, there is a mutable class EditableDN derived from
-DN whose instances can be modified. The primary difference between the
-immutable and mutable variants is:
-
-* Immutable variants are preferred.
-
-* Mutable variants are exactly identical in behavior to their
- immutable parent class (except for supporting assignment, etc.)
-
-* Immutable objects that test as equal will be the same as dict keys
- and set members even if they are different objects. Mutable variants
- are not hashable and thus cannot be used as a dict key nor inserted
- into a set.
-
-* Only mutable variants support modification via assignment, insert or
- in-place addition (e.g. +=).
-
-* In-place addtion (e.g. +=) works for both immutable and mutable
- variants. The distinction is for immutable objects the lhs is
- replaced with a new immutable result while a mutable object will be
- modfied in place and lhs object remains the same object.
-
-It is trival to coerce between an mutable and immutable AVA, RDN and
-DN types. These classes can clone their objects by passing an object
-of the same type to the constructor. For example:
-
- dn1 = DN(('cn', 'Bob')) # dn1 is immutable
- dn2 = EditableDN(dn1) # dn2 is mutable copy of dn1,
- # equal to dn1 until it's modified
+Immutability
+------------
- and visa-versa
+All the class types are immutable.
+As with other immutable types (such as str and int), you must not rely on
+the object identity operator ("is") for comparisons.
- dn1 = EditableDN(('cn', 'Bob')) # dn1 is mutable
- dn2 = DN(dn1) # dn2 is immutable copy of dn1, equal to dn1
+It is possible to "copy" an object by passing an object of the same type
+to the constructor. The result may share underlying structure.
'''
@@ -478,7 +422,7 @@ from ldap.dn import str2dn, dn2str
from ldap import DECODING_ERROR
import sys
-__all__ = ['AVA', 'EditableAVA', 'RDN', 'EditableRDN', 'DN', 'EditableDN']
+__all__ = 'AVA', 'RDN', 'DN'
def _adjust_indices(start, end, length):
'helper to fixup start/end slice values'
@@ -516,7 +460,7 @@ def str2rdn(value):
return rdns[0]
-def get_ava(*args, **kwds):
+def get_ava(*args):
"""
Get AVA from args in open ldap format(raw). Optimized for construction
from openldap format.
@@ -535,10 +479,7 @@ def get_ava(*args, **kwds):
ava = None
l = len(args)
if l == 3: # raw values - constructed FROM RDN
- if kwds.get('mutable', False):
- ava = args
- else:
- ava = (args[0], args[1], args[2])
+ ava = args
elif l == 2: # user defined values
ava = [_normalize_ava_input(args[0]), _normalize_ava_input(args[1]), 0]
elif l == 1: # slow mode, tuple, string,
@@ -642,10 +583,8 @@ class AVA(object):
The str method of an AVA returns the string representation in RFC 4514 DN
syntax with proper escaping.
'''
- is_mutable = False
-
- def __init__(self, *args, **kwds):
- self._ava = get_ava(*args, **{'mutable': self.is_mutable})
+ def __init__(self, *args):
+ self._ava = get_ava(*args)
def _get_attr(self):
return self._ava[0].decode('utf-8')
@@ -690,7 +629,7 @@ class AVA(object):
raise KeyError("\"%s\" not found in %s" % (key, self.__str__()))
def __hash__(self):
- # Hash is computed from AVA's string representation because it's immutable.
+ # Hash is computed from AVA's string representation.
#
# Because attrs & values are comparison case-insensitive the
# hash value between two objects which compare as equal but
@@ -737,24 +676,6 @@ class AVA(object):
return cmp_avas(self._ava, other._ava)
-class EditableAVA(AVA):
- '''
- Exactly identical to the AVA class except
-
- * Hash value is based on object identity, not object
- value. Objects that test as equal will be non-unique when
- used as a dict key or member of a set.
-
- * The attr and value properties may be modified after object creation.
-
- '''
- is_mutable = True
- __hash__ = None
-
- attr = property(AVA._get_attr, AVA._set_attr)
- value = property(AVA._get_value, AVA._set_value)
-
-
class RDN(object):
'''
@@ -862,7 +783,6 @@ class RDN(object):
syntax with proper escaping.
'''
- is_mutable = False
AVA_type = AVA
def __init__(self, *args, **kwds):
@@ -874,14 +794,7 @@ class RDN(object):
ava_count = len(args)
if raw: # fast raw mode
- try:
- if self.is_mutable:
- avas = args
- else:
- for arg in args:
- avas.append((arg[0], arg[1], arg[2]))
- except KeyError as e:
- raise TypeError('all AVA values in RAW mode must be in open ldap format')
+ avas = args
elif ava_count == 1 and isinstance(args[0], basestring):
avas = str2rdn(args[0])
sort = 1
@@ -957,7 +870,7 @@ class RDN(object):
value = property(_get_value)
def __hash__(self):
- # Hash is computed from RDN's string representation because it's immutable
+ # Hash is computed from RDN's string representation.
#
# Because attrs & values are comparison case-insensitive the
# hash value between two objects which compare as equal but
@@ -1007,69 +920,6 @@ class RDN(object):
sort_avas(result._avas)
return result
-class EditableRDN(RDN):
- '''
- Exactly identical to the RDN class except
-
- * Hash value is based on object identity, not object
- value. Objects that test as equal will be non-unique when
- used as a dict key or member of a set.
-
- * AVA components may be assigned via assignment statements.
-
- * In-place addition modifes the lhs object.
-
- * The attr and value properties may be modified after object creation.
- '''
-
- is_mutable = True
- __hash__ = None
- AVA_type = EditableAVA
-
- def __setitem__(self, key, value):
-
- if isinstance(key, (int, long)):
- self._avas[key] = get_ava(value)
- elif isinstance(key, slice):
- avas = self._avas_from_sequence(value)
- self._avas[key] = avas
- elif isinstance(key, basestring):
- if isinstance(value, list):
- raise TypeError("cannot assign multiple AVA's to single entry")
- new_ava = get_ava(value)
- found = False
- i = 0
- while i < len(self._avas):
- if key == self._avas[i][0].decode('utf-8'):
- found = True
- self._avas[i] = new_ava
- break
- i += 1
- if not found:
- raise KeyError("\"%s\" not found in %s" % (key, self.__str__()))
- else:
- raise TypeError("unsupported type for RDN indexing, must be int, basestring or slice; not %s" % \
- (key.__class__.__name__))
- sort_avas(self._avas)
-
- attr = property(RDN._get_attr, RDN._set_attr)
- value = property(RDN._get_value, RDN._set_value)
-
-
- def __iadd__(self, other):
- # If __iadd__ is not available Python will emulate += by
- # replacing the lhs object with the result of __add__ (if available).
- if isinstance(other, RDN):
- self._avas.extend(other.to_openldap())
- elif isinstance(other, AVA):
- self._avas.append(other.to_openldap())
- elif isinstance(other, basestring):
- self._avas.extend(self._avas_from_sequence([other]))
- else:
- raise TypeError("expected RDN, AVA or basestring but got %s" % (other.__class__.__name__))
-
- sort_avas(self._avas)
- return self
class DN(object):
'''
@@ -1218,7 +1068,6 @@ class DN(object):
syntax with proper escaping.
'''
- is_mutable = False
AVA_type = AVA
RDN_type = RDN
@@ -1236,8 +1085,6 @@ class DN(object):
if isinstance(value, unicode):
value = value.encode('utf-8')
rdns = str2dn(value)
- if self.is_mutable:
- self._copy_rdns(rdns) # AVAs to be list instead of tuple
except DECODING_ERROR:
raise ValueError("malformed RDN string = \"%s\"" % value)
for rdn in rdns:
@@ -1263,11 +1110,6 @@ class DN(object):
return rdns
def __deepcopy__(self, memo):
- if self.is_mutable:
- cls = self.__class__
- clone = cls.__new__(cls)
- clone.rdns = self._copy_rdns()
- return clone
return self
def _get_rdn(self, rdn):
@@ -1301,8 +1143,6 @@ class DN(object):
cls = self.__class__
new_dn = cls.__new__(cls)
new_dn.rdns = self.rdns[key]
- if self.is_mutable:
- new_dn.rdns = self._copy_rdns(new_dn.rdns)
return new_dn
elif isinstance(key, basestring):
for rdn in self.rdns:
@@ -1315,7 +1155,7 @@ class DN(object):
(key.__class__.__name__))
def __hash__(self):
- # Hash is computed from DN's string representation because it's immutable
+ # Hash is computed from DN's string representation.
#
# Because attrs & values are comparison case-insensitive the
# hash value between two objects which compare as equal but
@@ -1542,114 +1382,3 @@ class DN(object):
if i == -1:
raise ValueError("pattern not found")
return i
-
-class EditableDN(DN):
- '''
- Exactly identical to the DN class except
-
- * Hash value is based on object identity, not object
- value. Objects that test as equal will be non-unique when
- used as a dict key or member of a set.
-
- * RDN components may be assigned via assignment statements.
-
- * RDN components may be inserted.
-
- * In-place addition modifes the lhs object.
-
- '''
-
- is_mutable = True
- __hash__ = None
- AVA_type = EditableAVA
- RDN_type = EditableRDN
-
- def __setitem__(self, key, value):
- if isinstance(key, (int, long)):
- new_rdns = self._rdns_from_value(value)
- if len(new_rdns) > 1:
- raise TypeError("cannot assign multiple RDN's to single entry")
- self.rdns[key] = new_rdns[0]
- elif isinstance(key, slice):
- rdns = self._rdns_from_sequence(value)
- self.rdns[key] = rdns
- elif isinstance(key, basestring):
- new_rdns = self._rdns_from_value(value)
- if len(new_rdns) > 1:
- raise TypeError("cannot assign multiple values to single entry")
- found = False
- i = 0
- while i < len(self.rdns):
- if key == self.rdns[i][0][0].decode('utf-8'):
- found = True
- self.rdns[i] = new_rdns[0]
- break
- i += 1
- if not found:
- raise KeyError("\"%s\" not found in %s" % (key, self.__str__()))
- else:
- raise TypeError("unsupported type for DN indexing, must be int, basestring or slice; not %s" % \
- (key.__class__.__name__))
-
- def __iadd__(self, other):
- # If __iadd__ is not available Python will emulate += by
- # replacing the lhs object with the result of __add__ (if available).
- if isinstance(other, DN):
- self.rdns.extend(other._copy_rdns())
- elif isinstance(other, RDN):
- self.rdns.append(other.to_openldap())
- elif isinstance(other, basestring):
- dn = self.__class__(other)
- self.__iadd__(dn)
- else:
- raise TypeError("expected DN, RDN or basestring but got %s" % (other.__class__.__name__))
-
- return self
-
- def insert(self, i, x):
- '''
- x must be a 2-value tuple or list promotable to an RDN object,
- or a RDN object.
-
- dn.insert(i, x) is the same as s[i:i] = [x]
-
- When a negative index is passed as the first parameter to the
- insert() method, the list length is added, as for slice
- indices. If it is still negative, it is truncated to zero, as
- for slice indices.
- '''
-
- rdns = self._rdns_from_value(x)
- if len(rdns) > 1:
- raise TypeError("cannot assign multiple RDN's to single entry")
-
- self.rdns.insert(i, rdns[0])
-
- def replace(self, old, new, count=sys.maxsize):
- '''
- Replace all occurrences of old DN (or RDN) with new DN (or
- RDN). If the optional argument count is given, only the first
- count occurrences are replaced.
-
- Returns the number of replacements made.
- '''
-
- if not isinstance(old, (DN, RDN)):
- raise TypeError("old must be DN or RDN but got %s" % (old.__class__.__name__))
- if not isinstance(new, (DN, RDN)):
- raise TypeError("new must be DN or RDN but got %s" % (new.__class__.__name__))
-
-
- start = 0
- pat_len = len(old)
- n_replaced = 0
- while n_replaced < count:
- index = self.find(old, start)
- if index < 0:
- return n_replaced
- self[index : index+pat_len] = new
- n_replaced += 1
- start = index + pat_len
-
- return n_replaced
-