Skip to content

Commit 3d009dd

Browse files
committed
Python 3 preparation: comparisons.
Python 3 no longer has the cmp function, or cmp= arguments to sorting functions / methods (key= must be used instead), and requires rich comparison methods such as __lt__ to be defined instead of using __cmp__. All of the comparison mechanisms supported in Python 3 are also supported in Python 2. This patch makes the corresponding changes in Roundup to use key functions and rich comparison methods. In the case of the JournalPassword and Permission classes, only __eq__ and __ne__ are defined as I don't see ordered comparisons as useful there (and for Permission, the old __cmp__ function didn't try to provide a valid ordering). In the case of the Date class, I kept the __cmp__ method and implemented the others in terms of it, to avoid excess repetitiveness in duplicating implementation code for all six rich comparison methods. In roundup/admin.py, help_commands_html used operator.attrgetter to produce the second argument of sorted() - which would be reasonable for a key function, but the second argument is the cmp function in Python 2, not a key function (and the key function must be a named argument not a positional argument in Python 3). That function appears to be completely unused, so I expect that code never worked. This patch adds the missing key= to that sorted() call, but it would also be reasonable to remove the unused function completely instead.
1 parent 3a1cbb2 commit 3d009dd

File tree

10 files changed

+135
-56
lines changed

10 files changed

+135
-56
lines changed

roundup/admin.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ def help_commands_html(self, indent_re=re.compile(r'^(\s+)\S+')):
153153
""" Produce an HTML command list.
154154
"""
155155
commands = sorted(iter(self.commands.values()),
156-
operator.attrgetter('__name__'))
156+
key=operator.attrgetter('__name__'))
157157
for command in commands:
158158
h = _(command.__doc__).split('\n')
159159
name = command.__name__[3:]

roundup/cgi/form_parser.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,7 @@ def parse(self, create=0, num_re=re.compile('^\d+$')):
467467
value = existing
468468
# Sort the value in the same order used by
469469
# Multilink.from_raw.
470-
value.sort(lambda x, y: cmp(int(x),int(y)))
470+
value.sort(key=int)
471471

472472
elif value == '':
473473
# other types should be None'd if there's no value
@@ -511,7 +511,7 @@ def parse(self, create=0, num_re=re.compile('^\d+$')):
511511
# The canonical order (given in Multilink.from_raw) is
512512
# by the numeric value of the IDs.
513513
if isinstance(proptype, hyperdb.Multilink):
514-
existing.sort(lambda x, y: cmp(int(x),int(y)))
514+
existing.sort(key=int)
515515

516516
# "missing" existing values may not be None
517517
if not existing:

roundup/cgi/templating.py

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -620,16 +620,16 @@ def properties(self, sort=1, cansearch=True):
620620
l.append(htmlklass(self._client, self._classname, '',
621621
prop, name, value, self._anonymous))
622622
if sort:
623-
l.sort(lambda a,b:cmp(a._name, b._name))
623+
l.sort(key=lambda a:a._name)
624624
return l
625625

626626
def list(self, sort_on=None):
627627
""" List all items in this class.
628628
"""
629629
# get the list and sort it nicely
630630
l = self._klass.list()
631-
sortfunc = make_sort_function(self._db, self._classname, sort_on)
632-
l.sort(sortfunc)
631+
keyfunc = make_key_function(self._db, self._classname, sort_on)
632+
l.sort(key=keyfunc)
633633

634634
# check perms
635635
check = self._client.db.security.hasPermission
@@ -1338,10 +1338,30 @@ def __repr__(self):
13381338
self._prop, self._value)
13391339
def __str__(self):
13401340
return self.plain()
1341-
def __cmp__(self, other):
1341+
def __lt__(self, other):
13421342
if isinstance(other, HTMLProperty):
1343-
return cmp(self._value, other._value)
1344-
return cmp(self._value, other)
1343+
return self._value < other._value
1344+
return self._value < other
1345+
def __le__(self, other):
1346+
if isinstance(other, HTMLProperty):
1347+
return self._value <= other._value
1348+
return self._value <= other
1349+
def __eq__(self, other):
1350+
if isinstance(other, HTMLProperty):
1351+
return self._value == other._value
1352+
return self._value == other
1353+
def __ne__(self, other):
1354+
if isinstance(other, HTMLProperty):
1355+
return self._value != other._value
1356+
return self._value != other
1357+
def __gt__(self, other):
1358+
if isinstance(other, HTMLProperty):
1359+
return self._value > other._value
1360+
return self._value > other
1361+
def __ge__(self, other):
1362+
if isinstance(other, HTMLProperty):
1363+
return self._value >= other._value
1364+
return self._value >= other
13451365

13461366
def __bool__(self):
13471367
return not not self._value
@@ -2258,12 +2278,12 @@ def __init__(self, *args, **kwargs):
22582278
if self._value:
22592279
display_value = lookupIds(self._db, self._prop, self._value,
22602280
fail_ok=1, do_lookup=False)
2261-
sortfun = make_sort_function(self._db, self._prop.classname)
2281+
keyfun = make_key_function(self._db, self._prop.classname)
22622282
# sorting fails if the value contains
22632283
# items not yet stored in the database
22642284
# ignore these errors to preserve user input
22652285
try:
2266-
display_value.sort(sortfun)
2286+
display_value.sort(key=keyfun)
22672287
except:
22682288
pass
22692289
self._value = display_value
@@ -2303,7 +2323,7 @@ def reverse(self):
23032323
def sorted(self, property):
23042324
""" Return this multilink sorted by the given property """
23052325
value = list(self.__iter__())
2306-
value.sort(lambda a,b:cmp(a[property], b[property]))
2326+
value.sort(key=lambda a:a[property])
23072327
return value
23082328

23092329
def __contains__(self, value):
@@ -2501,21 +2521,19 @@ def register_propclass(prop, cls):
25012521
propclasses.append((prop, cls))
25022522

25032523

2504-
def make_sort_function(db, classname, sort_on=None):
2505-
"""Make a sort function for a given class.
2524+
def make_key_function(db, classname, sort_on=None):
2525+
"""Make a sort key function for a given class.
25062526
25072527
The list being sorted may contain mixed ids and labels.
25082528
"""
25092529
linkcl = db.getclass(classname)
25102530
if sort_on is None:
25112531
sort_on = linkcl.orderprop()
2512-
def sortfunc(a, b):
2532+
def keyfunc(a):
25132533
if num_re.match(a):
25142534
a = linkcl.get(a, sort_on)
2515-
if num_re.match(b):
2516-
b = linkcl.get(b, sort_on)
2517-
return cmp(a, b)
2518-
return sortfunc
2535+
return a
2536+
return keyfunc
25192537

25202538
def handleListCGIValue(value):
25212539
""" Value is either a single item or a list of items. Each item has a

roundup/date.py

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@
3030
except ImportError:
3131
pytz = None
3232

33+
try:
34+
cmp
35+
except NameError:
36+
# Python 3.
37+
def cmp(a, b):
38+
return (a > b) - (a < b)
39+
3340
from roundup import i18n
3441

3542
# no, I don't know why we must anchor the date RE when we only ever use it
@@ -585,6 +592,19 @@ def __cmp__(self, other, int_seconds=0):
585592
return cmp(int(self.second), int(other.second))
586593
return cmp(self.second, other.second)
587594

595+
def __lt__(self, other):
596+
return self.__cmp__(other) < 0
597+
def __le__(self, other):
598+
return self.__cmp__(other) <= 0
599+
def __eq__(self, other):
600+
return self.__cmp__(other) == 0
601+
def __ne__(self, other):
602+
return self.__cmp__(other) != 0
603+
def __gt__(self, other):
604+
return self.__cmp__(other) > 0
605+
def __ge__(self, other):
606+
return self.__cmp__(other) >= 0
607+
588608
def __str__(self):
589609
"""Return this date as a string in the yyyy-mm-dd.hh:mm:ss format."""
590610
return self.formal()
@@ -834,13 +854,53 @@ def set(self, spec, allowdate=1, interval_re=re.compile('''
834854
y = now - (date + self)
835855
self.__init__(y.get_tuple())
836856

837-
def __cmp__(self, other):
857+
def __lt__(self, other):
838858
"""Compare this interval to another interval."""
839859

840860
if other is None:
841861
# we are always larger than None
842-
return 1
843-
return cmp(self.as_seconds(), other.as_seconds())
862+
return False
863+
return self.as_seconds() < other.as_seconds()
864+
865+
def __le__(self, other):
866+
"""Compare this interval to another interval."""
867+
868+
if other is None:
869+
# we are always larger than None
870+
return False
871+
return self.as_seconds() <= other.as_seconds()
872+
873+
def __eq__(self, other):
874+
"""Compare this interval to another interval."""
875+
876+
if other is None:
877+
# we are always larger than None
878+
return False
879+
return self.as_seconds() == other.as_seconds()
880+
881+
def __ne__(self, other):
882+
"""Compare this interval to another interval."""
883+
884+
if other is None:
885+
# we are always larger than None
886+
return True
887+
return self.as_seconds() != other.as_seconds()
888+
889+
def __gt__(self, other):
890+
"""Compare this interval to another interval."""
891+
892+
if other is None:
893+
# we are always larger than None
894+
return True
895+
return self.as_seconds() > other.as_seconds()
896+
897+
def __ge__(self, other):
898+
"""Compare this interval to another interval."""
899+
900+
if other is None:
901+
# we are always larger than None
902+
return True
903+
return self.as_seconds() >= other.as_seconds()
844904

845905
def __str__(self):
846906
"""Return this interval as a string."""

roundup/password.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -223,19 +223,22 @@ def dummystr(self):
223223

224224
__str__ = dummystr
225225

226-
def __cmp__(self, other):
226+
def __eq__(self, other):
227227
"""Compare this password against another password."""
228228
# check to see if we're comparing instances
229229
if isinstance(other, self.__class__):
230230
if self.scheme != other.scheme:
231-
return cmp(self.scheme, other.scheme)
232-
return cmp(self.password, other.password)
231+
return False
232+
return self.password == other.password
233233

234234
# assume password is plaintext
235235
if self.password is None:
236236
raise ValueError('Password not set')
237-
return cmp(self.password, encodePassword(other, self.scheme,
238-
self.password or None))
237+
return self.password == encodePassword(other, self.scheme,
238+
self.password or None)
239+
240+
def __ne__(self, other):
241+
return not self.__eq__(other)
239242

240243
class Password(JournalPassword):
241244
"""The class encapsulates a Password property type value in the database.

roundup/security.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -155,18 +155,21 @@ def __repr__(self):
155155
self.klass, self.properties, self.check,
156156
self.limit_perm_to_props_only)
157157

158-
def __cmp__(self, other):
158+
def __eq__(self, other):
159159
if self.name != other.name:
160-
return cmp(self.name, other.name)
160+
return False
161161

162-
if self.klass != other.klass: return 1
163-
if self.properties != other.properties: return 1
164-
if self.check != other.check: return 1
162+
if self.klass != other.klass: return False
163+
if self.properties != other.properties: return False
164+
if self.check != other.check: return False
165165
if self.limit_perm_to_props_only != \
166-
other.limit_perm_to_props_only: return 1
166+
other.limit_perm_to_props_only: return False
167167

168168
# match
169-
return 0
169+
return True
170+
171+
def __ne__(self, other):
172+
return not self.__eq__(other)
170173

171174
def __getitem__(self,index):
172175
return (self.name, self.klass, self.properties, self.check,

scripts/contributors.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -136,19 +136,16 @@ def first_year(name):
136136
"""Return year of the first contribution"""
137137
return sorted(list(names[name]))[0]
138138

139-
def year_cmp(name1, name2):
139+
def year_key(name):
140140
"""
141-
Year comparison function. First sort by latest contribution year (desc).
141+
Year key function. First sort by latest contribution year (desc).
142142
If it matches, compare first contribution year (asc). This ensures that
143143
the most recent and long-term contributors are at the top.
144144
"""
145-
if last_year(name1) != last_year(name2):
146-
return last_year(name1) - last_year(name2)
147-
else:
148-
return first_year(name2) - first_year(name1)
145+
return (last_year(name), -first_year(name))
149146

150147
print("Copyright (c)")
151-
for author in sorted(list(names), cmp=year_cmp, reverse=True):
148+
for author in sorted(list(names), key=year_key, reverse=True):
152149
years = list(names[author])
153150
yearstr = compress(years)
154151

scripts/import_sf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ def to_date(ts):
279279

280280
# sort messages and assign ids
281281
d['messages'] = []
282-
message_data.sort(lambda a,b:cmp(a['date'],b['date']))
282+
message_data.sort(key=lambda a:a['date'])
283283
for message in message_data:
284284
message_id += 1
285285
message['id'] = str(message_id)

scripts/roundup-reminder

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,16 +41,9 @@ db = instance.open('admin')
4141

4242
resolved_id = db.status.lookup('resolved')
4343

44-
def listCompare(x, y):
45-
"compare two tuples such that order is positive on [0] and negative on [1]"
46-
if x[0] < y[0]:
47-
return -1
48-
if x[0] > y[0]:
49-
return 1
50-
if x[1] > y[1]:
51-
return -1
52-
if x[1] < y[1]:
53-
return 1
44+
def listKey(x):
45+
"key for tuples such that order is positive on [0] and negative on [1]"
46+
return (x[0], -x[1])
5447
return 0
5548

5649
# loop through all the users
@@ -73,7 +66,7 @@ for user_id in db.user.list():
7366
db.issue.get(issue_id, 'creation'), issue_id))
7467

7568
# sort the issues by timeliness and creation date
76-
l.sort(listCompare)
69+
l.sort(key=listKey)
7770
if not l:
7871
continue
7972

test/test_templating.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,12 @@ class HTMLProperty(HTMLInputMixin, HTMLPermissions):
354354
def __init__(self, client, classname, nodeid, prop, name, value,
355355
def __repr__(self):
356356
def __str__(self):
357-
def __cmp__(self, other):
357+
def __lt__(self, other):
358+
def __le__(self, other):
359+
def __eq__(self, other):
360+
def __ne__(self, other):
361+
def __gt__(self, other):
362+
def __ge__(self, other):
358363
def is_edit_ok(self):
359364
def is_view_ok(self):
360365
@@ -418,8 +423,8 @@ def plain(self, escape=0):
418423
def field(self, size=30, showid=0):
419424
def menu(self, size=None, height=None, showid=0, additional=[],
420425
421-
def make_sort_function(db, classname, sort_on=None):
422-
def sortfunc(a, b):
426+
def make_key_function(db, classname, sort_on=None):
427+
def keyfunc(a):
423428
424429
def find_sort_key(linkcl):
425430

0 commit comments

Comments
 (0)