canonicalwebteam.store-api 5.0.0__py3-none-any.whl → 6.0.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,525 +0,0 @@
1
- from os import getenv
2
-
3
- import requests
4
-
5
- from canonicalwebteam.store_api.store import Store
6
- from canonicalwebteam.store_api.publisher import Publisher
7
-
8
- CHARMSTORE_API_URL = getenv("CHARMSTORE_API_URL", "https://api.charmhub.io/")
9
- CHARMSTORE_PUBLISHER_API_URL = getenv(
10
- "CHARMSTORE_PUBLISHER_API_URL", "https://api.charmhub.io/"
11
- )
12
- CHARMSTORE_VALID_PACKAGE_TYPES = ["charm", "bundle"]
13
-
14
-
15
- class CharmStore(Store):
16
- def __init__(self, session=requests.Session(), store=None):
17
- super().__init__(session, store)
18
-
19
- self.config = {
20
- 2: {"base_url": f"{CHARMSTORE_API_URL}v2/charms/"},
21
- }
22
-
23
- def find(
24
- self,
25
- query="",
26
- category="",
27
- publisher="",
28
- type=None,
29
- provides=[],
30
- requires=[],
31
- fields=[],
32
- ):
33
- """
34
- Given a search term, return an array of matching search results.
35
- v2 API only.
36
- """
37
- url = self.get_endpoint_url("find", 2)
38
- headers = self.config[2].get("headers")
39
- params = {
40
- "q": query,
41
- "category": category,
42
- "publisher": publisher,
43
- "type": type,
44
- }
45
- if fields:
46
- params["fields"] = ",".join(fields)
47
-
48
- if provides:
49
- params["provides"] = ",".join(provides)
50
-
51
- if requires:
52
- params["requires"] = ",".join(requires)
53
-
54
- return self.process_response(
55
- self.session.get(url, params=params, headers=headers)
56
- )
57
-
58
-
59
- class CharmPublisher(Publisher):
60
- def __init__(self, session=requests.Session()):
61
- super().__init__(session)
62
-
63
- self.config = {
64
- 1: {"base_url": f"{CHARMSTORE_PUBLISHER_API_URL}v1/"},
65
- }
66
-
67
- self.session.headers.update({"Bakery-Protocol-Version": "2"})
68
-
69
- def _get_authorization_header(self, publisher_auth):
70
- """
71
- Return the formatted Authorization header for the publisher API.
72
- """
73
- return {"Authorization": f"Macaroon {publisher_auth}"}
74
-
75
- def get_macaroon(self):
76
- """
77
- Return a bakery v2 macaroon from the publisher API to be discharged
78
- Documentation: https://api.charmhub.io/docs/default.html#get_macaroon
79
- Endpoint URL: [GET] https://api.charmhub.io/v1/tokens
80
- """
81
- response = self.session.get(url=self.get_endpoint_url("tokens"))
82
-
83
- return self.process_response(response)["macaroon"]
84
-
85
- def issue_macaroon(self, permissions, description=None, ttl=None):
86
- """
87
- Return a bakery v2 macaroon from the publisher API to be discharged
88
- Documentation: https://api.charmhub.io/docs/default.html#issue_macaroon
89
- Endpoint URL: [POST] https://api.charmhub.io/v1/tokens
90
- """
91
- data = {"permissions": permissions}
92
-
93
- if description:
94
- data["description"] = description
95
-
96
- if ttl:
97
- data["ttl"] = ttl
98
-
99
- response = self.session.post(
100
- url=self.get_endpoint_url("tokens"),
101
- json=data,
102
- )
103
-
104
- return self.process_response(response)["macaroon"]
105
-
106
- def exchange_macaroons(self, issued_macaroon):
107
- """
108
- Return an exchanged snapstore-only authentication macaroon.
109
- Documentation:
110
- https://api.charmhub.io/docs/default.html#exchange_macaroons
111
- Endpoint URL: [POST] https://api.charmhub.io/v1/tokens/exchange
112
- """
113
-
114
- response = self.session.post(
115
- url=self.get_endpoint_url("tokens/exchange"),
116
- headers={"Macaroons": issued_macaroon},
117
- json={},
118
- )
119
-
120
- return self.process_response(response)["macaroon"]
121
-
122
- def macaroon_info(self, publisher_auth):
123
- """
124
- Return information about the authenticated macaroon token.
125
- Documentation: https://api.charmhub.io/docs/default.html#macaroon_info
126
- Endpoint URL: [GET] https://api.charmhub.io/v1/tokens/whoami
127
- """
128
- response = self.session.get(
129
- url=self.get_endpoint_url("tokens/whoami"),
130
- headers=self._get_authorization_header(publisher_auth),
131
- )
132
-
133
- return self.process_response(response)
134
-
135
- def whoami(self, publisher_auth):
136
- """
137
- Return information about the authenticated macaroon token.
138
- Documentation: 'DEPRECATED'
139
- Endpoint URL: [GET] https://api.charmhub.io/v1/whoami
140
- """
141
- response = self.session.get(
142
- url=self.get_endpoint_url("whoami"),
143
- headers=self._get_authorization_header(publisher_auth),
144
- )
145
-
146
- return self.process_response(response)
147
-
148
- def get_account_packages(
149
- self,
150
- publisher_auth,
151
- package_type,
152
- include_collaborations=False,
153
- status=None,
154
- ):
155
- """
156
- Return publisher packages
157
- Documentation: https://api.charmhub.io/docs/default.html
158
- Endpoint URL: [GET] https://api.charmhub.io/v1/charm
159
-
160
- Args:
161
- publisher_auth: Serialized macaroon to consume the API.
162
- package_type: Type of packages to obtain.
163
- include_collaborations (optional): Include shared charms
164
- status (optional): Only packages with the given status
165
-
166
- Returns:
167
- A list of packages
168
- """
169
-
170
- if package_type not in CHARMSTORE_VALID_PACKAGE_TYPES:
171
- raise ValueError(
172
- "Invalid package type. Expected one of: %s"
173
- % CHARMSTORE_VALID_PACKAGE_TYPES
174
- )
175
-
176
- params = {}
177
-
178
- if include_collaborations:
179
- params["include-collaborations"] = "true"
180
-
181
- response = self.session.get(
182
- url=self.get_endpoint_url(package_type),
183
- headers=self._get_authorization_header(publisher_auth),
184
- params=params,
185
- )
186
- packages = self.process_response(response)["results"]
187
-
188
- if status:
189
- packages = [p for p in packages if p["status"] == status]
190
-
191
- return packages
192
-
193
- def get_package_metadata(self, publisher_auth, package_type, name):
194
- """
195
- Get general metadata for a package.
196
- Documentation:
197
- https://api.charmhub.io/docs/default.html#package_metadata
198
- Endpoint URL: [GET] https://api.charmhub.io/v1/charm/<name>
199
- namespace: charm for both charms and bundles
200
- name: Package name
201
-
202
- Args:
203
- publisher_auth: Serialized macaroon to consume the API.
204
- package_type: Type of packages to obtain.
205
-
206
- Returns:
207
- Package general metadata
208
- """
209
-
210
- if package_type not in CHARMSTORE_VALID_PACKAGE_TYPES:
211
- raise ValueError(
212
- "Invalid package type. Expected one of: %s"
213
- % CHARMSTORE_VALID_PACKAGE_TYPES
214
- )
215
-
216
- response = self.session.get(
217
- url=self.get_endpoint_url(f"{package_type}/{name}"),
218
- headers=self._get_authorization_header(publisher_auth),
219
- )
220
-
221
- return self.process_response(response)["metadata"]
222
-
223
- def update_package_metadata(
224
- self, publisher_auth, package_type, name, data
225
- ):
226
- """
227
- Update general metadata for a package.
228
- Documentation:
229
- https://api.charmhub.io/docs/default.html#update_package_metadata
230
- Endpoint URL: [PATCH]
231
- https://api.charmhub.io/v1/charm/<name>
232
- namespace: charm for both charms and bundles
233
- name: Package name
234
-
235
- Args:
236
- publisher_auth: Serialized macaroon to consume the API.
237
- package_type: Type of packages to obtain.
238
- name: Package name
239
- data: Dict with changes to apply
240
-
241
- Returns:
242
- Package general metadata with changes applied
243
- """
244
-
245
- if package_type not in CHARMSTORE_VALID_PACKAGE_TYPES:
246
- raise ValueError(
247
- "Invalid package type. Expected one of: %s"
248
- % CHARMSTORE_VALID_PACKAGE_TYPES
249
- )
250
-
251
- response = self.session.patch(
252
- url=self.get_endpoint_url(f"{package_type}/{name}"),
253
- headers=self._get_authorization_header(publisher_auth),
254
- json=data,
255
- )
256
-
257
- return self.process_response(response)["metadata"]
258
-
259
- def register_package_name(self, publisher_auth, data):
260
- """
261
- Register a package name.
262
- Documentation: https://api.charmhub.io/docs/default.html#register_name
263
- Endpoint URL: [POST] https://api.charmhub.io/v1/charm
264
-
265
- Args:
266
- publisher_auth: Serialized macaroon to consume the API.
267
- data: Dict with name, type and visibility of the package
268
-
269
- Returns:
270
- Newly registered name id
271
- """
272
-
273
- response = self.session.post(
274
- url=self.get_endpoint_url("charm"),
275
- headers=self._get_authorization_header(publisher_auth),
276
- json=data,
277
- )
278
-
279
- return self.process_response(response)
280
-
281
- def unregister_package_name(self, publisher_auth, name):
282
- """
283
- Unregister a package name.
284
- Documentation:
285
- https://api.charmhub.io/docs/default.html#unregister_package
286
- Endpoint URL: [DELETE] https://api.charmhub.io/v1/charm/<name>
287
-
288
- Args:
289
- publisher_auth: Serialized macaroon to consume the API.
290
- name: Name of the package to unregister
291
- Returns:
292
- The package name ID if successful
293
- Otherwise, returns an error list
294
- """
295
- url = self.get_endpoint_url(f"charm/{name}")
296
- response = self.session.delete(
297
- url=url,
298
- headers=self._get_authorization_header(publisher_auth),
299
- )
300
- return response
301
-
302
- def get_charm_libraries(self, charm_name):
303
- """
304
- Get libraries for a charm.
305
- Documentation:
306
- https://api.charmhub.io/docs/libraries.html#fetch_libraries
307
- Endpoint URL: [POST] https://api.charmhub.io/v1/charm/libraries/bulk
308
- """
309
- response = self.session.post(
310
- url=self.get_endpoint_url("charm/libraries/bulk"),
311
- json=[{"charm-name": charm_name}],
312
- )
313
-
314
- return self.process_response(response)
315
-
316
- def get_charm_library(self, charm_name, library_id, api_version=None):
317
- """
318
- Get library metadata and content
319
- Documentation:
320
- https://api.charmhub.io/docs/libraries.html#fetch_library
321
- Endpoint URL: [GET]
322
- https://api.charmhub.io/v1/charm/libraries/<charm_name>/<library_id>
323
-
324
- Args:
325
- charm_name: Name of the charm
326
- library_id: ID of the library
327
- api_version: API version to use
328
- """
329
- params = {}
330
-
331
- if api_version is not None:
332
- params["api"] = api_version
333
-
334
- response = self.session.get(
335
- url=self.get_endpoint_url(
336
- f"charm/libraries/{charm_name}/{library_id}"
337
- ),
338
- params=params,
339
- )
340
-
341
- return self.process_response(response)
342
-
343
- def get_releases(self, publisher_auth, name):
344
- """
345
- List of all releases for a package.
346
- Documentation:
347
- https://api.charmhub.io/docs/default.html#list_releases
348
- Endpoint URL: [GET]
349
- https://api.charmhub.io/v1/charm/<name>/releases
350
-
351
- Args:
352
- publisher_auth: Serialized macaroon to consume the API.
353
- name: Name of the package
354
- """
355
- response = self.session.get(
356
- url=self.get_endpoint_url(f"charm/{name}/releases"),
357
- headers=self._get_authorization_header(publisher_auth),
358
- )
359
- return self.process_response(response)
360
-
361
- def get_collaborators(self, publisher_auth, name):
362
- """
363
- Get collaborators (accepted invites) for the given package.
364
- Documentation:
365
- https://api.charmhub.io/docs/collaborator.html#get_collaborators
366
- Endpoint URL: [GET]
367
- https://api.charmhub.io/v1/charm/<name>/collaborators
368
-
369
- Args:
370
- publisher_auth: Serialized macaroon to consume the API.
371
- name: Name of the package
372
- """
373
- response = self.session.get(
374
- url=self.get_endpoint_url(f"charm/{name}/collaborators"),
375
- headers=self._get_authorization_header(publisher_auth),
376
- )
377
- return self.process_response(response)
378
-
379
- def get_pending_invites(self, publisher_auth, name):
380
- """
381
- Get pending collaborator invites for the given package.
382
- Documentation:
383
- https://api.charmhub.io/docs/collaborator.html#get_pending_invites
384
- Endpoint URL: [GET]
385
- https://api.charmhub.io/v1/charm/<name>/collaborators/invites
386
-
387
- Args:
388
- publisher_auth: Serialized macaroon to consume the API.
389
- name: Name of the package
390
- """
391
- response = self.session.get(
392
- url=self.get_endpoint_url(f"charm/{name}/collaborators/invites"),
393
- headers=self._get_authorization_header(publisher_auth),
394
- )
395
- return self.process_response(response)
396
-
397
- def invite_collaborators(self, publisher_auth, name, emails):
398
- """
399
- Invite one or more collaborators for a package.
400
- Documentation:
401
- https://api.charmhub.io/docs/collaborator.html#invite_collaborators
402
- Endpoint URL: [POST]
403
- https://api.charmhub.io/v1/charm/<name>/collaborators/invites
404
-
405
- Args:
406
- publisher_auth: Serialized macaroon to consume the API.
407
- name: Name of the package
408
- emails: List of emails to invite
409
- """
410
- payload = {"invites": []}
411
-
412
- for email in emails:
413
- payload["invites"].append({"email": email})
414
-
415
- response = self.session.post(
416
- url=self.get_endpoint_url(f"charm/{name}/collaborators/invites"),
417
- headers=self._get_authorization_header(publisher_auth),
418
- json=payload,
419
- )
420
- return self.process_response(response)
421
-
422
- def revoke_invites(self, publisher_auth, name, emails):
423
- """
424
- Revoke invites to the specified emails for the package.
425
- Documentation:
426
- https://api.charmhub.io/docs/collaborator.html#revoke_invites
427
- Endpoint URL: [POST]
428
- https://api.charmhub.io/v1/charm/<name>/collaborators/invites/revoke
429
-
430
- Args:
431
- publisher_auth: Serialized macaroon to consume the API.
432
- name: Name of the package
433
- emails: List of emails to revoke
434
- """
435
- payload = {"invites": []}
436
-
437
- for email in emails:
438
- payload["invites"].append({"email": email})
439
-
440
- response = self.session.post(
441
- url=self.get_endpoint_url(
442
- f"charm/{name}/collaborators/invites/revoke"
443
- ),
444
- headers=self._get_authorization_header(publisher_auth),
445
- json=payload,
446
- )
447
- return response
448
-
449
- def accept_invite(self, publisher_auth, name, token):
450
- """
451
- Accept a collaborator invite.
452
- Documentation:
453
- https://api.charmhub.io/docs/collaborator.html#accept_invite
454
- Endpoint URL: [POST]
455
- https://api.charmhub.io/v1/charm/<name>/collaborators/invites/accept
456
-
457
- Args:
458
- publisher_auth: Serialized macaroon to consume the API.
459
- name: Name of the package
460
- token: Invite token
461
- """
462
- response = self.session.post(
463
- url=self.get_endpoint_url(
464
- f"charm/{name}/collaborators/invites/accept"
465
- ),
466
- headers=self._get_authorization_header(publisher_auth),
467
- json={"token": token},
468
- )
469
- return response
470
-
471
- def reject_invite(self, publisher_auth, name, token):
472
- """
473
- Reject a collaborator invite.
474
- Documentation:
475
- https://api.charmhub.io/docs/collaborator.html#reject_invite
476
- Endpoint URL: [POST]
477
- https://api.charmhub.io/v1/charm/<name>/collaborators/invites/reject
478
-
479
- Args:
480
- publisher_auth: Serialized macaroon to consume the API.
481
- name: Name of the package
482
- token: Invite token
483
- """
484
- response = self.session.post(
485
- url=self.get_endpoint_url(
486
- f"charm/{name}/collaborators/invites/reject"
487
- ),
488
- headers=self._get_authorization_header(publisher_auth),
489
- json={"token": token},
490
- )
491
- return response
492
-
493
- def create_track(
494
- self,
495
- publisher_auth,
496
- charm_name,
497
- track_name,
498
- version_pattern=None,
499
- auto_phasing_percentage=None,
500
- ):
501
- """
502
- Create a track for a charm base on the charm's guardrail pattern.
503
- Documentation: https://api.charmhub.io/docs/default.html#create_tracks
504
- Endpoint URL: [POST]
505
- https://api.charmhub.io/v1/charm/<charm_name>/tracks
506
-
507
- Args:
508
- publisher_auth: Serialized macaroon to consume the API.
509
- charm_name: Name of the charm
510
- track_name: Name of the track
511
- version_pattern: Version pattern for the track (optional)
512
- auto_phasing_percentage: phasing percentage for track (optional)
513
- """
514
-
515
- payload = {
516
- "name": track_name,
517
- "version-pattern": version_pattern,
518
- "automatic-phasing-percentage": auto_phasing_percentage,
519
- }
520
- response = self.session.post(
521
- url=self.get_endpoint_url(f"charm/{charm_name}/tracks"),
522
- headers=self._get_authorization_header(publisher_auth),
523
- json=[payload],
524
- )
525
- return response