Merge lp:~cjwatson/launchpad/snap-extend-find-by-url into lp:launchpad
- snap-extend-find-by-url
- Merge into devel
Proposed by
Colin Watson
Status: | Merged |
---|---|
Merged at revision: | 18324 |
Proposed branch: | lp:~cjwatson/launchpad/snap-extend-find-by-url |
Merge into: | lp:launchpad |
Diff against target: |
295 lines (+190/-22) 3 files modified
lib/lp/snappy/interfaces/snap.py (+25/-2) lib/lp/snappy/model/snap.py (+20/-9) lib/lp/snappy/tests/test_snap.py (+145/-11) |
To merge this branch: | bzr merge lp:~cjwatson/launchpad/snap-extend-find-by-url |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
William Grant | code | Approve | |
Review via email: mp+316338@code.launchpad.net |
Commit message
Allow limiting SnapSet.findByURL results by owner; add SnapSet.
Description of the change
build.snapcraft.io is going to need to show a dashboard of a user's repositories, including which of them are already enabled. We'll of course need to do lots of caching, but we'll also need a bulk interface to let us discover which snaps exist in Launchpad for a set of GitHub repositories. I think it's best to just do this by URL prefix.
To post a comment you must log in.
Revision history for this message
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/snappy/interfaces/snap.py' |
2 | --- lib/lp/snappy/interfaces/snap.py 2017-02-01 06:31:30 +0000 |
3 | +++ lib/lp/snappy/interfaces/snap.py 2017-02-03 14:33:36 +0000 |
4 | @@ -655,12 +655,14 @@ |
5 | :raises BadSnapSearchContext: if the context is not understood. |
6 | """ |
7 | |
8 | - @operation_parameters(url=TextLine(title=_("The URL to search for."))) |
9 | + @operation_parameters( |
10 | + url=TextLine(title=_("The URL to search for.")), |
11 | + owner=Reference(IPerson, title=_("Owner"), required=False)) |
12 | @call_with(visible_by_user=REQUEST_USER) |
13 | @operation_returns_collection_of(ISnap) |
14 | @export_read_operation() |
15 | @operation_for_version("devel") |
16 | - def findByURL(url, visible_by_user=None): |
17 | + def findByURL(url, owner=None, visible_by_user=None): |
18 | """Return all snap packages that build from the given URL. |
19 | |
20 | This currently only works for packages that build directly from a |
21 | @@ -668,6 +670,27 @@ |
22 | hosted in Launchpad. |
23 | |
24 | :param url: A URL. |
25 | + :param owner: Only return packages owned by this user. |
26 | + :param visible_by_user: If not None, only return packages visible by |
27 | + this user; otherwise, only return publicly-visible packages. |
28 | + """ |
29 | + |
30 | + @operation_parameters( |
31 | + url_prefix=TextLine(title=_("The URL prefix to search for.")), |
32 | + owner=Reference(IPerson, title=_("Owner"), required=False)) |
33 | + @call_with(visible_by_user=REQUEST_USER) |
34 | + @operation_returns_collection_of(ISnap) |
35 | + @export_read_operation() |
36 | + @operation_for_version("devel") |
37 | + def findByURLPrefix(url_prefix, owner=None, visible_by_user=None): |
38 | + """Return all snap packages that build from a URL with this prefix. |
39 | + |
40 | + This currently only works for packages that build directly from a |
41 | + URL, rather than being linked to a Bazaar branch or Git repository |
42 | + hosted in Launchpad. |
43 | + |
44 | + :param url_prefix: A URL prefix. |
45 | + :param owner: Only return packages owned by this user. |
46 | :param visible_by_user: If not None, only return packages visible by |
47 | this user; otherwise, only return publicly-visible packages. |
48 | """ |
49 | |
50 | === modified file 'lib/lp/snappy/model/snap.py' |
51 | --- lib/lp/snappy/model/snap.py 2017-01-04 22:59:04 +0000 |
52 | +++ lib/lp/snappy/model/snap.py 2017-02-03 14:33:36 +0000 |
53 | @@ -799,26 +799,37 @@ |
54 | snaps.order_by(Desc(Snap.date_last_modified)) |
55 | return snaps |
56 | |
57 | - def findByURL(self, url, visible_by_user=None): |
58 | - """See `ISnapSet`.""" |
59 | - clauses = [Snap.git_repository_url == url] |
60 | + def _findByURLVisibilityClause(self, visible_by_user): |
61 | # XXX cjwatson 2016-11-25: This is in principle a poor query, but we |
62 | # don't yet have the access grant infrastructure to do better, and |
63 | - # in any case since we're querying for a single URL the numbers |
64 | - # involved should be very small. |
65 | + # in any case the numbers involved should be very small. |
66 | if visible_by_user is None: |
67 | - visibility_clause = Snap.private == False |
68 | + return Snap.private == False |
69 | else: |
70 | roles = IPersonRoles(visible_by_user) |
71 | if roles.in_admin or roles.in_commercial_admin: |
72 | - visibility_clause = True |
73 | + return True |
74 | else: |
75 | - visibility_clause = Or( |
76 | + return Or( |
77 | Snap.private == False, |
78 | Snap.owner_id.is_in(Select( |
79 | TeamParticipation.teamID, |
80 | TeamParticipation.person == visible_by_user))) |
81 | - clauses.append(visibility_clause) |
82 | + |
83 | + def findByURL(self, url, owner=None, visible_by_user=None): |
84 | + """See `ISnapSet`.""" |
85 | + clauses = [Snap.git_repository_url == url] |
86 | + if owner is not None: |
87 | + clauses.append(Snap.owner == owner) |
88 | + clauses.append(self._findByURLVisibilityClause(visible_by_user)) |
89 | + return IStore(Snap).find(Snap, *clauses) |
90 | + |
91 | + def findByURLPrefix(self, url_prefix, owner=None, visible_by_user=None): |
92 | + """See `ISnapSet`.""" |
93 | + clauses = [Snap.git_repository_url.startswith(url_prefix)] |
94 | + if owner is not None: |
95 | + clauses.append(Snap.owner == owner) |
96 | + clauses.append(self._findByURLVisibilityClause(visible_by_user)) |
97 | return IStore(Snap).find(Snap, *clauses) |
98 | |
99 | def preloadDataForSnaps(self, snaps, user=None): |
100 | |
101 | === modified file 'lib/lp/snappy/tests/test_snap.py' |
102 | --- lib/lp/snappy/tests/test_snap.py 2017-01-04 20:52:12 +0000 |
103 | +++ lib/lp/snappy/tests/test_snap.py 2017-02-03 14:33:36 +0000 |
104 | @@ -832,16 +832,48 @@ |
105 | def test_findByURL(self): |
106 | # ISnapSet.findByURL returns visible Snaps with the given URL. |
107 | urls = [u"https://git.example.org/foo", u"https://git.example.org/bar"] |
108 | - snaps = [] |
109 | - for url in urls: |
110 | - snaps.append(self.factory.makeSnap( |
111 | - git_ref=self.factory.makeGitRefRemote(repository_url=url))) |
112 | - snaps.append( |
113 | - self.factory.makeSnap(branch=self.factory.makeAnyBranch())) |
114 | - snaps.append( |
115 | - self.factory.makeSnap(git_ref=self.factory.makeGitRefs()[0])) |
116 | - self.assertContentEqual( |
117 | - [snaps[0]], getUtility(ISnapSet).findByURL(urls[0])) |
118 | + owners = [self.factory.makePerson() for i in range(2)] |
119 | + snaps = [] |
120 | + for url in urls: |
121 | + for owner in owners: |
122 | + snaps.append(self.factory.makeSnap( |
123 | + registrant=owner, owner=owner, |
124 | + git_ref=self.factory.makeGitRefRemote(repository_url=url))) |
125 | + snaps.append( |
126 | + self.factory.makeSnap(branch=self.factory.makeAnyBranch())) |
127 | + snaps.append( |
128 | + self.factory.makeSnap(git_ref=self.factory.makeGitRefs()[0])) |
129 | + self.assertContentEqual( |
130 | + snaps[:2], getUtility(ISnapSet).findByURL(urls[0])) |
131 | + self.assertContentEqual( |
132 | + [snaps[0]], |
133 | + getUtility(ISnapSet).findByURL(urls[0], owner=owners[0])) |
134 | + |
135 | + def test_findByURLPrefix(self): |
136 | + # ISnapSet.findByURLPrefix returns visible Snaps with the given URL |
137 | + # prefix. |
138 | + urls = [ |
139 | + u"https://git.example.org/foo/a", |
140 | + u"https://git.example.org/foo/b", |
141 | + u"https://git.example.org/bar", |
142 | + ] |
143 | + owners = [self.factory.makePerson() for i in range(2)] |
144 | + snaps = [] |
145 | + for url in urls: |
146 | + for owner in owners: |
147 | + snaps.append(self.factory.makeSnap( |
148 | + registrant=owner, owner=owner, |
149 | + git_ref=self.factory.makeGitRefRemote(repository_url=url))) |
150 | + snaps.append( |
151 | + self.factory.makeSnap(branch=self.factory.makeAnyBranch())) |
152 | + snaps.append( |
153 | + self.factory.makeSnap(git_ref=self.factory.makeGitRefs()[0])) |
154 | + prefix = u"https://git.example.org/foo/" |
155 | + self.assertContentEqual( |
156 | + snaps[:4], getUtility(ISnapSet).findByURLPrefix(prefix)) |
157 | + self.assertContentEqual( |
158 | + [snaps[0], snaps[2]], |
159 | + getUtility(ISnapSet).findByURLPrefix(prefix, owner=owners[0])) |
160 | |
161 | def test__findStaleSnaps(self): |
162 | # Stale; not built automatically. |
163 | @@ -1325,8 +1357,10 @@ |
164 | for private in (False, True): |
165 | ref = self.factory.makeGitRefRemote(repository_url=url) |
166 | snaps.append(self.factory.makeSnap( |
167 | - registrant=person, git_ref=ref, private=private)) |
168 | + registrant=person, owner=person, git_ref=ref, |
169 | + private=private)) |
170 | with admin_logged_in(): |
171 | + person_urls = [api_url(person) for person in persons] |
172 | ws_snaps = [ |
173 | self.webservice.getAbsoluteUrl(api_url(snap)) |
174 | for snap in snaps] |
175 | @@ -1341,6 +1375,13 @@ |
176 | self.assertContentEqual( |
177 | [ws_snaps[0], ws_snaps[2]], |
178 | [entry["self_link"] for entry in response.jsonBody()["entries"]]) |
179 | + response = anon_webservice.named_get( |
180 | + "/+snaps", "findByURL", url=urls[0], owner=person_urls[0], |
181 | + api_version="devel") |
182 | + self.assertEqual(200, response.status) |
183 | + self.assertContentEqual( |
184 | + [ws_snaps[0]], |
185 | + [entry["self_link"] for entry in response.jsonBody()["entries"]]) |
186 | # persons[0] can see both public snaps with this URL, as well as |
187 | # their own private snap. |
188 | webservice = webservice_for_person( |
189 | @@ -1351,6 +1392,13 @@ |
190 | self.assertContentEqual( |
191 | ws_snaps[:3], |
192 | [entry["self_link"] for entry in response.jsonBody()["entries"]]) |
193 | + response = webservice.named_get( |
194 | + "/+snaps", "findByURL", url=urls[0], owner=person_urls[0], |
195 | + api_version="devel") |
196 | + self.assertEqual(200, response.status) |
197 | + self.assertContentEqual( |
198 | + ws_snaps[:2], |
199 | + [entry["self_link"] for entry in response.jsonBody()["entries"]]) |
200 | # Admins can see all snaps with this URL. |
201 | commercial_admin_webservice = webservice_for_person( |
202 | commercial_admin, permission=OAuthPermission.READ_PRIVATE) |
203 | @@ -1360,6 +1408,92 @@ |
204 | self.assertContentEqual( |
205 | ws_snaps[:4], |
206 | [entry["self_link"] for entry in response.jsonBody()["entries"]]) |
207 | + response = commercial_admin_webservice.named_get( |
208 | + "/+snaps", "findByURL", url=urls[0], owner=person_urls[0], |
209 | + api_version="devel") |
210 | + self.assertEqual(200, response.status) |
211 | + self.assertContentEqual( |
212 | + ws_snaps[:2], |
213 | + [entry["self_link"] for entry in response.jsonBody()["entries"]]) |
214 | + |
215 | + def test_findByURLPrefix(self): |
216 | + # lp.snaps.findByURLPrefix returns visible Snaps with the given URL |
217 | + # prefix. |
218 | + self.pushConfig("launchpad", default_batch_size=10) |
219 | + persons = [self.factory.makePerson(), self.factory.makePerson()] |
220 | + urls = [ |
221 | + u"https://git.example.org/foo/a", |
222 | + u"https://git.example.org/foo/b", |
223 | + u"https://git.example.org/bar", |
224 | + ] |
225 | + snaps = [] |
226 | + for url in urls: |
227 | + for person in persons: |
228 | + for private in (False, True): |
229 | + ref = self.factory.makeGitRefRemote(repository_url=url) |
230 | + snaps.append(self.factory.makeSnap( |
231 | + registrant=person, owner=person, git_ref=ref, |
232 | + private=private)) |
233 | + with admin_logged_in(): |
234 | + person_urls = [api_url(person) for person in persons] |
235 | + ws_snaps = [ |
236 | + self.webservice.getAbsoluteUrl(api_url(snap)) |
237 | + for snap in snaps] |
238 | + commercial_admin = ( |
239 | + getUtility(ILaunchpadCelebrities).commercial_admin.teamowner) |
240 | + logout() |
241 | + prefix = u"https://git.example.org/foo/" |
242 | + # Anonymous requests can only see public snaps. |
243 | + anon_webservice = LaunchpadWebServiceCaller("test", "") |
244 | + response = anon_webservice.named_get( |
245 | + "/+snaps", "findByURLPrefix", url_prefix=prefix, |
246 | + api_version="devel") |
247 | + self.assertEqual(200, response.status) |
248 | + self.assertContentEqual( |
249 | + [ws_snaps[i] for i in (0, 2, 4, 6)], |
250 | + [entry["self_link"] for entry in response.jsonBody()["entries"]]) |
251 | + response = anon_webservice.named_get( |
252 | + "/+snaps", "findByURLPrefix", url_prefix=prefix, |
253 | + owner=person_urls[0], api_version="devel") |
254 | + self.assertEqual(200, response.status) |
255 | + self.assertContentEqual( |
256 | + [ws_snaps[i] for i in (0, 4)], |
257 | + [entry["self_link"] for entry in response.jsonBody()["entries"]]) |
258 | + # persons[0] can see all public snaps with this URL prefix, as well |
259 | + # as their own matching private snaps. |
260 | + webservice = webservice_for_person( |
261 | + persons[0], permission=OAuthPermission.READ_PRIVATE) |
262 | + response = webservice.named_get( |
263 | + "/+snaps", "findByURLPrefix", url_prefix=prefix, |
264 | + api_version="devel") |
265 | + self.assertEqual(200, response.status) |
266 | + self.assertContentEqual( |
267 | + [ws_snaps[i] for i in (0, 1, 2, 4, 5, 6)], |
268 | + [entry["self_link"] for entry in response.jsonBody()["entries"]]) |
269 | + response = webservice.named_get( |
270 | + "/+snaps", "findByURLPrefix", url_prefix=prefix, |
271 | + owner=person_urls[0], api_version="devel") |
272 | + self.assertEqual(200, response.status) |
273 | + self.assertContentEqual( |
274 | + [ws_snaps[i] for i in (0, 1, 4, 5)], |
275 | + [entry["self_link"] for entry in response.jsonBody()["entries"]]) |
276 | + # Admins can see all snaps with this URL prefix. |
277 | + commercial_admin_webservice = webservice_for_person( |
278 | + commercial_admin, permission=OAuthPermission.READ_PRIVATE) |
279 | + response = commercial_admin_webservice.named_get( |
280 | + "/+snaps", "findByURLPrefix", url_prefix=prefix, |
281 | + api_version="devel") |
282 | + self.assertEqual(200, response.status) |
283 | + self.assertContentEqual( |
284 | + ws_snaps[:8], |
285 | + [entry["self_link"] for entry in response.jsonBody()["entries"]]) |
286 | + response = commercial_admin_webservice.named_get( |
287 | + "/+snaps", "findByURLPrefix", url_prefix=prefix, |
288 | + owner=person_urls[0], api_version="devel") |
289 | + self.assertEqual(200, response.status) |
290 | + self.assertContentEqual( |
291 | + [ws_snaps[i] for i in (0, 1, 4, 5)], |
292 | + [entry["self_link"] for entry in response.jsonBody()["entries"]]) |
293 | |
294 | def setProcessors(self, user, snap, names): |
295 | ws = webservice_for_person( |