Make AVA, RDN & DN comparison case insensitive. No need for lowercase normalization.

Replace deepcopy with constructor (i.e. type call)
Can now "clone" with configuration changes by passing object
of the same type to it's constructor, e.g.
dn1 = DN(('cn', 'foo'))
dn2 = DN(dn1)
dn2 = DN(dn1, first_key_match=False)

Remove pairwise grouping for RDN's. Had previously removed it
for DN's, left it in for RDN's because it seemed to make sense
because of the way RDN's work but consistency is a higher goal.

Add keyword constructor parameters to pass configuration options.

Make first_key_match a configuration keyword.

Updated documentation.

Updated unit test.

FWIW, I noticed the unittest is now running 2x faster, not sure why,
removal of deepcopy? Anyway, hard to argue with performance doubling.
This commit is contained in:
John Dennis 2011-07-26 16:55:12 -04:00 committed by Rob Crittenden
parent 18aa73e06e
commit 264ed38fa2
2 changed files with 211 additions and 140 deletions

View File

@ -19,7 +19,6 @@
from ldap.dn import str2dn, dn2str from ldap.dn import str2dn, dn2str
from ldap import DECODING_ERROR from ldap import DECODING_ERROR
from copy import deepcopy
import sys import sys
__all__ = ['AVA', 'RDN', 'DN'] __all__ = ['AVA', 'RDN', 'DN']
@ -392,7 +391,7 @@ if container_dn in dn:
# (e.g. cmp()). The general rule is for objects supporting multiple # (e.g. cmp()). The general rule is for objects supporting multiple
# values first their lengths are compared, then if the lengths match # values first their lengths are compared, then if the lengths match
# the respective components of each are pair-wise compared until one # the respective components of each are pair-wise compared until one
# is discovered to be non-equal # is discovered to be non-equal. The comparision is case insensitive.
Concatenation and In-Place Addition: Concatenation and In-Place Addition:
@ -412,13 +411,35 @@ manipulate these objects.
''' '''
def _adjust_indices(start, end, length):
'helper to fixup start/end slice values'
if end > length:
end = length
elif end < 0:
end += length
if end < 0:
end = 0
if start < 0:
start += length
if start < 0:
start = 0
return start, end
class AVA(object): class AVA(object):
''' '''
AVA(arg0, ...)
An AVA is an LDAP Attribute Value Assertion. It is convenient to think of An AVA is an LDAP Attribute Value Assertion. It is convenient to think of
AVA's as a <attr,value> pair. AVA's are members of RDN's (Relative AVA's as a <attr,value> pair. AVA's are members of RDN's (Relative
Distinguished Name). Distinguished Name).
The AVA constructor may be invoked with any of the following methods: The AVA constructor is passed a sequence of args and a set of
keyword parameters used for configuration.
The arg sequence may be:
1) With 2 string (or unicode) arguments, the first argument will be the 1) With 2 string (or unicode) arguments, the first argument will be the
attr, the 2nd the value. attr, the 2nd the value.
@ -454,14 +475,16 @@ class AVA(object):
AVA objects support equality testing and comparsion (e.g. cmp()). When they AVA objects support equality testing and comparsion (e.g. cmp()). When they
are compared the attr is compared first, if the 2 attr's are equal then the are compared the attr is compared first, if the 2 attr's are equal then the
values are compared. values are compared. The comparision is case insensitive (because attr's map
to numeric OID's and their values derive from from the 'name' atribute type
(OID 2.5.4.41) whose EQUALITY MATCH RULE is caseIgnoreMatch.
The str method of an AVA returns the string representation in RFC 4514 DN The str method of an AVA returns the string representation in RFC 4514 DN
syntax with proper escaping. syntax with proper escaping.
''' '''
flags = 0 flags = 0
def __init__(self, *args): def __init__(self, *args, **kwds):
if len(args) == 1: if len(args) == 1:
arg = args[0] arg = args[0]
if isinstance(arg, basestring): if isinstance(arg, basestring):
@ -496,42 +519,84 @@ class AVA(object):
if not isinstance(value, basestring): if not isinstance(value, basestring):
raise TypeError("value must be basestring, got %s instead" % value.__class__.__name__) raise TypeError("value must be basestring, got %s instead" % value.__class__.__name__)
self.attr = attr.decode('utf-8') attr = attr.decode('utf-8')
self.value = value.decode('utf-8') value = value.decode('utf-8')
self._attr = attr
self._value = value
def _get_attr(self):
return self._attr
def _set_attr(self, new_attr):
if not isinstance(new_attr, basestring):
raise TypeError("attr must be basestring, got %s instead" % new_attr.__class__.__name__)
self._attr = new_attr
attr = property(_get_attr, _set_attr)
def _get_value(self):
return self._value
def _set_value(self, new_value):
if not isinstance(new_value, basestring):
raise TypeError("value must be basestring, got %s instead" % new_value.__class__.__name__)
self._value = new_value
value = property(_get_value, _set_value)
def _to_openldap(self): def _to_openldap(self):
return [[(self.attr.encode('utf-8'), self.value.encode('utf-8'), self.flags)]] return [[(self._attr.encode('utf-8'), self._value.encode('utf-8'), self.flags)]]
def __str__(self): def __str__(self):
return dn2str(self._to_openldap()) return dn2str(self._to_openldap())
def __getitem__(self, key): def __getitem__(self, key):
if isinstance(key, basestring): if isinstance(key, basestring):
if key == self.attr: if key == self._attr:
return self.value return self._value
raise KeyError("\"%s\" not found in %s" % (key, self.__str__())) raise KeyError("\"%s\" not found in %s" % (key, self.__str__()))
else: else:
raise TypeError("unsupported type for AVA indexing, must be basestring; not %s" % \ raise TypeError("unsupported type for AVA indexing, must be basestring; not %s" % \
(key.__class__.__name__)) (key.__class__.__name__))
def __eq__(self, other): def __eq__(self, other):
'''
The attr comparison is case insensitive because attr is
really an LDAP attribute type which means it's specified with
an OID (dotted number) and not a string. Since OID's are
numeric the human readable name which maps to the OID is not
significant in case.
The value comparison is also case insensitive because the all
attribute types used in a DN are derived from the 'name'
atribute type (OID 2.5.4.41) whose EQUALITY MATCH RULE is
caseIgnoreMatch.
'''
if not isinstance(other, self.__class__): if not isinstance(other, self.__class__):
raise TypeError("expected AVA but got %s" % (other.__class__.__name__)) raise TypeError("expected AVA but got %s" % (other.__class__.__name__))
return self.attr == other.attr and self.value == other.value return self._attr.lower() == other.attr.lower() and \
self._value.lower() == other.value.lower()
def __cmp__(self, other): def __cmp__(self, other):
'comparision is case insensitive, see __eq__ doc for explanation'
if not isinstance(other, self.__class__): if not isinstance(other, self.__class__):
raise TypeError("expected AVA but got %s" % (other.__class__.__name__)) raise TypeError("expected AVA but got %s" % (other.__class__.__name__))
result = cmp(self.attr, other.attr) result = cmp(self._attr.lower(), other.attr.lower())
if result != 0: return result if result != 0:
result = cmp(self.value, other.value) return result
result = cmp(self._value.lower(), other.value.lower())
return result return result
class RDN(object): class RDN(object):
''' '''
RDN(arg0, ..., first_key_match=True)
An RDN is a LDAP Relative Distinguished Name. RDN's are members of DN's An RDN is a LDAP Relative Distinguished Name. RDN's are members of DN's
(Distinguished Name). An RDN contains 1 or more AVA's. If the RDN contains (Distinguished Name). An RDN contains 1 or more AVA's. If the RDN contains
more than one AVA it is said to be a multi-valued RDN. When an RDN is more than one AVA it is said to be a multi-valued RDN. When an RDN is
@ -542,13 +607,12 @@ class RDN(object):
within an RDN). Single valued RDN's are the norm and thus the RDN within an RDN). Single valued RDN's are the norm and thus the RDN
constructor has simple syntax for them. constructor has simple syntax for them.
The RDN constructor may be invoked in a variety of different ways. The RDN constructor is passed a sequence of args and a set of
keyword parameters used for configuration.
* When two adjacent string (or unicode) argument appear together in the The constructor iterates though the sequence and adds AVA's to the RDN.
argument list they are taken to be the <attr,value> pair of an AVA. An AVA
object is constructed and inserted into the RDN. Multiple pairs of strings The arg sequence may be:
arguments may appear in the argument list, each pair adds one additional AVA
to the RDN.
* A 2-valued tuple or list denotes the <attr,value> pair of an AVA. The * A 2-valued tuple or list denotes the <attr,value> pair of an AVA. The
first member is the attr and the second member is the value, both members first member is the attr and the second member is the value, both members
@ -562,28 +626,26 @@ class RDN(object):
more AVA <attr,value> pairs. The parsing recognizes the DN syntax escaping more AVA <attr,value> pairs. The parsing recognizes the DN syntax escaping
rules. rules.
Note, a DN syntax argument is distguished from AVA string pairs by testing * A AVA object, the AVA will be copied into the new RDN respecting
to see if two strings appear adjacent in the argument list, if so those two the constructors keyword configuration parameters.
strings are interpretted as an <attr,value> AVA pair and consumed.
* A AVA object. Each AVA object in the argument list will be added to the RDN. * A RDN object, the AVA's in the RDN are copied into the new RDN
respecting the constructors keyword configuration parameters.
Single AVA Examples: Single AVA Examples:
RDN('cn', 'Bob') # 2 adjacent strings yield 1 AVA
RDN(('cn', 'Bob')) # tuple yields 1 AVA RDN(('cn', 'Bob')) # tuple yields 1 AVA
RDN('cn=Bob') # DN syntax with 1 AVA RDN('cn=Bob') # DN syntax with 1 AVA
RDN(AVA('cn', 'Bob')) # AVA object adds 1 AVA RDN(AVA('cn', 'Bob')) # AVA object adds 1 AVA
Multiple AVA Examples: Multiple AVA Examples:
RDN('cn', 'Bob', 'ou', 'people') # 2 strings pairs yield 2 AVA's
RDN(('cn', 'Bob'),('ou', 'people')) # 2 tuples yields 2 AVA's RDN(('cn', 'Bob'),('ou', 'people')) # 2 tuples yields 2 AVA's
RDN('cn=Bob+ou=people') # DN syntax with 2 AVA's RDN('cn=Bob+ou=people') # DN syntax with 2 AVA's
RDN(AVA('cn', 'Bob'),AVA('ou', 'people')) # 2 AVA objects adds 2 AVA's RDN(AVA('cn', 'Bob'),AVA('ou', 'people')) # 2 AVA objects adds 2 AVA's
RDN('cn', 'Bob', "ou=people') # 3 strings, 1st two strings form 1 AVA RDN(('cn', 'Bob'), 'ou=people') # 2 args, 1st tuple forms 1 AVA,
# 3rd string DN syntax for 1 AVA, # 2nd DN syntax string adds 1 AVA,
# adds 2 AVA's in total # 2 AVA's in total
Note: The RHS of a slice assignment is interpreted exactly in the Note: The RHS of a slice assignment is interpreted exactly in the
same manner as the constructor argument list (see above examples). same manner as the constructor argument list (see above examples).
@ -613,7 +675,7 @@ class RDN(object):
and value properties return the attr and value properties of the first AVA and value properties return the attr and value properties of the first AVA
in the RDN, for example: in the RDN, for example:
rdn = RDN('cn', 'Bob') # rdn has 1 AVA whose attr == 'cn' and value == 'Bob' rdn = RDN(('cn', 'Bob')) # rdn has 1 AVA whose attr == 'cn' and value == 'Bob'
len(rdn) -> 1 len(rdn) -> 1
rdn.attr -> u'cn' # exactly equivalent to rdn[0].attr rdn.attr -> u'cn' # exactly equivalent to rdn[0].attr
rdn.value -> u'Bob' # exactly equivalent to rdn[0].value rdn.value -> u'Bob' # exactly equivalent to rdn[0].value
@ -644,14 +706,22 @@ class RDN(object):
flags = 0 flags = 0
def __init__(self, *args): def __init__(self, *args, **kwds):
self.first_key_match = True self.first_key_match = kwds.get('first_key_match', True)
self.avas = self._avas_from_sequence(args) self.avas = self._avas_from_sequence(args)
self.avas.sort() self.avas.sort()
def _ava_from_value(self, value): def _ava_from_value(self, value):
if isinstance(value, AVA): if isinstance(value, AVA):
return deepcopy(value) return AVA(value.attr, value.value)
elif isinstance(value, RDN):
avas = []
for ava in value.avas:
avas.append(AVA(ava.attr, ava.value))
if len(avas) == 1:
return avas[0]
else:
return avas
elif isinstance(value, basestring): elif isinstance(value, basestring):
try: try:
rdns = str2dn(value.encode('utf-8')) rdns = str2dn(value.encode('utf-8'))
@ -679,22 +749,12 @@ class RDN(object):
def _avas_from_sequence(self, seq): def _avas_from_sequence(self, seq):
avas = [] avas = []
i = 0 for item in seq:
while i < len(seq): ava = self._ava_from_value(item)
if i+1 < len(seq) and \ if isinstance(ava, list):
isinstance(seq[i], basestring) and \ avas.extend(ava)
isinstance(seq[i+1], basestring):
ava = AVA(seq[i], seq[i+1])
avas.append(ava)
i += 2
else: else:
arg = seq[i] avas.append(ava)
ava = self._ava_from_value(arg)
if isinstance(ava, list):
avas.extend(ava)
else:
avas.append(ava)
i += 1
return avas return avas
def _to_openldap(self): def _to_openldap(self):
@ -753,7 +813,8 @@ class RDN(object):
if key == self.avas[i].attr: if key == self.avas[i].attr:
found = True found = True
self.avas[i] = new_ava self.avas[i] = new_ava
if self.first_key_match: break if self.first_key_match:
break
i += 1 i += 1
if not found: if not found:
raise KeyError("\"%s\" not found in %s" % (key, self.__str__())) raise KeyError("\"%s\" not found in %s" % (key, self.__str__()))
@ -805,25 +866,27 @@ class RDN(object):
raise TypeError("expected RDN but got %s" % (other.__class__.__name__)) raise TypeError("expected RDN but got %s" % (other.__class__.__name__))
result = cmp(len(self), len(other)) result = cmp(len(self), len(other))
if result != 0: return result if result != 0:
return result
i = 0 i = 0
while i < len(self): while i < len(self):
result = cmp(self[i], other[i]) result = cmp(self[i], other[i])
if result != 0: return result if result != 0:
return result
i += 1 i += 1
return 0 return 0
def __add__(self, other): def __add__(self, other):
result = deepcopy(self) result = RDN(self, first_key_match=self.first_key_match)
if isinstance(other, self.__class__): if isinstance(other, RDN):
for ava in other.avas: for ava in other.avas:
result.avas.append(deepcopy(ava)) result.avas.append(AVA(ava.attr, ava.value))
elif isinstance(other, AVA): elif isinstance(other, AVA):
result.avas.append(deepcopy(other)) result.avas.append(AVA(other.attr, other.value))
elif isinstance(other, basestring): elif isinstance(other, basestring):
rdn = RDN(other) rdn = RDN(other)
for ava in rdn.avas: for ava in rdn.avas:
result.avas.append(deepcopy(ava)) result.avas.append(AVA(ava.attr, ava.value))
else: else:
raise TypeError("expected RDN, AVA or basestring but got %s" % (other.__class__.__name__)) raise TypeError("expected RDN, AVA or basestring but got %s" % (other.__class__.__name__))
@ -831,15 +894,15 @@ class RDN(object):
return result return result
def __iadd__(self, other): def __iadd__(self, other):
if isinstance(other, self.__class__): if isinstance(other, RDN):
for ava in other.avas: for ava in other.avas:
self.avas.append(deepcopy(ava)) self.avas.append(AVA(ava.attr, ava.value))
elif isinstance(other, AVA): elif isinstance(other, AVA):
self.avas.append(deepcopy(other)) self.avas.append(AVA(other.attr, other.value))
elif isinstance(other, basestring): elif isinstance(other, basestring):
rdn = RDN(other) rdn = RDN(other)
for ava in rdn.avas: for ava in rdn.avas:
self.avas.append(deepcopy(ava)) self.avas.append(AVA(ava.attr, ava.value))
else: else:
raise TypeError("expected RDN, AVA or basestring but got %s" % (other.__class__.__name__)) raise TypeError("expected RDN, AVA or basestring but got %s" % (other.__class__.__name__))
@ -848,11 +911,17 @@ class RDN(object):
class DN(object): class DN(object):
''' '''
DN(arg0, ..., first_key_match=True)
A DN is a LDAP Distinguished Name. A DN is an ordered sequence of RDN's. A DN is a LDAP Distinguished Name. A DN is an ordered sequence of RDN's.
The DN constructor accepts a sequence. The constructor iterates The DN constructor is passed a sequence of args and a set of
through the sequence and adds the RDN's it finds in order to the keyword parameters used for configuration. normalize means the
DN object. Each item in the sequence may be: attr and value will be converted to lower case.
The constructor iterates through the sequence and adds the RDN's
it finds in order to the DN object. Each item in the sequence may
be:
* A 2-valued tuple or list. The first member is the attr and the * A 2-valued tuple or list. The first member is the attr and the
second member is the value of an RDN, both members must be second member is the value of an RDN, both members must be
@ -866,11 +935,12 @@ class DN(object):
to yield one or more RDN's which will be appended in order to to yield one or more RDN's which will be appended in order to
the DN. The parsing recognizes the DN syntax escaping rules. the DN. The parsing recognizes the DN syntax escaping rules.
* A RDN object. Each RDN object in the argument list will be * A RDN object, the RDN will copied respecting the constructors
appended to the DN in order. keyword configuration parameters and appended in order.
* A DN object. Each DN object in the argument list will append in order * A DN object, the RDN's in the DN are copied respecting the
it's RDN's to the DN. constructors keyword configuration parameters and appended in
order.
Single DN Examples: Single DN Examples:
@ -972,17 +1042,18 @@ class DN(object):
flags = 0 flags = 0
def __init__(self, *args): def __init__(self, *args, **kwds):
self.first_key_match = kwds.get('first_key_match', True)
self.first_key_match = True self.first_key_match = True
self.rdns = self._rdns_from_sequence(args) self.rdns = self._rdns_from_sequence(args)
def _rdn_from_value(self, value): def _rdn_from_value(self, value):
if isinstance(value, RDN): if isinstance(value, RDN):
return deepcopy(value) return RDN(value, first_key_match=self.first_key_match)
elif isinstance(value, DN): elif isinstance(value, DN):
rdns = [] rdns = []
for rdn in value.rdns: for rdn in value.rdns:
rdns.append(deepcopy(rdn)) rdns.append(RDN(rdn, first_key_match=self.first_key_match))
if len(rdns) == 1: if len(rdns) == 1:
return rdns[0] return rdns[0]
else: else:
@ -995,7 +1066,7 @@ class DN(object):
avas = [] avas = []
for ava_tuple in rdn_list: for ava_tuple in rdn_list:
avas.append(AVA(ava_tuple[0], ava_tuple[1])) avas.append(AVA(ava_tuple[0], ava_tuple[1]))
rdn = RDN(*avas) rdn = RDN(*avas, first_key_match=self.first_key_match)
rdns.append(rdn) rdns.append(rdn)
except DECODING_ERROR: except DECODING_ERROR:
raise ValueError("malformed RDN string = \"%s\"" % value) raise ValueError("malformed RDN string = \"%s\"" % value)
@ -1006,7 +1077,7 @@ class DN(object):
elif isinstance(value, (tuple, list)): elif isinstance(value, (tuple, list)):
if len(value) != 2: if len(value) != 2:
raise ValueError("tuple or list must be 2-valued, not \"%s\"" % (rdn)) raise ValueError("tuple or list must be 2-valued, not \"%s\"" % (rdn))
rdn = RDN(value) rdn = RDN(value, first_key_match=self.first_key_match)
return rdn return rdn
else: else:
raise TypeError("must be str,unicode,tuple, or RDN, got %s instead" % \ raise TypeError("must be str,unicode,tuple, or RDN, got %s instead" % \
@ -1098,7 +1169,8 @@ class DN(object):
raise TypeError("expected DN but got %s" % (other.__class__.__name__)) raise TypeError("expected DN but got %s" % (other.__class__.__name__))
result = cmp(len(self), len(other)) result = cmp(len(self), len(other))
if result != 0: return result if result != 0:
return result
return self._cmp_sequence(other, 0, len(self)) return self._cmp_sequence(other, 0, len(self))
def _cmp_sequence(self, pattern, self_start, pat_len): def _cmp_sequence(self, pattern, self_start, pat_len):
@ -1106,35 +1178,36 @@ class DN(object):
pat_idx = 0 pat_idx = 0
while pat_idx < pat_len: while pat_idx < pat_len:
result = cmp(self[self_idx], pattern[pat_idx]) result = cmp(self[self_idx], pattern[pat_idx])
if result != 0: return result if result != 0:
return result
self_idx += 1 self_idx += 1
pat_idx += 1 pat_idx += 1
return 0 return 0
def __add__(self, other): def __add__(self, other):
result = deepcopy(self) result = DN(self, first_key_match=self.first_key_match)
if isinstance(other, self.__class__): if isinstance(other, self.__class__):
for rdn in other.rdns: for rdn in other.rdns:
result.rdns.append(deepcopy(rdn)) result.rdns.append(RDN(rdn, first_key_match=self.first_key_match))
elif isinstance(other, RDN): elif isinstance(other, RDN):
result.rdns.append(deepcopy(other)) result.rdns.append(RDN(other, first_key_match=self.first_key_match))
elif isinstance(other, basestring): elif isinstance(other, basestring):
dn = DN(other) dn = DN(other, first_key_match=self.first_key_match)
for rdn in dn.rdns: for rdn in dn.rdns:
result.rdns.append(deepcopy(rdn)) result.rdns.append(rdn)
else: else:
raise TypeError("expected DN, RDN or basestring but got %s" % (other.__class__.__name__)) raise TypeError("expected DN, RDN or basestring but got %s" % (other.__class__.__name__))
return result return result
def __iadd__(self, other): def __iadd__(self, other):
if isinstance(other, self.__class__): if isinstance(other, DN):
for rdn in other.rdns: for rdn in other.rdns:
self.rdns.append(deepcopy(rdn)) self.rdns.append(RDN(rdn, first_key_match=self.first_key_match))
elif isinstance(other, RDN): elif isinstance(other, RDN):
self.rdns.append(deepcopy(other)) self.rdns.append(RDN(other, first_key_match=self.first_key_match))
elif isinstance(other, basestring): elif isinstance(other, basestring):
dn = DN(other) dn = DN(other, first_key_match=self.first_key_match)
self.__iadd__(dn) self.__iadd__(dn)
else: else:
raise TypeError("expected DN, RDN or basestring but got %s" % (other.__class__.__name__)) raise TypeError("expected DN, RDN or basestring but got %s" % (other.__class__.__name__))
@ -1174,23 +1247,6 @@ class DN(object):
return self._tailmatch(suffix, start, end, +1) return self._tailmatch(suffix, start, end, +1)
def _adjust_indices(self, start, end, length):
'helper to fixup start/end slice values'
if end > length:
end = length
elif end < 0:
end += length
if end < 0:
end = 0
if start < 0:
start += length
if start < 0:
start = 0
return start, end
def _tailmatch(self, pattern, start, end, direction): def _tailmatch(self, pattern, start, end, direction):
''' '''
Matches the end (direction >= 0) or start (direction < 0) of self Matches the end (direction >= 0) or start (direction < 0) of self
@ -1207,11 +1263,11 @@ class DN(object):
self_len = len(self) self_len = len(self)
start, end = self._adjust_indices(start, end, self_len) start, end = _adjust_indices(start, end, self_len)
if direction < 0: # starswith if direction < 0: # starswith
if start+pat_len > self_len: if start+pat_len > self_len:
return 0; return 0
else: # endswith else: # endswith
if end-start < pat_len or start > self_len: if end-start < pat_len or start > self_len:
return 0 return 0
@ -1222,7 +1278,7 @@ class DN(object):
if isinstance(pattern, DN): if isinstance(pattern, DN):
if end-start >= pat_len: if end-start >= pat_len:
return not self._cmp_sequence(pattern, start, pat_len) return not self._cmp_sequence(pattern, start, pat_len)
return 0; return 0
else: else:
return self.rdns[start] == pattern return self.rdns[start] == pattern

View File

@ -37,7 +37,7 @@ def make_rdn_args(low, high, kind, attr=None, value=None):
elif kind == 'list': elif kind == 'list':
result.append([new_attr, new_value]) result.append([new_attr, new_value])
elif kind == 'RDN': elif kind == 'RDN':
result.append(RDN(new_attr, new_value)) result.append(RDN((new_attr, new_value)))
else: else:
raise ValueError("Unknown kind = %s" % kind) raise ValueError("Unknown kind = %s" % kind)
@ -162,6 +162,28 @@ class TestAVA(unittest.TestCase):
result = cmp(ava1, self.ava1) result = cmp(ava1, self.ava1)
self.assertEqual(result, 0) self.assertEqual(result, 0)
# Upper case attr should still be equal
ava1 = AVA(self.attr1.upper(), self.value1)
self.assertFalse(ava1.attr == self.attr1)
self.assertTrue(ava1.value == self.value1)
self.assertTrue(ava1 == self.ava1)
self.assertFalse(ava1 != self.ava1)
result = cmp(ava1, self.ava1)
self.assertEqual(result, 0)
# Upper case value should still be equal
ava1 = AVA(self.attr1, self.value1.upper())
self.assertTrue(ava1.attr == self.attr1)
self.assertFalse(ava1.value == self.value1)
self.assertTrue(ava1 == self.ava1)
self.assertFalse(ava1 != self.ava1)
result = cmp(ava1, self.ava1)
self.assertEqual(result, 0)
# Make ava1's attr greater # Make ava1's attr greater
ava1.attr = self.attr1 + "1" ava1.attr = self.attr1 + "1"
@ -199,7 +221,7 @@ class TestRDN(unittest.TestCase):
self.ava1 = AVA(self.attr1, self.value1) self.ava1 = AVA(self.attr1, self.value1)
self.str_rdn1 = '%s=%s' % (self.attr1, self.value1) self.str_rdn1 = '%s=%s' % (self.attr1, self.value1)
self.rdn1 = RDN(self.attr1, self.value1) self.rdn1 = RDN((self.attr1, self.value1))
self.attr2 = 'ou' self.attr2 = 'ou'
self.value2 = 'people' self.value2 = 'people'
@ -207,7 +229,7 @@ class TestRDN(unittest.TestCase):
self.ava2 = AVA(self.attr2, self.value2) self.ava2 = AVA(self.attr2, self.value2)
self.str_rdn2 = '%s=%s' % (self.attr2, self.value2) self.str_rdn2 = '%s=%s' % (self.attr2, self.value2)
self.rdn2 = RDN(self.attr2, self.value2) self.rdn2 = RDN((self.attr2, self.value2))
self.str_ava3 = '%s=%s+%s=%s' % (self.attr1, self.value1, self.attr2, self.value2) self.str_ava3 = '%s=%s+%s=%s' % (self.attr1, self.value1, self.attr2, self.value2)
@ -216,13 +238,6 @@ class TestRDN(unittest.TestCase):
def test_create(self): def test_create(self):
# Create with single attr,value pair # Create with single attr,value pair
rdn1 = RDN(self.attr1, self.value1)
self.assertEqual(len(rdn1), 1)
self.assertEqual(rdn1, self.rdn1)
self.assertIsInstance(rdn1[0], AVA)
self.assertEqual(rdn1[0], self.ava1)
# Create with single attr,value pair passed as a tuple
rdn1 = RDN((self.attr1, self.value1)) rdn1 = RDN((self.attr1, self.value1))
self.assertEqual(len(rdn1), 1) self.assertEqual(len(rdn1), 1)
self.assertEqual(rdn1, self.rdn1) self.assertEqual(rdn1, self.rdn1)
@ -230,7 +245,7 @@ class TestRDN(unittest.TestCase):
self.assertEqual(rdn1[0], self.ava1) self.assertEqual(rdn1[0], self.ava1)
# Create with multiple attr,value pairs # Create with multiple attr,value pairs
rdn3 = RDN(self.attr1, self.value1, self.attr2, self.value2) rdn3 = RDN((self.attr1, self.value1), (self.attr2, self.value2))
self.assertEqual(len(rdn3), 2) self.assertEqual(len(rdn3), 2)
self.assertEqual(rdn3, self.rdn3) self.assertEqual(rdn3, self.rdn3)
self.assertIsInstance(rdn3[0], AVA) self.assertIsInstance(rdn3[0], AVA)
@ -250,7 +265,7 @@ class TestRDN(unittest.TestCase):
# Create with multiple attr,value pairs but reverse # Create with multiple attr,value pairs but reverse
# constructor parameter ordering. RDN canonical ordering # constructor parameter ordering. RDN canonical ordering
# should remain the same # should remain the same
rdn3 = RDN(self.attr2, self.value2, self.attr1, self.value1) rdn3 = RDN((self.attr2, self.value2), (self.attr1, self.value1))
self.assertEqual(len(rdn3), 2) self.assertEqual(len(rdn3), 2)
self.assertEqual(rdn3, self.rdn3) self.assertEqual(rdn3, self.rdn3)
self.assertIsInstance(rdn3[0], AVA) self.assertIsInstance(rdn3[0], AVA)
@ -333,7 +348,7 @@ class TestRDN(unittest.TestCase):
def test_cmp(self): def test_cmp(self):
# Equality # Equality
rdn1 = RDN(self.attr1, self.value1) rdn1 = RDN((self.attr1, self.value1))
self.assertTrue(rdn1 == self.rdn1) self.assertTrue(rdn1 == self.rdn1)
self.assertFalse(rdn1 != self.rdn1) self.assertFalse(rdn1 != self.rdn1)
@ -404,50 +419,50 @@ class TestRDN(unittest.TestCase):
self.assertEqual(self.rdn3[:], [self.ava1, self.ava2]) self.assertEqual(self.rdn3[:], [self.ava1, self.ava2])
def test_assignments(self): def test_assignments(self):
rdn = RDN(self.attr1, self.value1) rdn = RDN((self.attr1, self.value1))
rdn[0] = self.ava2 rdn[0] = self.ava2
self.assertEqual(rdn, self.rdn2) self.assertEqual(rdn, self.rdn2)
rdn = RDN(self.attr1, self.value1) rdn = RDN((self.attr1, self.value1))
rdn[0] = (self.attr2, self.value2) rdn[0] = (self.attr2, self.value2)
self.assertEqual(rdn, self.rdn2) self.assertEqual(rdn, self.rdn2)
rdn = RDN(self.attr1, self.value1) rdn = RDN((self.attr1, self.value1))
rdn[self.attr1] = self.str_ava2 rdn[self.attr1] = self.str_ava2
self.assertEqual(rdn[0], self.ava2) self.assertEqual(rdn[0], self.ava2)
# Can't assign multiples to single entry # Can't assign multiples to single entry
rdn = RDN(self.attr1, self.value1) rdn = RDN((self.attr1, self.value1))
with self.assertRaises(TypeError): with self.assertRaises(TypeError):
rdn[self.attr1] = self.str_ava3 rdn[self.attr1] = self.str_ava3
rdn = RDN(self.attr1, self.value1) rdn = RDN((self.attr1, self.value1))
with self.assertRaises(TypeError): with self.assertRaises(TypeError):
rdn[self.attr1] = (self.attr1, self.value1, self.attr2, self.value2) rdn[self.attr1] = (self.attr1, self.value1, self.attr2, self.value2)
rdn = RDN(self.attr1, self.value1) rdn = RDN((self.attr1, self.value1))
with self.assertRaises(TypeError): with self.assertRaises(TypeError):
rdn[self.attr1] = [(self.attr1, self.value1), (self.attr2, self.value2)] rdn[self.attr1] = [(self.attr1, self.value1), (self.attr2, self.value2)]
# Slices # Slices
rdn = RDN(self.attr1, self.value1) rdn = RDN((self.attr1, self.value1))
self.assertEqual(rdn, self.rdn1) self.assertEqual(rdn, self.rdn1)
rdn[0:1] = [self.ava2] rdn[0:1] = [self.ava2]
self.assertEqual(rdn, self.rdn2) self.assertEqual(rdn, self.rdn2)
rdn = RDN(self.attr1, self.value1) rdn = RDN((self.attr1, self.value1))
self.assertEqual(rdn, self.rdn1) self.assertEqual(rdn, self.rdn1)
rdn[:] = [(self.attr2, self.value2)] rdn[:] = [(self.attr2, self.value2)]
self.assertEqual(rdn, self.rdn2) self.assertEqual(rdn, self.rdn2)
rdn = RDN(self.attr1, self.value1) rdn = RDN((self.attr1, self.value1))
self.assertEqual(rdn, self.rdn1) self.assertEqual(rdn, self.rdn1)
rdn[:] = [(self.attr1, self.value1),(self.attr2, self.value2)] rdn[:] = [(self.attr1, self.value1),(self.attr2, self.value2)]
self.assertEqual(rdn, self.rdn3) self.assertEqual(rdn, self.rdn3)
rdn = RDN(self.attr1, self.value1) rdn = RDN((self.attr1, self.value1))
self.assertEqual(rdn, self.rdn1) self.assertEqual(rdn, self.rdn1)
rdn[0:1] = [self.attr1, self.value1, self.attr2, self.value2] rdn[0:1] = [(self.attr1, self.value1), (self.attr2, self.value2)]
self.assertEqual(rdn, self.rdn3) self.assertEqual(rdn, self.rdn3)
@ -480,23 +495,23 @@ class TestRDN(unittest.TestCase):
def test_concat(self): def test_concat(self):
rdn1 = RDN(self.attr1, self.value1) rdn1 = RDN((self.attr1, self.value1))
rdn2 = RDN(self.attr2, self.value2) rdn2 = RDN((self.attr2, self.value2))
# in-place addtion # in-place addtion
rdn1 += rdn2 rdn1 += rdn2
self.assertEqual(rdn1, self.rdn3) self.assertEqual(rdn1, self.rdn3)
rdn1 = RDN(self.attr1, self.value1) rdn1 = RDN((self.attr1, self.value1))
rdn1 += self.ava2 rdn1 += self.ava2
self.assertEqual(rdn1, self.rdn3) self.assertEqual(rdn1, self.rdn3)
rdn1 = RDN(self.attr1, self.value1) rdn1 = RDN((self.attr1, self.value1))
rdn1 += self.str_ava2 rdn1 += self.str_ava2
self.assertEqual(rdn1, self.rdn3) self.assertEqual(rdn1, self.rdn3)
# concatenation # concatenation
rdn1 = RDN(self.attr1, self.value1) rdn1 = RDN((self.attr1, self.value1))
rdn3 = rdn1 + rdn2 rdn3 = rdn1 + rdn2
self.assertEqual(rdn3, self.rdn3) self.assertEqual(rdn3, self.rdn3)
@ -516,7 +531,7 @@ class TestDN(unittest.TestCase):
self.ava1 = AVA(self.attr1, self.value1) self.ava1 = AVA(self.attr1, self.value1)
self.str_rdn1 = '%s=%s' % (self.attr1, self.value1) self.str_rdn1 = '%s=%s' % (self.attr1, self.value1)
self.rdn1 = RDN(self.attr1, self.value1) self.rdn1 = RDN((self.attr1, self.value1))
self.attr2 = 'ou' self.attr2 = 'ou'
self.value2 = 'people' self.value2 = 'people'
@ -524,7 +539,7 @@ class TestDN(unittest.TestCase):
self.ava2 = AVA(self.attr2, self.value2) self.ava2 = AVA(self.attr2, self.value2)
self.str_rdn2 = '%s=%s' % (self.attr2, self.value2) self.str_rdn2 = '%s=%s' % (self.attr2, self.value2)
self.rdn2 = RDN(self.attr2, self.value2) self.rdn2 = RDN((self.attr2, self.value2))
self.str_dn1 = self.str_rdn1 self.str_dn1 = self.str_rdn1
self.dn1 = DN(self.rdn1) self.dn1 = DN(self.rdn1)
@ -535,12 +550,12 @@ class TestDN(unittest.TestCase):
self.str_dn3 = '%s,%s' % (self.str_rdn1, self.str_rdn2) self.str_dn3 = '%s,%s' % (self.str_rdn1, self.str_rdn2)
self.dn3 = DN(self.rdn1, self.rdn2) self.dn3 = DN(self.rdn1, self.rdn2)
self.base_rdn1 = RDN('dc', 'redhat') self.base_rdn1 = RDN(('dc', 'redhat'))
self.base_rdn2 = RDN('dc', 'com') self.base_rdn2 = RDN(('dc', 'com'))
self.base_dn = DN(self.base_rdn1, self.base_rdn2) self.base_dn = DN(self.base_rdn1, self.base_rdn2)
self.container_rdn1 = RDN('cn', 'sudorules') self.container_rdn1 = RDN(('cn', 'sudorules'))
self.container_rdn2 = RDN('cn', 'sudo') self.container_rdn2 = RDN(('cn', 'sudo'))
self.container_dn = DN(self.container_rdn1, self.container_rdn2) self.container_dn = DN(self.container_rdn1, self.container_rdn2)
self.base_container_dn = DN((self.attr1, self.value1), self.base_container_dn = DN((self.attr1, self.value1),
@ -581,7 +596,7 @@ class TestDN(unittest.TestCase):
self.assertEqual(dn1[1], self.rdn2) self.assertEqual(dn1[1], self.rdn2)
# Create with multiple attr,value pairs passed as tuple and RDN # Create with multiple attr,value pairs passed as tuple and RDN
dn1 = DN((self.attr1, self.value1), RDN(self.attr2, self.value2)) dn1 = DN((self.attr1, self.value1), RDN((self.attr2, self.value2)))
self.assertEqual(len(dn1), 2) self.assertEqual(len(dn1), 2)
self.assertIsInstance(dn1[0], RDN) self.assertIsInstance(dn1[0], RDN)
self.assertIsInstance(dn1[0].attr, unicode) self.assertIsInstance(dn1[0].attr, unicode)
@ -809,7 +824,7 @@ class TestDN(unittest.TestCase):
value = alt_rdn_value_arg(i) value = alt_rdn_value_arg(i)
dn1[i] = attr, value dn1[i] = attr, value
dn2[orig_attr] = (attr, value) dn2[orig_attr] = (attr, value)
dn3[i] = RDN(attr, value) dn3[i] = RDN((attr, value))
self.assertEqual(dn1, dn2) self.assertEqual(dn1, dn2)
self.assertEqual(dn1, dn3) self.assertEqual(dn1, dn3)