Skip to content

Commit 2fcec9c

Browse files
committed
helper to allow comparing dicts and None values in Python 3
1 parent ac3dc65 commit 2fcec9c

File tree

4 files changed

+105
-47
lines changed

4 files changed

+105
-47
lines changed

roundup/anypy/cmp_.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
try:
2+
None < 0
3+
def NoneAndDictComparable(v):
4+
return v
5+
except TypeError:
6+
# comparator to allow comparisons against None and dict
7+
# comparisons (these were allowed in Python 2, but aren't allowed
8+
# in Python 3 any more)
9+
class NoneAndDictComparable(object):
10+
def __init__(self, value):
11+
self.value = value
12+
13+
def __cmp__(self, other):
14+
if not isinstance(other, self.__class__):
15+
raise TypeError('not comparable')
16+
17+
if self.value == other.value:
18+
return 0
19+
20+
elif self.value is None:
21+
return -1
22+
23+
elif other.value is None:
24+
return 1
25+
26+
elif type(self.value) == tuple and type(other.value) == tuple:
27+
for lhs, rhs in zip(self.value, other.value):
28+
lhsCmp = NoneAndDictComparable(lhs)
29+
rhsCmp = NoneAndDictComparable(rhs)
30+
result = lhsCmp.__cmp__(rhsCmp)
31+
if result != 0:
32+
return result
33+
34+
return len(self.value) - len(other.value)
35+
36+
elif type(self.value) == dict and type(other.value) == dict:
37+
diff = len(self.value) - len(other.value)
38+
if diff == 0:
39+
lhsItems = tuple(sorted(self.value.items(),
40+
key=NoneAndDictComparable))
41+
rhsItems = tuple(sorted(other.value.items(),
42+
key=NoneAndDictComparable))
43+
return -1 if NoneAndDictComparable(lhsItems) < NoneAndDictComparable(rhsItems) else 1
44+
else:
45+
return diff
46+
47+
elif self.value < other.value:
48+
return -1
49+
50+
else:
51+
return 1
52+
53+
def __eq__(self, other):
54+
return self.__cmp__(other) == 0
55+
def __ne__(self, other):
56+
return self.__cmp__(other) != 0
57+
def __lt__(self, other):
58+
return self.__cmp__(other) < 0
59+
def __le__(self, other):
60+
return self.__cmp__(other) <= 0
61+
def __ge__(self, other):
62+
return self.__cmp__(other) >= 0
63+
def __gt__(self, other):
64+
return self.__cmp__(other) > 0
65+
66+
def _test():
67+
Comp = NoneAndDictComparable
68+
69+
assert Comp(None) < Comp(0)
70+
assert Comp(None) < Comp('')
71+
assert Comp(None) < Comp({})
72+
assert Comp((0, None)) < Comp((0, 0))
73+
assert not Comp(0) < Comp(None)
74+
assert not Comp('') < Comp(None)
75+
assert not Comp({}) < Comp(None)
76+
assert not Comp((0, 0)) < Comp((0, None))
77+
78+
assert Comp((0, 0)) < Comp((0, 0, None))
79+
assert Comp((0, None, None)) < Comp((0, 0, 0))
80+
81+
assert Comp(0) < Comp(1)
82+
assert Comp(1) > Comp(0)
83+
assert not Comp(1) < Comp(0)
84+
assert not Comp(0) > Comp(0)
85+
86+
assert Comp({ 0: None }) < Comp({ 0: 0 })
87+
assert Comp({ 0: 0 }) < Comp({ 0: 1 })
88+
89+
assert Comp({ 0: 0 }) == Comp({ 0: 0 })
90+
assert Comp({ 0: 0 }) != Comp({ 0: 1 })
91+
assert Comp({ 0: 0, 1: 1 }) > Comp({ 0: 1 })
92+
assert Comp({ 0: 0, 1: 1 }) < Comp({ 0: 0, 2: 2 })
93+
94+
if __name__ == '__main__':
95+
_test()

roundup/hyperdb.py

Lines changed: 2 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -30,53 +30,10 @@
3030
from .support import ensureParentsExist, PrioList
3131
from roundup.i18n import _
3232
from roundup.cgi.exceptions import DetectorError
33+
from roundup.anypy.cmp_ import NoneAndDictComparable
3334

3435
logger = logging.getLogger('roundup.hyperdb')
3536

36-
try:
37-
None < 0
38-
def _NoneComparable(v):
39-
return v
40-
except TypeError:
41-
class _NoneComparable(object):
42-
def __init__(self, value):
43-
self.value = value
44-
45-
def __cmp__(self, other):
46-
if not isinstance(other, self.__class__):
47-
raise TypeError('not comparable')
48-
49-
if self.value is None and other.value is None:
50-
return 0
51-
elif self.value is None:
52-
return -1
53-
elif other.value is None:
54-
return 1
55-
elif type(self.value) == type(()) and type(other.value) == type(()):
56-
for lhs, rhs in zip(self.value, other.value):
57-
result = _NoneComparable(lhs).__cmp__(_NoneComparable(rhs))
58-
if result != 0:
59-
return result
60-
return len(self.value) - len(other.value)
61-
elif self.value < other.value:
62-
return -1
63-
elif self.value > other.value:
64-
return 1
65-
else:
66-
return 0
67-
68-
def __eq__(self, other):
69-
return self.__cmp__(other) == 0
70-
def __ne__(self, other):
71-
return self.__cmp__(other) != 0
72-
def __lt__(self, other):
73-
return self.__cmp__(other) < 0
74-
def __le__(self, other):
75-
return self.__cmp__(other) <= 0
76-
def __ge__(self, other):
77-
return self.__cmp__(other) >= 0
78-
def __gt__(self, other):
79-
return self.__cmp__(other) > 0
8037

8138
#
8239
# Types
@@ -652,7 +609,7 @@ def _sort(self, val):
652609
for dir, i in reversed(list(zip(directions, dir_idx))):
653610
rev = dir == '-'
654611
sortattr = sorted (sortattr,
655-
key = lambda x: _NoneComparable(x[i:idx]),
612+
key = lambda x: NoneAndDictComparable(x[i:idx]),
656613
reverse = rev)
657614
idx = i
658615
return [x[-1] for x in sortattr]

test/db_test_base.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
from roundup.exceptions import UsageError, Reject
3636

3737
from roundup.anypy.strings import b2s, s2b, u2s
38+
from roundup.anypy.cmp_ import NoneAndDictComparable
3839

3940
from .mocknull import MockNull
4041

@@ -2310,8 +2311,8 @@ def testImportExport(self):
23102311
j[1].second = float(int(j[1].second))
23112312
for j in rj:
23122313
j[1].second = float(int(j[1].second))
2313-
oj.sort(key = lambda x: x[:4])
2314-
rj.sort(key = lambda x: x[:4])
2314+
oj.sort(key = NoneAndDictComparable)
2315+
rj.sort(key = NoneAndDictComparable)
23152316
ae(oj, rj)
23162317

23172318
# make sure the retired items are actually imported

test/test_misc.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# misc tests
22

33
import unittest
4+
import roundup.anypy.cmp_
45
from roundup.cgi.accept_language import parse
56

67
class AcceptLanguageTest(unittest.TestCase):
@@ -15,3 +16,7 @@ def testParse(self):
1516
self.assertEqual(parse(None),[])
1617
self.assertEqual(parse(" "), [])
1718
self.assertEqual(parse("en,"), ['en'])
19+
20+
class CmpTest(unittest.TestCase):
21+
def testCmp(self):
22+
roundup.anypy.cmp_._test()

0 commit comments

Comments
 (0)