Merge lp:~cjwatson/launchpad/sourcefileurls-include-meta into lp:launchpad

Proposed by Colin Watson
Status: Merged
Merged at revision: 17861
Proposed branch: lp:~cjwatson/launchpad/sourcefileurls-include-meta
Merge into: lp:launchpad
Diff against target: 436 lines (+210/-50)
5 files modified
lib/lp/soyuz/browser/tests/test_publishing_webservice.py (+100/-24)
lib/lp/soyuz/interfaces/publishing.py (+7/-3)
lib/lp/soyuz/model/publishing.py (+19/-6)
lib/lp/soyuz/tests/test_publishing_models.py (+72/-14)
lib/lp/testing/__init__.py (+12/-3)
To merge this branch: bzr merge lp:~cjwatson/launchpad/sourcefileurls-include-meta
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+276928@code.launchpad.net

Commit message

Add include_meta option to SPPH.sourceFileUrls, paralleling BPPH.binaryFileUrls.

Description of the change

Add include_meta option to SPPH.sourceFileUrls, paralleling BPPH.binaryFileUrls.

This is mainly to support dgit, which needs to be able to fetch .dsc files from Launchpad and verify their contents without having to fetch a full Sources file (even if one were available for the .dsc in question) and its trust chain.

The include_meta option is rather weird, in that it causes the method in question to return a completely different type, and had I been doing this from scratch I would probably have added a new getSourceFiles method instead and deprecated sourceFileUrls over time. However, BPPH.binaryFileUrls(include_meta=True) already existed (https://code.launchpad.net/~michael.nelson/launchpad/include-binary-size/+merge/150831), and other things being equal I prefer new interfaces to look like existing ones. I tried to make the implementations a little more in line with each other as well.

While I was here, I also added sha256 to BPPH.binaryFileUrls(include_meta=True), to drag things a little further into the modern world. I didn't see any point in exporting sha1 in the new SPPH.sourceFileUrls(include_meta=True) interface.

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

I'd lean toward consistency wrt. SHA-1, but it doesn't really matter.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/soyuz/browser/tests/test_publishing_webservice.py'
--- lib/lp/soyuz/browser/tests/test_publishing_webservice.py 2015-11-26 13:31:45 +0000
+++ lib/lp/soyuz/browser/tests/test_publishing_webservice.py 2015-12-02 13:21:01 +0000
@@ -3,20 +3,84 @@
33
4"""Test webservice methods related to the publisher."""4"""Test webservice methods related to the publisher."""
55
6from testtools.matchers import IsInstance6from functools import partial
77
8from lp.services.database.sqlbase import flush_database_caches8from lp.services.librarian.browser import ProxiedLibraryFileAlias
9from lp.services.webapp.interfaces import OAuthPermission9from lp.services.webapp.interfaces import OAuthPermission
10from lp.testing import (10from lp.testing import (
11 api_url,11 api_url,
12 login_person,
12 person_logged_in,13 person_logged_in,
13 RequestTimelineCollector,14 record_two_runs,
14 TestCaseWithFactory,15 TestCaseWithFactory,
15 )16 )
16from lp.testing.layers import LaunchpadFunctionalLayer17from lp.testing.layers import LaunchpadFunctionalLayer
18from lp.testing.matchers import HasQueryCount
17from lp.testing.pages import webservice_for_person19from lp.testing.pages import webservice_for_person
1820
1921
22class SourcePackagePublishingHistoryWebserviceTests(TestCaseWithFactory):
23
24 layer = LaunchpadFunctionalLayer
25
26 def make_spph_for(self, person):
27 with person_logged_in(person):
28 spr = self.factory.makeSourcePackageRelease()
29 self.factory.makeSourcePackageReleaseFile(sourcepackagerelease=spr)
30 spph = self.factory.makeSourcePackagePublishingHistory(
31 sourcepackagerelease=spr)
32 return spph, api_url(spph)
33
34 def test_sourceFileUrls(self):
35 person = self.factory.makePerson()
36 webservice = webservice_for_person(
37 person, permission=OAuthPermission.READ_PUBLIC)
38 spph, url = self.make_spph_for(person)
39
40 response = webservice.named_get(
41 url, 'sourceFileUrls', api_version='devel')
42
43 self.assertEqual(200, response.status)
44 urls = response.jsonBody()
45 with person_logged_in(person):
46 sprf = spph.sourcepackagerelease.files[0]
47 expected_urls = [
48 ProxiedLibraryFileAlias(
49 sprf.libraryfile, spph.archive).http_url]
50 self.assertEqual(expected_urls, urls)
51
52 def test_sourceFileUrls_include_meta(self):
53 person = self.factory.makePerson()
54 webservice = webservice_for_person(
55 person, permission=OAuthPermission.READ_PUBLIC)
56 spph, url = self.make_spph_for(person)
57
58 def create_file():
59 self.factory.makeSourcePackageReleaseFile(
60 sourcepackagerelease=spph.sourcepackagerelease)
61
62 def get_urls():
63 return webservice.named_get(
64 url, 'sourceFileUrls', include_meta=True, api_version='devel')
65
66 recorder1, recorder2 = record_two_runs(
67 get_urls, create_file, 2,
68 login_method=partial(login_person, person), record_request=True)
69 self.assertThat(recorder2, HasQueryCount.byEquality(recorder1))
70
71 response = get_urls()
72 self.assertEqual(200, response.status)
73 info = response.jsonBody()
74 with person_logged_in(person):
75 expected_info = [{
76 "url": ProxiedLibraryFileAlias(
77 sprf.libraryfile, spph.archive).http_url,
78 "size": sprf.libraryfile.content.filesize,
79 "sha256": sprf.libraryfile.content.sha256,
80 } for sprf in spph.sourcepackagerelease.files]
81 self.assertContentEqual(expected_info, info)
82
83
20class BinaryPackagePublishingHistoryWebserviceTests(TestCaseWithFactory):84class BinaryPackagePublishingHistoryWebserviceTests(TestCaseWithFactory):
2185
22 layer = LaunchpadFunctionalLayer86 layer = LaunchpadFunctionalLayer
@@ -33,36 +97,48 @@
33 person = self.factory.makePerson()97 person = self.factory.makePerson()
34 webservice = webservice_for_person(98 webservice = webservice_for_person(
35 person, permission=OAuthPermission.READ_PUBLIC)99 person, permission=OAuthPermission.READ_PUBLIC)
100 bpph, url = self.make_bpph_for(person)
36101
37 response = webservice.named_get(102 response = webservice.named_get(
38 self.make_bpph_for(person)[1], 'binaryFileUrls',103 url, 'binaryFileUrls', api_version='devel')
39 api_version='devel')
40104
41 self.assertEqual(200, response.status)105 self.assertEqual(200, response.status)
42 urls = response.jsonBody()106 urls = response.jsonBody()
43 self.assertEqual(1, len(urls))107 with person_logged_in(person):
44 self.assertTrue(urls[0], IsInstance(unicode))108 bpf = bpph.binarypackagerelease.files[0]
109 expected_urls = [
110 ProxiedLibraryFileAlias(
111 bpf.libraryfile, bpph.archive).http_url]
112 self.assertEqual(expected_urls, urls)
45113
46 def test_binaryFileUrls_include_meta(self):114 def test_binaryFileUrls_include_meta(self):
47 person = self.factory.makePerson()115 person = self.factory.makePerson()
48 webservice = webservice_for_person(116 webservice = webservice_for_person(
49 person, permission=OAuthPermission.READ_PUBLIC)117 person, permission=OAuthPermission.READ_PUBLIC)
50
51 bpph, url = self.make_bpph_for(person)118 bpph, url = self.make_bpph_for(person)
52 query_counts = []119
53 for i in range(3):120 def create_file():
54 flush_database_caches()121 self.factory.makeBinaryPackageFile(
55 with RequestTimelineCollector() as collector:122 binarypackagerelease=bpph.binarypackagerelease)
56 response = webservice.named_get(123
57 url, 'binaryFileUrls', include_meta=True,124 def get_urls():
58 api_version='devel')125 return webservice.named_get(
59 query_counts.append(collector.count)126 url, 'binaryFileUrls', include_meta=True, api_version='devel')
60 with person_logged_in(person):127
61 self.factory.makeBinaryPackageFile(128 recorder1, recorder2 = record_two_runs(
62 binarypackagerelease=bpph.binarypackagerelease)129 get_urls, create_file, 2,
63 self.assertEqual(query_counts[0], query_counts[-1])130 login_method=partial(login_person, person), record_request=True)
64131 self.assertThat(recorder2, HasQueryCount.byEquality(recorder1))
132
133 response = get_urls()
65 self.assertEqual(200, response.status)134 self.assertEqual(200, response.status)
66 urls = response.jsonBody()135 info = response.jsonBody()
67 self.assertEqual(3, len(urls))136 with person_logged_in(person):
68 self.assertThat(urls[0], IsInstance(dict))137 expected_info = [{
138 "url": ProxiedLibraryFileAlias(
139 bpf.libraryfile, bpph.archive).http_url,
140 "size": bpf.libraryfile.content.filesize,
141 "sha1": bpf.libraryfile.content.sha1,
142 "sha256": bpf.libraryfile.content.sha256,
143 } for bpf in bpph.binarypackagerelease.files]
144 self.assertContentEqual(expected_info, info)
69145
=== modified file 'lib/lp/soyuz/interfaces/publishing.py'
--- lib/lp/soyuz/interfaces/publishing.py 2015-04-08 10:35:22 +0000
+++ lib/lp/soyuz/interfaces/publishing.py 2015-12-02 13:21:01 +0000
@@ -578,9 +578,13 @@
578 """578 """
579579
580 @export_read_operation()580 @export_read_operation()
581 def sourceFileUrls():581 @operation_parameters(
582 include_meta=Bool(title=_("Include Metadata"), required=False))
583 def sourceFileUrls(include_meta=False):
582 """URLs for this source publication's uploaded source files.584 """URLs for this source publication's uploaded source files.
583585
586 :param include_meta: Return a list of dicts with keys url, size, and
587 sha256 for each URL instead of a simple list.
584 :return: A collection of URLs for this source.588 :return: A collection of URLs for this source.
585 """589 """
586590
@@ -869,8 +873,8 @@
869 def binaryFileUrls(include_meta=False):873 def binaryFileUrls(include_meta=False):
870 """URLs for this binary publication's binary files.874 """URLs for this binary publication's binary files.
871875
872 :param include_meta: Return a list of dicts with keys url, size876 :param include_meta: Return a list of dicts with keys url, size,
873 and sha1 for each url instead of a simple list.877 sha1, and sha256 for each URL instead of a simple list.
874 :return: A collection of URLs for this binary.878 :return: A collection of URLs for this binary.
875 """879 """
876880
877881
=== modified file 'lib/lp/soyuz/model/publishing.py'
--- lib/lp/soyuz/model/publishing.py 2015-07-08 16:05:11 +0000
+++ lib/lp/soyuz/model/publishing.py 2015-12-02 13:21:01 +0000
@@ -643,11 +643,23 @@
643 return getUtility(643 return getUtility(
644 IPublishingSet).getBuildStatusSummaryForSourcePublication(self)644 IPublishingSet).getBuildStatusSummaryForSourcePublication(self)
645645
646 def sourceFileUrls(self):646 def sourceFileUrls(self, include_meta=False):
647 """See `ISourcePackagePublishingHistory`."""647 """See `ISourcePackagePublishingHistory`."""
648 sources = Store.of(self).find(
649 (LibraryFileAlias, LibraryFileContent),
650 LibraryFileContent.id == LibraryFileAlias.contentID,
651 LibraryFileAlias.id == SourcePackageReleaseFile.libraryfileID,
652 SourcePackageReleaseFile.sourcepackagerelease ==
653 SourcePackageRelease.id,
654 SourcePackageRelease.id == self.sourcepackagereleaseID)
648 source_urls = proxied_urls(655 source_urls = proxied_urls(
649 [file.libraryfile for file in self.sourcepackagerelease.files],656 [source for source, _ in sources], self.archive)
650 self.archive)657 if include_meta:
658 meta = [
659 (content.filesize, content.sha256) for _, content in sources]
660 return [
661 dict(url=url, size=size, sha256=sha256)
662 for url, (size, sha256) in zip(source_urls, meta)]
651 return source_urls663 return source_urls
652664
653 def binaryFileUrls(self):665 def binaryFileUrls(self):
@@ -1064,9 +1076,10 @@
1064 [binary for binary, _ in binaries], self.archive)1076 [binary for binary, _ in binaries], self.archive)
1065 if include_meta:1077 if include_meta:
1066 meta = [1078 meta = [
1067 (content.filesize, content.sha1) for _, content in binaries]1079 (content.filesize, content.sha1, content.sha256)
1068 return [dict(url=url, size=size, sha1=sha1)1080 for _, content in binaries]
1069 for url, (size, sha1) in zip(binary_urls, meta)]1081 return [dict(url=url, size=size, sha1=sha1, sha256=sha256)
1082 for url, (size, sha1, sha256) in zip(binary_urls, meta)]
1070 return binary_urls1083 return binary_urls
10711084
10721085
10731086
=== modified file 'lib/lp/soyuz/tests/test_publishing_models.py'
--- lib/lp/soyuz/tests/test_publishing_models.py 2015-05-14 13:23:47 +0000
+++ lib/lp/soyuz/tests/test_publishing_models.py 2015-12-02 13:21:01 +0000
@@ -8,6 +8,7 @@
88
9from lp.app.errors import NotFoundError9from lp.app.errors import NotFoundError
10from lp.buildmaster.enums import BuildStatus10from lp.buildmaster.enums import BuildStatus
11from lp.registry.interfaces.sourcepackage import SourcePackageFileType
11from lp.services.database.constants import UTC_NOW12from lp.services.database.constants import UTC_NOW
12from lp.services.librarian.browser import ProxiedLibraryFileAlias13from lp.services.librarian.browser import ProxiedLibraryFileAlias
13from lp.services.webapp.publisher import canonical_url14from lp.services.webapp.publisher import canonical_url
@@ -154,12 +155,68 @@
154 spph = self.factory.makeSourcePackagePublishingHistory()155 spph = self.factory.makeSourcePackagePublishingHistory()
155 self.assertRaises(NotFoundError, spph.getFileByName, 'not-changelog')156 self.assertRaises(NotFoundError, spph.getFileByName, 'not-changelog')
156157
158 def getURLsForSPPH(self, spph, include_meta=False):
159 spr = spph.sourcepackagerelease
160 archive = spph.archive
161 urls = [ProxiedLibraryFileAlias(f.libraryfile, archive).http_url
162 for f in spr.files]
163
164 if include_meta:
165 meta = [(
166 f.libraryfile.content.filesize,
167 f.libraryfile.content.sha256,
168 ) for f in spr.files]
169
170 return [dict(url=url, size=size, sha256=sha256)
171 for url, (size, sha256) in zip(urls, meta)]
172 return urls
173
174 def makeSPPH(self, num_files=1):
175 archive = self.factory.makeArchive(private=False)
176 spr = self.factory.makeSourcePackageRelease(archive=archive)
177 filetypes = [
178 SourcePackageFileType.DSC, SourcePackageFileType.ORIG_TARBALL]
179 for count in range(num_files):
180 self.factory.makeSourcePackageReleaseFile(
181 sourcepackagerelease=spr, filetype=filetypes[count % 2])
182 return self.factory.makeSourcePackagePublishingHistory(
183 sourcepackagerelease=spr, archive=archive)
184
185 def test_sourceFileUrls_no_files(self):
186 spph = self.makeSPPH(num_files=0)
187
188 urls = spph.sourceFileUrls()
189
190 self.assertContentEqual([], urls)
191
192 def test_sourceFileUrls_one_file(self):
193 spph = self.makeSPPH(num_files=1)
194
195 urls = spph.sourceFileUrls()
196
197 self.assertContentEqual(self.getURLsForSPPH(spph), urls)
198
199 def test_sourceFileUrls_two_files(self):
200 spph = self.makeSPPH(num_files=2)
201
202 urls = spph.sourceFileUrls()
203
204 self.assertContentEqual(self.getURLsForSPPH(spph), urls)
205
206 def test_sourceFileUrls_include_meta(self):
207 spph = self.makeSPPH(num_files=2)
208
209 urls = spph.sourceFileUrls(include_meta=True)
210
211 self.assertContentEqual(
212 self.getURLsForSPPH(spph, include_meta=True), urls)
213
157214
158class TestBinaryPackagePublishingHistory(TestCaseWithFactory):215class TestBinaryPackagePublishingHistory(TestCaseWithFactory):
159216
160 layer = LaunchpadFunctionalLayer217 layer = LaunchpadFunctionalLayer
161218
162 def get_urls_for_bpph(self, bpph, include_meta=False):219 def getURLsForBPPH(self, bpph, include_meta=False):
163 bpr = bpph.binarypackagerelease220 bpr = bpph.binarypackagerelease
164 archive = bpph.archive221 archive = bpph.archive
165 urls = [ProxiedLibraryFileAlias(f.libraryfile, archive).http_url222 urls = [ProxiedLibraryFileAlias(f.libraryfile, archive).http_url
@@ -169,13 +226,14 @@
169 meta = [(226 meta = [(
170 f.libraryfile.content.filesize,227 f.libraryfile.content.filesize,
171 f.libraryfile.content.sha1,228 f.libraryfile.content.sha1,
229 f.libraryfile.content.sha256,
172 ) for f in bpr.files]230 ) for f in bpr.files]
173231
174 return [dict(url=url, size=size, sha1=sha1)232 return [dict(url=url, size=size, sha1=sha1, sha256=sha256)
175 for url, (size, sha1) in zip(urls, meta)]233 for url, (size, sha1, sha256) in zip(urls, meta)]
176 return urls234 return urls
177235
178 def make_bpph(self, num_binaries=1):236 def makeBPPH(self, num_binaries=1):
179 archive = self.factory.makeArchive(private=False)237 archive = self.factory.makeArchive(private=False)
180 bpr = self.factory.makeBinaryPackageRelease()238 bpr = self.factory.makeBinaryPackageRelease()
181 filetypes = [BinaryPackageFileType.DEB, BinaryPackageFileType.DDEB]239 filetypes = [BinaryPackageFileType.DEB, BinaryPackageFileType.DDEB]
@@ -186,40 +244,40 @@
186 binarypackagerelease=bpr, archive=archive)244 binarypackagerelease=bpr, archive=archive)
187245
188 def test_binaryFileUrls_no_binaries(self):246 def test_binaryFileUrls_no_binaries(self):
189 bpph = self.make_bpph(num_binaries=0)247 bpph = self.makeBPPH(num_binaries=0)
190248
191 urls = bpph.binaryFileUrls()249 urls = bpph.binaryFileUrls()
192250
193 self.assertContentEqual([], urls)251 self.assertContentEqual([], urls)
194252
195 def test_binaryFileUrls_one_binary(self):253 def test_binaryFileUrls_one_binary(self):
196 bpph = self.make_bpph(num_binaries=1)254 bpph = self.makeBPPH(num_binaries=1)
197255
198 urls = bpph.binaryFileUrls()256 urls = bpph.binaryFileUrls()
199257
200 self.assertContentEqual(self.get_urls_for_bpph(bpph), urls)258 self.assertContentEqual(self.getURLsForBPPH(bpph), urls)
201259
202 def test_binaryFileUrls_two_binaries(self):260 def test_binaryFileUrls_two_binaries(self):
203 bpph = self.make_bpph(num_binaries=2)261 bpph = self.makeBPPH(num_binaries=2)
204262
205 urls = bpph.binaryFileUrls()263 urls = bpph.binaryFileUrls()
206264
207 self.assertContentEqual(self.get_urls_for_bpph(bpph), urls)265 self.assertContentEqual(self.getURLsForBPPH(bpph), urls)
208266
209 def test_binaryFileUrls_include_meta(self):267 def test_binaryFileUrls_include_meta(self):
210 bpph = self.make_bpph(num_binaries=2)268 bpph = self.makeBPPH(num_binaries=2)
211269
212 urls = bpph.binaryFileUrls(include_meta=True)270 urls = bpph.binaryFileUrls(include_meta=True)
213271
214 self.assertContentEqual(272 self.assertContentEqual(
215 self.get_urls_for_bpph(bpph, include_meta=True), urls)273 self.getURLsForBPPH(bpph, include_meta=True), urls)
216274
217 def test_binaryFileUrls_removed(self):275 def test_binaryFileUrls_removed(self):
218 # binaryFileUrls returns URLs even if the files have been removed276 # binaryFileUrls returns URLs even if the files have been removed
219 # from the published archive.277 # from the published archive.
220 bpph = self.make_bpph(num_binaries=2)278 bpph = self.makeBPPH(num_binaries=2)
221 expected_urls = self.get_urls_for_bpph(bpph)279 expected_urls = self.getURLsForBPPH(bpph)
222 expected_urls_meta = self.get_urls_for_bpph(bpph, include_meta=True)280 expected_urls_meta = self.getURLsForBPPH(bpph, include_meta=True)
223 self.assertContentEqual(expected_urls, bpph.binaryFileUrls())281 self.assertContentEqual(expected_urls, bpph.binaryFileUrls())
224 self.assertContentEqual(282 self.assertContentEqual(
225 expected_urls_meta, bpph.binaryFileUrls(include_meta=True))283 expected_urls_meta, bpph.binaryFileUrls(include_meta=True))
226284
=== modified file 'lib/lp/testing/__init__.py'
--- lib/lp/testing/__init__.py 2015-10-04 01:28:19 +0000
+++ lib/lp/testing/__init__.py 2015-12-02 13:21:01 +0000
@@ -425,7 +425,8 @@
425425
426426
427def record_two_runs(tested_method, item_creator, first_round_number,427def record_two_runs(tested_method, item_creator, first_round_number,
428 second_round_number=None, login_method=None):428 second_round_number=None, login_method=None,
429 record_request=False):
429 """A helper that returns the two storm statement recorders430 """A helper that returns the two storm statement recorders
430 obtained when running tested_method after having run the431 obtained when running tested_method after having run the
431 method {item_creator} {first_round_number} times and then432 method {item_creator} {first_round_number} times and then
@@ -435,9 +436,17 @@
435 If {login_method} is not None, it is called before each batch of436 If {login_method} is not None, it is called before each batch of
436 {item_creator} calls.437 {item_creator} calls.
437438
439 If {record_request} is True, `RequestTimelineCollector` is used to get
440 the query counts, so {tested_method} should make a web request.
441 Otherwise, `StormStatementRecorder` is used to get the query count.
442
438 :return: a tuple containing the two recorders obtained by the successive443 :return: a tuple containing the two recorders obtained by the successive
439 runs.444 runs.
440 """445 """
446 if record_request:
447 recorder_factory = RequestTimelineCollector
448 else:
449 recorder_factory = StormStatementRecorder
441 if login_method is not None:450 if login_method is not None:
442 login_method()451 login_method()
443 for i in range(first_round_number):452 for i in range(first_round_number):
@@ -449,7 +458,7 @@
449 if queryInteraction() is not None:458 if queryInteraction() is not None:
450 clear_permission_cache()459 clear_permission_cache()
451 getUtility(ILaunchpadCelebrities).clearCache()460 getUtility(ILaunchpadCelebrities).clearCache()
452 with StormStatementRecorder() as recorder1:461 with recorder_factory() as recorder1:
453 tested_method()462 tested_method()
454 # Run {item_creator} {second_round_number} more times.463 # Run {item_creator} {second_round_number} more times.
455 if second_round_number is None:464 if second_round_number is None:
@@ -463,7 +472,7 @@
463 if queryInteraction() is not None:472 if queryInteraction() is not None:
464 clear_permission_cache()473 clear_permission_cache()
465 getUtility(ILaunchpadCelebrities).clearCache()474 getUtility(ILaunchpadCelebrities).clearCache()
466 with StormStatementRecorder() as recorder2:475 with recorder_factory() as recorder2:
467 tested_method()476 tested_method()
468 return recorder1, recorder2477 return recorder1, recorder2
469478