imbi-plugin-github 2.42.0__tar.gz → 2.43.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 (33) hide show
  1. {imbi_plugin_github-2.42.0 → imbi_plugin_github-2.43.0}/PKG-INFO +2 -2
  2. {imbi_plugin_github-2.42.0 → imbi_plugin_github-2.43.0}/pyproject.toml +2 -2
  3. {imbi_plugin_github-2.42.0 → imbi_plugin_github-2.43.0}/src/imbi/plugins/github/deployment.py +5 -0
  4. {imbi_plugin_github-2.42.0 → imbi_plugin_github-2.43.0}/src/imbi/plugins/github/plugin.py +10 -0
  5. imbi_plugin_github-2.43.0/src/imbi/plugins/github/public.py +84 -0
  6. imbi_plugin_github-2.43.0/src/imbi/plugins/github/secret_provider.py +734 -0
  7. imbi_plugin_github-2.43.0/tests/test_public.py +77 -0
  8. imbi_plugin_github-2.43.0/tests/test_secret_provider.py +603 -0
  9. imbi_plugin_github-2.43.0/tests/test_secret_provider_live.py +160 -0
  10. {imbi_plugin_github-2.42.0 → imbi_plugin_github-2.43.0}/.gitignore +0 -0
  11. {imbi_plugin_github-2.42.0 → imbi_plugin_github-2.43.0}/README.md +0 -0
  12. {imbi_plugin_github-2.42.0 → imbi_plugin_github-2.43.0}/moon.yml +0 -0
  13. {imbi_plugin_github-2.42.0 → imbi_plugin_github-2.43.0}/src/imbi/plugins/github/README.md +0 -0
  14. {imbi_plugin_github-2.42.0 → imbi_plugin_github-2.43.0}/src/imbi/plugins/github/__init__.py +0 -0
  15. {imbi_plugin_github-2.42.0 → imbi_plugin_github-2.43.0}/src/imbi/plugins/github/_app_auth.py +0 -0
  16. {imbi_plugin_github-2.42.0 → imbi_plugin_github-2.43.0}/src/imbi/plugins/github/_hosts.py +0 -0
  17. {imbi_plugin_github-2.42.0 → imbi_plugin_github-2.43.0}/src/imbi/plugins/github/_repos.py +0 -0
  18. {imbi_plugin_github-2.42.0 → imbi_plugin_github-2.43.0}/src/imbi/plugins/github/commits.py +0 -0
  19. {imbi_plugin_github-2.42.0 → imbi_plugin_github-2.43.0}/src/imbi/plugins/github/doctor.py +0 -0
  20. {imbi_plugin_github-2.42.0 → imbi_plugin_github-2.43.0}/src/imbi/plugins/github/identity.py +0 -0
  21. {imbi_plugin_github-2.42.0 → imbi_plugin_github-2.43.0}/src/imbi/plugins/github/lifecycle.py +0 -0
  22. {imbi_plugin_github-2.42.0 → imbi_plugin_github-2.43.0}/src/imbi/plugins/github/pull_requests.py +0 -0
  23. {imbi_plugin_github-2.42.0 → imbi_plugin_github-2.43.0}/src/imbi/plugins/github/py.typed +0 -0
  24. {imbi_plugin_github-2.42.0 → imbi_plugin_github-2.43.0}/tests/__init__.py +0 -0
  25. {imbi_plugin_github-2.42.0 → imbi_plugin_github-2.43.0}/tests/test_app_auth_scopes.py +0 -0
  26. {imbi_plugin_github-2.42.0 → imbi_plugin_github-2.43.0}/tests/test_commits.py +0 -0
  27. {imbi_plugin_github-2.42.0 → imbi_plugin_github-2.43.0}/tests/test_deployment.py +0 -0
  28. {imbi_plugin_github-2.42.0 → imbi_plugin_github-2.43.0}/tests/test_doctor.py +0 -0
  29. {imbi_plugin_github-2.42.0 → imbi_plugin_github-2.43.0}/tests/test_hosts.py +0 -0
  30. {imbi_plugin_github-2.42.0 → imbi_plugin_github-2.43.0}/tests/test_identity.py +0 -0
  31. {imbi_plugin_github-2.42.0 → imbi_plugin_github-2.43.0}/tests/test_lifecycle.py +0 -0
  32. {imbi_plugin_github-2.42.0 → imbi_plugin_github-2.43.0}/tests/test_pull_requests.py +0 -0
  33. {imbi_plugin_github-2.42.0 → imbi_plugin_github-2.43.0}/tests/test_repos.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: imbi-plugin-github
3
- Version: 2.42.0
3
+ Version: 2.43.0
4
4
  Summary: GitHub identity plugin for Imbi (github.com / GHEC / GHES)
5
5
  Author-email: "Gavin M. Roy" <gavinr@aweber.com>
6
6
  License-Expression: BSD-3-Clause
@@ -12,7 +12,7 @@ Classifier: Programming Language :: Python :: 3
12
12
  Classifier: Programming Language :: Python :: 3.14
13
13
  Requires-Python: >=3.14
14
14
  Requires-Dist: httpx>=0.27
15
- Requires-Dist: imbi-common[databases]==2.42.0
15
+ Requires-Dist: imbi-common[databases]==2.43.0
16
16
  Requires-Dist: pydantic>=2
17
17
  Requires-Dist: pyjwt[crypto]>=2.8
18
18
  Description-Content-Type: text/markdown
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "imbi-plugin-github"
3
- version = "2.42.0"
3
+ version = "2.43.0"
4
4
  description = "GitHub identity plugin for Imbi (github.com / GHEC / GHES)"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.14"
@@ -18,7 +18,7 @@ classifiers = [
18
18
  ]
19
19
  dependencies = [
20
20
  "httpx>=0.27",
21
- "imbi-common[databases]==2.42.0",
21
+ "imbi-common[databases]==2.43.0",
22
22
  "pydantic>=2",
23
23
  "pyjwt[crypto]>=2.8",
24
24
  ]
@@ -65,6 +65,7 @@ from imbi.common.plugins.base import (
65
65
  WorkflowFile,
66
66
  )
67
67
  from imbi.common.plugins.errors import PluginAuthenticationFailed
68
+ from imbi.plugins.github import public
68
69
  from imbi.plugins.github._hosts import flavor_host, host_to_api_base
69
70
  from imbi.plugins.github._repos import (
70
71
  derive_owner_repo_from_links,
@@ -654,6 +655,10 @@ class GitHubDeployment(DeploymentCapability):
654
655
  per call.
655
656
  """
656
657
 
658
+ @classmethod
659
+ def public_reader(cls, url: str) -> public.PublicRepository | None:
660
+ return public.PublicRepository.for_url(url)
661
+
657
662
  def _resolve_host(self, ctx: PluginContext) -> str:
658
663
  return flavor_host(ctx.integration_options, 'github deployment')
659
664
 
@@ -25,6 +25,7 @@ from imbi.common.plugins.base import (
25
25
  PluginManifest,
26
26
  PluginOption,
27
27
  WebhookActionsCapability,
28
+ WebhookSignature,
28
29
  )
29
30
  from imbi.plugins.github.commits import (
30
31
  GitHubCommitSync,
@@ -39,6 +40,7 @@ from imbi.plugins.github.pull_requests import (
39
40
  GitHubPullRequestSync,
40
41
  sync_pull_requests_descriptor,
41
42
  )
43
+ from imbi.plugins.github.secret_provider import GitHubApp
42
44
 
43
45
 
44
46
  class GitHubWebhookActions(WebhookActionsCapability):
@@ -58,6 +60,13 @@ class GitHubWebhookActions(WebhookActionsCapability):
58
60
  sync_pull_requests_descriptor,
59
61
  ]
60
62
 
63
+ @classmethod
64
+ def signature(cls) -> WebhookSignature:
65
+ # The secret of the GitHub webhook is the Webhook.secret.
66
+ return WebhookSignature(
67
+ header='x-hub-signature-256', prefix='sha256=', secret='webhook'
68
+ )
69
+
61
70
 
62
71
  # Integration-level options, asked once per Integration.
63
72
  _OPTIONS: list[PluginOption] = [
@@ -418,4 +427,5 @@ class GitHubPlugin(Plugin):
418
427
  ),
419
428
  ],
420
429
  ops_log_templates=_OPS_LOG_TEMPLATES,
430
+ secret_providers=[GitHubApp],
421
431
  )
@@ -0,0 +1,84 @@
1
+ """Credential-free reads of a public ``github.com`` repository.
2
+
3
+ :meth:`GitHubDeployment.public_reader
4
+ <imbi.plugins.github.deployment.GitHubDeployment.public_reader>`
5
+ returns a :class:`PublicRepository`. The host uses it when no
6
+ Integration of the organization can read a repository, for example a
7
+ public Claude Code plugin marketplace.
8
+ """
9
+
10
+ import re
11
+ import typing
12
+ import urllib.parse
13
+
14
+ import httpx
15
+
16
+ from imbi.common.plugins import base
17
+
18
+ _GITHUB_REPO = re.compile(r'[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+')
19
+
20
+
21
+ class PublicRepository:
22
+ """The :class:`~imbi.common.plugins.base.RepositoryReader` of a public
23
+ ``github.com`` repository. It ignores ``ctx`` and ``credentials``."""
24
+
25
+ # ponytail: 60 unauthenticated requests an hour per API pod IP; use
26
+ # an org token if many public marketplaces sync often.
27
+ def __init__(self, owner: str, repo: str) -> None:
28
+ self.base = f'https://api.github.com/repos/{owner}/{repo}'
29
+
30
+ @classmethod
31
+ def for_url(cls, url: str) -> PublicRepository | None:
32
+ """Return the reader of a ``github.com`` repository URL, or
33
+ ``None`` for another host or a path that is not a repository."""
34
+ parts = urllib.parse.urlsplit(url)
35
+ path = parts.path.strip('/').removesuffix('.git')
36
+ if parts.hostname != 'github.com' or not _GITHUB_REPO.fullmatch(path):
37
+ return None
38
+ return cls(*path.split('/'))
39
+
40
+ async def _get(
41
+ self, path: str, accept: str = 'application/vnd.github+json'
42
+ ) -> httpx.Response:
43
+ # A renamed or moved repository answers 301 to /repositories/{id}.
44
+ async with httpx.AsyncClient(
45
+ timeout=30, follow_redirects=True
46
+ ) as client:
47
+ return await client.get(
48
+ f'{self.base}{path}', headers={'Accept': accept}
49
+ )
50
+
51
+ async def resolve_committish(
52
+ self, _ctx: object, _credentials: object, ref: str, /
53
+ ) -> base.Ref:
54
+ response = await self._get(f'/commits/{urllib.parse.quote(ref)}')
55
+ response.raise_for_status()
56
+ return base.Ref(name=ref, kind='branch', sha=response.json()['sha'])
57
+
58
+ async def list_refs(
59
+ self,
60
+ _ctx: object,
61
+ _credentials: object,
62
+ /,
63
+ kind: typing.Literal['default', 'branch', 'tag', 'all'] = 'all',
64
+ ) -> list[base.Ref]:
65
+ """Return the default branch only, whatever ``kind`` is."""
66
+ del kind
67
+ response = await self._get('')
68
+ response.raise_for_status()
69
+ branch = str(response.json()['default_branch'])
70
+ ref = await self.resolve_committish(None, None, branch)
71
+ return [ref.model_copy(update={'kind': 'default', 'is_default': True})]
72
+
73
+ async def get_file(
74
+ self, _ctx: object, _credentials: object, path: str, ref: str, /
75
+ ) -> str | None:
76
+ response = await self._get(
77
+ f'/contents/{urllib.parse.quote(path)}?'
78
+ f'{urllib.parse.urlencode({"ref": ref})}',
79
+ 'application/vnd.github.raw+json',
80
+ )
81
+ if response.status_code == 404:
82
+ return None
83
+ response.raise_for_status()
84
+ return response.text