Merge lp:~stevenk/launchpad/sane-buildable-distroseries into lp:launchpad

Proposed by Steve Kowalik on 2012-09-05
Status: Merged
Approved by: William Grant on 2012-09-05
Approved revision: no longer in the source branch.
Merged at revision: 15908
Proposed branch: lp:~stevenk/launchpad/sane-buildable-distroseries
Merge into: lp:launchpad
Diff against target: 275 lines (+74/-44)
5 files modified
lib/lp/code/browser/sourcepackagerecipe.py (+2/-2)
lib/lp/code/model/sourcepackagerecipe.py (+5/-23)
lib/lp/code/model/tests/test_sourcepackagerecipe.py (+21/-1)
lib/lp/code/vocabularies/configure.zcml (+6/-2)
lib/lp/code/vocabularies/sourcepackagerecipe.py (+40/-16)
To merge this branch: bzr merge lp:~stevenk/launchpad/sane-buildable-distroseries
Reviewer Review Type Date Requested Status
William Grant code 2012-09-05 Approve on 2012-09-05
Review via email: mp+122791@code.launchpad.net

Commit Message

Destroy get_buildable_distroseries_set and buildable_distroseries_vocabulary and birth BuildableDistroSeries from their ashes.

Description of the Change

Destroy get_buildable_distroseries_set and buildable_distroseries_vocabulary
and birth BuildableDistroSeries from their ashes.

buildable_distroseries_vocabulary had the problem that as a SimpleVocabulary it would execute six queries per recipe when attempting to fetch recipes over the API due to ISourcePackageRecipe.distroseries having a vocab of BuildableDistrosSeries, and the LAZR code marshalling the vocab. Now we use an IHugeVocabulary which mitigates that problem.

I have also added a webservice test for this case -- fetching a single recipe from the API results in 28 queries rather 34.

To post a comment you must log in.
William Grant (wgrant) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/code/browser/sourcepackagerecipe.py'
2--- lib/lp/code/browser/sourcepackagerecipe.py 2012-08-15 01:04:53 +0000
3+++ lib/lp/code/browser/sourcepackagerecipe.py 2012-09-05 05:18:20 +0000
4@@ -99,7 +99,7 @@
5 MINIMAL_RECIPE_TEXT,
6 )
7 from lp.code.model.branchtarget import PersonBranchTarget
8-from lp.code.model.sourcepackagerecipe import get_buildable_distroseries_set
9+from lp.code.vocabularies.sourcepackagerecipe import BuildableDistroSeries
10 from lp.registry.interfaces.series import SeriesStatus
11 from lp.services.fields import PersonChoice
12 from lp.services.propertycache import cachedproperty
13@@ -787,7 +787,7 @@
14
15 @property
16 def initial_values(self):
17- distroseries = get_buildable_distroseries_set(self.user)
18+ distroseries = BuildableDistroSeries.findSeries(self.user)
19 series = [series for series in distroseries if series.status in (
20 SeriesStatus.CURRENT, SeriesStatus.DEVELOPMENT)]
21 return {
22
23=== modified file 'lib/lp/code/model/sourcepackagerecipe.py'
24--- lib/lp/code/model/sourcepackagerecipe.py 2012-08-15 01:04:53 +0000
25+++ lib/lp/code/model/sourcepackagerecipe.py 2012-09-05 05:18:20 +0000
26@@ -1,13 +1,10 @@
27 # Copyright 2009-2012 Canonical Ltd. This software is licensed under the
28 # GNU Affero General Public License version 3 (see the file LICENSE).
29
30-# pylint: disable-msg=F0401,W1001
31-
32 """Implementation of the `SourcePackageRecipe` content type."""
33
34 __metaclass__ = type
35 __all__ = [
36- 'get_buildable_distroseries_set',
37 'SourcePackageRecipe',
38 ]
39
40@@ -39,7 +36,6 @@
41 implements,
42 )
43
44-from lp.app.interfaces.launchpad import ILaunchpadCelebrities
45 from lp.buildmaster.enums import BuildStatus
46 from lp.buildmaster.model.buildfarmjob import BuildFarmJob
47 from lp.buildmaster.model.packagebuild import PackageBuild
48@@ -59,7 +55,7 @@
49 from lp.code.model.branch import Branch
50 from lp.code.model.sourcepackagerecipebuild import SourcePackageRecipeBuild
51 from lp.code.model.sourcepackagerecipedata import SourcePackageRecipeData
52-from lp.registry.interfaces.distroseries import IDistroSeriesSet
53+from lp.code.vocabularies.sourcepackagerecipe import BuildableDistroSeries
54 from lp.registry.interfaces.pocket import PackagePublishingPocket
55 from lp.registry.model.distroseries import DistroSeries
56 from lp.services.database.bulk import (
57@@ -80,24 +76,9 @@
58 cachedproperty,
59 get_property_cache,
60 )
61-from lp.soyuz.interfaces.archive import IArchiveSet
62 from lp.soyuz.model.archive import Archive
63
64
65-def get_buildable_distroseries_set(user):
66- ppas = getUtility(IArchiveSet).getPPAsForUser(user)
67- supported_distros = set([ppa.distribution for ppa in ppas])
68- # Now add in Ubuntu.
69- supported_distros.add(getUtility(ILaunchpadCelebrities).ubuntu)
70- distros = getUtility(IDistroSeriesSet).search()
71-
72- buildables = []
73- for distro in distros:
74- if distro.active and distro.distribution in supported_distros:
75- buildables.append(distro)
76- return buildables
77-
78-
79 def recipe_modified(recipe, event):
80 """Update the date_last_modified property when a recipe is modified.
81
82@@ -282,7 +263,8 @@
83 requester, self, distroseries).count() >= 5
84
85 def containsUnbuildableSeries(self, archive):
86- buildable_distros = set(get_buildable_distroseries_set(archive.owner))
87+ buildable_distros = set(
88+ BuildableDistroSeries.findSeries(archive.owner))
89 return len(set(self.distroseries).difference(buildable_distros)) >= 1
90
91 def requestBuild(self, archive, requester, distroseries,
92@@ -292,7 +274,7 @@
93 if not archive.is_ppa:
94 raise NonPPABuildRequest
95
96- buildable_distros = get_buildable_distroseries_set(archive.owner)
97+ buildable_distros = BuildableDistroSeries.findSeries(archive.owner)
98 if distroseries not in buildable_distros:
99 raise BuildNotAllowedForDistro(self, distroseries)
100
101@@ -325,7 +307,7 @@
102 """See `ISourcePackageRecipe`."""
103 builds = []
104 self.is_stale = False
105- buildable_distros = set(get_buildable_distroseries_set(
106+ buildable_distros = set(BuildableDistroSeries.findSeries(
107 self.daily_build_archive.owner))
108 build_for = set(self.distroseries).intersection(buildable_distros)
109 for distroseries in build_for:
110
111=== modified file 'lib/lp/code/model/tests/test_sourcepackagerecipe.py'
112--- lib/lp/code/model/tests/test_sourcepackagerecipe.py 2012-08-15 01:04:53 +0000
113+++ lib/lp/code/model/tests/test_sourcepackagerecipe.py 2012-09-05 05:18:20 +0000
114@@ -15,6 +15,7 @@
115 from lazr.lifecycle.event import ObjectModifiedEvent
116 from pytz import UTC
117 from storm.locals import Store
118+from testtools.matchers import Equals
119 import transaction
120 from zope.component import getUtility
121 from zope.event import notify
122@@ -61,6 +62,7 @@
123 )
124 from lp.services.propertycache import clear_property_cache
125 from lp.services.webapp.authorization import check_permission
126+from lp.services.webapp.publisher import canonical_url
127 from lp.services.webapp.testing import verifyObject
128 from lp.soyuz.enums import ArchivePurpose
129 from lp.soyuz.interfaces.archive import (
130@@ -74,6 +76,7 @@
131 login,
132 login_person,
133 person_logged_in,
134+ StormStatementRecorder,
135 TestCaseWithFactory,
136 ws_object,
137 )
138@@ -81,7 +84,11 @@
139 AppServerLayer,
140 DatabaseFunctionalLayer,
141 )
142-from lp.testing.matchers import DoesNotSnapshot
143+from lp.testing.matchers import (
144+ DoesNotSnapshot,
145+ HasQueryCount,
146+ )
147+from lp.testing.pages import webservice_for_person
148
149
150 class TestSourcePackageRecipe(TestCaseWithFactory):
151@@ -1196,3 +1203,16 @@
152 "archive": '%s/%s' %
153 (archive.owner.name, archive.name)})
154 self.assertEqual(build_info, list(recipe.getPendingBuildInfo()))
155+
156+ def test_query_count_of_webservice_recipe(self):
157+ owner = self.factory.makePerson()
158+ recipe = self.factory.makeSourcePackageRecipe(owner=owner)
159+ webservice = webservice_for_person(owner)
160+ with person_logged_in(owner):
161+ url = canonical_url(recipe, force_local_path=True)
162+ store = Store.of(recipe)
163+ store.flush()
164+ store.invalidate()
165+ with StormStatementRecorder() as recorder:
166+ webservice.get(url)
167+ self.assertThat(recorder, HasQueryCount(Equals(28)))
168
169=== modified file 'lib/lp/code/vocabularies/configure.zcml'
170--- lib/lp/code/vocabularies/configure.zcml 2011-12-24 17:49:30 +0000
171+++ lib/lp/code/vocabularies/configure.zcml 2012-09-05 05:18:20 +0000
172@@ -1,4 +1,4 @@
173-<!-- Copyright 2010 Canonical Ltd. This software is licensed under the
174+<!-- Copyright 2010-2012 Canonical Ltd. This software is licensed under the
175 GNU Affero General Public License version 3 (see the file LICENSE).
176 -->
177
178@@ -6,11 +6,15 @@
179
180 <securedutility
181 name="BuildableDistroSeries"
182- component=".sourcepackagerecipe.buildable_distroseries_vocabulary"
183+ component=".sourcepackagerecipe.BuildableDistroSeries"
184 provides="zope.schema.interfaces.IVocabularyFactory">
185 <allow interface="zope.schema.interfaces.IVocabularyFactory"/>
186 </securedutility>
187
188+ <class class=".sourcepackagerecipe.BuildableDistroSeries">
189+ <allow interface="lp.services.webapp.vocabulary.IHugeVocabulary"/>
190+ </class>
191+
192 <securedutility
193 name="TargetPPAs"
194 component=".sourcepackagerecipe.target_ppas_vocabulary"
195
196=== modified file 'lib/lp/code/vocabularies/sourcepackagerecipe.py'
197--- lib/lp/code/vocabularies/sourcepackagerecipe.py 2012-01-01 02:58:52 +0000
198+++ lib/lp/code/vocabularies/sourcepackagerecipe.py 2012-09-05 05:18:20 +0000
199@@ -1,36 +1,60 @@
200-# Copyright 2010 Canonical Ltd. This software is licensed under the
201+# Copyright 2010-2012 Canonical Ltd. This software is licensed under the
202 # GNU Affero General Public License version 3 (see the file LICENSE).
203
204 """Source Package Recipe vocabularies used in the lp/code modules."""
205+
206 __metaclass__ = type
207 __all__ = [
208- 'buildable_distroseries_vocabulary',
209+ 'BuildableDistroSeries',
210 'target_ppas_vocabulary',
211 ]
212
213 from zope.component import getUtility
214-from zope.schema.vocabulary import (
215- SimpleTerm,
216- SimpleVocabulary,
217- )
218+from zope.interface import implements
219+from zope.schema.vocabulary import SimpleTerm
220
221-from lp.code.model.sourcepackagerecipe import get_buildable_distroseries_set
222+from lp.app.interfaces.launchpad import ILaunchpadCelebrities
223+from lp.registry.interfaces.distroseries import IDistroSeriesSet
224+from lp.registry.model.distroseries import DistroSeries
225 from lp.services.webapp.authorization import check_permission
226 from lp.services.webapp.interfaces import ILaunchBag
227 from lp.services.webapp.sorting import sorted_dotted_numbers
228+from lp.services.webapp.vocabulary import (
229+ IHugeVocabulary,
230+ SQLObjectVocabularyBase,
231+ )
232 from lp.soyuz.browser.archive import make_archive_vocabulary
233 from lp.soyuz.interfaces.archive import IArchiveSet
234
235
236-def buildable_distroseries_vocabulary(context):
237- """Return a vocabulary of buildable distroseries."""
238- distros = get_buildable_distroseries_set(getUtility(ILaunchBag).user)
239- terms = sorted_dotted_numbers(
240- [SimpleTerm(distro, distro.id, distro.displayname)
241- for distro in distros],
242- key=lambda term: term.value.version)
243- terms.reverse()
244- return SimpleVocabulary(terms)
245+class BuildableDistroSeries(SQLObjectVocabularyBase):
246+ implements(IHugeVocabulary)
247+
248+ _table = DistroSeries
249+
250+ def toTerm(self, obj):
251+ """See `IVocabulary`."""
252+ return SimpleTerm(obj, obj.id, obj.displayname)
253+
254+ @classmethod
255+ def findSeries(self, user):
256+ ppas = getUtility(IArchiveSet).getPPAsForUser(user)
257+ supported_distros = set([ppa.distribution for ppa in ppas])
258+ # Now add in Ubuntu.
259+ supported_distros.add(getUtility(ILaunchpadCelebrities).ubuntu)
260+ all_series = getUtility(IDistroSeriesSet).search()
261+
262+ return [
263+ series for series in all_series
264+ if series.active and series.distribution in supported_distros]
265+
266+ def __iter__(self):
267+ distroseries = self.findSeries(getUtility(ILaunchBag).user)
268+ series = sorted_dotted_numbers(
269+ [self.toTerm(s) for s in distroseries],
270+ key=lambda term: term.value.version)
271+ series.reverse()
272+ return iter(series)
273
274
275 def target_ppas_vocabulary(context):