refactor(native-ingest): shared exception trio + base _failure_result (#899 DRY 2/3)
DRY pass commit 2. The two adapters re-implemented the same auth→drift→429→404
→http→network mapping in _failure_result; only the exception classes + drift
phrasing differed (divergence-bug risk: a new error_type handled in one and not
the other).
- native_ingest_common gains NativeIngestError / NativeAuthError / NativeDriftError
(status_code + retry_after on the base). Patreon{API,Auth,Drift}Error and
SubscribeStar{API,Auth,Drift}Error now subclass them via multiple inheritance,
keeping their isinstance-distinct platform names.
- Ingester._failure_result (base) does the whole mapping via the shared
NativeAuthError/NativeDriftError taxonomy + status_code; a new platform gets it
free. New drift_label kwarg supplies the per-platform API_DRIFT phrasing
("Patreon API" / "SubscribeStar markup"), preserving the existing message
(test asserts "Patreon API changed").
- Both adapters drop their near-identical _failure_result overrides and their now
-unused DownloadResult/ErrorType/*Auth/*Drift imports.
Verified at every consumer (rule 93/§8b): test_patreon_ingester (auth/drift/429/
404/network) and test_subscribestar_native (_failure_result mapping) both exercise
the base method now. Remaining: ingest_core L1/L3 logging (3/3).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -41,6 +41,9 @@ from ..utils.paths import filehash_from_url
|
||||
from ..utils.prosemirror import post_body_html
|
||||
from .native_ingest_common import (
|
||||
_MAX_429_RETRIES,
|
||||
NativeAuthError,
|
||||
NativeDriftError,
|
||||
NativeIngestError,
|
||||
basename_from_url,
|
||||
make_session,
|
||||
retry_after_seconds,
|
||||
@@ -73,30 +76,12 @@ _FIELDS_CAMPAIGN = "name,url"
|
||||
_CONTENT_IMG_RE = re.compile(r"<img\b[^>]*?\bsrc=[\"']([^\"']+)[\"']", re.IGNORECASE)
|
||||
|
||||
|
||||
class PatreonAPIError(Exception):
|
||||
"""Base for native Patreon client failures.
|
||||
|
||||
`status_code` carries the HTTP status when the failure was an HTTP response
|
||||
(None for transport-level / parse failures), so the ingester can map it to a
|
||||
DownloadResult.error_type (429 → rate_limited, 404 → not_found, …).
|
||||
`retry_after` carries the server's `Retry-After` seconds on a terminal 429, so
|
||||
the platform cooldown can match the server's hint instead of a flat default
|
||||
(plan #708 B1).
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: str,
|
||||
*,
|
||||
status_code: int | None = None,
|
||||
retry_after: float | None = None,
|
||||
):
|
||||
super().__init__(message)
|
||||
self.status_code = status_code
|
||||
self.retry_after = retry_after
|
||||
class PatreonAPIError(NativeIngestError):
|
||||
"""Base for native Patreon client failures. status_code / retry_after are
|
||||
inherited from NativeIngestError (HTTP status; 429 Retry-After hint)."""
|
||||
|
||||
|
||||
class PatreonAuthError(PatreonAPIError):
|
||||
class PatreonAuthError(PatreonAPIError, NativeAuthError):
|
||||
"""Authentication / authorization failure — missing or expired session
|
||||
cookies, an insufficient pledge tier, or an HTML login/challenge page served
|
||||
where JSON was expected. DISTINCT from drift: the fix is rotating the
|
||||
@@ -104,7 +89,7 @@ class PatreonAuthError(PatreonAPIError):
|
||||
"""
|
||||
|
||||
|
||||
class PatreonDriftError(PatreonAPIError):
|
||||
class PatreonDriftError(PatreonAPIError, NativeDriftError):
|
||||
"""A JSON response did not match the JSON:API shape we depend on.
|
||||
|
||||
Raised for: a missing top-level `data` list, `data` not a list, or a media
|
||||
|
||||
Reference in New Issue
Block a user