canonicalwebteam.store-api 5.0.0__py3-none-any.whl → 6.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,23 +1,23 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: canonicalwebteam.store-api
3
- Version: 5.0.0
3
+ Version: 6.1.0
4
4
  Summary:
5
5
  License: LGPL-3.0
6
6
  Author: Canonical Web Team
7
7
  Author-email: webteam@canonical.com
8
- Requires-Python: >=3.6,<4.0
8
+ Requires-Python: >=3.8,<4.0
9
9
  Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)
10
10
  Classifier: Programming Language :: Python :: 3
11
- Classifier: Programming Language :: Python :: 3.6
12
- Classifier: Programming Language :: Python :: 3.7
13
11
  Classifier: Programming Language :: Python :: 3.8
14
12
  Classifier: Programming Language :: Python :: 3.9
15
13
  Classifier: Programming Language :: Python :: 3.10
16
14
  Classifier: Programming Language :: Python :: 3.11
17
15
  Classifier: Programming Language :: Python :: 3.12
18
- Requires-Dist: coverage (>=5.4,<6.0)
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Requires-Dist: coverage (>=7.0,<8.0)
18
+ Requires-Dist: mypy (>=1.14.1,<2.0.0)
19
19
  Requires-Dist: pymacaroons (==0.13.0)
20
- Requires-Dist: requests (>=2.24.0,<3.0.0)
20
+ Requires-Dist: requests (>=2.32.2,<3.0.0)
21
21
  Description-Content-Type: text/markdown
22
22
 
23
23
  # Canonical Store Api - Python package
@@ -0,0 +1,11 @@
1
+ canonicalwebteam/__init__.py,sha256=ED6jHcYiuYpr_0vjGz0zx2lrrmJT9sDJCzIljoDfmlM,65
2
+ canonicalwebteam/exceptions.py,sha256=A49LRhiEIfXNB2zC-zNcZEcdh0ugw6G7fBbXbs-BWxA,1517
3
+ canonicalwebteam/store_api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ canonicalwebteam/store_api/base.py,sha256=DmfVcefWxGYNQxzYmaDVhmthTCg2vEwEnvczU9Bk5zE,2414
5
+ canonicalwebteam/store_api/dashboard.py,sha256=U8ccu2Sz-QjsYa-MHNt1gzzJ5iJQ1Xt_prTsgUP8EnY,22405
6
+ canonicalwebteam/store_api/devicegw.py,sha256=9HapLDDyHlmzCu0FvZzbNyVc0kDPi28TN77F4mJ_8Gg,9062
7
+ canonicalwebteam/store_api/publishergw.py,sha256=0NucF_LZJnmwy3YOmfK_YQlLJ_0qMLXjalFXbhMpEqk,28695
8
+ canonicalwebteam_store_api-6.1.0.dist-info/LICENSE,sha256=46mU2C5kSwOnkqkw9XQAJlhBL2JAf1_uCD8lVcXyMRg,7652
9
+ canonicalwebteam_store_api-6.1.0.dist-info/METADATA,sha256=_hh_7AhPgyGIDZUr9ogmWgJMhfcQHhLlivFIb3juLZ0,2253
10
+ canonicalwebteam_store_api-6.1.0.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
11
+ canonicalwebteam_store_api-6.1.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.9.0
2
+ Generator: poetry-core 2.1.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,420 +0,0 @@
1
- from canonicalwebteam.store_api.exceptions import (
2
- StoreApiConnectionError,
3
- StoreApiResourceNotFound,
4
- StoreApiResponseDecodeError,
5
- StoreApiResponseError,
6
- StoreApiResponseErrorList,
7
- PublisherAgreementNotSigned,
8
- PublisherMacaroonRefreshRequired,
9
- PublisherMissingUsername,
10
- )
11
- from pymacaroons import Macaroon
12
-
13
-
14
- class Publisher:
15
- def __init__(self, session):
16
- self.config = {1: {"base_url": ""}}
17
- self.session = session
18
-
19
- def process_response(self, response):
20
- # 5xx responses are not in JSON format
21
- if response.status_code >= 500:
22
- raise StoreApiConnectionError("Service Unavailable")
23
-
24
- try:
25
- body = response.json()
26
- except ValueError as decode_error:
27
- api_error_exception = StoreApiResponseDecodeError(
28
- "JSON decoding failed: {}".format(decode_error)
29
- )
30
- raise api_error_exception
31
-
32
- if self._is_macaroon_expired(response.headers):
33
- raise PublisherMacaroonRefreshRequired
34
-
35
- if not response.ok:
36
- error_list = (
37
- body["error_list"]
38
- if "error_list" in body
39
- else body.get("error-list")
40
- )
41
- if "error_list" in body or "error-list" in body:
42
- for error in error_list:
43
- if error["code"] == "user-missing-latest-tos":
44
- raise PublisherAgreementNotSigned
45
- if error["code"] == "user-not-ready":
46
- if "has not signed agreement" in error["message"]:
47
- raise PublisherAgreementNotSigned
48
- elif "username" in error["message"]:
49
- raise PublisherMissingUsername
50
- if error["code"] == "resource-not-found":
51
- raise StoreApiResourceNotFound
52
-
53
- raise StoreApiResponseErrorList(
54
- "The api returned a list of errors",
55
- response.status_code,
56
- error_list,
57
- )
58
- elif not body:
59
- raise StoreApiResponseError(
60
- "Unknown error from api", response.status_code
61
- )
62
-
63
- return body
64
-
65
- def get_endpoint_url(self, endpoint, api_version=1):
66
- base_url = self.config[api_version]["base_url"]
67
- return f"{base_url}{endpoint}"
68
-
69
- def _get_authorization_header(self, session):
70
- """
71
- Bind root and discharge macaroons and return the authorization header.
72
- """
73
- if "macaroon_root" in session:
74
- root = session["macaroon_root"]
75
- discharge = session["macaroon_discharge"]
76
-
77
- bound = (
78
- Macaroon.deserialize(root)
79
- .prepare_for_request(Macaroon.deserialize(discharge))
80
- .serialize()
81
- )
82
-
83
- return {
84
- "Authorization": f"macaroon root={root}, discharge={bound}"
85
- }
86
- # With Candid the header is Macaroons
87
- elif "macaroons" in session:
88
- return {"Macaroons": session["macaroons"]}
89
-
90
- def _is_macaroon_expired(self, headers):
91
- """
92
- Returns True if the macaroon needs to be refreshed from
93
- the header response.
94
- """
95
- return headers.get("WWW-Authenticate") == ("Macaroon needs_refresh=1")
96
-
97
- def get_account(self, session):
98
- """
99
- Documentation:
100
- https://dashboard.snapcraft.io/docs/reference/v1/account.html#get--dev-api-account
101
- Endpoint: [GET] https://dashboard.snapcraft.io/dev/api/account
102
- """
103
- headers = self._get_authorization_header(session)
104
- response = self.session.get(
105
- url=self.get_endpoint_url("account"), headers=headers
106
- )
107
-
108
- return self.process_response(response)
109
-
110
- def get_account_snaps(self, session):
111
- """
112
- Returns the snaps associated with a user account
113
- Documentation:
114
- https://dashboard.snapcraft.io/docs/reference/v1/account.html#get--dev-api-account
115
- Endpoint: [GET] https://dashboard.snapcraft.io/dev/api/account
116
- """
117
- return self.get_account(session).get("snaps", {}).get("16", {})
118
-
119
- def get_agreement(self, session):
120
- """
121
- Documentation:
122
- https://dashboard.snapcraft.io/docs/reference/v1/snap.html#release-a-snap-build-to-a-channel
123
- Endpoint: [GET] https://dashboard.snapcraft.io/dev/api/agreement
124
- """
125
- headers = self._get_authorization_header(session)
126
- agreement_response = self.session.get(
127
- url=self.get_endpoint_url("agreement/"), headers=headers
128
- )
129
-
130
- if self._is_macaroon_expired(agreement_response.headers):
131
- raise PublisherMacaroonRefreshRequired
132
-
133
- return agreement_response.json()
134
-
135
- def post_agreement(self, session, agreed):
136
- """
137
- Documentation:
138
- https://dashboard.snapcraft.io/docs/reference/v1/snap.html#release-a-snap-build-to-a-channel
139
- Endpoint: [POST] https://dashboard.snapcraft.io/dev/api/agreement
140
- """
141
- headers = self._get_authorization_header(session)
142
-
143
- json = {"latest_tos_accepted": agreed}
144
- agreement_response = self.session.post(
145
- url=self.get_endpoint_url("agreement/"), headers=headers, json=json
146
- )
147
-
148
- return self.process_response(agreement_response)
149
-
150
- def post_username(self, session, username):
151
- """
152
- Documentation:
153
- https://dashboard.snapcraft.io/docs/reference/v1/account.html#get--dev-api-account
154
- Endpoint: [PATCH] https://dashboard.snapcraft.io/dev/api/account
155
- """
156
- headers = self._get_authorization_header(session)
157
- json = {"short_namespace": username}
158
- username_response = self.session.patch(
159
- url=self.get_endpoint_url("account"), headers=headers, json=json
160
- )
161
-
162
- if username_response.status_code == 204:
163
- return {}
164
- else:
165
- return self.process_response(username_response)
166
-
167
- def get_publisher_metrics(self, session, json):
168
- """
169
- Documentation:
170
- https://dashboard.snapcraft.io/docs/reference/v1/snap.html#fetch-metrics-for-snaps
171
- Endpoint: [POST] https://dashboard.snapcraft.io/dev/api/snaps/metrics
172
- """
173
- headers = self._get_authorization_header(session)
174
- headers["Content-Type"] = "application/json"
175
-
176
- metrics_response = self.session.post(
177
- url=self.get_endpoint_url("snaps/metrics"),
178
- headers=headers,
179
- json=json,
180
- )
181
-
182
- return self.process_response(metrics_response)
183
-
184
- def post_register_name(
185
- self,
186
- session,
187
- snap_name,
188
- registrant_comment=None,
189
- is_private=False,
190
- store=None,
191
- ):
192
- """
193
- Documentation:
194
- https://dashboard.snapcraft.io/docs/reference/v1/snap.html#register-a-snap-name
195
- Endpoint: [POST] https://dashboard.snapcraft.io/dev/api/register-name/
196
- """
197
- json = {"snap_name": snap_name}
198
-
199
- if registrant_comment:
200
- json["registrant_comment"] = registrant_comment
201
-
202
- if is_private:
203
- json["is_private"] = is_private
204
-
205
- if store:
206
- json["store"] = store
207
-
208
- response = self.session.post(
209
- url=self.get_endpoint_url("register-name/"),
210
- headers=self._get_authorization_header(session),
211
- json=json,
212
- )
213
-
214
- return self.process_response(response)
215
-
216
- def post_register_name_dispute(self, session, snap_name, claim_comment):
217
- """
218
- Documentation:
219
- https://dashboard.snapcraft.io/docs/reference/v1/snap.html#register-a-snap-name-dispute
220
- Endpoint: [POST]
221
- https://dashboard.snapcraft.io/dev/api/register-name-dispute
222
- """
223
- json = {"snap_name": snap_name, "comment": claim_comment}
224
-
225
- response = self.session.post(
226
- url=self.get_endpoint_url("register-name-dispute/"),
227
- headers=self._get_authorization_header(session),
228
- json=json,
229
- )
230
-
231
- return self.process_response(response)
232
-
233
- def get_snap_info(self, snap_name, session):
234
- """
235
- Documentation:
236
- https://dashboard.snapcraft.io/docs/reference/v1/snap.html#obtaining-information-about-a-snap
237
- Endpoint: [GET]
238
- https://dashboard.snapcraft.io/dev/api/snaps/info/{snap_name}
239
- """
240
- response = self.session.get(
241
- url=self.get_endpoint_url(f"snaps/info/{snap_name}"),
242
- headers=self._get_authorization_header(session),
243
- )
244
-
245
- return self.process_response(response)
246
-
247
- def get_package_upload_macaroon(self, session, snap_name, channels):
248
- """
249
- Documentation:
250
- https://dashboard.snapcraft.io/docs/reference/v1/macaroon.html#request-a-macaroon
251
- Endpoint: [POST] https://dashboard.snapcraft.io/dev/api/acl/
252
- """
253
- json = {
254
- "packages": [{"name": snap_name, "series": "16"}],
255
- "permissions": ["package_upload"],
256
- "channels": channels,
257
- }
258
-
259
- response = self.session.post(
260
- url=self.get_endpoint_url("acl/"),
261
- headers=self._get_authorization_header(session),
262
- json=json,
263
- )
264
-
265
- return self.process_response(response)
266
-
267
- def get_snap_id(self, snap_name, session):
268
- """
269
- Documentation:
270
- https://dashboard.snapcraft.io/docs/reference/v1/snap.html#obtaining-information-about-a-snap
271
- Endpoint: https://dashboard.snapcraft.io/dev/api/snaps/info/{snap_name}
272
- """
273
- snap_info = self.get_snap_info(snap_name, session)
274
-
275
- return snap_info["snap_id"]
276
-
277
- def snap_metadata(self, snap_id, session, json=None):
278
- """
279
- Documentation:
280
- https://dashboard.snapcraft.io/docs/reference/v1/snap.html#managing-snap-metadata
281
- Endpoint: [PUT]
282
- https://dashboard.snapcraft.io/dev/api/snaps/{snap_id}/metadata
283
- """
284
- method = "PUT" if json is not None else None
285
-
286
- metadata_response = self.session.request(
287
- method=method,
288
- url=self.get_endpoint_url(f"snaps/{snap_id}/metadata"),
289
- params={"conflict_on_update": "true"},
290
- headers=self._get_authorization_header(session),
291
- json=json,
292
- )
293
-
294
- return self.process_response(metadata_response)
295
-
296
- def snap_screenshots(self, snap_id, session, data=None, files=None):
297
- """
298
- Documentation:
299
- https://dashboard.snapcraft.io/docs/reference/v1/snap.html#managing-snap-metadata
300
- Endpoint: [GET, PUT]
301
- https://dashboard.snapcraft.io/dev/api/snaps/{snap_id}/binary-metadata
302
- """
303
- method = "GET"
304
- files_array = None
305
- headers = self._get_authorization_header(session)
306
- headers["Accept"] = "application/json"
307
-
308
- if data:
309
- method = "PUT"
310
-
311
- files_array = []
312
- if files:
313
- for f in files:
314
- files_array.append(
315
- (f.filename, (f.filename, f.stream, f.mimetype))
316
- )
317
- else:
318
- # API requires a multipart request, but we have no files to
319
- # push https://github.com/requests/requests/issues/1081
320
- files_array = {"info": ("", data["info"])}
321
- data = None
322
-
323
- screenshot_response = self.session.request(
324
- method=method,
325
- url=self.get_endpoint_url(f"snaps/{snap_id}/binary-metadata"),
326
- params={"conflict_on_update": "true"},
327
- headers=headers,
328
- data=data,
329
- files=files_array,
330
- )
331
-
332
- return self.process_response(screenshot_response)
333
-
334
- def get_snap_revision(self, session, snap_id, revision_id):
335
- """
336
- Documentation:
337
- https://dashboard.snapcraft.io/docs/reference/v1/macaroon.html#request-a-macaroon
338
- Endpoint: [GET]
339
- https://dashboard.snapcraft.io/api/v2/snaps/{snap_id}/revisions/{revision_id}
340
- """
341
- response = self.session.get(
342
- url=self.get_endpoint_url(
343
- f"snaps/{snap_id}/revisions/{revision_id}", 2
344
- ),
345
- headers=self._get_authorization_header(session),
346
- )
347
-
348
- return self.process_response(response)
349
-
350
- def snap_revision_history(self, session, snap_id):
351
- """
352
- Documentation:
353
- https://dashboard.snapcraft.io/docs/reference/v1/snap.html#list-all-revisions-of-a-snap
354
- Endpoint: [GET]
355
- https://dashboard.snapcraft.io/dev/api/snaps/{snap_id}/history
356
- """
357
- response = self.session.get(
358
- url=self.get_endpoint_url(f"snaps/{snap_id}/history"),
359
- headers=self._get_authorization_header(session),
360
- )
361
-
362
- return self.process_response(response)
363
-
364
- def snap_release_history(self, session, snap_name, page=1):
365
- """
366
- Documentation:
367
- https://dashboard.snapcraft.io/docs/reference/v2/en/snaps.html#snap-releases
368
- Endpoint: [GET]
369
- https://dashboard.snapcraft.io/api/v2/snaps/{snap_name}/releases
370
- """
371
- response = self.session.get(
372
- url=self.get_endpoint_url(f"snaps/{snap_name}/releases", 2),
373
- params={"page": page},
374
- headers=self._get_authorization_header(session),
375
- )
376
-
377
- return self.process_response(response)
378
-
379
- def snap_channel_map(self, session, snap_name):
380
- """
381
- Documentation:
382
- https://dashboard.snapcraft.io/docs/reference/v2/en/snaps.html#snap-channel-map
383
- Endpoint: [GET]
384
- https://dashboard.snapcraft.io/api/v2/snaps/{snap_name}/channel-map
385
- """
386
- response = self.session.get(
387
- url=self.get_endpoint_url(f"snaps/{snap_name}/channel-map", 2),
388
- headers=self._get_authorization_header(session),
389
- )
390
-
391
- return self.process_response(response)
392
-
393
- def post_snap_release(self, session, snap_name, json):
394
- """
395
- Documentation:
396
- https://dashboard.snapcraft.io/docs/reference/v1/snap.html#release-a-snap-build-to-a-channel
397
- Endpoint: [POST] https://dashboard.snapcraft.io/dev/api/snap-release
398
- """
399
- response = self.session.post(
400
- url=self.get_endpoint_url("snap-release/"),
401
- headers=self._get_authorization_header(session),
402
- json=json,
403
- )
404
-
405
- return self.process_response(response)
406
-
407
- def post_close_channel(self, session, snap_id, json):
408
- """
409
- Documentation:
410
- https://dashboard.snapcraft.io/docs/reference/v1/snap.html#close-a-channel-for-a-snap-package
411
- Endpoint: [POST]
412
- https://dashboard.snapcraft.io/dev/api/snaps/{snap_id}/close
413
- """
414
- response = self.session.post(
415
- url=self.get_endpoint_url(f"snaps/{snap_id}/close"),
416
- headers=self._get_authorization_header(session),
417
- json=json,
418
- )
419
-
420
- return self.process_response(response)
File without changes