fix(lint): E402 is on — the ignore meant for main.py had let 11 late imports in elsewhere

ruff.toml ignored E402 everywhere for main.py's logging setup. Measured: 28 hits, of which 11
had nothing to do with it — runner.py's logger line between its imports, models.py importing
canonical_email mid-file, a test constant above its imports, and main.py's own third-party and
mimetypes imports. Those are moved up. What stays after configure_logging in main.py is marked
per statement (noqa E402, logging is configured first), its comment now says why the order
matters (a line logged while importing, not the logger's creation). The register row goes, the
ruff key whitelist loses ignore, and a late import is red again. The T20 comment counts the
third deliberate print (scripts/e2e_seed.py).
This commit is contained in:
2026-10-04 03:14:24 +02:00
parent a1224e9a8c
commit 64e8f1da3d
7 changed files with 34 additions and 40 deletions
+20 -24
View File
@@ -1,38 +1,36 @@
import asyncio
import math
import mimetypes
from contextlib import asynccontextmanager, suppress
from pathlib import Path
from fastapi import FastAPI, HTTPException, Request
from fastapi.encoders import jsonable_encoder
from fastapi.exceptions import RequestValidationError
# Logging is configured HERE, before the route modules are imported, so every logger they create at
# import time is already covered. ONE format for the api, the worker, the download child and alembic
# alike (app/logs.py) — this replaces the old uvicorn --log-config + the hand-rolled fallback that
# used to live here, and uvicorn's access log is replaced by the `http.request` middleware below.
from app import logs
from app.logs import configure_logging, current_level, log_requests, set_level
configure_logging("api")
log = logs.get_logger(__name__)
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse, HTMLResponse, JSONResponse
from fastapi.staticfiles import StaticFiles
from sqlalchemy.exc import DataError
from starlette.middleware.sessions import SessionMiddleware
from app import bodylimit
from app import collation
from app import logsink
from app import state
from app import sysconfig
from app.config import settings
from app.db import SessionLocal, engine
from app.plex import stream as plex_stream
from app.plex import subtitles as plex_subtitles
from app.routes import (
# Logging is configured HERE, before the app's own modules are imported, so a line one of them logs
# WHILE importing already has the format (a logger is configured whenever it is created —
# app/logs.py — but a line emitted before `configure_logging` goes out raw). ONE format for the api,
# the worker, the download child and alembic alike — this replaces the old uvicorn --log-config +
# the hand-rolled fallback that used to live here, and uvicorn's access log is replaced by the
# `http.request` middleware below. The imports after it are E402 on purpose, each one marked.
from app import logs
from app.logs import configure_logging, current_level, log_requests, set_level
configure_logging("api")
log = logs.get_logger(__name__)
from app import bodylimit, collation, logsink, state, sysconfig # noqa: E402 — logging is configured first
from app.config import settings # noqa: E402 — logging is configured first
from app.db import SessionLocal, engine # noqa: E402 — logging is configured first
from app.plex import stream as plex_stream # noqa: E402 — logging is configured first
from app.plex import subtitles as plex_subtitles # noqa: E402 — logging is configured first
from app.routes import ( # noqa: E402 — logging is configured first
admin,
admin_logs as admin_logs_routes,
audit as audit_routes,
@@ -60,7 +58,7 @@ from app.routes import (
tags,
version,
)
from app.scheduler import shutdown_scheduler, start_scheduler
from app.scheduler import shutdown_scheduler, start_scheduler # noqa: E402 — logging is configured first
@asynccontextmanager
@@ -332,8 +330,6 @@ app.include_router(setup_routes.router)
# Ensure modern image types resolve to the right Content-Type when FileResponse guesses from the
# filename (the runtime's mimetypes db doesn't always know .webp → it'd fall back to octet-stream).
import mimetypes
mimetypes.add_type("image/webp", ".webp")
mimetypes.add_type("image/avif", ".avif")
+1 -3
View File
@@ -23,6 +23,7 @@ from sqlalchemy.dialects.postgresql import JSONB, TSVECTOR
from sqlalchemy.orm import Mapped, mapped_column, relationship, validates
from app.db import Base
from app.utils import canonical_email
# Every user-supplied display string in this schema shares one width, and it lives HERE so the
# columns below are declared with it rather than with a literal: a name a person typed goes into
@@ -51,9 +52,6 @@ def display_text(raw: str | None) -> str | None:
return (raw or "").strip()[:DISPLAY_TEXT_MAX] or None
from app.utils import canonical_email
# The resume-position columns (VideoState / PlexState.position_seconds) are int4. The checkpoint
# routes bound their bodies by this, so an out-of-range position is a 422 rather than a 500.
POSITION_MAX_S = 2**31 - 1
+2 -2
View File
@@ -15,8 +15,6 @@ from sqlalchemy import func, or_, select, update
from sqlalchemy.orm import Session
from app import logs, progress, quota, sysconfig
log = logs.get_logger(__name__)
from app.db import SessionLocal
from app.models import Channel, OAuthToken, Subscription, User
from app.sync.explore import followed_channel
@@ -35,6 +33,8 @@ from app.sync.videos import (
from app.youtube.client import YouTubeClient, YouTubeError, is_quota_refusal
from app.youtube.rss import RssError, fetch_channel_feed
log = logs.get_logger(__name__)
# RSS feeds are free and network-bound, so fetch many at once; DB writes stay single-threaded.
RSS_POLL_WORKERS = 16
# Failed channels named in a poll's summary warning; the rest are counted by reason.