Merge ~cjwatson/launchpad:py3-orderable into launchpad:master

Proposed by Colin Watson
Status: Merged
Approved by: Colin Watson
Approved revision: 939df80ba6f7981be9c1d3b228689eccf2387603
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~cjwatson/launchpad:py3-orderable
Merge into: launchpad:master
Diff against target: 324 lines (+66/-30)
16 files modified
lib/lp/bugs/browser/bugtask.py (+7/-3)
lib/lp/bugs/browser/tests/person-bug-views.txt (+1/-1)
lib/lp/bugs/doc/externalbugtracker-sourceforge.txt (+3/-1)
lib/lp/bugs/model/tests/test_bugtask.py (+9/-6)
lib/lp/bugs/stories/webservice/xx-bug.txt (+5/-3)
lib/lp/bugs/subscribers/bug.py (+2/-1)
lib/lp/code/doc/revision.txt (+3/-1)
lib/lp/code/model/branchmergeproposal.py (+3/-2)
lib/lp/registry/stories/webservice/xx-person.txt (+4/-2)
lib/lp/registry/tests/test_distroseriesdifferencecomment.py (+1/-1)
lib/lp/services/doc/orderingcheck.txt (+7/-3)
lib/lp/services/webservice/stories/multiversion.txt (+4/-1)
lib/lp/soyuz/scripts/tests/test_ppa_apache_log_parser.py (+3/-1)
lib/lp/soyuz/stories/webservice/xx-hasbuildrecords.txt (+3/-1)
lib/lp/soyuz/stories/webservice/xx-source-package-publishing.txt (+2/-2)
lib/lp/translations/utilities/translationmerger.py (+9/-1)
Reviewer Review Type Date Requested Status
Cristian Gonzalez (community) Approve
Review via email: mp+398876@code.launchpad.net

Commit message

Fix sorting of unorderable objects on Python 3

Description of the change

On Python 3, objects without a meaningful natural ordering can't be ordered. Fix a number of places that were trying to do this: sometimes this involves using better sort keys, while sometimes we can use various strategies to avoid ordering them in the first place.

To post a comment you must log in.
Revision history for this message
Cristian Gonzalez (cristiangsp) wrote :

Looks good!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/lib/lp/bugs/browser/bugtask.py b/lib/lp/bugs/browser/bugtask.py
index a9d9953..2a139c0 100644
--- a/lib/lp/bugs/browser/bugtask.py
+++ b/lib/lp/bugs/browser/bugtask.py
@@ -690,10 +690,14 @@ class BugTaskView(LaunchpadView, BugViewMixin, FeedsMixin):
690 """690 """
691 event_groups = self._event_groups691 event_groups = self._event_groups
692692
693 def activity_sort_key(activity):
694 target = activity.target
695 return (
696 activity.datechanged, 0 if target is None else 1, target,
697 activity.attribute)
698
693 def group_activities_by_target(activities):699 def group_activities_by_target(activities):
694 activities = sorted(700 activities = sorted(activities, key=activity_sort_key)
695 activities, key=attrgetter(
696 "datechanged", "target", "attribute"))
697 return [701 return [
698 {"target": target, "activity": list(activity)}702 {"target": target, "activity": list(activity)}
699 for target, activity in groupby(703 for target, activity in groupby(
diff --git a/lib/lp/bugs/browser/tests/person-bug-views.txt b/lib/lp/bugs/browser/tests/person-bug-views.txt
index 439512d..5f1d5ab 100644
--- a/lib/lp/bugs/browser/tests/person-bug-views.txt
+++ b/lib/lp/bugs/browser/tests/person-bug-views.txt
@@ -356,7 +356,7 @@ particular bug (see bug 1357):
356356
357 >>> commented_bugtasks_view = create_view(no_priv, '+commentedbugs')357 >>> commented_bugtasks_view = create_view(no_priv, '+commentedbugs')
358 >>> commented_bugs = list(commented_bugtasks_view.search().batch)358 >>> commented_bugs = list(commented_bugtasks_view.search().batch)
359 >>> [bugtask.bug.id for bugtask in sorted(commented_bugs)]359 >>> [bugtask.bug.id for bugtask in commented_bugs]
360 [1, 1, 1]360 [1, 1, 1]
361361
362362
diff --git a/lib/lp/bugs/doc/externalbugtracker-sourceforge.txt b/lib/lp/bugs/doc/externalbugtracker-sourceforge.txt
index a01dbdc..bcaf316 100644
--- a/lib/lp/bugs/doc/externalbugtracker-sourceforge.txt
+++ b/lib/lp/bugs/doc/externalbugtracker-sourceforge.txt
@@ -215,9 +215,11 @@ been checked.
215215
216 >>> transaction.commit()216 >>> transaction.commit()
217217
218 >>> from operator import attrgetter
218 >>> with sourceforge.responses(trace_calls=True):219 >>> with sourceforge.responses(trace_calls=True):
219 ... bug_watch_updater.updateBugWatches(220 ... bug_watch_updater.updateBugWatches(
220 ... sourceforge, sorted(example_bug_tracker.watches))221 ... sourceforge,
222 ... sorted(example_bug_tracker.watches, key=attrgetter('id')))
221 INFO Updating 1 watches for 1 bugs on http://bugs.some.where223 INFO Updating 1 watches for 1 bugs on http://bugs.some.where
222 GET http://bugs.some.where/support/tracker.php?aid=1722251224 GET http://bugs.some.where/support/tracker.php?aid=1722251
223225
diff --git a/lib/lp/bugs/model/tests/test_bugtask.py b/lib/lp/bugs/model/tests/test_bugtask.py
index 527120e..21a91e4 100644
--- a/lib/lp/bugs/model/tests/test_bugtask.py
+++ b/lib/lp/bugs/model/tests/test_bugtask.py
@@ -11,7 +11,11 @@ import unittest
1111
12from lazr.lifecycle.snapshot import Snapshot12from lazr.lifecycle.snapshot import Snapshot
13from storm.store import Store13from storm.store import Store
14from testtools.matchers import Equals14from testtools.matchers import (
15 Equals,
16 MatchesSetwise,
17 MatchesStructure,
18 )
15from testtools.testcase import ExpectedException19from testtools.testcase import ExpectedException
16import transaction20import transaction
17from zope.component import getUtility21from zope.component import getUtility
@@ -199,12 +203,11 @@ class TestBugTaskCreation(TestCaseWithFactory):
199 bug_many, mark,203 bug_many, mark,
200 [evolution, a_distro, warty],204 [evolution, a_distro, warty],
201 status=BugTaskStatus.FIXRELEASED)205 status=BugTaskStatus.FIXRELEASED)
202 tasks = [(t.product, t.distribution, t.distroseries) for t in taskset]
203 tasks.sort()
204206
205 self.assertEqual(tasks[0][2], warty)207 self.assertThat(taskset, MatchesSetwise(
206 self.assertEqual(tasks[1][1], a_distro)208 MatchesStructure.byEquality(product=evolution),
207 self.assertEqual(tasks[2][0], evolution)209 MatchesStructure.byEquality(distribution=a_distro),
210 MatchesStructure.byEquality(distroseries=warty)))
208211
209 def test_accesspolicyartifacts_updated(self):212 def test_accesspolicyartifacts_updated(self):
210 # createManyTasks updates the AccessPolicyArtifacts related213 # createManyTasks updates the AccessPolicyArtifacts related
diff --git a/lib/lp/bugs/stories/webservice/xx-bug.txt b/lib/lp/bugs/stories/webservice/xx-bug.txt
index 04480a5..6121737 100644
--- a/lib/lp/bugs/stories/webservice/xx-bug.txt
+++ b/lib/lp/bugs/stories/webservice/xx-bug.txt
@@ -312,9 +312,11 @@ Bug tasks
312Each bug may be associated with one or more bug tasks. Much of the312Each bug may be associated with one or more bug tasks. Much of the
313data in a bug task is derived from the bug.313data in a bug task is derived from the bug.
314314
315 >>> from operator import itemgetter
315 >>> bug_one_bugtasks_url = bug_one['bug_tasks_collection_link']316 >>> bug_one_bugtasks_url = bug_one['bug_tasks_collection_link']
316 >>> bug_one_bugtasks = sorted(webservice.get(317 >>> bug_one_bugtasks = sorted(webservice.get(
317 ... bug_one_bugtasks_url).jsonBody()['entries'])318 ... bug_one_bugtasks_url).jsonBody()['entries'],
319 ... key=itemgetter('self_link'))
318 >>> len(bug_one_bugtasks)320 >>> len(bug_one_bugtasks)
319 3321 3
320322
@@ -825,7 +827,6 @@ Bug subscriptions
825827
826We can get the collection of subscriptions to a bug.828We can get the collection of subscriptions to a bug.
827829
828 >>> from operator import itemgetter
829 >>> bug_one_subscriptions_url = bug_one['subscriptions_collection_link']830 >>> bug_one_subscriptions_url = bug_one['subscriptions_collection_link']
830 >>> subscriptions = webservice.get(bug_one_subscriptions_url).jsonBody()831 >>> subscriptions = webservice.get(bug_one_subscriptions_url).jsonBody()
831 >>> subscription_entries = sorted(832 >>> subscription_entries = sorted(
@@ -1056,7 +1057,8 @@ case aspects of the bugtask (like status) are slaved to the remote bug
1056report described by the bugwatch.1057report described by the bugwatch.
10571058
1058 >>> bug_one_bug_watches = sorted(webservice.get(1059 >>> bug_one_bug_watches = sorted(webservice.get(
1059 ... bug_one['bug_watches_collection_link']).jsonBody()['entries'])1060 ... bug_one['bug_watches_collection_link']).jsonBody()['entries'],
1061 ... key=itemgetter('self_link'))
1060 >>> len(bug_one_bug_watches)1062 >>> len(bug_one_bug_watches)
1061 41063 4
10621064
diff --git a/lib/lp/bugs/subscribers/bug.py b/lib/lp/bugs/subscribers/bug.py
index 11851eb..d88d27b 100644
--- a/lib/lp/bugs/subscribers/bug.py
+++ b/lib/lp/bugs/subscribers/bug.py
@@ -15,6 +15,7 @@ __all__ = [
1515
1616
17import datetime17import datetime
18from operator import attrgetter
1819
19from zope.security.proxy import removeSecurityProxy20from zope.security.proxy import removeSecurityProxy
2021
@@ -227,7 +228,7 @@ def send_bug_details_to_new_bug_subscribers(
227 recipients = bug.getBugNotificationRecipients()228 recipients = bug.getBugNotificationRecipients()
228229
229 bug_notification_builder = BugNotificationBuilder(bug, event_creator)230 bug_notification_builder = BugNotificationBuilder(bug, event_creator)
230 for to_person in sorted(to_persons):231 for to_person in sorted(to_persons, key=attrgetter('id')):
231 reason, rationale = recipients.getReason(232 reason, rationale = recipients.getReason(
232 str(removeSecurityProxy(to_person).preferredemail.email))233 str(removeSecurityProxy(to_person).preferredemail.email))
233 subject, contents = generate_bug_add_email(234 subject, contents = generate_bug_add_email(
diff --git a/lib/lp/code/doc/revision.txt b/lib/lp/code/doc/revision.txt
index 46f0219..e514d69 100644
--- a/lib/lp/code/doc/revision.txt
+++ b/lib/lp/code/doc/revision.txt
@@ -112,7 +112,9 @@ sequence attribute is None.
112 >>> ancestry = IStore(BranchRevision).find(112 >>> ancestry = IStore(BranchRevision).find(
113 ... BranchRevision, BranchRevision.branch == branch)113 ... BranchRevision, BranchRevision.branch == branch)
114 >>> for branch_revision in sorted(ancestry,114 >>> for branch_revision in sorted(ancestry,
115 ... key=lambda r:(r.sequence, r.revision.id), reverse=True):115 ... key=lambda r: (
116 ... 0 if r.sequence is None else 1, r.sequence, r.revision.id),
117 ... reverse=True):
116 ... print(branch_revision.sequence, branch_revision.revision.id)118 ... print(branch_revision.sequence, branch_revision.revision.id)
117 6 9119 6 9
118 5 8120 5 8
diff --git a/lib/lp/code/model/branchmergeproposal.py b/lib/lp/code/model/branchmergeproposal.py
index 061fb1e..55a9197 100644
--- a/lib/lp/code/model/branchmergeproposal.py
+++ b/lib/lp/code/model/branchmergeproposal.py
@@ -1290,9 +1290,10 @@ class BranchMergeProposal(SQLBase, BugLinkTargetMixin):
12901290
1291 def getRevisionsSinceReviewStart(self):1291 def getRevisionsSinceReviewStart(self):
1292 """Get the grouped revisions since the review started."""1292 """Get the grouped revisions since the review started."""
1293 all_comments = list(self.all_comments)
1293 entries = [1294 entries = [
1294 ((comment.date_created, -1), comment) for comment1295 ((comment.date_created, i - len(all_comments)), comment)
1295 in self.all_comments]1296 for i, comment in enumerate(all_comments)]
1296 entries.extend(self._getNewerRevisions())1297 entries.extend(self._getNewerRevisions())
1297 entries.sort()1298 entries.sort()
1298 current_group = []1299 current_group = []
diff --git a/lib/lp/registry/stories/webservice/xx-person.txt b/lib/lp/registry/stories/webservice/xx-person.txt
index 73450d0..619784c 100644
--- a/lib/lp/registry/stories/webservice/xx-person.txt
+++ b/lib/lp/registry/stories/webservice/xx-person.txt
@@ -303,8 +303,10 @@ Team memberships are first-class objects with their own URLs.
303303
304Team memberships also have data fields.304Team memberships also have data fields.
305305
306 >>> salgado_landscape = sorted(webservice.get(306 >>> salgado_landscape = [
307 ... salgado_memberships).jsonBody()['entries'])[1]307 ... entry for entry in webservice.get(
308 ... salgado_memberships).jsonBody()['entries']
309 ... if entry['team_link'].endswith('~landscape-developers')][0]
308 >>> for key in sorted(salgado_landscape):310 >>> for key in sorted(salgado_landscape):
309 ... print(key)311 ... print(key)
310 date_expires312 date_expires
diff --git a/lib/lp/registry/tests/test_distroseriesdifferencecomment.py b/lib/lp/registry/tests/test_distroseriesdifferencecomment.py
index f95aac7..6276647 100644
--- a/lib/lp/registry/tests/test_distroseriesdifferencecomment.py
+++ b/lib/lp/registry/tests/test_distroseriesdifferencecomment.py
@@ -33,7 +33,7 @@ def get_comment_source():
3333
34def randomize_list(original_list):34def randomize_list(original_list):
35 """Sort a list (or other iterable) in random order. Return list."""35 """Sort a list (or other iterable) in random order. Return list."""
36 return sorted(original_list, key=lambda _: random)36 return sorted(original_list, key=lambda _: random())
3737
3838
39class DistroSeriesDifferenceCommentTestCase(TestCaseWithFactory):39class DistroSeriesDifferenceCommentTestCase(TestCaseWithFactory):
diff --git a/lib/lp/services/doc/orderingcheck.txt b/lib/lp/services/doc/orderingcheck.txt
index 209b6e0..e2367aa 100644
--- a/lib/lp/services/doc/orderingcheck.txt
+++ b/lib/lp/services/doc/orderingcheck.txt
@@ -10,8 +10,12 @@ seem worth the trouble.
10 >>> from lp.services.orderingcheck import OrderingCheck10 >>> from lp.services.orderingcheck import OrderingCheck
1111
12 >>> def sort_key(item):12 >>> def sort_key(item):
13 ... """Simple sorting key for integers: the integer itself."""13 ... """Simple sorting key for integers.
14 ... return item14 ...
15 ... This could just be the integer itself, but in order to support
16 ... None on Python 3 we need an additional term.
17 ... """
18 ... return (0 if item is None else 1, item)
1519
16The OrderingCheck makes it clean and easy. You create an OrderingCheck20The OrderingCheck makes it clean and easy. You create an OrderingCheck
17with the same arguments that go into Python's standard sorting21with the same arguments that go into Python's standard sorting
@@ -51,7 +55,7 @@ error.
51Edge cases55Edge cases
52----------56----------
5357
54It is safe to use the None value. Python places it below any other58It is safe to use the None value. sort_key places it below any other
55integer.59integer.
5660
57 >>> checker = OrderingCheck(key=sort_key)61 >>> checker = OrderingCheck(key=sort_key)
diff --git a/lib/lp/services/webservice/stories/multiversion.txt b/lib/lp/services/webservice/stories/multiversion.txt
index 22f9722..683325f 100644
--- a/lib/lp/services/webservice/stories/multiversion.txt
+++ b/lib/lp/services/webservice/stories/multiversion.txt
@@ -46,11 +46,14 @@ In the 'beta' version of the web service, mutator methods like
46IBugTask.transitionToStatus are published as named operations. In46IBugTask.transitionToStatus are published as named operations. In
47subsequent versions, those named operations are not published.47subsequent versions, those named operations are not published.
4848
49 >>> from operator import itemgetter
50
49 >>> def get_bugtask_path(version):51 >>> def get_bugtask_path(version):
50 ... bug_one = webservice.get("/bugs/1", api_version=version).jsonBody()52 ... bug_one = webservice.get("/bugs/1", api_version=version).jsonBody()
51 ... bug_one_bugtasks_url = bug_one['bug_tasks_collection_link']53 ... bug_one_bugtasks_url = bug_one['bug_tasks_collection_link']
52 ... bug_one_bugtasks = sorted(webservice.get(54 ... bug_one_bugtasks = sorted(webservice.get(
53 ... bug_one_bugtasks_url).jsonBody()['entries'])55 ... bug_one_bugtasks_url).jsonBody()['entries'],
56 ... key=itemgetter('self_link'))
54 ... bugtask_path = bug_one_bugtasks[0]['self_link']57 ... bugtask_path = bug_one_bugtasks[0]['self_link']
55 ... return bugtask_path58 ... return bugtask_path
5659
diff --git a/lib/lp/soyuz/scripts/tests/test_ppa_apache_log_parser.py b/lib/lp/soyuz/scripts/tests/test_ppa_apache_log_parser.py
index 0b82990..187323c 100644
--- a/lib/lp/soyuz/scripts/tests/test_ppa_apache_log_parser.py
+++ b/lib/lp/soyuz/scripts/tests/test_ppa_apache_log_parser.py
@@ -152,4 +152,6 @@ class TestScriptRunning(TestCaseWithFactory):
152 sorted(152 sorted(
153 [(result.binary_package_release, result.archive, result.day,153 [(result.binary_package_release, result.archive, result.day,
154 result.country, result.count) for result in results],154 result.country, result.count) for result in results],
155 key=lambda r: (r[0].id, r[2], r[3].name if r[3] else None)))155 key=lambda r: (
156 r[0].id, r[2],
157 r[3] is not None, r[3].name if r[3] else None)))
diff --git a/lib/lp/soyuz/stories/webservice/xx-hasbuildrecords.txt b/lib/lp/soyuz/stories/webservice/xx-hasbuildrecords.txt
index 3246cca..4b3ca4e 100644
--- a/lib/lp/soyuz/stories/webservice/xx-hasbuildrecords.txt
+++ b/lib/lp/soyuz/stories/webservice/xx-hasbuildrecords.txt
@@ -42,8 +42,10 @@ Celso Providelo PPA builds can be browsed via the API.
4242
43An entry can be selected in the returned collection.43An entry can be selected in the returned collection.
4444
45 >>> from operator import itemgetter
45 >>> from lazr.restful.testing.webservice import pprint_entry46 >>> from lazr.restful.testing.webservice import pprint_entry
46 >>> pprint_entry(sorted(ppa_builds['entries'])[0])47 >>> pprint_entry(
48 ... sorted(ppa_builds['entries'], key=itemgetter('title'))[0])
47 arch_tag: 'hppa'49 arch_tag: 'hppa'
48 ...50 ...
49 title: 'hppa build of mozilla-firefox 0.9 in ubuntu warty RELEASE'51 title: 'hppa build of mozilla-firefox 0.9 in ubuntu warty RELEASE'
diff --git a/lib/lp/soyuz/stories/webservice/xx-source-package-publishing.txt b/lib/lp/soyuz/stories/webservice/xx-source-package-publishing.txt
index 761b7aa..92e2764 100644
--- a/lib/lp/soyuz/stories/webservice/xx-source-package-publishing.txt
+++ b/lib/lp/soyuz/stories/webservice/xx-source-package-publishing.txt
@@ -292,7 +292,7 @@ binaries have been copied and published in the same context archive.
292 >>> source_pub = pubs['entries'][0]292 >>> source_pub = pubs['entries'][0]
293 >>> builds = webservice.named_get(293 >>> builds = webservice.named_get(
294 ... source_pub['self_link'], 'getBuilds').jsonBody()294 ... source_pub['self_link'], 'getBuilds').jsonBody()
295 >>> for entry in sorted(builds['entries']):295 >>> for entry in builds['entries']:
296 ... print(entry['title'])296 ... print(entry['title'])
297 i386 build of pmount 0.1-1 in ubuntu warty RELEASE297 i386 build of pmount 0.1-1 in ubuntu warty RELEASE
298298
@@ -310,7 +310,7 @@ of that publication.
310 >>> source_pub = pubs['entries'][0]310 >>> source_pub = pubs['entries'][0]
311 >>> builds = webservice.named_get(311 >>> builds = webservice.named_get(
312 ... source_pub['self_link'], 'getPublishedBinaries').jsonBody()312 ... source_pub['self_link'], 'getPublishedBinaries').jsonBody()
313 >>> for entry in sorted(builds['entries']):313 >>> for entry in builds['entries']:
314 ... print(entry['display_name'])314 ... print(entry['display_name'])
315 pmount 0.1-1 in warty hppa315 pmount 0.1-1 in warty hppa
316 pmount 0.1-1 in warty i386316 pmount 0.1-1 in warty i386
diff --git a/lib/lp/translations/utilities/translationmerger.py b/lib/lp/translations/utilities/translationmerger.py
index 192c32b..7164395 100644
--- a/lib/lp/translations/utilities/translationmerger.py
+++ b/lib/lp/translations/utilities/translationmerger.py
@@ -282,8 +282,16 @@ class MessageSharingMerge(LaunchpadScript):
282 class_count = len(equivalence_classes)282 class_count = len(equivalence_classes)
283 log.info("Merging %d template equivalence classes." % class_count)283 log.info("Merging %d template equivalence classes." % class_count)
284284
285 def equivalence_class_sort_key(name):
286 template_name, package_name = name
287 if package_name is None:
288 return template_name, 0, None
289 else:
290 return template_name, 1, package_name
291
285 tm = TransactionManager(self.txn, self.options.dry_run)292 tm = TransactionManager(self.txn, self.options.dry_run)
286 for number, name in enumerate(sorted(equivalence_classes)):293 for number, name in enumerate(sorted(
294 equivalence_classes, key=equivalence_class_sort_key)):
287 templates = equivalence_classes[name]295 templates = equivalence_classes[name]
288 log.info(296 log.info(
289 "Merging equivalence class '%s': %d template(s) (%d / %d)" % (297 "Merging equivalence class '%s': %d template(s) (%d / %d)" % (

Subscribers

People subscribed via source and target branches

to status/vote changes: