fc3d: DownloadService finalize hook — Source.consecutive_failures/last_error/last_checked_at
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -134,14 +134,14 @@ class DownloadService:
|
||||
raise LookupError(f"source {source_id} not found")
|
||||
|
||||
if not source.enabled:
|
||||
ev = DownloadEvent(
|
||||
source_id=source_id, status="skipped",
|
||||
error="source disabled",
|
||||
finished_at=datetime.now(UTC),
|
||||
)
|
||||
ev = DownloadEvent(source_id=source_id, status="pending")
|
||||
self.async_session.add(ev)
|
||||
await self.async_session.commit()
|
||||
await self.async_session.refresh(ev)
|
||||
await self._finalize_event_and_source(
|
||||
event_id=ev.id, source_id=source_id, status="skipped",
|
||||
error_message="source disabled",
|
||||
)
|
||||
return {"status": "skipped", "event_id": ev.id}
|
||||
|
||||
existing = (await self.async_session.execute(
|
||||
@@ -258,7 +258,8 @@ class DownloadService:
|
||||
run_stats["quarantined_count"] = dl_result.files_quarantined
|
||||
stderr_summary = self.gdl._extract_errors_warnings(dl_result.stderr)
|
||||
|
||||
ev.status = "ok" if (dl_result.success and import_summary["errors"] == 0) else "error"
|
||||
status = "ok" if (dl_result.success and import_summary["errors"] == 0) else "error"
|
||||
ev.status = status
|
||||
ev.finished_at = datetime.now(UTC)
|
||||
ev.files_count = import_summary["attached"]
|
||||
ev.bytes_downloaded = bytes_downloaded
|
||||
@@ -273,5 +274,50 @@ class DownloadService:
|
||||
"quarantined_paths": dl_result.quarantined_paths or None,
|
||||
"import_summary": import_summary,
|
||||
}
|
||||
await self._update_source_health(
|
||||
source_id=ctx["source_id"], status=status, error_message=ev.error,
|
||||
)
|
||||
await self.async_session.commit()
|
||||
return event_id
|
||||
|
||||
async def _update_source_health(
|
||||
self, *, source_id: int, status: str, error_message: str | None,
|
||||
) -> None:
|
||||
"""FC-3d: update Source.{consecutive_failures, last_error, last_checked_at}.
|
||||
|
||||
ok -> failures = 0, error = None, checked_at = now
|
||||
error -> failures += 1, error = error_message, checked_at = now
|
||||
skipped -> failures unchanged, error = None, checked_at = now
|
||||
"""
|
||||
source = (await self.async_session.execute(
|
||||
select(Source).where(Source.id == source_id)
|
||||
)).scalar_one()
|
||||
now = datetime.now(UTC)
|
||||
if status == "ok":
|
||||
source.consecutive_failures = 0
|
||||
source.last_error = None
|
||||
elif status == "error":
|
||||
source.consecutive_failures = (source.consecutive_failures or 0) + 1
|
||||
source.last_error = error_message
|
||||
elif status == "skipped":
|
||||
source.last_error = None
|
||||
source.last_checked_at = now
|
||||
|
||||
async def _finalize_event_and_source(
|
||||
self, *, event_id: int, source_id: int, status: str,
|
||||
error_message: str | None,
|
||||
) -> None:
|
||||
"""Finalize the event AND the source-health columns in one
|
||||
transactional step. Used by the skipped early-out path and by
|
||||
unit tests that exercise the source-health writes directly.
|
||||
"""
|
||||
ev = (await self.async_session.execute(
|
||||
select(DownloadEvent).where(DownloadEvent.id == event_id)
|
||||
)).scalar_one()
|
||||
ev.status = status
|
||||
ev.finished_at = datetime.now(UTC)
|
||||
ev.error = error_message
|
||||
await self._update_source_health(
|
||||
source_id=source_id, status=status, error_message=error_message,
|
||||
)
|
||||
await self.async_session.commit()
|
||||
|
||||
Reference in New Issue
Block a user