github-rest-api 0.40.0__tar.gz → 0.42.0__tar.gz

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.
Files changed (45) hide show
  1. {github_rest_api-0.40.0 → github_rest_api-0.42.0}/PKG-INFO +2 -1
  2. github_rest_api-0.42.0/github_rest_api/__init__.py +11 -0
  3. {github_rest_api-0.40.0 → github_rest_api-0.42.0}/github_rest_api/github.py +176 -28
  4. {github_rest_api-0.40.0 → github_rest_api-0.42.0}/pyproject.toml +2 -1
  5. {github_rest_api-0.40.0 → github_rest_api-0.42.0}/tests/test_github.py +11 -1
  6. {github_rest_api-0.40.0 → github_rest_api-0.42.0}/uv.lock +104 -1
  7. github_rest_api-0.40.0/.claude/settings.json +0 -9
  8. github_rest_api-0.40.0/github_rest_api/__init__.py +0 -5
  9. {github_rest_api-0.40.0 → github_rest_api-0.42.0}/.devcontainer/devcontainer.json +0 -0
  10. {github_rest_api-0.40.0 → github_rest_api-0.42.0}/.github/workflows/create_pr_to_main.yaml +0 -0
  11. {github_rest_api-0.40.0 → github_rest_api-0.42.0}/.github/workflows/lint.yaml +0 -0
  12. {github_rest_api-0.40.0 → github_rest_api-0.42.0}/.github/workflows/release.yaml +0 -0
  13. {github_rest_api-0.40.0 → github_rest_api-0.42.0}/.github/workflows/remove_branch.yaml +0 -0
  14. {github_rest_api-0.40.0 → github_rest_api-0.42.0}/.github/workflows/test.yaml +0 -0
  15. {github_rest_api-0.40.0 → github_rest_api-0.42.0}/.gitignore +0 -0
  16. {github_rest_api-0.40.0 → github_rest_api-0.42.0}/GEMINI.md +0 -0
  17. {github_rest_api-0.40.0 → github_rest_api-0.42.0}/README.md +0 -0
  18. {github_rest_api-0.40.0 → github_rest_api-0.42.0}/github_rest_api/scripts/__init__.py +0 -0
  19. {github_rest_api-0.40.0 → github_rest_api-0.42.0}/github_rest_api/scripts/cargo/__init__.py +0 -0
  20. {github_rest_api-0.40.0 → github_rest_api-0.42.0}/github_rest_api/scripts/cargo/benchmark.py +0 -0
  21. {github_rest_api-0.40.0 → github_rest_api-0.42.0}/github_rest_api/scripts/cargo/profiling.py +0 -0
  22. {github_rest_api-0.40.0 → github_rest_api-0.42.0}/github_rest_api/scripts/cargo/utils.py +0 -0
  23. {github_rest_api-0.40.0 → github_rest_api-0.42.0}/github_rest_api/scripts/container/__init__.py +0 -0
  24. {github_rest_api-0.40.0 → github_rest_api-0.42.0}/github_rest_api/scripts/container/build_container_images.py +0 -0
  25. {github_rest_api-0.40.0 → github_rest_api-0.42.0}/github_rest_api/scripts/container/config_container.py +0 -0
  26. {github_rest_api-0.40.0 → github_rest_api-0.42.0}/github_rest_api/scripts/container/update_version_containerfile.py +0 -0
  27. {github_rest_api-0.40.0 → github_rest_api-0.42.0}/github_rest_api/scripts/github/__init__.py +0 -0
  28. {github_rest_api-0.40.0 → github_rest_api-0.42.0}/github_rest_api/scripts/github/add_github_repo.py +0 -0
  29. {github_rest_api-0.40.0 → github_rest_api-0.42.0}/github_rest_api/scripts/github/create_pull_request.py +0 -0
  30. {github_rest_api-0.40.0 → github_rest_api-0.42.0}/github_rest_api/scripts/github/release_on_github.py +0 -0
  31. {github_rest_api-0.40.0 → github_rest_api-0.42.0}/github_rest_api/scripts/github/remove_branch.py +0 -0
  32. {github_rest_api-0.40.0 → github_rest_api-0.42.0}/github_rest_api/scripts/github/workflows/create_pr_dev_to_main.yaml +0 -0
  33. {github_rest_api-0.40.0 → github_rest_api-0.42.0}/github_rest_api/scripts/github/workflows/create_pr_to_dev.yaml +0 -0
  34. {github_rest_api-0.40.0 → github_rest_api-0.42.0}/github_rest_api/scripts/github/workflows/create_pr_to_main.yaml +0 -0
  35. {github_rest_api-0.40.0 → github_rest_api-0.42.0}/github_rest_api/scripts/github/workflows/python/lint.yaml +0 -0
  36. {github_rest_api-0.40.0 → github_rest_api-0.42.0}/github_rest_api/scripts/github/workflows/python/test.yaml +0 -0
  37. {github_rest_api-0.40.0 → github_rest_api-0.42.0}/github_rest_api/scripts/github/workflows/remove_branch.yaml +0 -0
  38. {github_rest_api-0.40.0 → github_rest_api-0.42.0}/github_rest_api/scripts/utils.py +0 -0
  39. {github_rest_api-0.40.0 → github_rest_api-0.42.0}/github_rest_api/utils.py +0 -0
  40. {github_rest_api-0.40.0 → github_rest_api-0.42.0}/memory/MEMORY.md +0 -0
  41. {github_rest_api-0.40.0 → github_rest_api-0.42.0}/memory/feedback_test_runner.md +0 -0
  42. {github_rest_api-0.40.0 → github_rest_api-0.42.0}/tests/__init__.py +0 -0
  43. {github_rest_api-0.40.0 → github_rest_api-0.42.0}/tests/test_build_container_images.py +0 -0
  44. {github_rest_api-0.40.0 → github_rest_api-0.42.0}/tests/test_release_on_github.py +0 -0
  45. {github_rest_api-0.40.0 → github_rest_api-0.42.0}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: github-rest-api
3
- Version: 0.40.0
3
+ Version: 0.42.0
4
4
  Summary: Simple wrapper of GitHub REST APIs.
5
5
  Author-email: Ben Du <longendu@yahoo.com>
6
6
  Classifier: Programming Language :: Python :: 3 :: Only
@@ -10,6 +10,7 @@ Classifier: Programming Language :: Python :: 3.14
10
10
  Requires-Python: <4,>=3.12
11
11
  Requires-Dist: dulwich>=0.25.1
12
12
  Requires-Dist: psutil>=5.9.4
13
+ Requires-Dist: pynacl>=1.5
13
14
  Requires-Dist: pyyaml>=6
14
15
  Requires-Dist: requests>=2.28.2
15
16
  Requires-Dist: tenacity>=9.1.4
@@ -0,0 +1,11 @@
1
+ """GitHub REST APIs."""
2
+
3
+ from .github import (
4
+ Organization,
5
+ Repository,
6
+ RepositoryType,
7
+ SecretVisibility,
8
+ User,
9
+ )
10
+
11
+ __all__ = ["Organization", "Repository", "RepositoryType", "SecretVisibility", "User"]
@@ -1,10 +1,25 @@
1
1
  """Simple wrapper of GitHub REST APIs."""
2
2
 
3
3
  from abc import ABCMeta, abstractmethod
4
+ from base64 import b64encode
5
+ from collections.abc import Sequence
4
6
  from enum import StrEnum
5
7
  from typing import Any, Callable
6
8
  from pathlib import Path
7
9
  import requests
10
+ from nacl import encoding, public
11
+
12
+ URL_API = "https://api.github.com"
13
+
14
+
15
+ def _encrypt_secret(public_key: str, value: str) -> str:
16
+ """Encrypt a secret value using a LibSodium sealed box.
17
+ :param public_key: The base64-encoded public key to encrypt against.
18
+ :param value: The plaintext secret value to encrypt.
19
+ """
20
+ pkey = public.PublicKey(public_key.encode(), encoding.Base64Encoder)
21
+ encrypted = public.SealedBox(pkey).encrypt(value.encode())
22
+ return b64encode(encrypted).decode()
8
23
 
9
24
 
10
25
  def build_http_headers(token: str) -> dict[str, str]:
@@ -36,6 +51,12 @@ class GitHub:
36
51
  def _get(
37
52
  self, url: str, raise_for_status: bool = True, **kwargs
38
53
  ) -> requests.Response:
54
+ """Send a GET request to a GitHub REST API endpoint.
55
+ :param url: The endpoint URL to request.
56
+ :param raise_for_status: Whether to raise on a non-2xx response.
57
+ :param kwargs: Additional keyword arguments (e.g. `params`) forwarded
58
+ to `requests.get`.
59
+ """
39
60
  resp = requests.get(
40
61
  url=url,
41
62
  headers=self._headers,
@@ -49,6 +70,13 @@ class GitHub:
49
70
  def _post(
50
71
  self, url: str, headers=None, raise_for_status: bool = True, **kwargs
51
72
  ) -> requests.Response:
73
+ """Send a POST request to a GitHub REST API endpoint.
74
+ :param url: The endpoint URL to request.
75
+ :param headers: Request headers; defaults to the standard auth headers.
76
+ :param raise_for_status: Whether to raise on a non-2xx response.
77
+ :param kwargs: Additional keyword arguments (e.g. `json`) forwarded
78
+ to `requests.post`.
79
+ """
52
80
  if headers is None:
53
81
  headers = self._headers
54
82
  resp = requests.post(
@@ -67,17 +95,32 @@ class GitHub:
67
95
  resp.raise_for_status()
68
96
  return resp
69
97
 
70
- def _put(self, url, raise_for_status: bool = True) -> requests.Response:
98
+ def _put(
99
+ self, url: str, raise_for_status: bool = True, **kwargs
100
+ ) -> requests.Response:
101
+ """Send a PUT request to a GitHub REST API endpoint.
102
+ :param url: The endpoint URL to request.
103
+ :param raise_for_status: Whether to raise on a non-2xx response.
104
+ :param kwargs: Additional keyword arguments (e.g. `json`) forwarded
105
+ to `requests.put`.
106
+ """
71
107
  resp = requests.put(
72
108
  url=url,
73
109
  headers=self._headers,
74
110
  timeout=10,
111
+ **kwargs,
75
112
  )
76
113
  if raise_for_status:
77
114
  resp.raise_for_status()
78
115
  return resp
79
116
 
80
117
  def _patch(self, url, raise_for_status: bool = True, **kwargs) -> requests.Response:
118
+ """Send a PATCH request to a GitHub REST API endpoint.
119
+ :param url: The endpoint URL to request.
120
+ :param raise_for_status: Whether to raise on a non-2xx response.
121
+ :param kwargs: Additional keyword arguments (e.g. `json`) forwarded
122
+ to `requests.patch`.
123
+ """
81
124
  resp = requests.patch(
82
125
  url=url,
83
126
  headers=self._headers,
@@ -89,8 +132,19 @@ class GitHub:
89
132
  return resp
90
133
 
91
134
  def _extract_all(
92
- self, url: str, params: dict[str, Any] | None = None, n: int = 0
135
+ self,
136
+ url: str,
137
+ params: dict[str, Any] | None = None,
138
+ n: int = 0,
139
+ key: str | None = None,
93
140
  ) -> list[dict[str, Any]]:
141
+ """Collect all paginated items from a GitHub REST API endpoint.
142
+ :param url: The endpoint URL to request.
143
+ :param params: Query parameters forwarded to each request.
144
+ :param n: The maximum number of items to return (0 means all).
145
+ :param key: The key under which the list is nested in the response.
146
+ Defaults to None for endpoints that return a plain JSON array.
147
+ """
94
148
  params = params.copy() if params else {}
95
149
  if "per_page" not in params:
96
150
  params["per_page"] = min(100, n) if n > 0 else 100
@@ -100,10 +154,11 @@ class GitHub:
100
154
  resp = self._get(url=url, params=params.copy())
101
155
  resp.raise_for_status()
102
156
  data = resp.json()
103
- res.extend(data)
157
+ items = data if key is None else data[key]
158
+ res.extend(items)
104
159
  if n and len(res) >= n:
105
160
  return res[:n]
106
- if len(data) < params["per_page"]:
161
+ if len(items) < params["per_page"]:
107
162
  return res
108
163
  params["page"] += 1
109
164
 
@@ -118,8 +173,7 @@ class Repository(GitHub):
118
173
  """
119
174
  super().__init__(token)
120
175
  self._repo = repo
121
- self._url = "https://api.github.com/repos"
122
- self._url_repo = f"{self._url}/{repo}"
176
+ self._url_repo = f"{URL_API}/repos/{repo}"
123
177
  self._url_tags = f"{self._url_repo}/tags"
124
178
  self._url_transfer = f"{self._url_repo}/transfer"
125
179
  self._url_pull = f"{self._url_repo}/pulls"
@@ -127,6 +181,7 @@ class Repository(GitHub):
127
181
  self._url_refs = f"{self._url_repo}/git/refs"
128
182
  self._url_issues = f"{self._url_repo}/issues"
129
183
  self._url_releases = f"{self._url_repo}/releases"
184
+ self._url_secrets = f"{self._url_repo}/actions/secrets"
130
185
 
131
186
  def get_releases(self, n: int = 0) -> list[dict[str, Any]]:
132
187
  """List releases in this repository."""
@@ -157,8 +212,6 @@ class Repository(GitHub):
157
212
  For more details, please refer to
158
213
  https://docs.github.com/en/rest/releases/releases#create-a-release.
159
214
  """
160
- if not isinstance(json, dict):
161
- raise ValueError("A dict value is required for `json`.")
162
215
  return self._post(
163
216
  url=self._url_releases,
164
217
  json=json,
@@ -171,7 +224,9 @@ class Repository(GitHub):
171
224
  path = Path(path)
172
225
  with path.open(mode="rb") as fin:
173
226
  return self._post(
174
- url=f"{self._url_releases.replace('api', 'uploads', 1)}/{release}/assets",
227
+ url=f"{self._url_releases.replace('api', 'uploads', 1)}/{
228
+ release
229
+ }/assets",
175
230
  params={
176
231
  "name": name,
177
232
  },
@@ -193,8 +248,6 @@ class Repository(GitHub):
193
248
  about the pull request to be created.
194
249
  It's passed to the json parameter of requests.post.
195
250
  """
196
- if not isinstance(json, dict):
197
- raise ValueError("A dict value is required for `json`.")
198
251
  if not ("head" in json and "base" in json):
199
252
  raise ValueError("The data dict must contains keys head and base!")
200
253
  # return an existing PR
@@ -217,8 +270,6 @@ class Repository(GitHub):
217
270
  """Merge a pull request in this repository.
218
271
  :param pr_number: The number of the pull quest to be merged.
219
272
  """
220
- if not isinstance(pr_number, int):
221
- raise ValueError("An integer value is required for `pr_number`.")
222
273
  return self._put(
223
274
  url=f"{self._url_pull}/{pr_number}/merge",
224
275
  ).json()
@@ -228,10 +279,6 @@ class Repository(GitHub):
228
279
  :param update: The branch to update.
229
280
  :param upstream: The upstream branch.
230
281
  """
231
- if not isinstance(update, str):
232
- raise ValueError("A string value is required for `update`.")
233
- if not isinstance(upstream, str):
234
- raise ValueError("A string value is required for `upstream`.")
235
282
  pr = self.create_pull_request(
236
283
  {
237
284
  "base": update,
@@ -250,8 +297,6 @@ class Repository(GitHub):
250
297
 
251
298
  :param pr_number: The number of the pull request.
252
299
  """
253
- if not isinstance(pr_number, int):
254
- raise ValueError("An integer value is required for `pr_number`.")
255
300
  return self._extract_all(url=f"{self._url_pull}/{pr_number}/files", n=n)
256
301
 
257
302
  def get_branches(self, n: int = 0) -> list[dict[str, Any]]:
@@ -272,8 +317,6 @@ class Repository(GitHub):
272
317
  """Delete a reference from this repository.
273
318
  :param ref: The reference to delete from this repository.
274
319
  """
275
- if not isinstance(ref, str):
276
- raise ValueError("A string value is required for `ref`.")
277
320
  return self._delete(
278
321
  url=f"{self._url_refs}/{ref}",
279
322
  )
@@ -284,6 +327,43 @@ class Repository(GitHub):
284
327
  """
285
328
  return self.delete_ref(ref=f"heads/{branch}")
286
329
 
330
+ def get_secrets(self, n: int = 0) -> list[dict[str, Any]]:
331
+ """List secrets in this repository."""
332
+ return self._extract_all(url=self._url_secrets, n=n, key="secrets")
333
+
334
+ def delete_secret(self, name: str) -> requests.Response:
335
+ """Delete a secret from this repository.
336
+ :param name: The name of the secret to delete.
337
+ """
338
+ return self._delete(
339
+ url=f"{self._url_secrets}/{name}",
340
+ )
341
+
342
+ def get_secret_public_key(self) -> dict[str, Any]:
343
+ """Get the public key for encrypting secrets in this repository."""
344
+ return self._get(url=f"{self._url_secrets}/public-key").json()
345
+
346
+ def create_or_update_secret(
347
+ self, name: str, value: str, public_key: dict[str, Any] | None = None
348
+ ) -> requests.Response:
349
+ """Create or update a secret in this repository.
350
+ :param name: The name of the secret.
351
+ :param value: The plaintext value of the secret.
352
+ :param public_key: A public key (as returned by `get_secret_public_key`)
353
+ to encrypt the secret with. If not provided, it is fetched
354
+ automatically. Fetch it once and reuse it to avoid a redundant
355
+ request when creating or updating multiple secrets.
356
+ """
357
+ if public_key is None:
358
+ public_key = self.get_secret_public_key()
359
+ return self._put(
360
+ url=f"{self._url_secrets}/{name}",
361
+ json={
362
+ "encrypted_value": _encrypt_secret(public_key["key"], value),
363
+ "key_id": public_key["key_id"],
364
+ },
365
+ )
366
+
287
367
  def pr_has_change(
288
368
  self, pr_number: int, pred: Callable[[str], bool] = lambda _: True
289
369
  ) -> bool:
@@ -313,10 +393,6 @@ class Repository(GitHub):
313
393
  :param issue_number: The number of the issue.
314
394
  :param body: Body text of the new comment.
315
395
  """
316
- if not isinstance(issue_number, int):
317
- raise ValueError("An integer value is required for `issue_number`.")
318
- if not isinstance(body, str):
319
- raise ValueError("A string message is required for `body`.")
320
396
  return self._post(
321
397
  url=f"{self._url_issues}/{issue_number}/comments",
322
398
  json={"body": body},
@@ -344,6 +420,12 @@ class RepositoryType(StrEnum):
344
420
  PRIVATE = "private"
345
421
 
346
422
 
423
+ class SecretVisibility(StrEnum):
424
+ ALL = "all"
425
+ PRIVATE = "private"
426
+ SELECTED = "selected"
427
+
428
+
347
429
  class Owner(GitHub, metaclass=ABCMeta):
348
430
  """An abstract owner class representing an organization or user."""
349
431
 
@@ -354,6 +436,7 @@ class Owner(GitHub, metaclass=ABCMeta):
354
436
  """
355
437
  super().__init__(token)
356
438
  self._owner = owner
439
+ self._url_owner = ""
357
440
  self._url_repos = ""
358
441
  self._url_create_repo = ""
359
442
 
@@ -376,6 +459,14 @@ class Owner(GitHub, metaclass=ABCMeta):
376
459
  def create_repository(
377
460
  self, name: str, description: str = "", private: bool = True, **kwargs
378
461
  ) -> dict[str, Any]:
462
+ """Create a repository for this owner.
463
+ :param name: The name of the repository.
464
+ :param description: A short description of the repository.
465
+ :param private: Whether the repository is private.
466
+ :param kwargs: Additional keyword arguments forwarded to `_post`
467
+ (e.g. `params` or `raise_for_status`). Note `json` is already set
468
+ from the other parameters and must not be passed here.
469
+ """
379
470
  data = {
380
471
  "name": name,
381
472
  "description": description,
@@ -400,8 +491,9 @@ class User(Owner):
400
491
  self._set_urls()
401
492
 
402
493
  def _set_urls(self) -> None:
403
- self._url_repos = f"https://api.github.com/users/{self._owner}/repos"
404
- self._url_create_repo = "https://api.github.com/user/repos"
494
+ self._url_owner = f"{URL_API}/users/{self._owner}"
495
+ self._url_repos = f"{self._url_owner}/repos"
496
+ self._url_create_repo = f"{URL_API}/user/repos"
405
497
 
406
498
 
407
499
  class Organization(Owner):
@@ -416,5 +508,61 @@ class Organization(Owner):
416
508
  self._set_urls()
417
509
 
418
510
  def _set_urls(self) -> None:
419
- self._url_repos = f"https://api.github.com/orgs/{self._owner}/repos"
511
+ self._url_owner = f"{URL_API}/orgs/{self._owner}"
512
+ self._url_repos = f"{self._url_owner}/repos"
420
513
  self._url_create_repo = self._url_repos
514
+ self._url_secrets = f"{self._url_owner}/actions/secrets"
515
+
516
+ def get_secrets(self, n: int = 0) -> list[dict[str, Any]]:
517
+ """List secrets in this organization."""
518
+ return self._extract_all(url=self._url_secrets, n=n, key="secrets")
519
+
520
+ def delete_secret(self, name: str) -> requests.Response:
521
+ """Delete an organization secret.
522
+ :param name: The name of the secret to delete.
523
+ """
524
+ return self._delete(
525
+ url=f"{self._url_secrets}/{name}",
526
+ )
527
+
528
+ def get_secret_public_key(self) -> dict[str, Any]:
529
+ """Get the public key for encrypting secrets in this organization."""
530
+ return self._get(url=f"{self._url_secrets}/public-key").json()
531
+
532
+ def create_or_update_secret(
533
+ self,
534
+ name: str,
535
+ value: str,
536
+ public_key: dict[str, Any] | None = None,
537
+ visibility: SecretVisibility = SecretVisibility.ALL,
538
+ selected_repository_ids: Sequence[int] = (),
539
+ ) -> requests.Response:
540
+ """Create or update an organization secret.
541
+ :param name: The name of the secret.
542
+ :param value: The plaintext value of the secret.
543
+ :param public_key: A public key (as returned by `get_secret_public_key`)
544
+ to encrypt the secret with. If not provided, it is fetched
545
+ automatically. Fetch it once and reuse it to avoid a redundant
546
+ request when creating or updating multiple secrets.
547
+ :param visibility: Which repositories can access the secret
548
+ (all, private, or selected).
549
+ :param selected_repository_ids: Repository IDs that can access the secret
550
+ when visibility is `selected`.
551
+ """
552
+ if selected_repository_ids and visibility != SecretVisibility.SELECTED:
553
+ raise ValueError(
554
+ "`selected_repository_ids` can only be provided when `visibility` is 'selected'."
555
+ )
556
+ if public_key is None:
557
+ public_key = self.get_secret_public_key()
558
+ json: dict[str, Any] = {
559
+ "encrypted_value": _encrypt_secret(public_key["key"], value),
560
+ "key_id": public_key["key_id"],
561
+ "visibility": visibility,
562
+ }
563
+ if selected_repository_ids:
564
+ json["selected_repository_ids"] = list(selected_repository_ids)
565
+ return self._put(
566
+ url=f"{self._url_secrets}/{name}",
567
+ json=json,
568
+ )
@@ -4,7 +4,7 @@ requires = [ "hatchling" ]
4
4
 
5
5
  [project]
6
6
  name = "github-rest-api"
7
- version = "0.40.0"
7
+ version = "0.42.0"
8
8
  description = "Simple wrapper of GitHub REST APIs."
9
9
  readme = "README.md"
10
10
  authors = [ { name = "Ben Du", email = "longendu@yahoo.com" } ]
@@ -18,6 +18,7 @@ classifiers = [
18
18
  dependencies = [
19
19
  "dulwich>=0.25.1",
20
20
  "psutil>=5.9.4",
21
+ "pynacl>=1.5",
21
22
  "pyyaml>=6",
22
23
  "requests>=2.28.2",
23
24
  "tenacity>=9.1.4",
@@ -1,9 +1,19 @@
1
1
  import os
2
- from github_rest_api.github import User, Organization, Repository
2
+ from base64 import b64decode
3
+ from nacl import encoding, public
4
+ from github_rest_api.github import User, Organization, Repository, _encrypt_secret
3
5
 
4
6
  TOKEN = os.environ.get("GITHUB_TOKEN", "")
5
7
 
6
8
 
9
+ def test_encrypt_secret_roundtrip():
10
+ private_key = public.PrivateKey.generate()
11
+ public_key = private_key.public_key.encode(encoding.Base64Encoder).decode()
12
+ encrypted = _encrypt_secret(public_key, "s3cret-value")
13
+ decrypted = public.SealedBox(private_key).decrypt(b64decode(encrypted))
14
+ assert decrypted == b"s3cret-value"
15
+
16
+
7
17
  def test_user_get_repositories():
8
18
  user = User(TOKEN, "dclong")
9
19
  repos = user.get_repositories()
@@ -11,6 +11,63 @@ wheels = [
11
11
  { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" },
12
12
  ]
13
13
 
14
+ [[package]]
15
+ name = "cffi"
16
+ version = "2.0.0"
17
+ source = { registry = "https://pypi.org/simple" }
18
+ dependencies = [
19
+ { name = "pycparser", marker = "implementation_name != 'PyPy'" },
20
+ ]
21
+ sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" }
22
+ wheels = [
23
+ { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" },
24
+ { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" },
25
+ { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" },
26
+ { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" },
27
+ { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" },
28
+ { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" },
29
+ { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" },
30
+ { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" },
31
+ { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" },
32
+ { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" },
33
+ { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" },
34
+ { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" },
35
+ { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" },
36
+ { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" },
37
+ { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" },
38
+ { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" },
39
+ { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" },
40
+ { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" },
41
+ { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" },
42
+ { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" },
43
+ { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" },
44
+ { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" },
45
+ { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" },
46
+ { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" },
47
+ { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" },
48
+ { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" },
49
+ { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" },
50
+ { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" },
51
+ { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" },
52
+ { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" },
53
+ { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" },
54
+ { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" },
55
+ { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" },
56
+ { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" },
57
+ { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" },
58
+ { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" },
59
+ { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" },
60
+ { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" },
61
+ { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" },
62
+ { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" },
63
+ { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" },
64
+ { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" },
65
+ { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" },
66
+ { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" },
67
+ { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" },
68
+ { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" },
69
+ ]
70
+
14
71
  [[package]]
15
72
  name = "charset-normalizer"
16
73
  version = "3.4.7"
@@ -166,11 +223,12 @@ wheels = [
166
223
 
167
224
  [[package]]
168
225
  name = "github-rest-api"
169
- version = "0.40.0"
226
+ version = "0.41.0"
170
227
  source = { editable = "." }
171
228
  dependencies = [
172
229
  { name = "dulwich" },
173
230
  { name = "psutil" },
231
+ { name = "pynacl" },
174
232
  { name = "pyyaml" },
175
233
  { name = "requests" },
176
234
  { name = "tenacity" },
@@ -191,6 +249,7 @@ dev = [
191
249
  requires-dist = [
192
250
  { name = "dulwich", specifier = ">=0.25.1" },
193
251
  { name = "psutil", specifier = ">=5.9.4" },
252
+ { name = "pynacl", specifier = ">=1.5" },
194
253
  { name = "pyyaml", specifier = ">=6" },
195
254
  { name = "requests", specifier = ">=2.28.2" },
196
255
  { name = "tenacity", specifier = ">=9.1.4" },
@@ -280,6 +339,15 @@ wheels = [
280
339
  { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" },
281
340
  ]
282
341
 
342
+ [[package]]
343
+ name = "pycparser"
344
+ version = "3.0"
345
+ source = { registry = "https://pypi.org/simple" }
346
+ sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" }
347
+ wheels = [
348
+ { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" },
349
+ ]
350
+
283
351
  [[package]]
284
352
  name = "pygments"
285
353
  version = "2.20.0"
@@ -289,6 +357,41 @@ wheels = [
289
357
  { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" },
290
358
  ]
291
359
 
360
+ [[package]]
361
+ name = "pynacl"
362
+ version = "1.6.2"
363
+ source = { registry = "https://pypi.org/simple" }
364
+ dependencies = [
365
+ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
366
+ ]
367
+ sdist = { url = "https://files.pythonhosted.org/packages/d9/9a/4019b524b03a13438637b11538c82781a5eda427394380381af8f04f467a/pynacl-1.6.2.tar.gz", hash = "sha256:018494d6d696ae03c7e656e5e74cdfd8ea1326962cc401bcf018f1ed8436811c", size = 3511692, upload-time = "2026-01-01T17:48:10.851Z" }
368
+ wheels = [
369
+ { url = "https://files.pythonhosted.org/packages/4b/79/0e3c34dc3c4671f67d251c07aa8eb100916f250ee470df230b0ab89551b4/pynacl-1.6.2-cp314-cp314t-macosx_10_10_universal2.whl", hash = "sha256:622d7b07cc5c02c666795792931b50c91f3ce3c2649762efb1ef0d5684c81594", size = 390064, upload-time = "2026-01-01T17:31:57.264Z" },
370
+ { url = "https://files.pythonhosted.org/packages/eb/1c/23a26e931736e13b16483795c8a6b2f641bf6a3d5238c22b070a5112722c/pynacl-1.6.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d071c6a9a4c94d79eb665db4ce5cedc537faf74f2355e4d502591d850d3913c0", size = 809370, upload-time = "2026-01-01T17:31:59.198Z" },
371
+ { url = "https://files.pythonhosted.org/packages/87/74/8d4b718f8a22aea9e8dcc8b95deb76d4aae380e2f5b570cc70b5fd0a852d/pynacl-1.6.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fe9847ca47d287af41e82be1dd5e23023d3c31a951da134121ab02e42ac218c9", size = 1408304, upload-time = "2026-01-01T17:32:01.162Z" },
372
+ { url = "https://files.pythonhosted.org/packages/fd/73/be4fdd3a6a87fe8a4553380c2b47fbd1f7f58292eb820902f5c8ac7de7b0/pynacl-1.6.2-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:04316d1fc625d860b6c162fff704eb8426b1a8bcd3abacea11142cbd99a6b574", size = 844871, upload-time = "2026-01-01T17:32:02.824Z" },
373
+ { url = "https://files.pythonhosted.org/packages/55/ad/6efc57ab75ee4422e96b5f2697d51bbcf6cdcc091e66310df91fbdc144a8/pynacl-1.6.2-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44081faff368d6c5553ccf55322ef2819abb40e25afaec7e740f159f74813634", size = 1446356, upload-time = "2026-01-01T17:32:04.452Z" },
374
+ { url = "https://files.pythonhosted.org/packages/78/b7/928ee9c4779caa0a915844311ab9fb5f99585621c5d6e4574538a17dca07/pynacl-1.6.2-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:a9f9932d8d2811ce1a8ffa79dcbdf3970e7355b5c8eb0c1a881a57e7f7d96e88", size = 826814, upload-time = "2026-01-01T17:32:06.078Z" },
375
+ { url = "https://files.pythonhosted.org/packages/f7/a9/1bdba746a2be20f8809fee75c10e3159d75864ef69c6b0dd168fc60e485d/pynacl-1.6.2-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:bc4a36b28dd72fb4845e5d8f9760610588a96d5a51f01d84d8c6ff9849968c14", size = 1411742, upload-time = "2026-01-01T17:32:07.651Z" },
376
+ { url = "https://files.pythonhosted.org/packages/f3/2f/5e7ea8d85f9f3ea5b6b87db1d8388daa3587eed181bdeb0306816fdbbe79/pynacl-1.6.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3bffb6d0f6becacb6526f8f42adfb5efb26337056ee0831fb9a7044d1a964444", size = 801714, upload-time = "2026-01-01T17:32:09.558Z" },
377
+ { url = "https://files.pythonhosted.org/packages/06/ea/43fe2f7eab5f200e40fb10d305bf6f87ea31b3bbc83443eac37cd34a9e1e/pynacl-1.6.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2fef529ef3ee487ad8113d287a593fa26f48ee3620d92ecc6f1d09ea38e0709b", size = 1372257, upload-time = "2026-01-01T17:32:11.026Z" },
378
+ { url = "https://files.pythonhosted.org/packages/4d/54/c9ea116412788629b1347e415f72195c25eb2f3809b2d3e7b25f5c79f13a/pynacl-1.6.2-cp314-cp314t-win32.whl", hash = "sha256:a84bf1c20339d06dc0c85d9aea9637a24f718f375d861b2668b2f9f96fa51145", size = 231319, upload-time = "2026-01-01T17:32:12.46Z" },
379
+ { url = "https://files.pythonhosted.org/packages/ce/04/64e9d76646abac2dccf904fccba352a86e7d172647557f35b9fe2a5ee4a1/pynacl-1.6.2-cp314-cp314t-win_amd64.whl", hash = "sha256:320ef68a41c87547c91a8b58903c9caa641ab01e8512ce291085b5fe2fcb7590", size = 244044, upload-time = "2026-01-01T17:32:13.781Z" },
380
+ { url = "https://files.pythonhosted.org/packages/33/33/7873dc161c6a06f43cda13dec67b6fe152cb2f982581151956fa5e5cdb47/pynacl-1.6.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d29bfe37e20e015a7d8b23cfc8bd6aa7909c92a1b8f41ee416bbb3e79ef182b2", size = 188740, upload-time = "2026-01-01T17:32:15.083Z" },
381
+ { url = "https://files.pythonhosted.org/packages/be/7b/4845bbf88e94586ec47a432da4e9107e3fc3ce37eb412b1398630a37f7dd/pynacl-1.6.2-cp38-abi3-macosx_10_10_universal2.whl", hash = "sha256:c949ea47e4206af7c8f604b8278093b674f7c79ed0d4719cc836902bf4517465", size = 388458, upload-time = "2026-01-01T17:32:16.829Z" },
382
+ { url = "https://files.pythonhosted.org/packages/1e/b4/e927e0653ba63b02a4ca5b4d852a8d1d678afbf69b3dbf9c4d0785ac905c/pynacl-1.6.2-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8845c0631c0be43abdd865511c41eab235e0be69c81dc66a50911594198679b0", size = 800020, upload-time = "2026-01-01T17:32:18.34Z" },
383
+ { url = "https://files.pythonhosted.org/packages/7f/81/d60984052df5c97b1d24365bc1e30024379b42c4edcd79d2436b1b9806f2/pynacl-1.6.2-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:22de65bb9010a725b0dac248f353bb072969c94fa8d6b1f34b87d7953cf7bbe4", size = 1399174, upload-time = "2026-01-01T17:32:20.239Z" },
384
+ { url = "https://files.pythonhosted.org/packages/68/f7/322f2f9915c4ef27d140101dd0ed26b479f7e6f5f183590fd32dfc48c4d3/pynacl-1.6.2-cp38-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:46065496ab748469cdd999246d17e301b2c24ae2fdf739132e580a0e94c94a87", size = 835085, upload-time = "2026-01-01T17:32:22.24Z" },
385
+ { url = "https://files.pythonhosted.org/packages/3e/d0/f301f83ac8dbe53442c5a43f6a39016f94f754d7a9815a875b65e218a307/pynacl-1.6.2-cp38-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a66d6fb6ae7661c58995f9c6435bda2b1e68b54b598a6a10247bfcdadac996c", size = 1437614, upload-time = "2026-01-01T17:32:23.766Z" },
386
+ { url = "https://files.pythonhosted.org/packages/c4/58/fc6e649762b029315325ace1a8c6be66125e42f67416d3dbd47b69563d61/pynacl-1.6.2-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:26bfcd00dcf2cf160f122186af731ae30ab120c18e8375684ec2670dccd28130", size = 818251, upload-time = "2026-01-01T17:32:25.69Z" },
387
+ { url = "https://files.pythonhosted.org/packages/c9/a8/b917096b1accc9acd878819a49d3d84875731a41eb665f6ebc826b1af99e/pynacl-1.6.2-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:c8a231e36ec2cab018c4ad4358c386e36eede0319a0c41fed24f840b1dac59f6", size = 1402859, upload-time = "2026-01-01T17:32:27.215Z" },
388
+ { url = "https://files.pythonhosted.org/packages/85/42/fe60b5f4473e12c72f977548e4028156f4d340b884c635ec6b063fe7e9a5/pynacl-1.6.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:68be3a09455743ff9505491220b64440ced8973fe930f270c8e07ccfa25b1f9e", size = 791926, upload-time = "2026-01-01T17:32:29.314Z" },
389
+ { url = "https://files.pythonhosted.org/packages/fa/f9/e40e318c604259301cc091a2a63f237d9e7b424c4851cafaea4ea7c4834e/pynacl-1.6.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8b097553b380236d51ed11356c953bf8ce36a29a3e596e934ecabe76c985a577", size = 1363101, upload-time = "2026-01-01T17:32:31.263Z" },
390
+ { url = "https://files.pythonhosted.org/packages/48/47/e761c254f410c023a469284a9bc210933e18588ca87706ae93002c05114c/pynacl-1.6.2-cp38-abi3-win32.whl", hash = "sha256:5811c72b473b2f38f7e2a3dc4f8642e3a3e9b5e7317266e4ced1fba85cae41aa", size = 227421, upload-time = "2026-01-01T17:32:33.076Z" },
391
+ { url = "https://files.pythonhosted.org/packages/41/ad/334600e8cacc7d86587fe5f565480fde569dfb487389c8e1be56ac21d8ac/pynacl-1.6.2-cp38-abi3-win_amd64.whl", hash = "sha256:62985f233210dee6548c223301b6c25440852e13d59a8b81490203c3227c5ba0", size = 239754, upload-time = "2026-01-01T17:32:34.557Z" },
392
+ { url = "https://files.pythonhosted.org/packages/29/7d/5945b5af29534641820d3bd7b00962abbbdfee84ec7e19f0d5b3175f9a31/pynacl-1.6.2-cp38-abi3-win_arm64.whl", hash = "sha256:834a43af110f743a754448463e8fd61259cd4ab5bbedcf70f9dabad1d28a394c", size = 184801, upload-time = "2026-01-01T17:32:36.309Z" },
393
+ ]
394
+
292
395
  [[package]]
293
396
  name = "pyproject-fmt"
294
397
  version = "2.21.1"
@@ -1,9 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(jj log *)",
5
- "Bash(jj bookmark list *)",
6
- "Bash(jj diff *)"
7
- ]
8
- }
9
- }
@@ -1,5 +0,0 @@
1
- """GitHub REST APIs."""
2
-
3
- from .github import Organization, Repository, RepositoryType, User
4
-
5
- __all__ = ["Organization", "Repository", "RepositoryType", "User"]