Merge lp:~cjwatson/launchpad/queue-api-readonly into lp:launchpad

Proposed by Colin Watson
Status: Merged
Approved by: Colin Watson
Approved revision: no longer in the source branch.
Merged at revision: 15555
Proposed branch: lp:~cjwatson/launchpad/queue-api-readonly
Merge into: lp:launchpad
Diff against target: 926 lines (+411/-66)
13 files modified
lib/lp/archiveuploader/tests/nascentupload.txt (+2/-2)
lib/lp/archiveuploader/tests/test_buildduploads.py (+2/-2)
lib/lp/soyuz/configure.zcml (+6/-0)
lib/lp/soyuz/interfaces/binarypackagerelease.py (+5/-1)
lib/lp/soyuz/interfaces/queue.py (+86/-16)
lib/lp/soyuz/model/binarypackagerelease.py (+14/-2)
lib/lp/soyuz/model/queue.py (+105/-12)
lib/lp/soyuz/scripts/queue.py (+4/-4)
lib/lp/soyuz/tests/test_distroseriesqueue_debian_installer.py (+1/-1)
lib/lp/soyuz/tests/test_distroseriesqueue_dist_upgrader.py (+1/-1)
lib/lp/soyuz/tests/test_packageupload.py (+176/-22)
lib/lp/testing/factory.py (+3/-2)
lib/lp/testing/tests/test_factory.py (+6/-1)
To merge this branch: bzr merge lp:~cjwatson/launchpad/queue-api-readonly
Reviewer Review Type Date Requested Status
Richard Harding (community) Approve
Review via email: mp+113202@code.launchpad.net

Commit message

Export read-only properties and methods of PackageUpload required to implement an API version of the queue tool.

Description of the change

== Summary ==

Replace the queue tool in Launchpad with an API client (part four).

== Proposed fix ==

This branch exports the necessary read-only properties and methods on PackageUpload.

== Pre-implementation notes ==

I've gone round a few times with various people, particularly William Grant, on the exact way to export all of this stuff, because I gather that we want to avoid exposing the current data model in order that it can be rearranged in the future. This has led to the following design choices:

 * Everything is on devel. The only clients for this should be tools such as those in lp:ubuntu-archive-tools, which can be kept up to date if there's a need to change these interfaces.
 * Even though some of the underlying methods are on other objects, all the new exported methods are on PackageUpload rather than exporting anything else.
 * There are source packages with lots of binaries that sometimes need to be overridden individually (e.g. linux) and API requests aren't especially fast. I've therefore arranged for properties (including overrides) of all binaries in an upload to come back as a list of dicts in a single JSON response.

I extracted this branch from https://code.launchpad.net/~cjwatson/launchpad/queue-api/+merge/108967 at Benji's request.

== LOC Rationale ==

+347. As with https://code.launchpad.net/~cjwatson/launchpad/queue-api/+merge/108967, this arc will be LoC-negative.

== Tests ==

bin/test -vvct distroseriesqueue.txt -t xx-packageupload.txt -t test_distroseriesqueue -t test_packageupload

== Demo and Q/A ==

http://paste.ubuntu.com/1072964/ is the current version of my client. With this branch, it should be possible to get full information on all uploads equivalent to that provided by the queue tool, and it should be possible to use "queue fetch" to download them.

To post a comment you must log in.
Revision history for this message
Richard Harding (rharding) wrote :

Thanks, couple of points, but looks ok.

#245
I'm not familiar with the code, so changing a read only value makes me nervous. I want to make sure this is ok/safe?

#427/#436
Wouldn't these just turn into len(self.sources) vs accessing the now 'private' _sources to get the count?

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/archiveuploader/tests/nascentupload.txt'
2--- lib/lp/archiveuploader/tests/nascentupload.txt 2012-01-20 15:42:44 +0000
3+++ lib/lp/archiveuploader/tests/nascentupload.txt 2012-07-04 08:43:20 +0000
4@@ -534,7 +534,7 @@
5 >>> multibar_src_queue.status.name
6 'NEW'
7
8- >>> multibar_src_queue.sources.count()
9+ >>> len(multibar_src_queue.sources)
10 1
11 >>> multibar_spr = multibar_src_queue.sources[0].sourcepackagerelease
12 >>> multibar_spr.title
13@@ -596,7 +596,7 @@
14 >>> multibar_bin_queue = multibar_bin_upload.queue_root
15 >>> multibar_bin_queue.status.name
16 'NEW'
17- >>> multibar_bin_queue.builds.count()
18+ >>> len(multibar_bin_queue.builds)
19 1
20
21 The build considered as 'producer' of the upload binaries is the same
22
23=== modified file 'lib/lp/archiveuploader/tests/test_buildduploads.py'
24--- lib/lp/archiveuploader/tests/test_buildduploads.py 2011-12-30 06:14:56 +0000
25+++ lib/lp/archiveuploader/tests/test_buildduploads.py 2012-07-04 08:43:20 +0000
26@@ -1,4 +1,4 @@
27-# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
28+# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
29 # GNU Affero General Public License version 3 (see the file LICENSE).
30
31 """Test buildd uploads use-cases."""
32@@ -136,7 +136,7 @@
33 self.assertTrue(
34 queue_item is not None,
35 "Binary Upload Failed\nGot: %s" % self.log.getLogBuffer())
36- self.assertEqual(queue_item.builds.count(), 1)
37+ self.assertEqual(1, len(queue_item.builds))
38 return queue_item.builds[0].build
39
40 def _createBuild(self, archtag):
41
42=== modified file 'lib/lp/soyuz/configure.zcml'
43--- lib/lp/soyuz/configure.zcml 2012-06-22 15:14:20 +0000
44+++ lib/lp/soyuz/configure.zcml 2012-07-04 08:43:20 +0000
45@@ -157,18 +157,24 @@
46 distroseries
47 pocket
48 changesfile
49+ changes_file_url
50 signing_key
51 archive
52 sources
53+ sourceFileUrls
54 builds
55+ binaryFileUrls
56 customfiles
57 custom_file_urls
58+ customFileUrls
59+ getBinaryProperties
60 date_created
61 sourcepackagerelease
62 component_name
63 concrete_package_copy_job
64 contains_source
65 contains_build
66+ contains_copy
67 contains_translation
68 contains_installer
69 contains_upgrader
70
71=== modified file 'lib/lp/soyuz/interfaces/binarypackagerelease.py'
72--- lib/lp/soyuz/interfaces/binarypackagerelease.py 2011-12-24 16:54:44 +0000
73+++ lib/lp/soyuz/interfaces/binarypackagerelease.py 2012-07-04 08:43:20 +0000
74@@ -1,4 +1,4 @@
75-# Copyright 2009 Canonical Ltd. This software is licensed under the
76+# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
77 # GNU Affero General Public License version 3 (see the file LICENSE).
78
79 # pylint: disable-msg=E0211,E0213
80@@ -28,6 +28,7 @@
81 Bool,
82 Date,
83 Datetime,
84+ Dict,
85 Int,
86 List,
87 Object,
88@@ -98,6 +99,9 @@
89 description=_("True if there binary version was never published for "
90 "the architeture it was built for. False otherwise."))
91
92+ # This is a dictionary for fast retrieval over the webservice.
93+ properties = Dict(title=_("The properties of this binary."))
94+
95 def addFile(file):
96 """Create a BinaryPackageFile record referencing this build
97 and attach the provided library file alias (file).
98
99=== modified file 'lib/lp/soyuz/interfaces/queue.py'
100--- lib/lp/soyuz/interfaces/queue.py 2012-07-03 17:09:00 +0000
101+++ lib/lp/soyuz/interfaces/queue.py 2012-07-04 08:43:20 +0000
102@@ -28,6 +28,7 @@
103 from lazr.restful.declarations import (
104 error_status,
105 export_as_webservice_entry,
106+ export_read_operation,
107 export_write_operation,
108 exported,
109 operation_for_version,
110@@ -38,6 +39,7 @@
111 Interface,
112 )
113 from zope.schema import (
114+ Bool,
115 Choice,
116 Datetime,
117 Int,
118@@ -141,6 +143,14 @@
119
120 changesfile = Attribute("The librarian alias for the changes file "
121 "associated with this upload")
122+ changes_file_url = exported(
123+ TextLine(
124+ title=_("Changes file URL"),
125+ description=_("Librarian URL for the changes file associated with "
126+ "this upload. Will be None if the upload was copied "
127+ "from another series."),
128+ required=False, readonly=True),
129+ as_of="devel")
130
131 signing_key = Attribute("Changesfile Signing Key.")
132
133@@ -162,17 +172,18 @@
134 title=_("Archive"), required=True, readonly=True))
135 sources = Attribute("The queue sources associated with this queue item")
136 builds = Attribute("The queue builds associated with the queue item")
137+
138 customfiles = Attribute("Custom upload files associated with this "
139 "queue item")
140-
141 custom_file_urls = exported(
142 List(
143- title=_("Custom File URLs"),
144+ title=_("Custom file URLs"),
145 description=_("Librarian URLs for all the custom files attached "
146 "to this upload."),
147 value_type=TextLine(),
148 required=False,
149- readonly=True))
150+ readonly=True),
151+ ("devel", dict(exported=False)), exported=True)
152
153 displayname = exported(
154 TextLine(
155@@ -191,17 +202,39 @@
156 sourcepackagerelease = Attribute(
157 "The source package release for this item")
158
159- package_name = TextLine(
160- title=_("Name of the uploaded source package"), readonly=True)
161-
162- package_version = TextLine(
163- title=_("Source package version"), readonly=True)
164-
165- component_name = TextLine(
166- title=_("Source package component name"), readonly=True)
167-
168- contains_source = Attribute("whether or not this upload contains sources")
169- contains_build = Attribute("whether or not this upload contains binaries")
170+ package_name = exported(
171+ TextLine(
172+ title=_("Name of the uploaded source package"), readonly=True),
173+ as_of="devel")
174+
175+ package_version = exported(
176+ TextLine(title=_("Source package version"), readonly=True),
177+ as_of="devel")
178+
179+ component_name = exported(
180+ TextLine(title=_("Source package component name"), readonly=True),
181+ as_of="devel")
182+
183+ section_name = exported(
184+ TextLine(title=_("Source package section name"), readonly=True),
185+ as_of="devel")
186+
187+ contains_source = exported(
188+ Bool(
189+ title=_("Whether or not this upload contains sources"),
190+ readonly=True),
191+ as_of="devel")
192+ contains_build = exported(
193+ Bool(
194+ title=_("Whether or not this upload contains binaries"),
195+ readonly=True),
196+ as_of="devel")
197+ contains_copy = exported(
198+ Bool(
199+ title=_("Whether or not this upload contains a copy from another "
200+ "series."),
201+ readonly=True),
202+ as_of="devel")
203 contains_installer = Attribute(
204 "whether or not this upload contains installers images")
205 contains_translation = Attribute(
206@@ -225,8 +258,38 @@
207 on all the binarypackagerelease records arising from the build.
208 """)
209
210- section_name = TextLine(
211- title=_("Source package sectio name"), readonly=True)
212+ @export_read_operation()
213+ @operation_for_version("devel")
214+ def sourceFileUrls():
215+ """URLs for all the source files attached to this upload.
216+
217+ :return: A collection of URLs for this upload.
218+ """
219+
220+ @export_read_operation()
221+ @operation_for_version("devel")
222+ def binaryFileUrls():
223+ """URLs for all the binary files attached to this upload.
224+
225+ :return: A collection of URLs for this upload.
226+ """
227+
228+ @export_read_operation()
229+ @operation_for_version("devel")
230+ def customFileUrls():
231+ """URLs for all the custom files attached to this upload.
232+
233+ :return: A collection of URLs for this upload.
234+ """
235+
236+ @export_read_operation()
237+ @operation_for_version("devel")
238+ def getBinaryProperties():
239+ """The properties of the binaries associated with this queue item.
240+
241+ :return: A list of dictionaries, each containing the properties of a
242+ single binary.
243+ """
244
245 def setNew():
246 """Set queue state to NEW."""
247@@ -388,6 +451,13 @@
248 title=_("The related build"), required=True, readonly=False,
249 )
250
251+ def binaries():
252+ """Returns the properties of the binaries in this build.
253+
254+ For fast retrieval over the webservice, these are returned as a list
255+ of dictionaries, one per binary.
256+ """
257+
258 def publish(logger=None):
259 """Publish this queued source in the distroseries referred to by
260 the parent queue item.
261
262=== modified file 'lib/lp/soyuz/model/binarypackagerelease.py'
263--- lib/lp/soyuz/model/binarypackagerelease.py 2012-04-16 23:02:44 +0000
264+++ lib/lp/soyuz/model/binarypackagerelease.py 2012-07-04 08:43:20 +0000
265@@ -1,4 +1,4 @@
266-# Copyright 2009 Canonical Ltd. This software is licensed under the
267+# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
268 # GNU Affero General Public License version 3 (see the file LICENSE).
269
270 # pylint: disable-msg=E0611,W0212
271@@ -131,6 +131,19 @@
272 self.binarypackagename)
273 return distroarchseries_binary_package.currentrelease is None
274
275+ @property
276+ def properties(self):
277+ """See `IBinaryPackageRelease`."""
278+ return {
279+ "name": self.name,
280+ "version": self.version,
281+ "is_new": self.is_new,
282+ "architecture": self.build.arch_tag,
283+ "component": self.component.name,
284+ "section": self.section.name,
285+ "priority": self.priority.name,
286+ }
287+
288 @cachedproperty
289 def files(self):
290 return list(
291@@ -201,4 +214,3 @@
292 def binary_package_version(self):
293 """See `IBinaryPackageReleaseDownloadCount`."""
294 return self.binary_package_release.version
295-
296
297=== modified file 'lib/lp/soyuz/model/queue.py'
298--- lib/lp/soyuz/model/queue.py 2012-07-03 17:09:00 +0000
299+++ lib/lp/soyuz/model/queue.py 2012-07-04 08:43:20 +0000
300@@ -13,6 +13,7 @@
301 'PackageUploadSet',
302 ]
303
304+from itertools import chain
305 import os
306 import shutil
307 import StringIO
308@@ -50,8 +51,10 @@
309 from lp.registry.interfaces.pocket import PackagePublishingPocket
310 from lp.registry.model.sourcepackagename import SourcePackageName
311 from lp.services.config import config
312+from lp.services.database.bulk import load_referencing
313 from lp.services.database.constants import UTC_NOW
314 from lp.services.database.datetimecol import UtcDateTimeCol
315+from lp.services.database.decoratedresultset import DecoratedResultSet
316 from lp.services.database.enumcol import EnumCol
317 from lp.services.database.lpstorm import (
318 IMasterStore,
319@@ -61,11 +64,15 @@
320 SQLBase,
321 sqlvalues,
322 )
323+from lp.services.librarian.browser import ProxiedLibraryFileAlias
324 from lp.services.librarian.interfaces.client import DownloadFailed
325 from lp.services.librarian.model import LibraryFileAlias
326 from lp.services.librarian.utils import copy_and_close
327 from lp.services.mail.signedmessage import strip_pgp_signature
328-from lp.services.propertycache import cachedproperty
329+from lp.services.propertycache import (
330+ cachedproperty,
331+ get_property_cache,
332+ )
333 from lp.soyuz.adapters.notification import notify
334 from lp.soyuz.adapters.overrides import SourceOverride
335 from lp.soyuz.enums import (
336@@ -238,11 +245,44 @@
337
338 # Join this table to the PackageUploadBuild and the
339 # PackageUploadSource objects which are related.
340- sources = SQLMultipleJoin('PackageUploadSource',
341+ _sources = SQLMultipleJoin('PackageUploadSource',
342+ joinColumn='packageupload')
343+ # Does not include source builds.
344+ _builds = SQLMultipleJoin('PackageUploadBuild',
345 joinColumn='packageupload')
346- # Does not include source builds.
347- builds = SQLMultipleJoin('PackageUploadBuild',
348- joinColumn='packageupload')
349+
350+ @cachedproperty
351+ def sources(self):
352+ return list(self._sources)
353+
354+ def sourceFileUrls(self):
355+ """See `IPackageUpload`."""
356+ if self.contains_source:
357+ return [
358+ ProxiedLibraryFileAlias(
359+ file.libraryfile, self.archive).http_url
360+ for file in self.sourcepackagerelease.files]
361+ else:
362+ return []
363+
364+ @cachedproperty
365+ def builds(self):
366+ return list(self._builds)
367+
368+ def binaryFileUrls(self):
369+ """See `IPackageUpload`."""
370+ return [
371+ ProxiedLibraryFileAlias(file.libraryfile, self.archive).http_url
372+ for build in self.builds
373+ for bpr in build.build.binarypackages
374+ for file in bpr.files]
375+
376+ @property
377+ def changes_file_url(self):
378+ if self.changesfile is not None:
379+ return self.changesfile.getURL()
380+ else:
381+ return None
382
383 def getSourceBuild(self):
384 #avoid circular import
385@@ -258,8 +298,12 @@
386 PackageUploadSource.packageupload == self.id).one()
387
388 # Also the custom files associated with the build.
389- customfiles = SQLMultipleJoin('PackageUploadCustom',
390- joinColumn='packageupload')
391+ _customfiles = SQLMultipleJoin('PackageUploadCustom',
392+ joinColumn='packageupload')
393+
394+ @cachedproperty
395+ def customfiles(self):
396+ return list(self._customfiles)
397
398 @property
399 def custom_file_urls(self):
400@@ -267,6 +311,18 @@
401 return tuple(
402 file.libraryfilealias.getURL() for file in self.customfiles)
403
404+ def customFileUrls(self):
405+ """See `IPackageUpload`."""
406+ return [
407+ ProxiedLibraryFileAlias(
408+ file.libraryfilealias, self.archive).http_url
409+ for file in self.customfiles]
410+
411+ def getBinaryProperties(self):
412+ """See `IPackageUpload`."""
413+ return list(chain.from_iterable(
414+ build.binaries for build in self.builds))
415+
416 def setNew(self):
417 """See `IPackageUpload`."""
418 if self.status == PackageUploadStatus.NEW:
419@@ -551,7 +607,7 @@
420 def acceptFromCopy(self):
421 """See `IPackageUpload`."""
422 assert self.is_delayed_copy, 'Can only process delayed-copies.'
423- assert self.sources.count() == 1, (
424+ assert len(self.sources) == 1, (
425 'Source is mandatory for delayed copies.')
426 self.setAccepted()
427
428@@ -588,7 +644,7 @@
429
430 def _isSingleSourceUpload(self):
431 """Return True if this upload contains only a single source."""
432- return ((self.sources.count() == 1) and
433+ return ((len(self.sources) == 1) and
434 (not bool(self.builds)) and
435 (not bool(self.customfiles)))
436
437@@ -597,12 +653,17 @@
438 @cachedproperty
439 def contains_source(self):
440 """See `IPackageUpload`."""
441- return self.sources
442+ return bool(self.sources)
443
444 @cachedproperty
445 def contains_build(self):
446 """See `IPackageUpload`."""
447- return self.builds
448+ return bool(self.builds)
449+
450+ @cachedproperty
451+ def contains_copy(self):
452+ """See `IPackageUpload`."""
453+ return self.package_copy_job_id is not None
454
455 @cachedproperty
456 def from_build(self):
457@@ -816,18 +877,21 @@
458
459 def addSource(self, spr):
460 """See `IPackageUpload`."""
461+ del get_property_cache(self).sources
462 return PackageUploadSource(
463 packageupload=self,
464 sourcepackagerelease=spr.id)
465
466 def addBuild(self, build):
467 """See `IPackageUpload`."""
468+ del get_property_cache(self).builds
469 return PackageUploadBuild(
470 packageupload=self,
471 build=build.id)
472
473 def addCustom(self, library_file, custom_type):
474 """See `IPackageUpload`."""
475+ del get_property_cache(self).customfiles
476 return PackageUploadCustom(
477 packageupload=self,
478 libraryfilealias=library_file.id,
479@@ -1095,6 +1159,12 @@
480
481 build = ForeignKey(dbName='build', foreignKey='BinaryPackageBuild')
482
483+ @property
484+ def binaries(self):
485+ """See `IPackageUploadBuild`."""
486+ for binary in self.build.binarypackages:
487+ yield binary.properties
488+
489 def checkComponentAndSection(self):
490 """See `IPackageUploadBuild`."""
491 distroseries = self.packageupload.distroseries
492@@ -1710,7 +1780,30 @@
493 PackageUpload.distroseries == distroseries,
494 *conditions)
495 query = query.order_by(Desc(PackageUpload.id))
496- return query.config(distinct=True)
497+ query = query.config(distinct=True)
498+
499+ def preload_hook(rows):
500+ puses = load_referencing(
501+ PackageUploadSource, rows, ["packageuploadID"])
502+ pubs = load_referencing(
503+ PackageUploadBuild, rows, ["packageuploadID"])
504+ pucs = load_referencing(
505+ PackageUploadCustom, rows, ["packageuploadID"])
506+
507+ for pu in rows:
508+ cache = get_property_cache(pu)
509+ cache.sources = []
510+ cache.builds = []
511+ cache.customfiles = []
512+
513+ for pus in puses:
514+ get_property_cache(pus.packageupload).sources.append(pus)
515+ for pub in pubs:
516+ get_property_cache(pub.packageupload).builds.append(pub)
517+ for puc in pucs:
518+ get_property_cache(puc.packageupload).customfiles.append(puc)
519+
520+ return DecoratedResultSet(query, pre_iter_hook=preload_hook)
521
522 def getBuildByBuildIDs(self, build_ids):
523 """See `IPackageUploadSet`."""
524
525=== modified file 'lib/lp/soyuz/scripts/queue.py'
526--- lib/lp/soyuz/scripts/queue.py 2012-06-29 08:40:05 +0000
527+++ lib/lp/soyuz/scripts/queue.py 2012-07-04 08:43:20 +0000
528@@ -1,4 +1,4 @@
529-# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
530+# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
531 # GNU Affero General Public License version 3 (see the file LICENSE).
532
533 # pylint: disable-msg=W0231
534@@ -260,8 +260,8 @@
535 False: '-',
536 }
537 return (
538- source_tag[bool(queue_item.contains_source)] +
539- binary_tag[bool(queue_item.contains_build)])
540+ source_tag[queue_item.contains_source] +
541+ binary_tag[queue_item.contains_build])
542
543 def displayItem(self, queue_item):
544 """Display one line summary of the queue item provided."""
545@@ -286,7 +286,7 @@
546 Optionally pass a binarypackagename via 'only' argument to display
547 only exact matches within the selected build queue items.
548 """
549- if queue_item.package_copy_job or not queue_item.sources.is_empty():
550+ if queue_item.package_copy_job or queue_item.sources:
551 self.display(
552 "\t | * %s/%s Component: %s Section: %s" % (
553 queue_item.package_name,
554
555=== modified file 'lib/lp/soyuz/tests/test_distroseriesqueue_debian_installer.py'
556--- lib/lp/soyuz/tests/test_distroseriesqueue_debian_installer.py 2012-05-28 12:50:34 +0000
557+++ lib/lp/soyuz/tests/test_distroseriesqueue_debian_installer.py 2012-07-04 08:43:20 +0000
558@@ -49,7 +49,7 @@
559
560 def test_accepts_correct_upload(self):
561 upload = self.uploadTestData()
562- self.assertEqual(1, upload.queue_root.customfiles.count())
563+ self.assertEqual(1, len(upload.queue_root.customfiles))
564
565 def test_generates_mail(self):
566 # Two e-mail messages were generated (acceptance and announcement).
567
568=== modified file 'lib/lp/soyuz/tests/test_distroseriesqueue_dist_upgrader.py'
569--- lib/lp/soyuz/tests/test_distroseriesqueue_dist_upgrader.py 2012-05-25 13:27:41 +0000
570+++ lib/lp/soyuz/tests/test_distroseriesqueue_dist_upgrader.py 2012-07-04 08:43:20 +0000
571@@ -107,7 +107,7 @@
572 # Make sure that we can use the librarian files.
573 transaction.commit()
574 self.assertFalse(upload.queue_root.realiseUpload(self.logger))
575- self.assertEqual(1, upload.queue_root.customfiles.count())
576+ self.assertEqual(1, len(upload.queue_root.customfiles))
577 self.assertRaises(
578 DistUpgraderBadVersion, upload.queue_root.customfiles[0].publish,
579 self.logger)
580
581=== modified file 'lib/lp/soyuz/tests/test_packageupload.py'
582--- lib/lp/soyuz/tests/test_packageupload.py 2012-07-02 20:06:00 +0000
583+++ lib/lp/soyuz/tests/test_packageupload.py 2012-07-04 08:43:20 +0000
584@@ -12,9 +12,11 @@
585 BadRequest,
586 Unauthorized,
587 )
588+from testtools.matchers import Equals
589 import transaction
590 from zope.component import getUtility
591 from zope.security.proxy import removeSecurityProxy
592+from zope.schema import getFields
593
594 from lp.archivepublisher.interfaces.publisherconfig import IPublisherConfigSet
595 from lp.archiveuploader.tests import datadir
596@@ -25,6 +27,7 @@
597 from lp.services.config import config
598 from lp.services.database.lpstorm import IStore
599 from lp.services.job.interfaces.job import JobStatus
600+from lp.services.librarian.browser import ProxiedLibraryFileAlias
601 from lp.services.log.logger import BufferLogger
602 from lp.services.mail import stub
603 from lp.soyuz.adapters.overrides import SourceOverride
604@@ -37,6 +40,7 @@
605 from lp.soyuz.interfaces.archivepermission import IArchivePermissionSet
606 from lp.soyuz.interfaces.component import IComponentSet
607 from lp.soyuz.interfaces.queue import (
608+ IPackageUpload,
609 IPackageUploadSet,
610 QueueInconsistentStateError,
611 )
612@@ -48,6 +52,7 @@
613 api_url,
614 launchpadlib_for,
615 person_logged_in,
616+ StormStatementRecorder,
617 TestCaseWithFactory,
618 )
619 from lp.testing.dbuser import switch_dbuser
620@@ -55,7 +60,10 @@
621 LaunchpadFunctionalLayer,
622 LaunchpadZopelessLayer,
623 )
624-from lp.testing.matchers import Provides
625+from lp.testing.matchers import (
626+ HasQueryCount,
627+ Provides,
628+ )
629
630
631 class PackageUploadTestCase(TestCaseWithFactory):
632@@ -860,6 +868,20 @@
633 list(reversed(ordered_uploads)),
634 list(getUtility(IPackageUploadSet).getAll(series)))
635
636+ def test_getAll_can_preload_exported_properties(self):
637+ # getAll preloads everything exported on the webservice.
638+ distroseries = self.factory.makeDistroSeries()
639+ self.factory.makeSourcePackageUpload(distroseries=distroseries)
640+ self.factory.makeBuildPackageUpload(distroseries=distroseries)
641+ self.factory.makeCustomPackageUpload(distroseries=distroseries)
642+ uploads = list(getUtility(IPackageUploadSet).getAll(distroseries))
643+ with StormStatementRecorder() as recorder:
644+ for name, field in getFields(IPackageUpload).items():
645+ if field.queryTaggedValue("lazr.restful.exported") is not None:
646+ for upload in uploads:
647+ getattr(upload, name)
648+ self.assertThat(recorder, HasQueryCount(Equals(0)))
649+
650 def test_rejectFromQueue_no_changes_file(self):
651 # If the PackageUpload has no changesfile, we can still reject it.
652 pu = self.factory.makePackageUpload()
653@@ -876,12 +898,13 @@
654 def setUp(self):
655 super(TestPackageUploadWebservice, self).setUp()
656 self.webservice = None
657-
658- def makeDistroSeries(self):
659 self.distroseries = self.factory.makeDistroSeries()
660 self.main = self.factory.makeComponent("main")
661 self.factory.makeComponentSelection(
662 distroseries=self.distroseries, component=self.main)
663+ self.universe = self.factory.makeComponent("universe")
664+ self.factory.makeComponentSelection(
665+ distroseries=self.distroseries, component=self.universe)
666
667 def makeQueueAdmin(self, components):
668 person = self.factory.makePerson()
669@@ -898,14 +921,52 @@
670 self.webservice = launchpadlib_for("testing", person)
671 return self.webservice.load(api_url(obj))
672
673+ def makeSourcePackageUpload(self, person, **kwargs):
674+ with person_logged_in(person):
675+ upload = self.factory.makeSourcePackageUpload(
676+ distroseries=self.distroseries, **kwargs)
677+ transaction.commit()
678+ spr = upload.sourcepackagerelease
679+ for extension in ("dsc", "tar.gz"):
680+ filename = "%s_%s.%s" % (spr.name, spr.version, extension)
681+ lfa = self.factory.makeLibraryFileAlias(filename=filename)
682+ spr.addFile(lfa)
683+ transaction.commit()
684+ return upload, self.load(upload, person)
685+
686+ def makeBinaryPackageUpload(self, person, binarypackagename=None,
687+ component=None):
688+ with person_logged_in(person):
689+ upload = self.factory.makeBuildPackageUpload(
690+ distroseries=self.distroseries,
691+ binarypackagename=binarypackagename, component=component)
692+ self.factory.makeBinaryPackageRelease(
693+ build=upload.builds[0].build, component=component)
694+ transaction.commit()
695+ for build in upload.builds:
696+ for bpr in build.build.binarypackages:
697+ filename = "%s_%s_%s.deb" % (
698+ bpr.name, bpr.version, bpr.build.arch_tag)
699+ lfa = self.factory.makeLibraryFileAlias(filename=filename)
700+ bpr.addFile(lfa)
701+ transaction.commit()
702+ return upload, self.load(upload, person)
703+
704+ def makeCustomPackageUpload(self, person, **kwargs):
705+ with person_logged_in(person):
706+ upload = self.factory.makeCustomPackageUpload(
707+ distroseries=self.distroseries, **kwargs)
708+ transaction.commit()
709+ return upload, self.load(upload, person)
710+
711 def assertRequiresEdit(self, method_name, **kwargs):
712 """Test that a web service queue method requires launchpad.Edit."""
713 with admin_logged_in():
714 upload = self.factory.makeSourcePackageUpload()
715 transaction.commit()
716 ws_upload = self.load(upload)
717- self.assertRaises(Unauthorized, getattr(ws_upload, method_name),
718- **kwargs)
719+ self.assertRaises(
720+ Unauthorized, getattr(ws_upload, method_name), **kwargs)
721
722 def test_edit_permissions(self):
723 self.assertRequiresEdit("acceptFromQueue")
724@@ -913,42 +974,135 @@
725
726 def test_acceptFromQueue_archive_admin(self):
727 # acceptFromQueue as an archive admin accepts the upload.
728- self.makeDistroSeries()
729 person = self.makeQueueAdmin([self.main])
730- with person_logged_in(person):
731- upload = self.factory.makeSourcePackageUpload(
732- distroseries=self.distroseries, component=self.main)
733- transaction.commit()
734+ upload, ws_upload = self.makeSourcePackageUpload(
735+ person, component=self.main)
736
737- ws_upload = self.load(upload, person)
738 self.assertEqual("New", ws_upload.status)
739 ws_upload.acceptFromQueue()
740 self.assertEqual("Done", ws_upload.status)
741
742 def test_double_accept_raises_BadRequest(self):
743 # Trying to accept an upload twice returns 400 instead of OOPSing.
744- self.makeDistroSeries()
745 person = self.makeQueueAdmin([self.main])
746+ upload, _ = self.makeSourcePackageUpload(person, component=self.main)
747+
748 with person_logged_in(person):
749- upload = self.factory.makeSourcePackageUpload(
750- distroseries=self.distroseries, component=self.main)
751 upload.setAccepted()
752- transaction.commit()
753-
754 ws_upload = self.load(upload, person)
755 self.assertEqual("Accepted", ws_upload.status)
756 self.assertRaises(BadRequest, ws_upload.acceptFromQueue)
757
758 def test_rejectFromQueue_archive_admin(self):
759 # rejectFromQueue as an archive admin rejects the upload.
760- self.makeDistroSeries()
761 person = self.makeQueueAdmin([self.main])
762- with person_logged_in(person):
763- upload = self.factory.makeSourcePackageUpload(
764- distroseries=self.distroseries, component=self.main)
765- transaction.commit()
766+ upload, ws_upload = self.makeSourcePackageUpload(
767+ person, component=self.main)
768
769- ws_upload = self.load(upload, person)
770 self.assertEqual("New", ws_upload.status)
771 ws_upload.rejectFromQueue()
772 self.assertEqual("Rejected", ws_upload.status)
773+
774+ def test_source_info(self):
775+ # API clients can inspect properties of source uploads.
776+ person = self.makeQueueAdmin([self.universe])
777+ upload, ws_upload = self.makeSourcePackageUpload(
778+ person, sourcepackagename="hello", component=self.universe)
779+
780+ self.assertTrue(ws_upload.contains_source)
781+ self.assertFalse(ws_upload.contains_build)
782+ self.assertFalse(ws_upload.contains_copy)
783+ self.assertEqual("hello", ws_upload.display_name)
784+ self.assertEqual(upload.package_version, ws_upload.display_version)
785+ self.assertEqual("source", ws_upload.display_arches)
786+ self.assertEqual("hello", ws_upload.package_name)
787+ self.assertEqual(upload.package_version, ws_upload.package_version)
788+ self.assertEqual("universe", ws_upload.component_name)
789+ self.assertEqual(upload.section_name, ws_upload.section_name)
790+
791+ def test_source_fetch(self):
792+ # API clients can fetch files attached to source uploads.
793+ person = self.makeQueueAdmin([self.universe])
794+ upload, ws_upload = self.makeSourcePackageUpload(
795+ person, component=self.universe)
796+ ws_source_file_urls = ws_upload.sourceFileUrls()
797+ self.assertNotEqual(0, len(ws_source_file_urls))
798+ with person_logged_in(person):
799+ source_file_urls = [
800+ ProxiedLibraryFileAlias(
801+ file.libraryfile, upload.archive).http_url
802+ for file in upload.sourcepackagerelease.files]
803+ self.assertContentEqual(source_file_urls, ws_source_file_urls)
804+
805+ def test_binary_info(self):
806+ # API clients can inspect properties of binary uploads.
807+ person = self.makeQueueAdmin([self.universe])
808+ upload, ws_upload = self.makeBinaryPackageUpload(
809+ person, component=self.universe)
810+ with person_logged_in(person):
811+ arch = upload.builds[0].build.arch_tag
812+ bprs = upload.builds[0].build.binarypackages
813+
814+ self.assertFalse(ws_upload.contains_source)
815+ self.assertTrue(ws_upload.contains_build)
816+ ws_binaries = ws_upload.getBinaryProperties()
817+ self.assertEqual(len(list(bprs)), len(ws_binaries))
818+ for bpr, binary in zip(bprs, ws_binaries):
819+ expected_binary = {
820+ "is_new": True,
821+ "name": bpr.name,
822+ "version": bpr.version,
823+ "architecture": arch,
824+ "component": "universe",
825+ "section": bpr.section.name,
826+ "priority": bpr.priority.name,
827+ }
828+ self.assertContentEqual(expected_binary.keys(), binary.keys())
829+ for key, value in expected_binary.items():
830+ self.assertEqual(value, binary[key])
831+
832+ def test_binary_fetch(self):
833+ # API clients can fetch files attached to binary uploads.
834+ person = self.makeQueueAdmin([self.universe])
835+ upload, ws_upload = self.makeBinaryPackageUpload(
836+ person, component=self.universe)
837+
838+ ws_binary_file_urls = ws_upload.binaryFileUrls()
839+ self.assertNotEqual(0, len(ws_binary_file_urls))
840+ with person_logged_in(person):
841+ binary_file_urls = [
842+ ProxiedLibraryFileAlias(
843+ file.libraryfile, upload.archive).http_url
844+ for bpr in upload.builds[0].build.binarypackages
845+ for file in bpr.files]
846+ self.assertContentEqual(binary_file_urls, ws_binary_file_urls)
847+
848+ def test_custom_info(self):
849+ # API clients can inspect properties of custom uploads.
850+ person = self.makeQueueAdmin([self.universe])
851+ upload, ws_upload = self.makeCustomPackageUpload(
852+ person, custom_type=PackageUploadCustomFormat.DEBIAN_INSTALLER,
853+ filename="debian-installer-images_1.tar.gz")
854+
855+ self.assertFalse(ws_upload.contains_source)
856+ self.assertFalse(ws_upload.contains_build)
857+ self.assertFalse(ws_upload.contains_copy)
858+ self.assertEqual(
859+ "debian-installer-images_1.tar.gz", ws_upload.display_name)
860+ self.assertEqual("-", ws_upload.display_version)
861+ self.assertEqual("raw-installer", ws_upload.display_arches)
862+
863+ def test_custom_fetch(self):
864+ # API clients can fetch files attached to custom uploads.
865+ person = self.makeQueueAdmin([self.universe])
866+ upload, ws_upload = self.makeCustomPackageUpload(
867+ person, custom_type=PackageUploadCustomFormat.DEBIAN_INSTALLER,
868+ filename="debian-installer-images_1.tar.gz")
869+ ws_custom_file_urls = ws_upload.customFileUrls()
870+ self.assertNotEqual(0, len(ws_custom_file_urls))
871+ with person_logged_in(person):
872+ custom_file_urls = [
873+ ProxiedLibraryFileAlias(
874+ file.libraryfilealias, upload.archive).http_url
875+ for file in upload.customfiles]
876+ self.assertContentEqual(custom_file_urls, ws_custom_file_urls)
877
878=== modified file 'lib/lp/testing/factory.py'
879--- lib/lp/testing/factory.py 2012-06-22 17:26:53 +0000
880+++ lib/lp/testing/factory.py 2012-07-04 08:43:20 +0000
881@@ -3495,7 +3495,7 @@
882
883 def makeBuildPackageUpload(self, distroseries=None, pocket=None,
884 binarypackagename=None,
885- source_package_release=None):
886+ source_package_release=None, component=None):
887 """Make a `PackageUpload` with a `PackageUploadBuild` attached."""
888 if distroseries is None:
889 distroseries = self.makeDistroSeries()
890@@ -3506,7 +3506,8 @@
891 source_package_release=source_package_release, pocket=pocket)
892 upload.addBuild(build)
893 self.makeBinaryPackageRelease(
894- binarypackagename=binarypackagename, build=build)
895+ binarypackagename=binarypackagename, build=build,
896+ component=component)
897 return upload
898
899 def makeCustomPackageUpload(self, distroseries=None, pocket=None,
900
901=== modified file 'lib/lp/testing/tests/test_factory.py'
902--- lib/lp/testing/tests/test_factory.py 2012-06-22 17:26:53 +0000
903+++ lib/lp/testing/tests/test_factory.py 2012-07-04 08:43:20 +0000
904@@ -780,16 +780,21 @@
905 def test_makeBuildPackageUpload_passes_on_args(self):
906 distroseries = self.factory.makeDistroSeries()
907 bpn = self.factory.makeBinaryPackageName()
908+ spr = self.factory.makeSourcePackageRelease()
909+ component = self.factory.makeComponent()
910 pu = self.factory.makeBuildPackageUpload(
911 distroseries=distroseries, pocket=PackagePublishingPocket.PROPOSED,
912- binarypackagename=bpn)
913+ binarypackagename=bpn, source_package_release=spr,
914+ component=component)
915 build = list(pu.builds)[0].build
916 self.assertEqual(distroseries, pu.distroseries)
917 self.assertEqual(distroseries.distribution, pu.archive.distribution)
918 self.assertEqual(PackagePublishingPocket.PROPOSED, pu.pocket)
919+ self.assertEqual(spr, build.source_package_release)
920 release = IStore(distroseries).find(
921 BinaryPackageRelease, BinaryPackageRelease.build == build).one()
922 self.assertEqual(bpn, release.binarypackagename)
923+ self.assertEqual(component, release.component)
924
925 # makeCustomPackageUpload
926 def test_makeCustomPackageUpload_makes_proxied_IPackageUpload(self):