Merge lp:~stevenk/launchpad/moar-preload-distroseries-queue into lp:launchpad

Proposed by Steve Kowalik
Status: Merged
Approved by: Steve Kowalik
Approved revision: no longer in the source branch.
Merged at revision: 16396
Proposed branch: lp:~stevenk/launchpad/moar-preload-distroseries-queue
Merge into: lp:launchpad
Diff against target: 568 lines (+139/-84)
11 files modified
lib/lp/security.py (+4/-6)
lib/lp/services/webapp/adapter.py (+0/-4)
lib/lp/soyuz/browser/queue.py (+19/-22)
lib/lp/soyuz/browser/tests/test_queue.py (+23/-2)
lib/lp/soyuz/configure.zcml (+2/-1)
lib/lp/soyuz/model/archive.py (+1/-1)
lib/lp/soyuz/model/binarypackagebuild.py (+1/-1)
lib/lp/soyuz/model/publishing.py (+12/-19)
lib/lp/soyuz/model/queue.py (+60/-21)
lib/lp/soyuz/model/sourcepackagerelease.py (+4/-7)
lib/lp/soyuz/tests/test_packageupload.py (+13/-0)
To merge this branch: bzr merge lp:~stevenk/launchpad/moar-preload-distroseries-queue
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+141581@code.launchpad.net

Commit message

Teach IPackageUploadSet.getAll() (and by extension the batching that drives DistroSeries:+queue) to do a lot more pre-loading.

Description of the change

Force the PackageUpload security adapter to use the cachedproperties on it, after making sure they are set carefully in add{Source,Build,Custom}.

Do lots of preloading in IPackageUploadSet.getAll() and in the preloading helper behind DistroSeries:+queue, like making ISourcePackageRelease.published_archives a cachedproperty, and populating it, and setting changes_file_id so we can preload all LFAs for changesfiles.

Write two query count tests, one for DistroSeries:+queue, and the other for IDistroSeries.getPackageUploads() (which is a thin wrapper around IPackageUploadSet.getAll())

To post a comment you must log in.
Revision history for this message
William Grant (wgrant) wrote :

143 + sprs = prefill_packageupload_caches(
144 + uploads, packageuploadsources, pubs, pucs)

Is this not already done by the underlying model method?

444 +def prefill_packageupload_caches(uploads, puses, pubs, pucs):

My eyes are burning.

Oh, god, they burn.

Can you add a bit of VWS, perhaps? There are several logical sections which could be separated.

468 + for spr in sprs:
469 + get_property_cache(spr).published_archives = []

The other three caches get away without this. Are they initialised elsewhere? Perhaps move them into this function as well.

471 + spr = get_property_cache(publication.sourcepackagerelease)

That's no SPR.

506 + @cachedproperty
507 + def published_archives(self):
508 + return list(self._published_archives())

There's no need for a separate _published_archives here, as nothing needs the actual ResultSet. Just inline it.

Additionally, you might want to invalidate this sometimes, though it's not used much so it's not hugely important. But IIRC SPPH creation is well encapsulated in PS.newSourcePublication,

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/security.py'
2--- lib/lp/security.py 2012-12-19 22:01:13 +0000
3+++ lib/lp/security.py 2013-01-03 00:18:23 +0000
4@@ -1,8 +1,6 @@
5 # Copyright 2009-2012 Canonical Ltd. This software is licensed under the
6 # GNU Affero General Public License version 3 (see the file LICENSE).
7
8-# pylint: disable-msg=F0401
9-
10 """Security policies for using content objects."""
11
12 __metaclass__ = type
13@@ -1836,10 +1834,10 @@
14 # We cannot use self.obj.sourcepackagerelease, as that causes
15 # interference with the property cache if we are called in the
16 # process of adding a source or a build.
17- if not self.obj._sources.is_empty():
18- spr = self.obj._sources[0].sourcepackagerelease
19- elif not self.obj._builds.is_empty():
20- spr = self.obj._builds[0].build.source_package_release
21+ if self.obj.sources:
22+ spr = self.obj.sources[0].sourcepackagerelease
23+ elif self.obj.builds:
24+ spr = self.obj.builds[0].build.source_package_release
25 else:
26 spr = None
27 if spr is not None:
28
29=== modified file 'lib/lp/services/webapp/adapter.py'
30--- lib/lp/services/webapp/adapter.py 2012-11-29 18:08:12 +0000
31+++ lib/lp/services/webapp/adapter.py 2013-01-03 00:18:23 +0000
32@@ -1,9 +1,6 @@
33 # Copyright 2009-2011 Canonical Ltd. This software is licensed under the
34 # GNU Affero General Public License version 3 (see the file LICENSE).
35
36-# We use global in this module.
37-# pylint: disable-msg=W0602
38-
39 __metaclass__ = type
40
41 from functools import partial
42@@ -442,7 +439,6 @@
43 for using connections from the main thread.
44 """
45 # Record the ID of the main thread.
46- # pylint: disable-msg=W0603
47 global _main_thread_id
48 _main_thread_id = thread.get_ident()
49
50
51=== modified file 'lib/lp/soyuz/browser/queue.py'
52--- lib/lp/soyuz/browser/queue.py 2012-12-12 03:35:48 +0000
53+++ lib/lp/soyuz/browser/queue.py 2013-01-03 00:18:23 +0000
54@@ -10,7 +10,7 @@
55 'QueueItemsView',
56 ]
57
58-import operator
59+from operator import attrgetter
60
61 from lazr.delegates import delegates
62 from zope.component import getUtility
63@@ -20,7 +20,7 @@
64 NotFoundError,
65 UnexpectedFormData,
66 )
67-from lp.registry.model.person import Person
68+from lp.registry.interfaces.person import IPersonSet
69 from lp.services.database.bulk import (
70 load_referencing,
71 load_related,
72@@ -55,8 +55,10 @@
73 )
74 from lp.soyuz.interfaces.section import ISectionSet
75 from lp.soyuz.model.archive import Archive
76+from lp.soyuz.model.component import Component
77 from lp.soyuz.model.packagecopyjob import PackageCopyJob
78 from lp.soyuz.model.queue import PackageUploadSource
79+from lp.soyuz.model.section import Section
80 from lp.soyuz.model.sourcepackagerelease import SourcePackageRelease
81
82
83@@ -124,8 +126,7 @@
84 build_ids = [binary_file.binarypackagerelease.build.id
85 for binary_file in binary_files]
86 upload_set = getUtility(IPackageUploadSet)
87- package_upload_builds = upload_set.getBuildByBuildIDs(
88- build_ids)
89+ package_upload_builds = upload_set.getBuildByBuildIDs(build_ids)
90 package_upload_builds_dict = {}
91 for package_upload_build in package_upload_builds:
92 package_upload_builds_dict[
93@@ -135,8 +136,8 @@
94 def binary_files_dict(self, package_upload_builds_dict, binary_files):
95 """Build a dictionary of lists of binary files keyed by upload ID.
96
97- To do this efficiently we need to get all the PacakgeUploadBuild
98- records at once, otherwise the Ibuild.package_upload property
99+ To do this efficiently we need to get all the PackageUploadBuild
100+ records at once, otherwise the IBuild.package_upload property
101 causes one query per iteration of the loop.
102 """
103 build_upload_files = {}
104@@ -208,7 +209,9 @@
105 PackageCopyJob, uploads, ['package_copy_job_id'])
106 load_related(Archive, package_copy_jobs, ['source_archive_id'])
107 jobs = load_related(Job, package_copy_jobs, ['job_id'])
108- load_related(Person, jobs, ['requester_id'])
109+ person_ids = map(attrgetter('requester_id'), jobs)
110+ list(getUtility(IPersonSet).getPrecachedPersonsFromIDs(
111+ person_ids, need_validity=True))
112
113 def decoratedQueueBatch(self):
114 """Return the current batch, converted to decorated objects.
115@@ -224,20 +227,21 @@
116
117 upload_ids = [upload.id for upload in uploads]
118 binary_file_set = getUtility(IBinaryPackageFileSet)
119- binary_files = binary_file_set.getByPackageUploadIDs(upload_ids)
120+ binary_files = list(binary_file_set.getByPackageUploadIDs(upload_ids))
121 binary_file_set.loadLibraryFiles(binary_files)
122 packageuploadsources = load_referencing(
123 PackageUploadSource, uploads, ['packageuploadID'])
124 source_file_set = getUtility(ISourcePackageReleaseFileSet)
125- source_files = source_file_set.getByPackageUploadIDs(upload_ids)
126-
127+ source_files = list(source_file_set.getByPackageUploadIDs(upload_ids))
128 source_sprs = load_related(
129 SourcePackageRelease, packageuploadsources,
130 ['sourcepackagereleaseID'])
131
132+ load_related(Section, source_sprs, ['sectionID'])
133+ load_related(Component, source_sprs, ['componentID'])
134+
135 # Get a dictionary of lists of binary files keyed by upload ID.
136- package_upload_builds_dict = self.builds_dict(
137- upload_ids, binary_files)
138+ package_upload_builds_dict = self.builds_dict(upload_ids, binary_files)
139
140 build_upload_files, binary_package_names = self.binary_files_dict(
141 package_upload_builds_dict, binary_files)
142@@ -461,7 +465,7 @@
143 sorted by their name.
144 """
145 return sorted(
146- self.context.sections, key=operator.attrgetter('name'))
147+ self.context.sections, key=attrgetter('name'))
148
149 def priorities(self):
150 """An iterable of priorities from PackagePublishingPriority."""
151@@ -516,8 +520,6 @@
152
153 if self.contains_source:
154 self.sourcepackagerelease = self.sources[0].sourcepackagerelease
155-
156- if self.contains_source:
157 self.package_sets = package_sets.get(
158 self.sourcepackagerelease.sourcepackagenameID, [])
159 else:
160@@ -561,8 +563,7 @@
161 if title is None:
162 title = alt
163 return structured(
164- '<img alt="[%s]" src="/@@/%s" title="%s" />',
165- alt, icon, title)
166+ '<img alt="[%s]" src="/@@/%s" title="%s" />', alt, icon, title)
167
168 def composeIconList(self):
169 """List icons that should be shown for this upload."""
170@@ -599,9 +600,5 @@
171 icon_string = structured('\n'.join(['%s'] * len(icons)), *icons)
172 link = self.composeNameAndChangesLink()
173 return structured(
174- """<div id="%s">
175- %s
176- %s
177- (%s)
178- </div>""",
179+ """<div id="%s"> %s %s (%s)</div>""",
180 iconlist_id, icon_string, link, self.displayarchs).escapedtext
181
182=== modified file 'lib/lp/soyuz/browser/tests/test_queue.py'
183--- lib/lp/soyuz/browser/tests/test_queue.py 2012-12-12 04:59:52 +0000
184+++ lib/lp/soyuz/browser/tests/test_queue.py 2013-01-03 00:18:23 +0000
185@@ -6,6 +6,8 @@
186 __metaclass__ = type
187
188 from lxml import html
189+from storm.store import Store
190+from testtools.matchers import Equals
191 import transaction
192 from zope.component import (
193 getUtility,
194@@ -26,12 +28,14 @@
195 login_person,
196 logout,
197 person_logged_in,
198+ StormStatementRecorder,
199 TestCaseWithFactory,
200 )
201 from lp.testing.layers import (
202 LaunchpadFunctionalLayer,
203 LaunchpadZopelessLayer,
204 )
205+from lp.testing.matchers import HasQueryCount
206 from lp.testing.sampledata import ADMIN_EMAIL
207 from lp.testing.views import create_initialized_view
208
209@@ -361,14 +365,31 @@
210 self.assertIn(
211 upload.package_copy_job.job.requester.displayname, html_text)
212
213+ def test_query_count(self):
214+ login(ADMIN_EMAIL)
215+ uploads = []
216+ distroseries = self.factory.makeDistroSeries()
217+ for i in range(5):
218+ uploads.append(self.factory.makeSourcePackageUpload(distroseries))
219+ uploads.append(self.factory.makeCustomPackageUpload(distroseries))
220+ uploads.append(self.factory.makeCopyJobPackageUpload(distroseries))
221+ for i in range(15):
222+ uploads.append(self.factory.makeBuildPackageUpload(distroseries))
223+ queue_admin = self.factory.makeArchiveAdmin(distroseries.main_archive)
224+ Store.of(uploads[0]).invalidate()
225+ with person_logged_in(queue_admin):
226+ with StormStatementRecorder() as recorder:
227+ view = self.makeView(distroseries, queue_admin)
228+ view()
229+ self.assertThat(recorder, HasQueryCount(Equals(52)))
230+
231
232 class TestCompletePackageUpload(TestCaseWithFactory):
233
234 layer = LaunchpadZopelessLayer
235
236 def makeCompletePackageUpload(self, upload=None, build_upload_files=None,
237- source_upload_files=None,
238- package_sets=None):
239+ source_upload_files=None, package_sets=None):
240 if upload is None:
241 upload = self.factory.makeSourcePackageUpload()
242 if build_upload_files is None:
243
244=== modified file 'lib/lp/soyuz/configure.zcml'
245--- lib/lp/soyuz/configure.zcml 2012-12-10 06:27:12 +0000
246+++ lib/lp/soyuz/configure.zcml 2013-01-03 00:18:23 +0000
247@@ -192,7 +192,8 @@
248 section_name
249 components
250 searchable_names
251- searchable_versions"/>
252+ searchable_versions
253+ changes_file_id"/>
254 <require
255 permission="launchpad.Edit"
256 attributes="
257
258=== modified file 'lib/lp/soyuz/model/archive.py'
259--- lib/lp/soyuz/model/archive.py 2012-11-13 13:43:31 +0000
260+++ lib/lp/soyuz/model/archive.py 2013-01-03 00:18:23 +0000
261@@ -1554,7 +1554,7 @@
262 PackageUploadSource.sourcepackagereleaseID,
263 PackageUploadSource.packageuploadID == PackageUpload.id,
264 PackageUpload.status == PackageUploadStatus.DONE,
265- PackageUpload.changesfileID == LibraryFileAlias.id,
266+ PackageUpload.changes_file_id == LibraryFileAlias.id,
267 )
268 else:
269 raise NotFoundError(filename)
270
271=== modified file 'lib/lp/soyuz/model/binarypackagebuild.py'
272--- lib/lp/soyuz/model/binarypackagebuild.py 2012-11-15 01:42:33 +0000
273+++ lib/lp/soyuz/model/binarypackagebuild.py 2013-01-03 00:18:23 +0000
274@@ -180,7 +180,7 @@
275 Join(PackageUpload,
276 PackageUploadBuild.packageuploadID == PackageUpload.id),
277 Join(LibraryFileAlias,
278- LibraryFileAlias.id == PackageUpload.changesfileID),
279+ LibraryFileAlias.id == PackageUpload.changes_file_id),
280 Join(LibraryFileContent,
281 LibraryFileContent.id == LibraryFileAlias.contentID),
282 ]
283
284=== modified file 'lib/lp/soyuz/model/publishing.py'
285--- lib/lp/soyuz/model/publishing.py 2012-11-15 23:28:13 +0000
286+++ lib/lp/soyuz/model/publishing.py 2013-01-03 00:18:23 +0000
287@@ -1,8 +1,6 @@
288 # Copyright 2009-2012 Canonical Ltd. This software is licensed under the
289 # GNU Affero General Public License version 3 (see the file LICENSE).
290
291-# pylint: disable-msg=E0611,W0212
292-
293 __metaclass__ = type
294
295 __all__ = [
296@@ -1560,12 +1558,12 @@
297 packageupload=packageupload)
298 DistributionSourcePackage.ensure(pub)
299
300- if create_dsd_job:
301- if archive == distroseries.main_archive:
302- dsd_job_source = getUtility(IDistroSeriesDifferenceJobSource)
303- dsd_job_source.createForPackagePublication(
304- distroseries, sourcepackagerelease.sourcepackagename,
305- pocket)
306+ if create_dsd_job and archive == distroseries.main_archive:
307+ dsd_job_source = getUtility(IDistroSeriesDifferenceJobSource)
308+ dsd_job_source.createForPackagePublication(
309+ distroseries, sourcepackagerelease.sourcepackagename, pocket)
310+ Store.of(sourcepackagerelease).flush()
311+ del get_property_cache(sourcepackagerelease).published_archives
312 return pub
313
314 def getBuildsForSourceIds(self, source_publication_ids, archive=None,
315@@ -1802,8 +1800,7 @@
316
317 return result_set
318
319- def getBinaryPublicationsForSources(self,
320- one_or_more_source_publications):
321+ def getBinaryPublicationsForSources(self, one_or_more_source_publications):
322 """See `IPublishingSet`."""
323 source_publication_ids = self._extractIDs(
324 one_or_more_source_publications)
325@@ -1850,11 +1847,8 @@
326
327 def getChangesFilesForSources(self, one_or_more_source_publications):
328 """See `IPublishingSet`."""
329- # Import PackageUpload and PackageUploadSource locally
330- # to avoid circular imports, since PackageUpload uses
331- # SourcePackagePublishingHistory.
332- from lp.soyuz.model.queue import (
333- PackageUpload, PackageUploadSource)
334+ # Avoid circular imports.
335+ from lp.soyuz.model.queue import PackageUpload, PackageUploadSource
336
337 source_publication_ids = self._extractIDs(
338 one_or_more_source_publications)
339@@ -1864,7 +1858,7 @@
340 (SourcePackagePublishingHistory, PackageUpload,
341 SourcePackageRelease, LibraryFileAlias, LibraryFileContent),
342 LibraryFileContent.id == LibraryFileAlias.contentID,
343- LibraryFileAlias.id == PackageUpload.changesfileID,
344+ LibraryFileAlias.id == PackageUpload.changes_file_id,
345 PackageUpload.id == PackageUploadSource.packageuploadID,
346 PackageUpload.status == PackageUploadStatus.DONE,
347 PackageUpload.distroseriesID ==
348@@ -1882,14 +1876,13 @@
349
350 def getChangesFileLFA(self, spr):
351 """See `IPublishingSet`."""
352- # Import PackageUpload and PackageUploadSource locally to avoid
353- # circular imports.
354+ # Avoid circular imports.
355 from lp.soyuz.model.queue import PackageUpload, PackageUploadSource
356
357 store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)
358 result_set = store.find(
359 LibraryFileAlias,
360- LibraryFileAlias.id == PackageUpload.changesfileID,
361+ LibraryFileAlias.id == PackageUpload.changes_file_id,
362 PackageUpload.status == PackageUploadStatus.DONE,
363 PackageUpload.distroseriesID == spr.upload_distroseries.id,
364 PackageUpload.archiveID == spr.upload_archive.id,
365
366=== modified file 'lib/lp/soyuz/model/queue.py'
367--- lib/lp/soyuz/model/queue.py 2012-12-26 01:04:05 +0000
368+++ lib/lp/soyuz/model/queue.py 2013-01-03 00:18:23 +0000
369@@ -52,7 +52,10 @@
370 from lp.registry.model.sourcepackagename import SourcePackageName
371 from lp.services.auditor.client import AuditorClient
372 from lp.services.config import config
373-from lp.services.database.bulk import load_referencing
374+from lp.services.database.bulk import (
375+ load_referencing,
376+ load_related,
377+ )
378 from lp.services.database.constants import UTC_NOW
379 from lp.services.database.datetimecol import UtcDateTimeCol
380 from lp.services.database.decoratedresultset import DecoratedResultSet
381@@ -112,6 +115,7 @@
382 QueueStateWriteProtectedError,
383 )
384 from lp.soyuz.interfaces.section import ISectionSet
385+from lp.soyuz.model.distroarchseries import DistroArchSeries
386 from lp.soyuz.pas import BuildDaemonPackagesArchSpecific
387
388 # There are imports below in PackageUploadCustom for various bits
389@@ -174,8 +178,8 @@
390 dbName='pocket', unique=False, notNull=True,
391 schema=PackagePublishingPocket)
392
393- changesfile = ForeignKey(
394- dbName='changesfile', foreignKey="LibraryFileAlias", notNull=False)
395+ changes_file_id = Int(name='changesfile')
396+ changesfile = Reference(changes_file_id, 'LibraryFileAlias.id')
397
398 archive = ForeignKey(dbName="archive", foreignKey="Archive", notNull=True)
399
400@@ -814,15 +818,16 @@
401
402 def addSource(self, spr):
403 """See `IPackageUpload`."""
404- del get_property_cache(self).sources
405 self.addSearchableNames([spr.name])
406 self.addSearchableVersions([spr.version])
407- return PackageUploadSource(
408+ pus = PackageUploadSource(
409 packageupload=self, sourcepackagerelease=spr.id)
410+ Store.of(self).flush()
411+ del get_property_cache(self).sources
412+ return pus
413
414 def addBuild(self, build):
415 """See `IPackageUpload`."""
416- del get_property_cache(self).builds
417 names = [build.source_package_release.name]
418 versions = []
419 for bpr in build.binarypackages:
420@@ -830,15 +835,20 @@
421 versions.append(bpr.version)
422 self.addSearchableNames(names)
423 self.addSearchableVersions(versions)
424- return PackageUploadBuild(packageupload=self, build=build.id)
425+ pub = PackageUploadBuild(packageupload=self, build=build.id)
426+ Store.of(self).flush()
427+ del get_property_cache(self).builds
428+ return pub
429
430 def addCustom(self, library_file, custom_type):
431 """See `IPackageUpload`."""
432- del get_property_cache(self).customfiles
433 self.addSearchableNames([library_file.filename])
434- return PackageUploadCustom(
435+ puc = PackageUploadCustom(
436 packageupload=self, libraryfilealias=library_file.id,
437 customformat=custom_type)
438+ Store.of(self).flush()
439+ del get_property_cache(self).customfiles
440+ return puc
441
442 def isPPA(self):
443 """See `IPackageUpload`."""
444@@ -1665,18 +1675,7 @@
445 pucs = load_referencing(
446 PackageUploadCustom, rows, ["packageuploadID"])
447
448- for pu in rows:
449- cache = get_property_cache(pu)
450- cache.sources = []
451- cache.builds = []
452- cache.customfiles = []
453-
454- for pus in puses:
455- get_property_cache(pus.packageupload).sources.append(pus)
456- for pub in pubs:
457- get_property_cache(pub.packageupload).builds.append(pub)
458- for puc in pucs:
459- get_property_cache(puc.packageupload).customfiles.append(puc)
460+ prefill_packageupload_caches(rows, puses, pubs, pucs)
461
462 return DecoratedResultSet(query, pre_iter_hook=preload_hook)
463
464@@ -1704,3 +1703,43 @@
465 return IStore(PackageUpload).find(
466 PackageUpload,
467 PackageUpload.package_copy_job_id.is_in(pcj_ids))
468+
469+
470+def prefill_packageupload_caches(uploads, puses, pubs, pucs):
471+ # Circular imports.
472+ from lp.soyuz.model.archive import Archive
473+ from lp.soyuz.model.binarypackagebuild import BinaryPackageBuild
474+ from lp.soyuz.model.publishing import SourcePackagePublishingHistory
475+ from lp.soyuz.model.sourcepackagerelease import SourcePackageRelease
476+
477+ for pu in uploads:
478+ cache = get_property_cache(pu)
479+ cache.sources = []
480+ cache.builds = []
481+ cache.customfiles = []
482+
483+ for pus in puses:
484+ get_property_cache(pus.packageupload).sources.append(pus)
485+ for pub in pubs:
486+ get_property_cache(pub.packageupload).builds.append(pub)
487+ for puc in pucs:
488+ get_property_cache(puc.packageupload).customfiles.append(puc)
489+
490+ source_sprs = load_related(
491+ SourcePackageRelease, puses, ['sourcepackagereleaseID'])
492+ bpbs = load_related(BinaryPackageBuild, pubs, ['buildID'])
493+ load_related(DistroArchSeries, bpbs, ['distro_arch_series_id'])
494+ binary_sprs = load_related(
495+ SourcePackageRelease, bpbs, ['source_package_release_id'])
496+ sprs = source_sprs + binary_sprs
497+
498+ load_related(SourcePackageName, sprs, ['sourcepackagenameID'])
499+ load_related(LibraryFileAlias, uploads, ['changes_file_id'])
500+ publications = load_referencing(
501+ SourcePackagePublishingHistory, sprs, ['sourcepackagereleaseID'])
502+ load_related(Archive, publications, ['archiveID'])
503+ for spr_cache in sprs:
504+ get_property_cache(spr_cache).published_archives = []
505+ for publication in publications:
506+ spr_cache = get_property_cache(publication.sourcepackagerelease)
507+ spr_cache.published_archives.append(publication.archive)
508
509=== modified file 'lib/lp/soyuz/model/sourcepackagerelease.py'
510--- lib/lp/soyuz/model/sourcepackagerelease.py 2012-11-26 12:53:30 +0000
511+++ lib/lp/soyuz/model/sourcepackagerelease.py 2013-01-03 00:18:23 +0000
512@@ -1,8 +1,6 @@
513 # Copyright 2009-2012 Canonical Ltd. This software is licensed under the
514 # GNU Affero General Public License version 3 (see the file LICENSE).
515
516-# pylint: disable-msg=E0611,W0212
517-
518 __metaclass__ = type
519 __all__ = [
520 'SourcePackageRelease',
521@@ -291,14 +289,13 @@
522 @property
523 def current_publishings(self):
524 """See ISourcePackageRelease."""
525- from lp.soyuz.model.distroseriessourcepackagerelease \
526- import DistroSeriesSourcePackageRelease
527+ from lp.soyuz.model.distroseriessourcepackagerelease import (
528+ DistroSeriesSourcePackageRelease)
529 return [DistroSeriesSourcePackageRelease(pub.distroseries, self)
530 for pub in self.publishings]
531
532- @property
533+ @cachedproperty
534 def published_archives(self):
535- """See `ISourcePackageRelease`."""
536 archives = set(
537 pub.archive for pub in self.publishings.prejoin(['archive']))
538 return sorted(archives, key=operator.attrgetter('id'))
539@@ -503,7 +500,7 @@
540 Join(PackageUpload,
541 PackageUploadSource.packageuploadID == PackageUpload.id),
542 Join(LibraryFileAlias,
543- LibraryFileAlias.id == PackageUpload.changesfileID),
544+ LibraryFileAlias.id == PackageUpload.changes_file_id),
545 Join(LibraryFileContent,
546 LibraryFileContent.id == LibraryFileAlias.contentID),
547 ]
548
549=== modified file 'lib/lp/soyuz/tests/test_packageupload.py'
550--- lib/lp/soyuz/tests/test_packageupload.py 2012-12-17 05:10:29 +0000
551+++ lib/lp/soyuz/tests/test_packageupload.py 2013-01-03 00:18:23 +0000
552@@ -1292,3 +1292,16 @@
553 "customformat": "raw-translations",
554 }
555 self.assertEqual(expected_custom, ws_binaries[-1])
556+
557+ def test_getPackageUploads_query_count(self):
558+ person = self.makeQueueAdmin([self.universe])
559+ uploads = []
560+ for i in range(5):
561+ upload, _ = self.makeBinaryPackageUpload(
562+ person, component=self.universe)
563+ uploads.append(upload)
564+ ws_distroseries = self.load(self.distroseries, person)
565+ IStore(uploads[0].__class__).invalidate()
566+ with StormStatementRecorder() as recorder:
567+ ws_distroseries.getPackageUploads()
568+ self.assertThat(recorder, HasQueryCount(Equals(27)))