From d526447496913be57654f62822d4db1f47fe8680 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Wed, 17 Jun 2026 12:09:40 -0400 Subject: [PATCH] fix(dispatch): resolve native ingester class at call time (test monkeypatch) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The _NATIVE_INGESTERS dict captured PatreonIngester/SubscribeStarIngester at import, so test_download_service's monkeypatch.setattr(db_mod, "PatreonIngester", _FakeIngester) no longer affected dispatch → the fake's run() never ran → KeyError 'campaign_id' on empty run_kwargs (integration run 1215). Replace the dict with a _native_ingester_cls() call-time lookup that reads the module globals, so monkeypatching the class names works again. Co-Authored-By: Claude Opus 4.8 --- backend/app/services/download_backends.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/backend/app/services/download_backends.py b/backend/app/services/download_backends.py index 350f009..48e6ceb 100644 --- a/backend/app/services/download_backends.py +++ b/backend/app/services/download_backends.py @@ -34,18 +34,22 @@ from .subscribestar_ingester import SubscribeStarIngester # deviantart) until they migrate too. NATIVE_INGESTER_PLATFORMS = frozenset({"patreon", "subscribestar"}) -# Native ingester classes keyed by platform (uniform constructor signature, so -# _run_native_ingester / preview build whichever the source needs). -_NATIVE_INGESTERS = { - "patreon": PatreonIngester, - "subscribestar": SubscribeStarIngester, -} - # Mirrors patreon_resolver._CAMPAIGNS_URL — surfaced in resolution-failure # messages so the operator sees the exact lookup endpoint that was hit. _CAMPAIGNS_API = "https://www.patreon.com/api/campaigns" +def _native_ingester_cls(platform: str): + """The native ingester class for `platform` (uniform constructor signature). + A call-time lookup (not a module-level dict captured at import) so tests can + monkeypatch db_mod.PatreonIngester / SubscribeStarIngester and have the + dispatch pick up the replacement.""" + return { + "patreon": PatreonIngester, + "subscribestar": SubscribeStarIngester, + }[platform] + + def uses_native_ingester(platform: str) -> bool: """True when `platform` is served by the native ingester (not gallery-dl). The single predicate the download path and verify both route on.""" @@ -150,7 +154,7 @@ async def _run_native_ingester( if source_config.sleep_request is not None else max(0.5, rate_limit / 4) ) - ingester = _NATIVE_INGESTERS[platform]( + ingester = _native_ingester_cls(platform)( images_root=gdl.images_root, cookies_path=ctx["cookies_path"], session_factory=sync_session_factory, @@ -209,7 +213,7 @@ async def preview_source( "(cookies expired, or the creator moved/renamed?)." ) } - ingester = _NATIVE_INGESTERS[platform]( + ingester = _native_ingester_cls(platform)( images_root=images_root, cookies_path=cookies_path, session_factory=sync_session_factory,