imbi-plugin-github 1.5.0__tar.gz → 2.9.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 (25) hide show
  1. {imbi_plugin_github-1.5.0 → imbi_plugin_github-2.9.0}/PKG-INFO +2 -2
  2. {imbi_plugin_github-1.5.0 → imbi_plugin_github-2.9.0}/pyproject.toml +4 -3
  3. {imbi_plugin_github-1.5.0 → imbi_plugin_github-2.9.0}/src/imbi_plugin_github/_hosts.py +17 -0
  4. {imbi_plugin_github-1.5.0 → imbi_plugin_github-2.9.0}/src/imbi_plugin_github/_repos.py +41 -15
  5. imbi_plugin_github-2.9.0/src/imbi_plugin_github/commits.py +593 -0
  6. {imbi_plugin_github-1.5.0 → imbi_plugin_github-2.9.0}/src/imbi_plugin_github/deployment.py +12 -13
  7. {imbi_plugin_github-1.5.0 → imbi_plugin_github-2.9.0}/src/imbi_plugin_github/lifecycle.py +411 -26
  8. imbi_plugin_github-2.9.0/tests/test_commits.py +549 -0
  9. {imbi_plugin_github-1.5.0 → imbi_plugin_github-2.9.0}/tests/test_deployment.py +4 -4
  10. {imbi_plugin_github-1.5.0 → imbi_plugin_github-2.9.0}/tests/test_lifecycle.py +507 -5
  11. imbi_plugin_github-2.9.0/tests/test_repos.py +73 -0
  12. {imbi_plugin_github-1.5.0 → imbi_plugin_github-2.9.0}/uv.lock +7 -7
  13. {imbi_plugin_github-1.5.0 → imbi_plugin_github-2.9.0}/.github/workflows/publish.yml +0 -0
  14. {imbi_plugin_github-1.5.0 → imbi_plugin_github-2.9.0}/.github/workflows/test.yml +0 -0
  15. {imbi_plugin_github-1.5.0 → imbi_plugin_github-2.9.0}/.gitignore +0 -0
  16. {imbi_plugin_github-1.5.0 → imbi_plugin_github-2.9.0}/.pre-commit-config.yaml +0 -0
  17. {imbi_plugin_github-1.5.0 → imbi_plugin_github-2.9.0}/CLAUDE.md +0 -0
  18. {imbi_plugin_github-1.5.0 → imbi_plugin_github-2.9.0}/LICENSE +0 -0
  19. {imbi_plugin_github-1.5.0 → imbi_plugin_github-2.9.0}/README.md +0 -0
  20. {imbi_plugin_github-1.5.0 → imbi_plugin_github-2.9.0}/justfile +0 -0
  21. {imbi_plugin_github-1.5.0 → imbi_plugin_github-2.9.0}/src/imbi_plugin_github/__init__.py +0 -0
  22. {imbi_plugin_github-1.5.0 → imbi_plugin_github-2.9.0}/src/imbi_plugin_github/identity.py +0 -0
  23. {imbi_plugin_github-1.5.0 → imbi_plugin_github-2.9.0}/tests/__init__.py +0 -0
  24. {imbi_plugin_github-1.5.0 → imbi_plugin_github-2.9.0}/tests/test_hosts.py +0 -0
  25. {imbi_plugin_github-1.5.0 → imbi_plugin_github-2.9.0}/tests/test_identity.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: imbi-plugin-github
3
- Version: 1.5.0
3
+ Version: 2.9.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: BSD-3-Clause
@@ -13,7 +13,7 @@ Classifier: Programming Language :: Python :: 3
13
13
  Classifier: Programming Language :: Python :: 3.14
14
14
  Requires-Python: >=3.14
15
15
  Requires-Dist: httpx>=0.27
16
- Requires-Dist: imbi-common>=2.7.0
16
+ Requires-Dist: imbi-common[databases]==2.9.0
17
17
  Requires-Dist: pydantic>=2
18
18
  Description-Content-Type: text/markdown
19
19
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "imbi-plugin-github"
3
- version = "1.5.0"
3
+ version = "2.9.0"
4
4
  description = "GitHub identity plugin for Imbi (github.com / GHEC / GHES)"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.14"
@@ -16,7 +16,7 @@ classifiers = [
16
16
  ]
17
17
  dependencies = [
18
18
  "httpx>=0.27",
19
- "imbi-common>=2.7.0",
19
+ "imbi-common[databases]==2.9.0",
20
20
  "pydantic>=2",
21
21
  ]
22
22
 
@@ -30,12 +30,13 @@ github-deployment-es = "imbi_plugin_github.deployment:GitHubEnterpriseServerDepl
30
30
  github-lifecycle = "imbi_plugin_github.lifecycle:GitHubLifecyclePlugin"
31
31
  github-lifecycle-ec = "imbi_plugin_github.lifecycle:GitHubEnterpriseCloudLifecyclePlugin"
32
32
  github-lifecycle-es = "imbi_plugin_github.lifecycle:GitHubEnterpriseServerLifecyclePlugin"
33
+ github-commit-sync = "imbi_plugin_github.commits:GitHubCommitSyncPlugin"
33
34
 
34
35
  [dependency-groups]
35
36
  dev = [
36
37
  "basedpyright",
37
38
  "coverage[toml]",
38
- "imbi-common[server,databases]",
39
+ "imbi-common[server,databases]==2.9.0",
39
40
  "pre-commit",
40
41
  "pytest",
41
42
  "pytest-asyncio",
@@ -48,3 +48,20 @@ def require_ghec_tenant_host(host: str, label: str) -> str:
48
48
  f'got {host!r}'
49
49
  )
50
50
  return host
51
+
52
+
53
+ def host_to_api_base(host: str) -> str:
54
+ """Map a resolved GitHub host to its REST API base.
55
+
56
+ The single source of truth for GitHub's flavor routing:
57
+ ``github.com`` -> ``api.github.com``, a ``*.ghe.com`` tenant ->
58
+ ``api.<tenant>.ghe.com``, and a GHES appliance -> ``<host>/api/v3``.
59
+ Shared by the deployment plugins (which resolve the host per call
60
+ via ``_resolve_host``) and the commit-sync webhook action (which
61
+ resolves it from connected-plugin options at runtime).
62
+ """
63
+ if host == 'github.com':
64
+ return 'https://api.github.com'
65
+ if host.endswith('.ghe.com'):
66
+ return f'https://api.{host}'
67
+ return f'https://{host}/api/v3'
@@ -66,23 +66,32 @@ def parse_owner_repo(url: str, target_host: str) -> tuple[str, str] | None:
66
66
 
67
67
 
68
68
  def derive_owner_repo_from_links(
69
- links: dict[str, str], host: str
69
+ links: dict[str, str],
70
+ host: str,
71
+ *,
72
+ preferred_key: str | None = None,
70
73
  ) -> tuple[str, str] | None:
71
74
  """Find a project link pointing at ``host`` and parse owner/repo.
72
75
 
73
- Prefers an explicit ``github-repository`` link key when one is
74
- present and points at ``host``; otherwise scans the remaining
75
- same-host links and returns the first usable one. Returns
76
- ``None`` when nothing matches.
76
+ Prefers the dashboard link keyed by ``preferred_key`` (the bound
77
+ third-party-service slug) when present and pointing at ``host``,
78
+ then the legacy ``github-repository`` link key, then scans the
79
+ remaining same-host links and returns the first usable one.
80
+ Returns ``None`` when nothing matches.
77
81
  """
78
82
  target = host.lower()
79
- preferred = links.get('github-repository')
80
- if preferred is not None:
81
- owner_repo = parse_owner_repo(preferred, target)
82
- if owner_repo is not None:
83
- return owner_repo
83
+ tried: set[str] = set()
84
+ for key in (preferred_key, 'github-repository'):
85
+ if not key or key in tried:
86
+ continue
87
+ tried.add(key)
88
+ url = links.get(key)
89
+ if url is not None:
90
+ owner_repo = parse_owner_repo(url, target)
91
+ if owner_repo is not None:
92
+ return owner_repo
84
93
  for key, url in links.items():
85
- if key == 'github-repository':
94
+ if key in tried:
86
95
  continue
87
96
  owner_repo = parse_owner_repo(url, target)
88
97
  if owner_repo is not None:
@@ -91,7 +100,11 @@ def derive_owner_repo_from_links(
91
100
 
92
101
 
93
102
  def resolve_owner_repo(
94
- ctx: PluginContext, host: str, plugin_label: str
103
+ ctx: PluginContext,
104
+ host: str,
105
+ plugin_label: str,
106
+ *,
107
+ prefer_previous_slug: bool = False,
95
108
  ) -> tuple[str, str]:
96
109
  """Resolve the repo for a plugin call from ``ctx``.
97
110
 
@@ -100,12 +113,25 @@ def resolve_owner_repo(
100
113
  :class:`ValueError` when neither path produces a candidate so the
101
114
  caller can surface a clear "set the GitHub Repository link"
102
115
  message to the operator.
116
+
117
+ ``prefer_previous_slug`` makes the slug fallback prefer
118
+ ``ctx.previous_project_slug`` (when set) over ``ctx.project_slug``
119
+ so callers reacting to a slug rename (e.g. ``on_project_updated``)
120
+ can still locate the pre-rename repo on GitHub.
103
121
  """
104
- derived = derive_owner_repo_from_links(ctx.project_links, host)
122
+ derived = derive_owner_repo_from_links(
123
+ ctx.project_links,
124
+ host,
125
+ preferred_key=ctx.third_party_service_slug,
126
+ )
105
127
  if derived is not None:
106
128
  return derived
107
- if ctx.project_type_slugs and ctx.project_slug:
108
- return ctx.project_type_slugs[0], ctx.project_slug
129
+ if ctx.project_type_slugs:
130
+ slug = ctx.project_slug
131
+ if prefer_previous_slug and ctx.previous_project_slug:
132
+ slug = ctx.previous_project_slug
133
+ if slug:
134
+ return ctx.project_type_slugs[0], slug
109
135
  raise ValueError(
110
136
  f'{plugin_label} could not determine the target repository: '
111
137
  "set the project's GitHub Repository link or tag the project "