Files
FabledCurator/backend/app/tasks/import_file.py
T
bvandeusen b4e0d680f1 fix(fc2a): satisfy ruff 0.15.13 lint — UP017, UP042, I001
Ruff lint surfaced 23 violations across three rules; all addressed:

UP017 (Use datetime.UTC alias):
  Replaced 13 sites of datetime.now(timezone.utc) with datetime.now(UTC),
  also adjusted from-imports accordingly. UTC is a Python 3.11+ alias for
  timezone.utc that ruff's pyupgrade rules prefer.

UP042 (StrEnum):
  Replaced `class TagKind(str, Enum)` and `class SkipReason(str, Enum)`
  with `class Foo(StrEnum)`. StrEnum was added in Python 3.11 stdlib and
  is the modern idiom. Behavior is equivalent for our usage (the .value
  attribute, str(member) semantics).

I001 (Import sorting):
  Added `known-first-party = ["backend"]` to ruff.toml's [lint.isort] so
  ruff groups `backend.*` imports correctly. Without it, ruff treated
  them as third-party and demanded a different grouping. The existing
  import order is stdlib → third-party → first-party → local relative,
  which ruff now accepts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 14:11:35 -04:00

123 lines
4.2 KiB
Python

"""import_media_file task: imports one file via the Importer service and
updates the ImportTask state machine + ImportBatch counters atomically.
"""
from datetime import UTC, datetime
from pathlib import Path
from sqlalchemy import create_engine, select, update
from sqlalchemy.orm import sessionmaker
from ..celery_app import celery
from ..config import get_config
from ..models import ImportBatch, ImportSettings, ImportTask
from ..services.importer import Importer
from ..services.thumbnailer import Thumbnailer
def _sync_session_factory():
cfg = get_config()
engine = create_engine(cfg.database_url_sync, future=True, pool_pre_ping=True)
return sessionmaker(engine, expire_on_commit=False)
IMAGES_ROOT = Path("/images")
@celery.task(name="backend.app.tasks.import_file.import_media_file", bind=True)
def import_media_file(self, import_task_id: int) -> dict:
"""Returns a dict so the eager-mode tests can assert without DB."""
SessionLocal = _sync_session_factory()
with SessionLocal() as session:
task = session.get(ImportTask, import_task_id)
if task is None:
return {"status": "missing", "task_id": import_task_id}
task.status = "processing"
task.started_at = datetime.now(UTC)
session.add(task)
session.commit()
settings = session.execute(
select(ImportSettings).where(ImportSettings.id == 1)
).scalar_one()
import_root = Path(settings.import_scan_path)
importer = Importer(
session=session,
images_root=IMAGES_ROOT,
import_root=import_root,
thumbnailer=Thumbnailer(images_root=IMAGES_ROOT),
settings=settings,
)
try:
result = importer.import_one(Path(task.source_path))
except Exception as exc: # pragma: no cover — pipeline crash
task.status = "failed"
task.error = f"{type(exc).__name__}: {exc}"
task.finished_at = datetime.now(UTC)
session.execute(
update(ImportBatch)
.where(ImportBatch.id == task.batch_id)
.values(failed=ImportBatch.failed + 1)
)
session.add(task)
session.commit()
raise
if result.status == "imported":
task.status = "complete"
task.result_image_id = result.image_id
counter_col_name = "imported"
counter_col = ImportBatch.imported
elif result.status == "skipped":
task.status = "skipped"
task.error = (
f"{result.skip_reason.value}: {result.error}"
if result.skip_reason
else result.error
)
task.result_image_id = result.image_id
counter_col_name = "skipped"
counter_col = ImportBatch.skipped
else:
task.status = "failed"
task.error = result.error
counter_col_name = "failed"
counter_col = ImportBatch.failed
task.finished_at = datetime.now(UTC)
session.execute(
update(ImportBatch)
.where(ImportBatch.id == task.batch_id)
.values({counter_col_name: counter_col + 1})
)
session.add(task)
session.commit()
# Enqueue the thumbnail task for newly imported images.
if result.status == "imported" and result.image_id is not None:
from .thumbnail import generate_thumbnail
generate_thumbnail.delay(result.image_id)
# If this was the last task in the batch, mark the batch complete.
remaining = session.execute(
select(ImportTask.id)
.where(ImportTask.batch_id == task.batch_id)
.where(ImportTask.status.in_(["pending", "queued", "processing"]))
.limit(1)
).scalar_one_or_none()
if remaining is None:
session.execute(
update(ImportBatch)
.where(ImportBatch.id == task.batch_id)
.values(
status="complete",
finished_at=datetime.now(UTC),
)
)
session.commit()
return {"task_id": import_task_id, "status": task.status}