Files
siftlode/backend/ruff.toml
T
peter 3c16012524 build(backend): close the lint gaps the E32 audit measured
ruff gains RUF100, PLW1510 and flake8-bugbear, every hit fixed rather than
ignored. B008 stays on with FastAPI's Depends/Query declared immutable.
basedpyright moves to standard mode, pytest to --strict-markers, and a hand
run of pytest now requires the database unless SIFTLODE_TEST_REQUIRE_DB=0.

PLW1510 found a real bug: the three ffprobe probes in plex/stream.py never
looked at the exit status, so a probe that failed was read as the file's
truth (an HDR source sent down the HW-safe path, a multi-audio file collapsed
to one track, a subtitle offset taken from garbage) and the warning each
except branch promises never fired. They now run with check=True.
2026-10-03 16:04:06 +02:00

52 lines
3.7 KiB
TOML

# Ruff config for the Siftlode backend. Keeps ruff's default rule set (E4/E7/E9 + F = the
# high-signal pyflakes/pycodestyle checks that catch real dead code + bugs), but ignores E402:
# a handful of modules (e.g. main.py) deliberately run setup — logging config, sys.path — before
# importing route modules, so "module import not at top of file" is intentional there, not cruft.
#
# line-length 100 (ruff's default is 88): matches the frontend prettier printWidth so both halves of
# the repo wrap at the same width, and it's the width `ruff format` enforces (gated via
# `ruff format --check` alongside `ruff check`).
line-length = 100
# The interpreter this code actually runs on, so ruff judges syntax and upgrade hints against the
# right one. Left unset it falls back to a default that has nothing to do with us. `/.python-version`
# is the single source of truth; tests/test_python_engine_contract.py holds this to it.
target-version = "py312"
[lint]
# Beyond the default set, the rules that hold a LOG LINE to what E22.5 makes of it:
# LOG — stdlib logging misuse (`.warn()`, a logger built by hand, `exc_info` on `.exception`)
# G — the call's shape: %-style args, never an f-string the level filter cannot skip, and
# never `str(exc)` where `exc_info` would carry the traceback
# T20 — `print` instead of a log line. Two deliberate ones remain, each with its `# noqa`
# and a why: `downloads/runner.py`'s usage line is for whoever typed it, and
# `logsink.py` reports the SINK's own failure, which cannot be logged into the sink.
# TRY400 — `.error()` in an `except` where `.exception()` would keep the traceback
# TRY401 — the exception spelled into the message AND passed as `exc_info` (it is in both)
# S112 — `except: continue`, the silent swallow inside a loop
# S110 — `except: pass`, the same thing outside one
# BLE001 — a blind `except Exception`. Every one that remains carries `# noqa: BLE001 — <why>`,
# which is the point: catching everything is sometimes right (a worker thread with
# nobody to raise to, a best-effort backfill inside a sync) and always worth a sentence.
# The wider rule these two enforce lives in tests/test_swallowed_exceptions.py: a handler that
# neither logs nor re-raises says `# silent-ok: <why>`. Ruff sees only the two narrow shapes.
#
# E32 (gate integrity) added three more, each measured before it went on and with every hit fixed:
# RUF100 — a `# noqa` that suppresses nothing. A stale one is a claim with no rule behind it, and
# it stays armed for the day the line changes (24 were removed). A comment that merely
# explains a noqa below it names the rule as `# TRY400: …`, never `# noqa TRY400`.
# PLW1510 — `subprocess.run` without an explicit `check=`. Found the real E32 bug: three ffprobe
# probes read the output of a probe that had FAILED and took it for the file's truth.
# B — flake8-bugbear: loop-variable capture, `raise` without `from` inside `except`, a
# `zip` that silently truncates (each one is `strict=True` now — the lengths are an
# invariant, so a mismatch should be loud). B008 (a call in a default argument) stays ON:
# FastAPI's `Depends(...)` / `Query(...)` ARE that shape by design (474 hits, all of
# them), so those two are declared immutable below instead of switching the rule off —
# any other call in a default still fails.
extend-select = [
"LOG", "G", "T20", "TRY400", "TRY401", "S110", "S112", "BLE001", "RUF100", "PLW1510", "B",
]
ignore = ["E402"]
[lint.flake8-bugbear]
extend-immutable-calls = ["fastapi.Depends", "fastapi.Query"]