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>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
"""Import admin API: trigger scan, list tasks, retry, clear."""
|
||||
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from datetime import UTC, datetime, timedelta
|
||||
|
||||
from quart import Blueprint, jsonify, request
|
||||
from sqlalchemy import delete, select, update
|
||||
@@ -137,7 +137,7 @@ async def clear_completed():
|
||||
return jsonify({"error": "status must be a list"}), 400
|
||||
|
||||
cutoff = (
|
||||
datetime.now(timezone.utc) - timedelta(days=int(age_days)) if age_days else None
|
||||
datetime.now(UTC) - timedelta(days=int(age_days)) if age_days else None
|
||||
)
|
||||
|
||||
Session = _session_factory()
|
||||
|
||||
@@ -7,7 +7,7 @@ different kinds and the same character name can exist in different fandoms.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from enum import StrEnum
|
||||
|
||||
from sqlalchemy import (
|
||||
CheckConstraint,
|
||||
@@ -25,7 +25,7 @@ from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from .base import Base
|
||||
|
||||
|
||||
class TagKind(str, Enum):
|
||||
class TagKind(StrEnum):
|
||||
artist = "artist"
|
||||
character = "character"
|
||||
fandom = "fandom"
|
||||
|
||||
@@ -11,7 +11,7 @@ by the task. The Quart side uses an async session via the same models.
|
||||
import hashlib
|
||||
import shutil
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from enum import StrEnum
|
||||
from pathlib import Path
|
||||
|
||||
from PIL import Image
|
||||
@@ -25,7 +25,7 @@ from ..utils.slug import slugify
|
||||
from .thumbnailer import Thumbnailer
|
||||
|
||||
|
||||
class SkipReason(str, Enum):
|
||||
class SkipReason(StrEnum):
|
||||
too_small = "too_small"
|
||||
too_transparent = "too_transparent"
|
||||
single_color = "single_color"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
updates the ImportTask state machine + ImportBatch counters atomically.
|
||||
"""
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
|
||||
from sqlalchemy import create_engine, select, update
|
||||
@@ -34,7 +34,7 @@ def import_media_file(self, import_task_id: int) -> dict:
|
||||
return {"status": "missing", "task_id": import_task_id}
|
||||
|
||||
task.status = "processing"
|
||||
task.started_at = datetime.now(timezone.utc)
|
||||
task.started_at = datetime.now(UTC)
|
||||
session.add(task)
|
||||
session.commit()
|
||||
|
||||
@@ -55,7 +55,7 @@ def import_media_file(self, import_task_id: int) -> dict:
|
||||
except Exception as exc: # pragma: no cover — pipeline crash
|
||||
task.status = "failed"
|
||||
task.error = f"{type(exc).__name__}: {exc}"
|
||||
task.finished_at = datetime.now(timezone.utc)
|
||||
task.finished_at = datetime.now(UTC)
|
||||
session.execute(
|
||||
update(ImportBatch)
|
||||
.where(ImportBatch.id == task.batch_id)
|
||||
@@ -86,7 +86,7 @@ def import_media_file(self, import_task_id: int) -> dict:
|
||||
counter_col_name = "failed"
|
||||
counter_col = ImportBatch.failed
|
||||
|
||||
task.finished_at = datetime.now(timezone.utc)
|
||||
task.finished_at = datetime.now(UTC)
|
||||
session.execute(
|
||||
update(ImportBatch)
|
||||
.where(ImportBatch.id == task.batch_id)
|
||||
@@ -114,7 +114,7 @@ def import_media_file(self, import_task_id: int) -> dict:
|
||||
.where(ImportBatch.id == task.batch_id)
|
||||
.values(
|
||||
status="complete",
|
||||
finished_at=datetime.now(timezone.utc),
|
||||
finished_at=datetime.now(UTC),
|
||||
)
|
||||
)
|
||||
session.commit()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Periodic maintenance: recover stuck import tasks, garbage-collect old finished tasks."""
|
||||
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from datetime import UTC, datetime, timedelta
|
||||
|
||||
from sqlalchemy import create_engine, delete, select, update
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
@@ -29,7 +29,7 @@ def recover_interrupted_tasks() -> int:
|
||||
forever) without resetting slow-but-still-running jobs.
|
||||
"""
|
||||
SessionLocal = _sync_session_factory()
|
||||
cutoff = datetime.now(timezone.utc) - timedelta(minutes=STUCK_THRESHOLD_MINUTES)
|
||||
cutoff = datetime.now(UTC) - timedelta(minutes=STUCK_THRESHOLD_MINUTES)
|
||||
with SessionLocal() as session:
|
||||
stuck_ids = session.execute(
|
||||
select(ImportTask.id)
|
||||
@@ -63,7 +63,7 @@ def cleanup_old_tasks() -> int:
|
||||
rather than an archive. Matches IR's default.
|
||||
"""
|
||||
SessionLocal = _sync_session_factory()
|
||||
cutoff = datetime.now(timezone.utc) - timedelta(days=OLD_TASK_DAYS)
|
||||
cutoff = datetime.now(UTC) - timedelta(days=OLD_TASK_DAYS)
|
||||
with SessionLocal() as session:
|
||||
result = session.execute(
|
||||
delete(ImportTask)
|
||||
|
||||
@@ -19,5 +19,10 @@ ignore = [
|
||||
"alembic/versions/*.py" = ["E", "F", "I", "UP"]
|
||||
"tests/*" = ["F401"]
|
||||
|
||||
[lint.isort]
|
||||
# Tell ruff that `backend` is our first-party module so imports get
|
||||
# grouped correctly: stdlib, third-party, first-party, then local relative.
|
||||
known-first-party = ["backend"]
|
||||
|
||||
[format]
|
||||
quote-style = "double"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from datetime import UTC, datetime, timedelta
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -18,7 +18,7 @@ async def client(app):
|
||||
|
||||
|
||||
async def _seed(db, count: int = 3):
|
||||
base = datetime.now(timezone.utc)
|
||||
base = datetime.now(UTC)
|
||||
for i in range(count):
|
||||
r = ImageRecord(
|
||||
path=f"/images/x/{i}.jpg",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from datetime import UTC, datetime, timedelta
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -46,7 +46,7 @@ async def test_list_tasks_with_status_filter(client, db):
|
||||
for status in ("complete", "failed", "complete"):
|
||||
db.add(ImportTask(
|
||||
batch_id=batch.id, source_path="/x", task_type="media", status=status,
|
||||
finished_at=datetime.now(timezone.utc),
|
||||
finished_at=datetime.now(UTC),
|
||||
))
|
||||
await db.commit()
|
||||
|
||||
@@ -74,14 +74,14 @@ async def test_clear_completed(client, db):
|
||||
batch = ImportBatch(triggered_by="manual", source_path="/import", scan_mode="quick")
|
||||
db.add(batch)
|
||||
await db.flush()
|
||||
long_ago = datetime.now(timezone.utc) - timedelta(days=30)
|
||||
long_ago = datetime.now(UTC) - timedelta(days=30)
|
||||
db.add(ImportTask(
|
||||
batch_id=batch.id, source_path="/x", task_type="media",
|
||||
status="complete", finished_at=long_ago,
|
||||
))
|
||||
db.add(ImportTask(
|
||||
batch_id=batch.id, source_path="/y", task_type="media",
|
||||
status="complete", finished_at=datetime.now(timezone.utc),
|
||||
status="complete", finished_at=datetime.now(UTC),
|
||||
))
|
||||
await db.commit()
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from datetime import UTC, datetime, timedelta
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -12,7 +12,7 @@ from backend.app.services.gallery_service import (
|
||||
|
||||
|
||||
def _now():
|
||||
return datetime.now(timezone.utc)
|
||||
return datetime.now(UTC)
|
||||
|
||||
|
||||
async def _seed_images(db, count: int, sha_prefix: str = "0") -> list[ImageRecord]:
|
||||
@@ -37,7 +37,7 @@ async def _seed_images(db, count: int, sha_prefix: str = "0") -> list[ImageRecor
|
||||
|
||||
|
||||
def test_cursor_roundtrip():
|
||||
ts = datetime(2026, 5, 14, 12, 30, 0, tzinfo=timezone.utc)
|
||||
ts = datetime(2026, 5, 14, 12, 30, 0, tzinfo=UTC)
|
||||
encoded = encode_cursor(ts, 42)
|
||||
back_ts, back_id = decode_cursor(encoded)
|
||||
assert back_ts == ts
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
function runs synchronously inside the same DB transaction as the test.
|
||||
"""
|
||||
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from datetime import UTC, datetime, timedelta
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -26,7 +26,7 @@ def _make_batch(session) -> int:
|
||||
|
||||
def test_recover_interrupted_only_old(db_sync):
|
||||
batch_id = _make_batch(db_sync)
|
||||
now = datetime.now(timezone.utc)
|
||||
now = datetime.now(UTC)
|
||||
|
||||
fresh = ImportTask(
|
||||
batch_id=batch_id, source_path="/import/a.jpg", task_type="media",
|
||||
@@ -52,7 +52,7 @@ def test_recover_interrupted_only_old(db_sync):
|
||||
|
||||
def test_cleanup_old_deletes_finished_old(db_sync):
|
||||
batch_id = _make_batch(db_sync)
|
||||
now = datetime.now(timezone.utc)
|
||||
now = datetime.now(UTC)
|
||||
|
||||
old_complete = ImportTask(
|
||||
batch_id=batch_id, source_path="/import/a.jpg", task_type="media",
|
||||
|
||||
Reference in New Issue
Block a user