Merge ~cjwatson/launchpad:py3-frozenset-subclassing into launchpad:master

Proposed by Colin Watson
Status: Merged
Approved by: Colin Watson
Approved revision: 10d48f70516474d84b89bee5d611e14845268fa2
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~cjwatson/launchpad:py3-frozenset-subclassing
Merge into: launchpad:master
Diff against target: 138 lines (+66/-8)
2 files modified
lib/lp/bugs/model/bug.py (+62/-5)
lib/lp/bugs/model/tests/test_bugsubscriptioninfo.py (+4/-3)
Reviewer Review Type Date Requested Status
Ioana Lasc (community) Approve
Review via email: mp+385711@code.launchpad.net

Commit message

Fix frozenset subclassing for Python 3

Description of the change

As of Python 3.0, following the fix for https://bugs.python.org/issue1721812, operations on subclasses of frozenset that create new sets do so using the base type, not the subclass type. Refactor the custom sets in lp.bugs.model.bug to cope with this.

To post a comment you must log in.
Revision history for this message
Ioana Lasc (ilasc) :
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/model/bug.py b/lib/lp/bugs/model/bug.py
index 98169c6..0a9062a 100644
--- a/lib/lp/bugs/model/bug.py
+++ b/lib/lp/bugs/model/bug.py
@@ -29,6 +29,10 @@ import re
29from lazr.lifecycle.event import ObjectCreatedEvent29from lazr.lifecycle.event import ObjectCreatedEvent
30from lazr.lifecycle.snapshot import Snapshot30from lazr.lifecycle.snapshot import Snapshot
31import pytz31import pytz
32from six.moves.collections_abc import (
33 Iterable,
34 Set,
35 )
32from sqlobject import (36from sqlobject import (
33 BoolCol,37 BoolCol,
34 ForeignKey,38 ForeignKey,
@@ -2259,8 +2263,61 @@ def load_people(*where):
2259 need_preferred_email=True)2263 need_preferred_email=True)
22602264
22612265
2262class BugSubscriberSet(frozenset):2266class FrozenSetBasedSet(Set):
2263 """A set of bug subscribers2267 """A subclassable immutable set.
2268
2269 On Python 3, we can't simply subclass `frozenset`: set operations such
2270 as union will create a new set, but it will be a plain `frozenset` and
2271 will lack the appropriate custom properties. However, the `Set` ABC
2272 (which is in fact an immutable set; `MutableSet` is a separate ABC)
2273 knows to create new sets using the same class. Take advantage of this
2274 with a trivial implementation of `Set` that backs straight onto a
2275 `frozenset`.
2276
2277 The ABC doesn't implement the non-operator versions of set methods such
2278 as `union`, so do that here, at least for those methods we actually use.
2279 """
2280
2281 def __init__(self, iterable=None):
2282 self._frozenset = (
2283 frozenset() if iterable is None else frozenset(iterable))
2284
2285 def __iter__(self):
2286 return iter(self._frozenset)
2287
2288 def __contains__(self, value):
2289 return value in self._frozenset
2290
2291 def __len__(self):
2292 return len(self._frozenset)
2293
2294 def issubset(self, other):
2295 return self <= self._from_iterable(other)
2296
2297 def issuperset(self, other):
2298 return self >= self._from_iterable(other)
2299
2300 def union(self, *others):
2301 for other in others:
2302 if not isinstance(other, Iterable):
2303 raise NotImplementedError
2304 return self._from_iterable(value for value in chain(self, *others))
2305
2306 def intersection(self, *others):
2307 for other in others:
2308 if not isinstance(other, Iterable):
2309 raise NotImplementedError
2310 return self._from_iterable(
2311 value for value in chain(*others) if value in self)
2312
2313 def difference(self, *others):
2314 other = self._from_iterable([]).union(*others)
2315 return self._from_iterable(
2316 value for value in self if value not in other)
2317
2318
2319class BugSubscriberSet(FrozenSetBasedSet):
2320 """An immutable set of bug subscribers.
22642321
2265 Every member should provide `IPerson`.2322 Every member should provide `IPerson`.
2266 """2323 """
@@ -2274,8 +2331,8 @@ class BugSubscriberSet(frozenset):
2274 return tuple(sorted(self, key=person_sort_key))2331 return tuple(sorted(self, key=person_sort_key))
22752332
22762333
2277class BugSubscriptionSet(frozenset):2334class BugSubscriptionSet(FrozenSetBasedSet):
2278 """A set of bug subscriptions."""2335 """An immutable set of bug subscriptions."""
22792336
2280 @cachedproperty2337 @cachedproperty
2281 def sorted(self):2338 def sorted(self):
@@ -2299,7 +2356,7 @@ class BugSubscriptionSet(frozenset):
2299 return BugSubscriberSet(load_people(condition))2356 return BugSubscriberSet(load_people(condition))
23002357
23012358
2302class StructuralSubscriptionSet(frozenset):2359class StructuralSubscriptionSet(FrozenSetBasedSet):
2303 """A set of structural subscriptions."""2360 """A set of structural subscriptions."""
23042361
2305 @cachedproperty2362 @cachedproperty
diff --git a/lib/lp/bugs/model/tests/test_bugsubscriptioninfo.py b/lib/lp/bugs/model/tests/test_bugsubscriptioninfo.py
index f261fcb..c9565aa 100644
--- a/lib/lp/bugs/model/tests/test_bugsubscriptioninfo.py
+++ b/lib/lp/bugs/model/tests/test_bugsubscriptioninfo.py
@@ -8,6 +8,7 @@ __metaclass__ = type
8from contextlib import contextmanager8from contextlib import contextmanager
99
10import six10import six
11from six.moves.collections_abc import Set
11from storm.store import Store12from storm.store import Store
12from testtools.matchers import Equals13from testtools.matchers import Equals
13from zope.component import queryAdapter14from zope.component import queryAdapter
@@ -71,7 +72,7 @@ class TestSubscriptionRelatedSets(TestCaseWithFactory):
7172
72 def test_BugSubscriberSet(self):73 def test_BugSubscriberSet(self):
73 subscriber_set = BugSubscriberSet(self.subscribers_set)74 subscriber_set = BugSubscriberSet(self.subscribers_set)
74 self.assertIsInstance(subscriber_set, frozenset)75 self.assertIsInstance(subscriber_set, Set)
75 self.assertEqual(self.subscribers_set, subscriber_set)76 self.assertEqual(self.subscribers_set, subscriber_set)
76 self.assertEqual(self.subscribers_sorted, subscriber_set.sorted)77 self.assertEqual(self.subscribers_sorted, subscriber_set.sorted)
7778
@@ -82,7 +83,7 @@ class TestSubscriptionRelatedSets(TestCaseWithFactory):
82 bug.subscribe(subscriber, subscriber)83 bug.subscribe(subscriber, subscriber)
83 for subscriber in self.subscribers_set)84 for subscriber in self.subscribers_set)
84 subscription_set = BugSubscriptionSet(subscriptions)85 subscription_set = BugSubscriptionSet(subscriptions)
85 self.assertIsInstance(subscription_set, frozenset)86 self.assertIsInstance(subscription_set, Set)
86 self.assertEqual(subscriptions, subscription_set)87 self.assertEqual(subscriptions, subscription_set)
87 # BugSubscriptionSet.sorted returns a tuple of subscriptions ordered88 # BugSubscriptionSet.sorted returns a tuple of subscriptions ordered
88 # by subscribers.89 # by subscribers.
@@ -102,7 +103,7 @@ class TestSubscriptionRelatedSets(TestCaseWithFactory):
102 product.addSubscription(subscriber, subscriber)103 product.addSubscription(subscriber, subscriber)
103 for subscriber in self.subscribers_set)104 for subscriber in self.subscribers_set)
104 subscription_set = StructuralSubscriptionSet(subscriptions)105 subscription_set = StructuralSubscriptionSet(subscriptions)
105 self.assertIsInstance(subscription_set, frozenset)106 self.assertIsInstance(subscription_set, Set)
106 self.assertEqual(subscriptions, subscription_set)107 self.assertEqual(subscriptions, subscription_set)
107 # StructuralSubscriptionSet.sorted returns a tuple of subscriptions108 # StructuralSubscriptionSet.sorted returns a tuple of subscriptions
108 # ordered by subscribers.109 # ordered by subscribers.

Subscribers

People subscribed via source and target branches

to status/vote changes: