Merge lp:~cjwatson/launchpad/snap-find-by-store-name into lp:launchpad

Proposed by Colin Watson
Status: Merged
Merged at revision: 19030
Proposed branch: lp:~cjwatson/launchpad/snap-find-by-store-name
Merge into: lp:launchpad
Diff against target: 297 lines (+152/-3)
4 files modified
database/schema/patch-2210-05-0.sql (+9/-0)
lib/lp/snappy/interfaces/snap.py (+17/-0)
lib/lp/snappy/model/snap.py (+11/-3)
lib/lp/snappy/tests/test_snap.py (+115/-0)
To merge this branch: bzr merge lp:~cjwatson/launchpad/snap-find-by-store-name
Reviewer Review Type Date Requested Status
William Grant code db Approve
Review via email: mp+370562@code.launchpad.net

Commit message

Add SnapSet.findByStoreName.

Description of the change

This will be useful for the next iteration of build.snapcraft.io, and is handy for ad-hoc operational queries as well.

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

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'database/schema/patch-2210-05-0.sql'
2--- database/schema/patch-2210-05-0.sql 1970-01-01 00:00:00 +0000
3+++ database/schema/patch-2210-05-0.sql 2019-08-22 10:39:42 +0000
4@@ -0,0 +1,9 @@
5+-- Copyright 2019 Canonical Ltd. This software is licensed under the
6+-- GNU Affero General Public License version 3 (see the file LICENSE).
7+
8+SET client_min_messages=ERROR;
9+
10+CREATE INDEX snap__git_repository_url__idx ON Snap (git_repository_url);
11+CREATE INDEX snap__store_name__idx ON Snap (store_name);
12+
13+INSERT INTO LaunchpadDatabaseRevision VALUES (2210, 05, 0);
14
15=== modified file 'lib/lp/snappy/interfaces/snap.py'
16--- lib/lp/snappy/interfaces/snap.py 2019-06-21 11:30:26 +0000
17+++ lib/lp/snappy/interfaces/snap.py 2019-08-22 10:39:42 +0000
18@@ -1005,6 +1005,23 @@
19 this user; otherwise, only return publicly-visible packages.
20 """
21
22+ @operation_parameters(
23+ store_name=TextLine(
24+ title=_("The registered store package name to search for.")),
25+ owner=Reference(IPerson, title=_("Owner"), required=False))
26+ @call_with(visible_by_user=REQUEST_USER)
27+ @operation_returns_collection_of(ISnap)
28+ @export_read_operation()
29+ @operation_for_version("devel")
30+ def findByStoreName(store_name, owner=None, visible_by_user=None):
31+ """Return all snap packages with the given store package name.
32+
33+ :param store_name: A registered store package name.
34+ :param owner: Only return packages owned by this user.
35+ :param visible_by_user: If not None, only return packages visible by
36+ this user; otherwise, only return publicly-visible packages.
37+ """
38+
39 def preloadDataForSnaps(snaps, user):
40 """Load the data related to a list of snap packages."""
41
42
43=== modified file 'lib/lp/snappy/model/snap.py'
44--- lib/lp/snappy/model/snap.py 2019-08-21 10:36:27 +0000
45+++ lib/lp/snappy/model/snap.py 2019-08-22 10:39:42 +0000
46@@ -1244,7 +1244,7 @@
47 snaps.order_by(Desc(Snap.date_last_modified))
48 return snaps
49
50- def _findByURLVisibilityClause(self, visible_by_user):
51+ def _findSnapVisibilityClause(self, visible_by_user):
52 # XXX cjwatson 2016-11-25: This is in principle a poor query, but we
53 # don't yet have the access grant infrastructure to do better, and
54 # in any case the numbers involved should be very small.
55@@ -1266,7 +1266,7 @@
56 clauses = [Snap.git_repository_url == url]
57 if owner is not None:
58 clauses.append(Snap.owner == owner)
59- clauses.append(self._findByURLVisibilityClause(visible_by_user))
60+ clauses.append(self._findSnapVisibilityClause(visible_by_user))
61 return IStore(Snap).find(Snap, *clauses)
62
63 def findByURLPrefix(self, url_prefix, owner=None, visible_by_user=None):
64@@ -1283,7 +1283,15 @@
65 clauses = [Or(*prefix_clauses)]
66 if owner is not None:
67 clauses.append(Snap.owner == owner)
68- clauses.append(self._findByURLVisibilityClause(visible_by_user))
69+ clauses.append(self._findSnapVisibilityClause(visible_by_user))
70+ return IStore(Snap).find(Snap, *clauses)
71+
72+ def findByStoreName(self, store_name, owner=None, visible_by_user=None):
73+ """See `ISnapSet`."""
74+ clauses = [Snap.store_name == store_name]
75+ if owner is not None:
76+ clauses.append(Snap.owner == owner)
77+ clauses.append(self._findSnapVisibilityClause(visible_by_user))
78 return IStore(Snap).find(Snap, *clauses)
79
80 def preloadDataForSnaps(self, snaps, user=None):
81
82=== modified file 'lib/lp/snappy/tests/test_snap.py'
83--- lib/lp/snappy/tests/test_snap.py 2019-08-21 10:36:27 +0000
84+++ lib/lp/snappy/tests/test_snap.py 2019-08-22 10:39:42 +0000
85@@ -1677,6 +1677,34 @@
86 [snaps[0], snaps[2], snaps[4], snaps[6]],
87 getUtility(ISnapSet).findByURLPrefixes(prefixes, owner=owners[0]))
88
89+ def test_findByStoreName(self):
90+ # ISnapSet.findByStoreName returns visible Snaps with the given
91+ # store name.
92+ store_names = ["foo", "bar"]
93+ owners = [self.factory.makePerson() for i in range(2)]
94+ snaps = []
95+ for store_name in store_names:
96+ for owner in owners:
97+ for private in (False, True):
98+ snaps.append(self.factory.makeSnap(
99+ registrant=owner, owner=owner, private=private,
100+ store_name=store_name))
101+ snaps.append(self.factory.makeSnap())
102+ self.assertContentEqual(
103+ [snaps[0], snaps[2]],
104+ getUtility(ISnapSet).findByStoreName(store_names[0]))
105+ with person_logged_in(owners[0]):
106+ self.assertContentEqual(
107+ snaps[:2],
108+ getUtility(ISnapSet).findByStoreName(
109+ store_names[0], owner=owners[0],
110+ visible_by_user=owners[0]))
111+ self.assertContentEqual(
112+ [snaps[2]],
113+ getUtility(ISnapSet).findByStoreName(
114+ store_names[0], owner=owners[1],
115+ visible_by_user=owners[0]))
116+
117 def test_getSnapcraftYaml_bzr_snap_snapcraft_yaml(self):
118 def getInventory(unique_name, dirname, *args, **kwargs):
119 if dirname == "snap":
120@@ -2732,6 +2760,7 @@
121 commercial_admin = (
122 getUtility(ILaunchpadCelebrities).commercial_admin.teamowner)
123 logout()
124+
125 # Anonymous requests can only see public snaps.
126 anon_webservice = LaunchpadWebServiceCaller("test", "")
127 response = anon_webservice.named_get(
128@@ -2741,6 +2770,7 @@
129 self.assertContentEqual(
130 [ws_snaps[0]],
131 [entry["self_link"] for entry in response.jsonBody()["entries"]])
132+
133 # persons[0] can see their own private snap as well, but not those
134 # for other people.
135 webservice = webservice_for_person(
136@@ -2759,6 +2789,7 @@
137 self.assertContentEqual(
138 [ws_snaps[2]],
139 [entry["self_link"] for entry in response.jsonBody()["entries"]])
140+
141 # Admins can see all snaps.
142 commercial_admin_webservice = webservice_for_person(
143 commercial_admin, permission=OAuthPermission.READ_PRIVATE)
144@@ -2797,6 +2828,7 @@
145 commercial_admin = (
146 getUtility(ILaunchpadCelebrities).commercial_admin.teamowner)
147 logout()
148+
149 # Anonymous requests can only see public snaps.
150 anon_webservice = LaunchpadWebServiceCaller("test", "")
151 response = anon_webservice.named_get(
152@@ -2812,6 +2844,7 @@
153 self.assertContentEqual(
154 [ws_snaps[0]],
155 [entry["self_link"] for entry in response.jsonBody()["entries"]])
156+
157 # persons[0] can see both public snaps with this URL, as well as
158 # their own private snap.
159 webservice = webservice_for_person(
160@@ -2829,6 +2862,7 @@
161 self.assertContentEqual(
162 ws_snaps[:2],
163 [entry["self_link"] for entry in response.jsonBody()["entries"]])
164+
165 # Admins can see all snaps with this URL.
166 commercial_admin_webservice = webservice_for_person(
167 commercial_admin, permission=OAuthPermission.READ_PRIVATE)
168@@ -2873,6 +2907,7 @@
169 getUtility(ILaunchpadCelebrities).commercial_admin.teamowner)
170 logout()
171 prefix = "https://git.example.org/foo/"
172+
173 # Anonymous requests can only see public snaps.
174 anon_webservice = LaunchpadWebServiceCaller("test", "")
175 response = anon_webservice.named_get(
176@@ -2889,6 +2924,7 @@
177 self.assertContentEqual(
178 [ws_snaps[i] for i in (0, 4)],
179 [entry["self_link"] for entry in response.jsonBody()["entries"]])
180+
181 # persons[0] can see all public snaps with this URL prefix, as well
182 # as their own matching private snaps.
183 webservice = webservice_for_person(
184@@ -2907,6 +2943,7 @@
185 self.assertContentEqual(
186 [ws_snaps[i] for i in (0, 1, 4, 5)],
187 [entry["self_link"] for entry in response.jsonBody()["entries"]])
188+
189 # Admins can see all snaps with this URL prefix.
190 commercial_admin_webservice = webservice_for_person(
191 commercial_admin, permission=OAuthPermission.READ_PRIVATE)
192@@ -2955,6 +2992,7 @@
193 logout()
194 prefixes = [
195 "https://git.example.org/foo/", "https://git.example.org/bar/"]
196+
197 # Anonymous requests can only see public snaps.
198 anon_webservice = LaunchpadWebServiceCaller("test", "")
199 response = anon_webservice.named_get(
200@@ -2971,6 +3009,7 @@
201 self.assertContentEqual(
202 [ws_snaps[i] for i in (0, 4, 8, 12)],
203 [entry["self_link"] for entry in response.jsonBody()["entries"]])
204+
205 # persons[0] can see all public snaps with any of these URL
206 # prefixes, as well as their own matching private snaps.
207 webservice = webservice_for_person(
208@@ -2989,6 +3028,7 @@
209 self.assertContentEqual(
210 [ws_snaps[i] for i in (0, 1, 4, 5, 8, 9, 12, 13)],
211 [entry["self_link"] for entry in response.jsonBody()["entries"]])
212+
213 # Admins can see all snaps with any of these URL prefixes.
214 commercial_admin_webservice = webservice_for_person(
215 commercial_admin, permission=OAuthPermission.READ_PRIVATE)
216@@ -3007,6 +3047,81 @@
217 [ws_snaps[i] for i in (0, 1, 4, 5, 8, 9, 12, 13)],
218 [entry["self_link"] for entry in response.jsonBody()["entries"]])
219
220+ def test_findByStoreName(self):
221+ # lp.snaps.findByStoreName returns visible Snaps with the given
222+ # store name.
223+ persons = [self.factory.makePerson(), self.factory.makePerson()]
224+ store_names = ["foo", "bar"]
225+ snaps = []
226+ for store_name in store_names:
227+ for person in persons:
228+ for private in (False, True):
229+ snaps.append(self.factory.makeSnap(
230+ registrant=person, owner=person, private=private,
231+ store_name=store_name))
232+ with admin_logged_in():
233+ person_urls = [api_url(person) for person in persons]
234+ ws_snaps = [
235+ self.webservice.getAbsoluteUrl(api_url(snap))
236+ for snap in snaps]
237+ commercial_admin = (
238+ getUtility(ILaunchpadCelebrities).commercial_admin.teamowner)
239+ logout()
240+
241+ # Anonymous requests can only see public snaps.
242+ anon_webservice = LaunchpadWebServiceCaller("test", "")
243+ response = anon_webservice.named_get(
244+ "/+snaps", "findByStoreName", store_name=store_names[0],
245+ api_version="devel")
246+ self.assertEqual(200, response.status)
247+ self.assertContentEqual(
248+ [ws_snaps[0], ws_snaps[2]],
249+ [entry["self_link"] for entry in response.jsonBody()["entries"]])
250+ response = anon_webservice.named_get(
251+ "/+snaps", "findByStoreName", store_name=store_names[0],
252+ owner=person_urls[0], api_version="devel")
253+ self.assertEqual(200, response.status)
254+ self.assertContentEqual(
255+ [ws_snaps[0]],
256+ [entry["self_link"] for entry in response.jsonBody()["entries"]])
257+
258+ # persons[0] can see both public snaps with this store name, as well
259+ # as their own private snap.
260+ webservice = webservice_for_person(
261+ persons[0], permission=OAuthPermission.READ_PRIVATE)
262+ response = webservice.named_get(
263+ "/+snaps", "findByStoreName", store_name=store_names[0],
264+ api_version="devel")
265+ self.assertEqual(200, response.status)
266+ self.assertContentEqual(
267+ ws_snaps[:3],
268+ [entry["self_link"] for entry in response.jsonBody()["entries"]])
269+ response = webservice.named_get(
270+ "/+snaps", "findByStoreName", store_name=store_names[0],
271+ owner=person_urls[0], api_version="devel")
272+ self.assertEqual(200, response.status)
273+ self.assertContentEqual(
274+ ws_snaps[:2],
275+ [entry["self_link"] for entry in response.jsonBody()["entries"]])
276+
277+ # Admins can see all snaps with this store name.
278+ commercial_admin_webservice = webservice_for_person(
279+ commercial_admin, permission=OAuthPermission.READ_PRIVATE)
280+ response = commercial_admin_webservice.named_get(
281+ "/+snaps", "findByStoreName", store_name=store_names[0],
282+ api_version="devel")
283+ self.assertEqual(200, response.status)
284+ self.assertContentEqual(
285+ ws_snaps[:4],
286+ [entry["self_link"] for entry in response.jsonBody()["entries"]])
287+ response = commercial_admin_webservice.named_get(
288+ "/+snaps", "findByStoreName", store_name=store_names[0],
289+ owner=person_urls[0], api_version="devel")
290+ self.assertEqual(200, response.status)
291+ self.assertContentEqual(
292+ ws_snaps[:2],
293+ [entry["self_link"] for entry in response.jsonBody()["entries"]])
294+
295 def setProcessors(self, user, snap, names):
296 ws = webservice_for_person(
297 user, permission=OAuthPermission.WRITE_PUBLIC)