Files
ersatztv/ruff.toml
timothyandtimothy d4c72697f2
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (push) Skipped
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (push) Skipped
Build ErsatzTV Image / Delimiter ban (release path) (push) Successful in 28s
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 8m29s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 5m54s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (push) Successful in 5m59s
Build ErsatzTV Image / Build & push image (amd64) (push) Failing after 15s
feat(780): commit a ruff config and enforce it in CI (#813)
Python lint here was a property of the operator's laptop: the global instructions
say to run ruff, no workflow ran it, and with no committed config ruff fell back
to whichever ~/.config/ruff/ruff.toml the machine happened to have.

- ruff.toml at the root, pinned ruff==0.12.11 in the script-tests job.
- Both lint steps pass an EXPLICIT population from `git ls-files` with
  `--no-force-exclude`, never `ruff check .` — an `exclude` empties a
  discovery-based run into a GREEN one (top level empties both commands, [lint]
  empties check, [format] empties format --check), and `ruff check .` over zero
  files exits 0 with only a stderr warning. Guarded by an empty-population arm.
- Tree clean: 74 findings at 706674272, 57 fixed in code, 17 per-site noqa with
  reasons inline. S105 deliberately per-site, not a directory blanket. RUF100
  selected so a suppression that suppresses nothing is itself a finding.
- pyright stays ungated; reasoning in the record.

Both steps witnessed red on the runner against the shipped bodies: run 2173 job
9176 (ruff check) and run 2170 job 9163 (ruff format --check).

Docs: new record ci.python-lint-ruff-config-committed, ci.script-tests-job
cross-ref, docs/ci-cd.md (also correcting a stale ~190-tests/~10s figure to the
measured 773 tests / ~4.5 min), docs/defect-shapes-773.md §5.2 resolved.

fixes #780

Co-authored-by: Timothy <timothy@noreply.gitea.tblindustries.be>
2026-08-22 00:33:18 +00:00

60 lines
3.2 KiB
TOML

# Ruff configuration for this repo's Python surface (all of it lives under `scripts/`).
#
# WHY THIS FILE EXISTS (ersatztv#780). Without a committed config, ruff falls back to whatever
# `~/.config/ruff/ruff.toml` the operator's machine happens to have — so a second machine lints this
# repo differently, or not at all. That is the environment-divergence class #643/#647/#648 (a shell
# gate whose behaviour was a function of an untested interpreter version) and #512 (a test that
# passed on a fast laptop and flaked on a starved CI VM). The settings below are pinned HERE so the
# lint verdict is a property of the repo, not of the machine.
#
# It is enforced by the `script-tests` job (`Script lint and tests (ruff + pytest)`) in
# .gitea/workflows/pr-checks.yml. A config nobody runs is the same divergence one step later.
#
# That job does NOT invoke `ruff check .`: it passes an explicit population from `git ls-files` with
# `--no-force-exclude`. An `exclude` added to this file silently empties a discovery-based run into a
# GREEN one — a top-level `exclude` empties both commands, one under `[lint]` empties `check`, one
# under `[format]` (where an appended line lands, by TOML rules) empties `format --check`. Adding
# `exclude` here will therefore not do what you expect, which is the point. The measured matrix is in
# `ci.python-lint-ruff-config-committed`.
#
# `pyright` is deliberately NOT gated: its only findings here are `reportMissingImports` for
# `etv_client` in scripts/scripted-schedules/entrypoint.py, which resolves only inside that script's
# deploy environment, and gating it would put a node toolchain on the git-only `small` lane for zero
# real findings. Revisit if this repo grows a typed Python surface.
target-version = "py311"
line-length = 120
[lint]
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # pyflakes
"I", # isort
"B", # flake8-bugbear
"UP", # pyupgrade
"SIM", # flake8-simplify
"S", # flake8-bandit (security)
# RUF100 is load-bearing, not tidiness: every `# noqa` below is an assertion that a real finding
# is being suppressed for a stated reason, and without this a suppression that suppresses nothing
# stays in the file reading as one. #780 did exactly that mid-branch — a `# noqa: UP031` on a site
# the same branch had already fixed in code — and found two more already in the tree: one whose
# rule had stopped firing, one for a rule this config never enables.
"RUF100",
]
ignore = [
"S603", # subprocess call - check for execution of untrusted input (too noisy for scripts)
"S607", # starting a process with a partial executable path
]
[lint.per-file-ignores]
# scripts/tests asserts, so S101 would fire on every test. S105 is deliberately NOT exempted here:
# the eight sites that trip it (`env["ETV_GITEA_TOKEN"] = "stub"`) carry a per-site `# noqa: S105`
# instead, so a real credential pasted into a fixture next year still reddens the gate. A directory
# blanket would have given up hardcoded-credential coverage over the largest Python surface in the
# repo, permanently, to suppress eight known lines.
"scripts/tests/**" = ["S101"]
[format]
quote-style = "double"