imbi-plugin-github 2.34.0__tar.gz → 2.35.1__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 (28) hide show
  1. {imbi_plugin_github-2.34.0 → imbi_plugin_github-2.35.1}/PKG-INFO +2 -2
  2. {imbi_plugin_github-2.34.0 → imbi_plugin_github-2.35.1}/pyproject.toml +2 -2
  3. {imbi_plugin_github-2.34.0 → imbi_plugin_github-2.35.1}/src/imbi/plugins/github/commits.py +27 -22
  4. {imbi_plugin_github-2.34.0 → imbi_plugin_github-2.35.1}/src/imbi/plugins/github/pull_requests.py +9 -8
  5. {imbi_plugin_github-2.34.0 → imbi_plugin_github-2.35.1}/tests/test_commits.py +33 -33
  6. {imbi_plugin_github-2.34.0 → imbi_plugin_github-2.35.1}/tests/test_pull_requests.py +5 -5
  7. {imbi_plugin_github-2.34.0 → imbi_plugin_github-2.35.1}/.gitignore +0 -0
  8. {imbi_plugin_github-2.34.0 → imbi_plugin_github-2.35.1}/README.md +0 -0
  9. {imbi_plugin_github-2.34.0 → imbi_plugin_github-2.35.1}/moon.yml +0 -0
  10. {imbi_plugin_github-2.34.0 → imbi_plugin_github-2.35.1}/src/imbi/plugins/github/README.md +0 -0
  11. {imbi_plugin_github-2.34.0 → imbi_plugin_github-2.35.1}/src/imbi/plugins/github/__init__.py +0 -0
  12. {imbi_plugin_github-2.34.0 → imbi_plugin_github-2.35.1}/src/imbi/plugins/github/_app_auth.py +0 -0
  13. {imbi_plugin_github-2.34.0 → imbi_plugin_github-2.35.1}/src/imbi/plugins/github/_hosts.py +0 -0
  14. {imbi_plugin_github-2.34.0 → imbi_plugin_github-2.35.1}/src/imbi/plugins/github/_repos.py +0 -0
  15. {imbi_plugin_github-2.34.0 → imbi_plugin_github-2.35.1}/src/imbi/plugins/github/deployment.py +0 -0
  16. {imbi_plugin_github-2.34.0 → imbi_plugin_github-2.35.1}/src/imbi/plugins/github/doctor.py +0 -0
  17. {imbi_plugin_github-2.34.0 → imbi_plugin_github-2.35.1}/src/imbi/plugins/github/identity.py +0 -0
  18. {imbi_plugin_github-2.34.0 → imbi_plugin_github-2.35.1}/src/imbi/plugins/github/lifecycle.py +0 -0
  19. {imbi_plugin_github-2.34.0 → imbi_plugin_github-2.35.1}/src/imbi/plugins/github/plugin.py +0 -0
  20. {imbi_plugin_github-2.34.0 → imbi_plugin_github-2.35.1}/src/imbi/plugins/github/py.typed +0 -0
  21. {imbi_plugin_github-2.34.0 → imbi_plugin_github-2.35.1}/tests/__init__.py +0 -0
  22. {imbi_plugin_github-2.34.0 → imbi_plugin_github-2.35.1}/tests/test_app_auth_scopes.py +0 -0
  23. {imbi_plugin_github-2.34.0 → imbi_plugin_github-2.35.1}/tests/test_deployment.py +0 -0
  24. {imbi_plugin_github-2.34.0 → imbi_plugin_github-2.35.1}/tests/test_doctor.py +0 -0
  25. {imbi_plugin_github-2.34.0 → imbi_plugin_github-2.35.1}/tests/test_hosts.py +0 -0
  26. {imbi_plugin_github-2.34.0 → imbi_plugin_github-2.35.1}/tests/test_identity.py +0 -0
  27. {imbi_plugin_github-2.34.0 → imbi_plugin_github-2.35.1}/tests/test_lifecycle.py +0 -0
  28. {imbi_plugin_github-2.34.0 → imbi_plugin_github-2.35.1}/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.34.0
3
+ Version: 2.35.1
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.34.0
15
+ Requires-Dist: imbi-common[databases]==2.35.1
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.34.0"
3
+ version = "2.35.1"
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.34.0",
21
+ "imbi-common[databases]==2.35.1",
22
22
  "pydantic>=2",
23
23
  "pyjwt[crypto]>=2.8",
24
24
  ]
@@ -19,11 +19,11 @@ backfill: there is no push payload, so the GitHub host is read from
19
19
  it walks the full default-branch history and the complete tag list rather
20
20
  than a single push delta.
21
21
 
22
- Commit / tag rows are written to the shared ClickHouse ``commits`` /
23
- ``tags`` tables via :func:`imbi.common.clickhouse.insert`. Writes are
24
- best-effort: a storage failure is logged and swallowed so an analytics
25
- hiccup never 5xxs the webhook, exactly as the gateway's own event
26
- recording behaves.
22
+ Commit / tag rows are published via :func:`imbi.common.iggy.publish` to
23
+ the ``commits`` / ``tags`` streams, which the ClickHouse sink drains into
24
+ the tables of the same name. Writes are best-effort: a publish failure is
25
+ logged and swallowed so an analytics hiccup never 5xxs the webhook,
26
+ exactly as the gateway's own event recording behaves.
27
27
  """
28
28
 
29
29
  from __future__ import annotations
@@ -40,7 +40,7 @@ import httpx
40
40
  import jsonpointer
41
41
  import pydantic
42
42
 
43
- from imbi.common import cache, clickhouse
43
+ from imbi.common import cache, clickhouse, iggy
44
44
  from imbi.common.json_pointer import JsonPointer
45
45
  from imbi.common.models import CommitRecord, TagRecord
46
46
  from imbi.common.plugins.base import (
@@ -67,6 +67,9 @@ from imbi.plugins.github.deployment import (
67
67
 
68
68
  LOGGER = logging.getLogger(__name__)
69
69
 
70
+ #: The Iggy topic every row this plugin publishes goes to.
71
+ _TOPIC = 'github'
72
+
70
73
  _HTTP_TIMEOUT_SECONDS = 10.0
71
74
  _ZERO_SHA = '0' * 40
72
75
  # GitHub's compare endpoint caps ``commits[]`` at 250 per page and
@@ -903,25 +906,26 @@ async def _fetch_commits_since(
903
906
  return out
904
907
 
905
908
 
906
- async def _insert_best_effort(
907
- table: str, records: list[pydantic.BaseModel], project_id: str
909
+ async def _publish_best_effort(
910
+ stream: str, records: list[pydantic.BaseModel], project_id: str
908
911
  ) -> int:
909
- """Insert ``records`` into ``table``; return rows written (0 on fail).
912
+ """Publish ``records`` to ``stream``; return rows sent (0 on fail).
910
913
 
911
- ``imbi.common.clickhouse.insert`` rejects an empty list, so an empty
912
- set short-circuits to 0. A storage failure is logged and swallowed,
914
+ The stream is named for the ClickHouse table the sink lands it in.
915
+ ``imbi.common.iggy.publish`` rejects an empty list, so an empty set
916
+ short-circuits to 0. A publish failure is logged and swallowed,
913
917
  mirroring the webhook actions -- an analytics hiccup must not fail the
914
918
  sync.
915
919
  """
916
920
  if not records:
917
921
  return 0
918
922
  try:
919
- await clickhouse.insert(table, records)
923
+ await iggy.publish(stream, _TOPIC, records)
920
924
  except Exception:
921
925
  LOGGER.exception(
922
926
  'github-commit-sync: failed to record %d %s rows for project %s',
923
927
  len(records),
924
- table,
928
+ stream,
925
929
  project_id,
926
930
  )
927
931
  return 0
@@ -1094,7 +1098,7 @@ async def sync_commits(
1094
1098
  if not records:
1095
1099
  return
1096
1100
  try:
1097
- await clickhouse.insert('commits', records)
1101
+ await iggy.publish('commits', _TOPIC, records)
1098
1102
  except Exception:
1099
1103
  LOGGER.exception(
1100
1104
  'github-commit-sync: failed to record %d commits for project %s',
@@ -1464,7 +1468,7 @@ async def sync_tags(
1464
1468
  if not records:
1465
1469
  return # every tag in this delivery was unpeelable (warned above)
1466
1470
  try:
1467
- await clickhouse.insert('tags', records)
1471
+ await iggy.publish('tags', _TOPIC, records)
1468
1472
  except Exception:
1469
1473
  LOGGER.exception(
1470
1474
  'github-commit-sync: failed to record %d tags for project %s',
@@ -1478,7 +1482,8 @@ sync_commits_descriptor = ActionDescriptor(
1478
1482
  label='Sync Commit History',
1479
1483
  description=(
1480
1484
  'Fetch the full set of commits in a push (via the GitHub compare '
1481
- 'API) and record them in the ClickHouse commits table.'
1485
+ 'API) and publish them to the stream that lands in the ClickHouse '
1486
+ 'commits table.'
1482
1487
  ),
1483
1488
  callable=typing.cast(
1484
1489
  'typing.Any', 'imbi.plugins.github.commits:sync_commits'
@@ -1492,8 +1497,8 @@ sync_tags_descriptor = ActionDescriptor(
1492
1497
  name='sync_tags',
1493
1498
  label='Sync Tag History',
1494
1499
  description=(
1495
- 'Record the pushed tag (and, when reconcile_all is set, the full '
1496
- 'tag list) in the ClickHouse tags table.'
1500
+ 'Publish the pushed tag (and, when reconcile_all is set, the full '
1501
+ 'tag list) to the stream that lands in the ClickHouse tags table.'
1497
1502
  ),
1498
1503
  callable=typing.cast(
1499
1504
  'typing.Any', 'imbi.plugins.github.commits:sync_tags'
@@ -1625,10 +1630,10 @@ class GitHubCommitSync(CommitSyncCapability):
1625
1630
  for item in raw_commits
1626
1631
  if item.get('sha')
1627
1632
  ]
1628
- commits_recorded = await _insert_best_effort(
1633
+ commits_recorded = await _publish_best_effort(
1629
1634
  'commits', commit_records, ctx.project_id
1630
1635
  )
1631
- tags_recorded = await _insert_best_effort(
1636
+ tags_recorded = await _publish_best_effort(
1632
1637
  'tags', list(tags), ctx.project_id
1633
1638
  )
1634
1639
  return commits_recorded, tags_recorded
@@ -1724,7 +1729,7 @@ class GitHubCommitSync(CommitSyncCapability):
1724
1729
  )
1725
1730
  if record is None:
1726
1731
  return 0 # unpeelable (warned by _tag_record)
1727
- return await _insert_best_effort('tags', [record], ctx.project_id)
1732
+ return await _publish_best_effort('tags', [record], ctx.project_id)
1728
1733
 
1729
1734
  async def sync_new_commits(
1730
1735
  self,
@@ -1791,7 +1796,7 @@ class GitHubCommitSync(CommitSyncCapability):
1791
1796
  for item in raw
1792
1797
  if item.get('sha')
1793
1798
  ]
1794
- return await _insert_best_effort('commits', records, ctx.project_id)
1799
+ return await _publish_best_effort('commits', records, ctx.project_id)
1795
1800
 
1796
1801
  async def _fetch_new_commits(
1797
1802
  self,
@@ -13,10 +13,11 @@ GitHub's list-PRs API omits per-file diff stats
13
13
  (``additions``/``deletions``/``changed_files``), so backfill rows
14
14
  store ``0`` for those fields; they are accurate on webhook-driven rows.
15
15
 
16
- PR rows are written to the shared ClickHouse ``pull_requests`` table via
17
- :func:`imbi.common.clickhouse.insert`. Writes are best-effort: a storage
18
- failure is logged and swallowed so an analytics hiccup never 5xxs the
19
- webhook, exactly as the gateway's own event recording behaves.
16
+ PR rows are published via :func:`imbi.common.iggy.publish` to the
17
+ ``pull_requests`` stream, which the ClickHouse sink drains into the table
18
+ of the same name. Writes are best-effort: a publish failure is logged and
19
+ swallowed so an analytics hiccup never 5xxs the webhook, exactly as the
20
+ gateway's own event recording behaves.
20
21
  """
21
22
 
22
23
  from __future__ import annotations
@@ -29,7 +30,7 @@ import httpx
29
30
  import jsonpointer
30
31
  import pydantic
31
32
 
32
- from imbi.common import clickhouse
33
+ from imbi.common import iggy
33
34
  from imbi.common.json_pointer import JsonPointer
34
35
  from imbi.common.models import PullRequestRecord
35
36
  from imbi.common.plugins.base import (
@@ -43,7 +44,7 @@ from imbi.plugins.github._repos import resolve_owner_repo
43
44
  from imbi.plugins.github.commits import (
44
45
  _BACKFILL_MAX_WAIT_SECONDS, # pyright: ignore[reportPrivateUsage]
45
46
  _client, # pyright: ignore[reportPrivateUsage]
46
- _insert_best_effort, # pyright: ignore[reportPrivateUsage]
47
+ _publish_best_effort, # pyright: ignore[reportPrivateUsage]
47
48
  _request, # pyright: ignore[reportPrivateUsage]
48
49
  _resolve, # pyright: ignore[reportPrivateUsage]
49
50
  _resolve_bearer, # pyright: ignore[reportPrivateUsage]
@@ -237,7 +238,7 @@ async def sync_pull_requests(
237
238
  )
238
239
  return
239
240
  try:
240
- await clickhouse.insert('pull_requests', [record])
241
+ await iggy.publish('pull_requests', 'github', [record])
241
242
  except Exception:
242
243
  LOGGER.exception(
243
244
  'github-pr-sync: failed to record PR #%d for project %s',
@@ -356,6 +357,6 @@ class GitHubPullRequestSync(PullRequestSyncCapability):
356
357
  record = _pr_record(pr, project_id=ctx.project_id)
357
358
  if record is not None:
358
359
  records.append(record)
359
- return await _insert_best_effort(
360
+ return await _publish_best_effort(
360
361
  'pull_requests', records, ctx.project_id
361
362
  )
@@ -25,7 +25,7 @@ from imbi.plugins.github.plugin import GitHubPlugin, GitHubWebhookActions
25
25
 
26
26
  _ZERO = '0' * 40
27
27
  _CREDS = {'access_token': 'gho_test'}
28
- _INSERT = 'imbi.plugins.github.commits.clickhouse.insert'
28
+ _INSERT = 'imbi.plugins.github.commits.iggy.publish'
29
29
  _QUERY = 'imbi.plugins.github.commits.clickhouse.query'
30
30
 
31
31
 
@@ -312,7 +312,7 @@ class SyncCommitsTestCase(unittest.IsolatedAsyncioTestCase):
312
312
  event=_event(_push(before=base, after=head)),
313
313
  )
314
314
  insert.assert_awaited_once()
315
- table, records = _await_args(insert)
315
+ table, _topic, records = _await_args(insert)
316
316
  self.assertEqual('commits', table)
317
317
  self.assertEqual(2, len(records))
318
318
  first = records[0]
@@ -368,7 +368,7 @@ class SyncCommitsTestCase(unittest.IsolatedAsyncioTestCase):
368
368
  event=_event(_push(before=_ZERO, after=head)),
369
369
  )
370
370
  insert.assert_awaited_once()
371
- _, records = _await_args(insert)
371
+ _, _, records = _await_args(insert)
372
372
  self.assertEqual('e' * 40, records[0].sha)
373
373
 
374
374
  @respx.mock
@@ -414,7 +414,7 @@ class SyncCommitsTestCase(unittest.IsolatedAsyncioTestCase):
414
414
  )
415
415
  self.assertTrue(route.called)
416
416
  insert.assert_awaited_once()
417
- table, records = _await_args(insert)
417
+ table, _topic, records = _await_args(insert)
418
418
  self.assertEqual('commits', table)
419
419
  self.assertEqual(1, len(records))
420
420
  self.assertEqual(head, records[0].sha)
@@ -451,7 +451,7 @@ class SyncCommitsTestCase(unittest.IsolatedAsyncioTestCase):
451
451
  action_config=_WORKFLOW_RUN_CONFIG,
452
452
  event=_event(_workflow_run(head=head)),
453
453
  )
454
- _, records = _await_args(insert)
454
+ _, _, records = _await_args(insert)
455
455
  self.assertEqual(first_pushed, records[0].pushed_at)
456
456
  self.assertEqual('pass', records[0].ci_status)
457
457
  lookup = next(
@@ -481,7 +481,7 @@ class SyncCommitsTestCase(unittest.IsolatedAsyncioTestCase):
481
481
  action_config=commits.SyncCommitsConfig(),
482
482
  event=_event(_push(before=base, after=head)),
483
483
  )
484
- _, records = _await_args(insert)
484
+ _, _, records = _await_args(insert)
485
485
  by_sha = {r.sha: r for r in records}
486
486
  self.assertEqual(first_pushed, by_sha['c' * 40].pushed_at)
487
487
  self.assertGreater(by_sha['d' * 40].pushed_at, first_pushed)
@@ -508,7 +508,7 @@ class SyncCommitsTestCase(unittest.IsolatedAsyncioTestCase):
508
508
  event=_event(_workflow_run(head=head)),
509
509
  )
510
510
  insert.assert_awaited_once()
511
- _, records = _await_args(insert)
511
+ _, _, records = _await_args(insert)
512
512
  self.assertEqual(head, records[0].sha)
513
513
 
514
514
  @respx.mock
@@ -590,7 +590,7 @@ class SyncCommitsTestCase(unittest.IsolatedAsyncioTestCase):
590
590
  '100', route.calls[0].request.url.params.get('per_page')
591
591
  )
592
592
  insert.assert_awaited_once()
593
- _, records = _await_args(insert)
593
+ _, _, records = _await_args(insert)
594
594
  self.assertEqual('fail', records[0].ci_status)
595
595
 
596
596
  @respx.mock
@@ -633,7 +633,7 @@ class SyncCommitsTestCase(unittest.IsolatedAsyncioTestCase):
633
633
  action_config=commits.SyncCommitsConfig(),
634
634
  event=_event(_push(before=base, after=head)),
635
635
  )
636
- _, records = _await_args(insert)
636
+ _, _, records = _await_args(insert)
637
637
  self.assertEqual(2, len(records))
638
638
 
639
639
  @respx.mock
@@ -716,7 +716,7 @@ class SyncCommitsCiStatusTestCase(unittest.IsolatedAsyncioTestCase):
716
716
  action_config=commits.SyncCommitsConfig(),
717
717
  event=_event(_push(before=base, after=head)),
718
718
  )
719
- _, records = _await_args(insert)
719
+ _, _, records = _await_args(insert)
720
720
  return records
721
721
 
722
722
  @respx.mock
@@ -848,7 +848,7 @@ class SyncTagsTestCase(unittest.IsolatedAsyncioTestCase):
848
848
  action_config=commits.SyncTagsConfig(),
849
849
  event=_event(self._tag_push(after=sha)),
850
850
  )
851
- table, records = _await_args(insert)
851
+ table, _topic, records = _await_args(insert)
852
852
  self.assertEqual('tags', table)
853
853
  self.assertEqual(1, len(records))
854
854
  self.assertIsInstance(records[0], TagRecord)
@@ -886,7 +886,7 @@ class SyncTagsTestCase(unittest.IsolatedAsyncioTestCase):
886
886
  action_config=commits.SyncTagsConfig(),
887
887
  event=_event(self._tag_push(after=sha)),
888
888
  )
889
- _, records = _await_args(insert)
889
+ _, _, records = _await_args(insert)
890
890
  self.assertEqual(
891
891
  datetime.datetime.fromisoformat('2026-02-03T04:05:06Z'),
892
892
  records[0].tagged_at,
@@ -920,7 +920,7 @@ class SyncTagsTestCase(unittest.IsolatedAsyncioTestCase):
920
920
  ),
921
921
  )
922
922
  self.assertTrue(route.called)
923
- _, records = _await_args(insert)
923
+ _, _, records = _await_args(insert)
924
924
  self.assertEqual(
925
925
  datetime.datetime.fromisoformat('2026-02-03T04:05:06Z'),
926
926
  records[0].tagged_at,
@@ -946,7 +946,7 @@ class SyncTagsTestCase(unittest.IsolatedAsyncioTestCase):
946
946
  action_config=commits.SyncTagsConfig(),
947
947
  event=_event(self._tag_push(after=sha)),
948
948
  )
949
- _, records = _await_args(insert)
949
+ _, _, records = _await_args(insert)
950
950
  self.assertIsNone(records[0].tagged_at)
951
951
 
952
952
  @respx.mock
@@ -983,7 +983,7 @@ class SyncTagsTestCase(unittest.IsolatedAsyncioTestCase):
983
983
  action_config=commits.SyncTagsConfig(),
984
984
  event=_event(self._tag_push(after=sha)),
985
985
  )
986
- _, records = _await_args(insert)
986
+ _, _, records = _await_args(insert)
987
987
  self.assertEqual('Release 1.2.3', records[0].message)
988
988
  self.assertEqual('Rel Bot', records[0].tagger_name)
989
989
  self.assertIsNotNone(records[0].tagged_at)
@@ -1060,7 +1060,7 @@ class SyncTagsTestCase(unittest.IsolatedAsyncioTestCase):
1060
1060
  action_config=commits.SyncTagsConfig(reconcile_all=True),
1061
1061
  event=_event(self._tag_push(after=tag_sha)),
1062
1062
  )
1063
- _, records = _await_args(insert)
1063
+ _, _, records = _await_args(insert)
1064
1064
  self.assertEqual(['v1.2.3'], [r.name for r in records])
1065
1065
  self.assertEqual(commit_sha, records[0].sha)
1066
1066
 
@@ -1114,7 +1114,7 @@ class SyncTagsTestCase(unittest.IsolatedAsyncioTestCase):
1114
1114
  action_config=commits.SyncTagsConfig(reconcile_all=True),
1115
1115
  event=_event(self._tag_push(after=sha)),
1116
1116
  )
1117
- _, records = _await_args(insert)
1117
+ _, _, records = _await_args(insert)
1118
1118
  names = {r.name for r in records}
1119
1119
  self.assertEqual({'v1.2.3', 'v1.0.0'}, names)
1120
1120
  urls = {r.name: r.url for r in records}
@@ -1189,7 +1189,7 @@ class SyncTagsTestCase(unittest.IsolatedAsyncioTestCase):
1189
1189
  action_config=commits.SyncTagsConfig(reconcile_all=True),
1190
1190
  event=_event(self._tag_push(after=sha)),
1191
1191
  )
1192
- _, records = _await_args(insert)
1192
+ _, _, records = _await_args(insert)
1193
1193
  names = {r.name for r in records}
1194
1194
  self.assertEqual({'v1.2.3', 'v1.0.0'}, names)
1195
1195
  urls = {r.name: r.url for r in records}
@@ -1667,13 +1667,13 @@ class SyncAllHistoryTestCase(unittest.IsolatedAsyncioTestCase):
1667
1667
  commit_call = next(
1668
1668
  c for c in insert.await_args_list if c.args[0] == 'commits'
1669
1669
  )
1670
- records = commit_call.args[1]
1670
+ records = commit_call.args[2]
1671
1671
  self.assertIsInstance(records[0], CommitRecord)
1672
1672
  self.assertEqual('main', records[0].ref)
1673
1673
  tag_call = next(
1674
1674
  c for c in insert.await_args_list if c.args[0] == 'tags'
1675
1675
  )
1676
- urls = {r.name: r.url for r in tag_call.args[1]}
1676
+ urls = {r.name: r.url for r in tag_call.args[2]}
1677
1677
  self.assertEqual(
1678
1678
  {
1679
1679
  'v1.0.0': 'https://github.com/octo/demo/releases/tag/v1.0.0',
@@ -1709,7 +1709,7 @@ class SyncAllHistoryTestCase(unittest.IsolatedAsyncioTestCase):
1709
1709
  commit_call = next(
1710
1710
  c for c in insert.await_args_list if c.args[0] == 'commits'
1711
1711
  )
1712
- by_sha = {r.sha: r for r in commit_call.args[1]}
1712
+ by_sha = {r.sha: r for r in commit_call.args[2]}
1713
1713
  self.assertEqual(first_pushed, by_sha['d' * 40].pushed_at)
1714
1714
  self.assertGreater(by_sha['c' * 40].pushed_at, first_pushed)
1715
1715
 
@@ -1738,7 +1738,7 @@ class SyncAllHistoryTestCase(unittest.IsolatedAsyncioTestCase):
1738
1738
  commit_call = next(
1739
1739
  c for c in insert.await_args_list if c.args[0] == 'commits'
1740
1740
  )
1741
- records = commit_call.args[1]
1741
+ records = commit_call.args[2]
1742
1742
  self.assertEqual('pass', records[0].ci_status)
1743
1743
  self.assertEqual('unknown', records[1].ci_status)
1744
1744
 
@@ -1948,7 +1948,7 @@ class SyncAllHistoryTestCase(unittest.IsolatedAsyncioTestCase):
1948
1948
  tag_call = next(
1949
1949
  c for c in insert.await_args_list if c.args[0] == 'tags'
1950
1950
  )
1951
- record = tag_call.args[1][0]
1951
+ record = tag_call.args[2][0]
1952
1952
  self.assertEqual('Release 2.0.0', record.message)
1953
1953
  self.assertEqual('Rel Bot', record.tagger_name)
1954
1954
  self.assertIsNotNone(record.tagged_at)
@@ -2005,7 +2005,7 @@ class SyncAllHistoryTestCase(unittest.IsolatedAsyncioTestCase):
2005
2005
  r
2006
2006
  for c in insert.await_args_list
2007
2007
  if c.args[0] == 'tags'
2008
- for r in c.args[1]
2008
+ for r in c.args[2]
2009
2009
  ]
2010
2010
 
2011
2011
  @respx.mock
@@ -2116,7 +2116,7 @@ class SyncTagTestCase(unittest.IsolatedAsyncioTestCase):
2116
2116
  )
2117
2117
  written, insert = await self._sync()
2118
2118
  self.assertEqual(1, written)
2119
- record = insert.await_args_list[0].args[1][0]
2119
+ record = insert.await_args_list[0].args[2][0]
2120
2120
  self.assertEqual('2.23.0', record.name)
2121
2121
  self.assertEqual('c' * 40, record.sha)
2122
2122
  self.assertEqual(
@@ -2152,7 +2152,7 @@ class SyncTagTestCase(unittest.IsolatedAsyncioTestCase):
2152
2152
  )
2153
2153
  written, insert = await self._sync()
2154
2154
  self.assertEqual(1, written)
2155
- record = insert.await_args_list[0].args[1][0]
2155
+ record = insert.await_args_list[0].args[2][0]
2156
2156
  self.assertEqual('c' * 40, record.sha)
2157
2157
  self.assertEqual('Release 2.23.0', record.message)
2158
2158
  self.assertEqual('Alice', record.tagger_name)
@@ -2163,7 +2163,7 @@ class SyncTagTestCase(unittest.IsolatedAsyncioTestCase):
2163
2163
  self._mock_release('2026-08-04T13:00:00Z')
2164
2164
  written, insert = await self._sync()
2165
2165
  self.assertEqual(1, written)
2166
- record = insert.await_args_list[0].args[1][0]
2166
+ record = insert.await_args_list[0].args[2][0]
2167
2167
  self.assertEqual(
2168
2168
  datetime.datetime(2026, 8, 4, 13, 0, tzinfo=datetime.UTC),
2169
2169
  record.tagged_at,
@@ -2284,7 +2284,7 @@ class SyncNewCommitsTestCase(unittest.IsolatedAsyncioTestCase):
2284
2284
  self.assertIn(
2285
2285
  f'{"a" * 40}...main', str(compare.calls.last.request.url)
2286
2286
  )
2287
- record = insert.await_args_list[0].args[1][0]
2287
+ record = insert.await_args_list[0].args[2][0]
2288
2288
  self.assertEqual('main', record.ref)
2289
2289
 
2290
2290
  @respx.mock
@@ -2304,7 +2304,7 @@ class SyncNewCommitsTestCase(unittest.IsolatedAsyncioTestCase):
2304
2304
  ):
2305
2305
  written, insert = await self._sync()
2306
2306
  self.assertEqual(2, written)
2307
- by_sha = {r.sha: r for r in insert.await_args_list[0].args[1]}
2307
+ by_sha = {r.sha: r for r in insert.await_args_list[0].args[2]}
2308
2308
  self.assertEqual(first_pushed, by_sha['c' * 40].pushed_at)
2309
2309
  self.assertGreater(by_sha['d' * 40].pushed_at, first_pushed)
2310
2310
 
@@ -2581,7 +2581,7 @@ class SyncCommitsThrottleTestCase(unittest.IsolatedAsyncioTestCase):
2581
2581
  )
2582
2582
  slept.assert_awaited_once_with(3.0)
2583
2583
  insert.assert_awaited_once()
2584
- _, records = _await_args(insert)
2584
+ _, _, records = _await_args(insert)
2585
2585
  self.assertEqual(1, len(records))
2586
2586
 
2587
2587
  @respx.mock
@@ -2757,7 +2757,7 @@ class AuthorAttributionTestCase(unittest.IsolatedAsyncioTestCase):
2757
2757
  action_config=commits.SyncCommitsConfig(),
2758
2758
  event=_event(_push(before=base, after=head)),
2759
2759
  )
2760
- _, records = _await_args(insert)
2760
+ _, _, records = _await_args(insert)
2761
2761
  self.assertEqual('dev@example.com', records[0].author_user)
2762
2762
  resolver.assert_awaited_once_with('42')
2763
2763
 
@@ -2777,7 +2777,7 @@ class AuthorAttributionTestCase(unittest.IsolatedAsyncioTestCase):
2777
2777
  action_config=commits.SyncCommitsConfig(),
2778
2778
  event=_event(_push(before=base, after=head)),
2779
2779
  )
2780
- _, records = _await_args(insert)
2780
+ _, _, records = _await_args(insert)
2781
2781
  self.assertEqual('', records[0].author_user)
2782
2782
 
2783
2783
  @respx.mock
@@ -2813,7 +2813,7 @@ class AuthorAttributionTestCase(unittest.IsolatedAsyncioTestCase):
2813
2813
  commit_call = next(
2814
2814
  c for c in insert.await_args_list if c.args[0] == 'commits'
2815
2815
  )
2816
- by_sha = {r.sha: r.author_user for r in commit_call.args[1]}
2816
+ by_sha = {r.sha: r.author_user for r in commit_call.args[2]}
2817
2817
  self.assertEqual('dev@example.com', by_sha['c' * 40])
2818
2818
  self.assertEqual('dev@example.com', by_sha['d' * 40])
2819
2819
  self.assertEqual('', by_sha['e' * 40])
@@ -28,11 +28,11 @@ def _gen_pem() -> str:
28
28
 
29
29
  # Generated once for the module; signing only needs a valid RSA key.
30
30
  _APP_KEY_PEM = _gen_pem()
31
- # ``sync_all_history`` inserts through ``_insert_best_effort`` (defined in
31
+ # ``sync_all_history`` publishes through ``_publish_best_effort`` (defined in
32
32
  # commits.py), so its ClickHouse call resolves in the commits namespace.
33
- _INSERT_BACKFILL = 'imbi.plugins.github.commits.clickhouse.insert'
34
- # The webhook action inserts directly through the pull_requests namespace.
35
- _INSERT_WEBHOOK = 'imbi.plugins.github.pull_requests.clickhouse.insert'
33
+ _INSERT_BACKFILL = 'imbi.plugins.github.commits.iggy.publish'
34
+ # The webhook action publishes directly through the pull_requests namespace.
35
+ _INSERT_WEBHOOK = 'imbi.plugins.github.pull_requests.iggy.publish'
36
36
 
37
37
 
38
38
  def _connection(
@@ -140,7 +140,7 @@ class SyncPullRequestsActionTestCase(unittest.IsolatedAsyncioTestCase):
140
140
  )
141
141
  insert.assert_awaited_once()
142
142
  assert insert.await_args is not None
143
- table, records = insert.await_args.args
143
+ table, _topic, records = insert.await_args.args
144
144
  self.assertEqual(table, 'pull_requests')
145
145
  self.assertEqual(records[0].pr_number, 7)
146
146
  self.assertEqual(records[0].author, 'octocat')