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