From 1199987b9713a777cbba0cf8910f83a0e5e6a2bc Mon Sep 17 00:00:00 2001 From: Joseph Myers Date: Sun, 17 Jun 2018 12:31:04 +0000 Subject: [PATCH 40/40] Python 3 preparation: comparisons. --- roundup/admin.py | 2 +- roundup/cgi/form_parser.py | 4 +-- roundup/cgi/templating.py | 50 ++++++++++++++++++++++++----------- roundup/date.py | 66 +++++++++++++++++++++++++++++++++++++++++++--- roundup/password.py | 13 +++++---- roundup/security.py | 17 +++++++----- scripts/contributors.py | 11 +++----- scripts/import_sf.py | 2 +- scripts/roundup-reminder | 15 +++-------- test/test_templating.py | 11 +++++--- 10 files changed, 135 insertions(+), 56 deletions(-) diff --git a/roundup/admin.py b/roundup/admin.py index 0d48b3a..d3088fd 100644 --- a/roundup/admin.py +++ b/roundup/admin.py @@ -153,7 +153,7 @@ matches only one command, e.g. l == li == lis == list.""")) """ Produce an HTML command list. """ commands = sorted(iter(self.commands.values()), - operator.attrgetter('__name__')) + key=operator.attrgetter('__name__')) for command in commands: h = _(command.__doc__).split('\n') name = command.__name__[3:] diff --git a/roundup/cgi/form_parser.py b/roundup/cgi/form_parser.py index 1e1ce33..2a64215 100644 --- a/roundup/cgi/form_parser.py +++ b/roundup/cgi/form_parser.py @@ -467,7 +467,7 @@ class FormParser: value = existing # Sort the value in the same order used by # Multilink.from_raw. - value.sort(lambda x, y: cmp(int(x),int(y))) + value.sort(key=int) elif value == '': # other types should be None'd if there's no value @@ -511,7 +511,7 @@ class FormParser: # The canonical order (given in Multilink.from_raw) is # by the numeric value of the IDs. if isinstance(proptype, hyperdb.Multilink): - existing.sort(lambda x, y: cmp(int(x),int(y))) + existing.sort(key=int) # "missing" existing values may not be None if not existing: diff --git a/roundup/cgi/templating.py b/roundup/cgi/templating.py index 1d48053..4d444ec 100644 --- a/roundup/cgi/templating.py +++ b/roundup/cgi/templating.py @@ -620,7 +620,7 @@ class HTMLClass(HTMLInputMixin, HTMLPermissions): l.append(htmlklass(self._client, self._classname, '', prop, name, value, self._anonymous)) if sort: - l.sort(lambda a,b:cmp(a._name, b._name)) + l.sort(key=lambda a:a._name) return l def list(self, sort_on=None): @@ -628,8 +628,8 @@ class HTMLClass(HTMLInputMixin, HTMLPermissions): """ # get the list and sort it nicely l = self._klass.list() - sortfunc = make_sort_function(self._db, self._classname, sort_on) - l.sort(sortfunc) + keyfunc = make_key_function(self._db, self._classname, sort_on) + l.sort(key=keyfunc) # check perms check = self._client.db.security.hasPermission @@ -1336,10 +1336,30 @@ class HTMLProperty(HTMLInputMixin, HTMLPermissions): self._prop, self._value) def __str__(self): return self.plain() - def __cmp__(self, other): + def __lt__(self, other): if isinstance(other, HTMLProperty): - return cmp(self._value, other._value) - return cmp(self._value, other) + return self._value < other._value + return self._value < other + def __le__(self, other): + if isinstance(other, HTMLProperty): + return self._value <= other._value + return self._value <= other + def __eq__(self, other): + if isinstance(other, HTMLProperty): + return self._value == other._value + return self._value == other + def __ne__(self, other): + if isinstance(other, HTMLProperty): + return self._value != other._value + return self._value != other + def __gt__(self, other): + if isinstance(other, HTMLProperty): + return self._value > other._value + return self._value > other + def __ge__(self, other): + if isinstance(other, HTMLProperty): + return self._value >= other._value + return self._value >= other def __bool__(self): return not not self._value @@ -2256,12 +2276,12 @@ class MultilinkHTMLProperty(HTMLProperty): if self._value: display_value = lookupIds(self._db, self._prop, self._value, fail_ok=1, do_lookup=False) - sortfun = make_sort_function(self._db, self._prop.classname) + keyfun = make_key_function(self._db, self._prop.classname) # sorting fails if the value contains # items not yet stored in the database # ignore these errors to preserve user input try: - display_value.sort(sortfun) + display_value.sort(key=keyfun) except: pass self._value = display_value @@ -2301,7 +2321,7 @@ class MultilinkHTMLProperty(HTMLProperty): def sorted(self, property): """ Return this multilink sorted by the given property """ value = list(self.__iter__()) - value.sort(lambda a,b:cmp(a[property], b[property])) + value.sort(key=lambda a:a[property]) return value def __contains__(self, value): @@ -2499,21 +2519,19 @@ def register_propclass(prop, cls): propclasses.append((prop, cls)) -def make_sort_function(db, classname, sort_on=None): - """Make a sort function for a given class. +def make_key_function(db, classname, sort_on=None): + """Make a sort key function for a given class. The list being sorted may contain mixed ids and labels. """ linkcl = db.getclass(classname) if sort_on is None: sort_on = linkcl.orderprop() - def sortfunc(a, b): + def keyfunc(a): if num_re.match(a): a = linkcl.get(a, sort_on) - if num_re.match(b): - b = linkcl.get(b, sort_on) - return cmp(a, b) - return sortfunc + return a + return keyfunc def handleListCGIValue(value): """ Value is either a single item or a list of items. Each item has a diff --git a/roundup/date.py b/roundup/date.py index 2d7e54e..48a5390 100644 --- a/roundup/date.py +++ b/roundup/date.py @@ -30,6 +30,13 @@ try: except ImportError: pytz = None +try: + cmp +except NameError: + # Python 3. + def cmp(a, b): + return (a > b) - (a < b) + from roundup import i18n # no, I don't know why we must anchor the date RE when we only ever use it @@ -585,6 +592,19 @@ class Date: return cmp(int(self.second), int(other.second)) return cmp(self.second, other.second) + def __lt__(self, other): + return self.__cmp__(other) < 0 + def __le__(self, other): + return self.__cmp__(other) <= 0 + def __eq__(self, other): + return self.__cmp__(other) == 0 + def __ne__(self, other): + return self.__cmp__(other) != 0 + def __gt__(self, other): + return self.__cmp__(other) > 0 + def __ge__(self, other): + return self.__cmp__(other) >= 0 + def __str__(self): """Return this date as a string in the yyyy-mm-dd.hh:mm:ss format.""" return self.formal() @@ -834,13 +854,53 @@ class Interval: y = now - (date + self) self.__init__(y.get_tuple()) - def __cmp__(self, other): + def __lt__(self, other): """Compare this interval to another interval.""" if other is None: # we are always larger than None - return 1 - return cmp(self.as_seconds(), other.as_seconds()) + return False + return self.as_seconds() < other.as_seconds() + + def __le__(self, other): + """Compare this interval to another interval.""" + + if other is None: + # we are always larger than None + return False + return self.as_seconds() <= other.as_seconds() + + def __eq__(self, other): + """Compare this interval to another interval.""" + + if other is None: + # we are always larger than None + return False + return self.as_seconds() == other.as_seconds() + + def __ne__(self, other): + """Compare this interval to another interval.""" + + if other is None: + # we are always larger than None + return True + return self.as_seconds() != other.as_seconds() + + def __gt__(self, other): + """Compare this interval to another interval.""" + + if other is None: + # we are always larger than None + return True + return self.as_seconds() > other.as_seconds() + + def __ge__(self, other): + """Compare this interval to another interval.""" + + if other is None: + # we are always larger than None + return True + return self.as_seconds() >= other.as_seconds() def __str__(self): """Return this interval as a string.""" diff --git a/roundup/password.py b/roundup/password.py index 30fdfa9..3686932 100644 --- a/roundup/password.py +++ b/roundup/password.py @@ -223,19 +223,22 @@ class JournalPassword: __str__ = dummystr - def __cmp__(self, other): + def __eq__(self, other): """Compare this password against another password.""" # check to see if we're comparing instances if isinstance(other, self.__class__): if self.scheme != other.scheme: - return cmp(self.scheme, other.scheme) - return cmp(self.password, other.password) + return False + return self.password == other.password # assume password is plaintext if self.password is None: raise ValueError('Password not set') - return cmp(self.password, encodePassword(other, self.scheme, - self.password or None)) + return self.password == encodePassword(other, self.scheme, + self.password or None) + + def __ne__(self, other): + return not self.__eq__(other) class Password(JournalPassword): """The class encapsulates a Password property type value in the database. diff --git a/roundup/security.py b/roundup/security.py index acbb7b9..e94a881 100644 --- a/roundup/security.py +++ b/roundup/security.py @@ -155,18 +155,21 @@ class Permission: self.klass, self.properties, self.check, self.limit_perm_to_props_only) - def __cmp__(self, other): + def __eq__(self, other): if self.name != other.name: - return cmp(self.name, other.name) + return False - if self.klass != other.klass: return 1 - if self.properties != other.properties: return 1 - if self.check != other.check: return 1 + if self.klass != other.klass: return False + if self.properties != other.properties: return False + if self.check != other.check: return False if self.limit_perm_to_props_only != \ - other.limit_perm_to_props_only: return 1 + other.limit_perm_to_props_only: return False # match - return 0 + return True + + def __ne__(self, other): + return not self.__eq__(other) def __getitem__(self,index): return (self.name, self.klass, self.properties, self.check, diff --git a/scripts/contributors.py b/scripts/contributors.py index 1d7ddf6..fa31b0a 100644 --- a/scripts/contributors.py +++ b/scripts/contributors.py @@ -136,19 +136,16 @@ if __name__ == '__main__': """Return year of the first contribution""" return sorted(list(names[name]))[0] - def year_cmp(name1, name2): + def year_key(name): """ - Year comparison function. First sort by latest contribution year (desc). + Year key function. First sort by latest contribution year (desc). If it matches, compare first contribution year (asc). This ensures that the most recent and long-term contributors are at the top. """ - if last_year(name1) != last_year(name2): - return last_year(name1) - last_year(name2) - else: - return first_year(name2) - first_year(name1) + return (last_year(name), -first_year(name)) print("Copyright (c)") - for author in sorted(list(names), cmp=year_cmp, reverse=True): + for author in sorted(list(names), key=year_key, reverse=True): years = list(names[author]) yearstr = compress(years) diff --git a/scripts/import_sf.py b/scripts/import_sf.py index e49721b..85d7d09 100644 --- a/scripts/import_sf.py +++ b/scripts/import_sf.py @@ -279,7 +279,7 @@ def import_xml(tracker_home, xml_file, file_dir): # sort messages and assign ids d['messages'] = [] - message_data.sort(lambda a,b:cmp(a['date'],b['date'])) + message_data.sort(key=lambda a:a['date']) for message in message_data: message_id += 1 message['id'] = str(message_id) diff --git a/scripts/roundup-reminder b/scripts/roundup-reminder index c155c9c..cf35494 100755 --- a/scripts/roundup-reminder +++ b/scripts/roundup-reminder @@ -41,16 +41,9 @@ db = instance.open('admin') resolved_id = db.status.lookup('resolved') -def listCompare(x, y): - "compare two tuples such that order is positive on [0] and negative on [1]" - if x[0] < y[0]: - return -1 - if x[0] > y[0]: - return 1 - if x[1] > y[1]: - return -1 - if x[1] < y[1]: - return 1 +def listKey(x): + "key for tuples such that order is positive on [0] and negative on [1]" + return (x[0], -x[1]) return 0 # loop through all the users @@ -73,7 +66,7 @@ for user_id in db.user.list(): db.issue.get(issue_id, 'creation'), issue_id)) # sort the issues by timeliness and creation date - l.sort(listCompare) + l.sort(key=listKey) if not l: continue diff --git a/test/test_templating.py b/test/test_templating.py index 475daa1..def8ce9 100644 --- a/test/test_templating.py +++ b/test/test_templating.py @@ -354,7 +354,12 @@ class HTMLProperty(HTMLInputMixin, HTMLPermissions): def __init__(self, client, classname, nodeid, prop, name, value, def __repr__(self): def __str__(self): - def __cmp__(self, other): + def __lt__(self, other): + def __le__(self, other): + def __eq__(self, other): + def __ne__(self, other): + def __gt__(self, other): + def __ge__(self, other): def is_edit_ok(self): def is_view_ok(self): @@ -418,8 +423,8 @@ class MultilinkHTMLProperty(HTMLProperty): def field(self, size=30, showid=0): def menu(self, size=None, height=None, showid=0, additional=[], -def make_sort_function(db, classname, sort_on=None): - def sortfunc(a, b): +def make_key_function(db, classname, sort_on=None): + def keyfunc(a): def find_sort_key(linkcl): -- 2.7.4