From c9101e3450880cd4b6dcff96a204a4348e3aa60b Mon Sep 17 00:00:00 2001 From: npeter83 Date: Sun, 20 Sep 2026 23:02:02 +0200 Subject: [PATCH] =?UTF-8?q?ci:=20type-check=20the=20backend=20=E2=80=94=20?= =?UTF-8?q?the=20gate's=20ninth=20lane=20(E27=20S3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three tsc projects held the frontend and NOTHING held the backend: ~36k lines of `app/`, annotated in 113 of 128 files, that no lane had ever read. Asymmetric by accident, not by decision. basedpyright, in basic mode. Measured against mypy on this tree with the dependencies actually installed — without them basedpyright errors on every import and mypy's --ignore-missing-imports quietly turns them into Any and UNDER-reports. Both land ~190-200 errors with the same distribution, so the choice fell to what differs: basedpyright is ~2x faster (4.4s vs 7.4s, and this runs on every `siftlode lint`), resolves our `app.*` imports from its own config with no MYPYPATH, and has a first-class `--writebaseline`. That last one is the ratchet itself; mypy would have needed a third-party tool for it. 198 pre-existing errors are baselined, so the lane fails on NEW errors only and the list burns down epic by epic. A green lane means no new damage, not a clean codebase. It runs in the pytest test image rather than on the host, and that is forced rather than chosen: `requirements.lock` cannot be installed on Windows at all — uvloop refuses to build there — so a host venv could only ever be the lock minus a package, and a type checker without the real third-party packages reports nonsense rather than nothing. The image installs the pin from requirements-dev.txt, so the lane's version IS the pin and there is no host copy to drift. The cost is that `siftlode lint` now needs Docker, which it did not before. --- .gitea/workflows/check.yml | 7 + CLAUDE.md | 3 +- backend/.basedpyright/baseline.json | 1682 +++++++++++++++++++++++++++ backend/pyrightconfig.json | 24 + backend/requirements-dev.txt | 4 + 5 files changed, 1719 insertions(+), 1 deletion(-) create mode 100644 backend/.basedpyright/baseline.json create mode 100644 backend/pyrightconfig.json diff --git a/.gitea/workflows/check.yml b/.gitea/workflows/check.yml index 54a66221..e42ea2b5 100644 --- a/.gitea/workflows/check.yml +++ b/.gitea/workflows/check.yml @@ -93,6 +93,13 @@ jobs: run: python -m ruff check --config backend/ruff.toml backend scripts - name: ruff format --check run: python -m ruff format --check --config backend/ruff.toml backend scripts + # The ninth lane (E27 S3). Run from backend/ so it resolves exactly as the local lane does — + # pyrightconfig.json's include/extraPaths are relative to the config, but the baseline is + # found relative to the working directory. New errors fail; the ~200 pre-existing ones are in + # .basedpyright/baseline.json and burn down epic by epic. + - name: basedpyright + run: python -m basedpyright --project pyrightconfig.json + working-directory: backend - name: pytest run: python -m pytest working-directory: backend diff --git a/CLAUDE.md b/CLAUDE.md index e7a6f481..b256535c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -12,12 +12,13 @@ provider. ## The gate — never merge or ship red -The local gate is **ruff (`check` + `format --check`) + typecheck (3 tsc projects) + eslint + prettier +The local gate is **ruff (`check` + `format --check`) + basedpyright (backend) + typecheck (3 tsc projects) + eslint + prettier --check + knip + vitest + pytest**. It runs on every `publish`, which **refuses to build on failure — there is deliberately no `--skip` flag.** Run it and get it green **before** any merge, release, or handing work to review. - Full gate (agents with the private ops dispatcher): `siftlode check`. - Directly, without the dispatcher: `cd frontend && npm run typecheck && npm run lint && npm run knip && npm run test`, plus backend `ruff check backend` + `ruff format --check backend` and `pytest` (the DB-lane needs Postgres). +- **The backend type lane runs in the test image, not on the host** — `requirements.lock` cannot be installed on Windows (uvloop), and a type checker without the real packages reports nonsense. It fails on NEW errors only; the pre-existing ones sit in `backend/.basedpyright/baseline.json` and burn down epic by epic. Fixed some? Regenerate with `--writebaseline`. - New DB-backed test? It must actually run, not silently skip — assume `SIFTLODE_TEST_REQUIRE_DB=1`. ## Workflow diff --git a/backend/.basedpyright/baseline.json b/backend/.basedpyright/baseline.json new file mode 100644 index 00000000..50e22559 --- /dev/null +++ b/backend/.basedpyright/baseline.json @@ -0,0 +1,1682 @@ +{ + "files": { + "./app/audit.py": [ + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 18, + "endColumn": 26, + "lineCount": 1 + } + } + ], + "./app/debugsession.py": [ + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 18, + "endColumn": 26, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 18, + "endColumn": 26, + "lineCount": 1 + } + } + ], + "./app/diagnostics.py": [ + { + "code": "reportOptionalMemberAccess", + "range": { + "startColumn": 40, + "endColumn": 49, + "lineCount": 1 + } + } + ], + "./app/discover/pins.py": [ + { + "code": "reportOptionalOperand", + "range": { + "startColumn": 8, + "endColumn": 9, + "lineCount": 5 + } + } + ], + "./app/discover/queries.py": [ + { + "code": "reportArgumentType", + "range": { + "startColumn": 50, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 50, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 50, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 50, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 50, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 50, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 50, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 50, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 50, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 50, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 50, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 50, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 50, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 50, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 50, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 50, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 50, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 50, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 50, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 50, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 50, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 50, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 50, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 50, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 50, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 50, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 50, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 50, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 50, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 50, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 50, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 33, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 33, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 33, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 28, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 28, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 28, + "endColumn": 39, + "lineCount": 1 + } + } + ], + "./app/discover/rails.py": [ + { + "code": "reportCallIssue", + "range": { + "startColumn": 4, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 18, + "endColumn": 41, + "lineCount": 1 + } + } + ], + "./app/downloads/adapters.py": [ + { + "code": "reportArgumentType", + "range": { + "startColumn": 30, + "endColumn": 38, + "lineCount": 1 + } + } + ], + "./app/downloads/backfill.py": [ + { + "code": "reportArgumentType", + "range": { + "startColumn": 34, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportReturnType", + "range": { + "startColumn": 23, + "endColumn": 27, + "lineCount": 1 + } + } + ], + "./app/downloads/credentials.py": [ + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 27, + "endColumn": 30, + "lineCount": 1 + } + } + ], + "./app/downloads/gc.py": [ + { + "code": "reportOperatorIssue", + "range": { + "startColumn": 16, + "endColumn": 28, + "lineCount": 1 + } + } + ], + "./app/downloads/pipeline.py": [ + { + "code": "reportArgumentType", + "range": { + "startColumn": 34, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 22, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportGeneralTypeIssues", + "range": { + "startColumn": 10, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 12, + "endColumn": 13, + "lineCount": 10 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 31, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 36, + "endColumn": 43, + "lineCount": 1 + } + } + ], + "./app/downloads/service.py": [ + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 18, + "endColumn": 26, + "lineCount": 1 + } + } + ], + "./app/downloads/urlguard.py": [ + { + "code": "reportReturnType", + "range": { + "startColumn": 11, + "endColumn": 41, + "lineCount": 1 + } + } + ], + "./app/feedquery.py": [ + { + "code": "reportCallIssue", + "range": { + "startColumn": 37, + "endColumn": 5, + "lineCount": 3 + } + }, + { + "code": "reportAssignmentType", + "range": { + "startColumn": 37, + "endColumn": 5, + "lineCount": 3 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 8, + "endColumn": 97, + "lineCount": 1 + } + } + ], + "./app/logsink.py": [ + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 18, + "endColumn": 26, + "lineCount": 1 + } + } + ], + "./app/notifications.py": [ + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 52, + "endColumn": 58, + "lineCount": 1 + } + } + ], + "./app/plex/lookups.py": [ + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 48, + "endColumn": 58, + "lineCount": 1 + } + } + ], + "./app/plex/people.py": [ + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 15, + "endColumn": 23, + "lineCount": 1 + } + } + ], + "./app/plex/stream.py": [ + { + "code": "reportOptionalMemberAccess", + "range": { + "startColumn": 51, + "endColumn": 55, + "lineCount": 1 + } + }, + { + "code": "reportOptionalMemberAccess", + "range": { + "startColumn": 23, + "endColumn": 25, + "lineCount": 1 + } + }, + { + "code": "reportOptionalMemberAccess", + "range": { + "startColumn": 18, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportOptionalMemberAccess", + "range": { + "startColumn": 75, + "endColumn": 79, + "lineCount": 1 + } + }, + { + "code": "reportOptionalMemberAccess", + "range": { + "startColumn": 10, + "endColumn": 23, + "lineCount": 1 + } + }, + { + "code": "reportOptionalMemberAccess", + "range": { + "startColumn": 10, + "endColumn": 12, + "lineCount": 1 + } + }, + { + "code": "reportOptionalMemberAccess", + "range": { + "startColumn": 10, + "endColumn": 17, + "lineCount": 1 + } + } + ], + "./app/plex/sync.py": [ + { + "code": "reportArgumentType", + "range": { + "startColumn": 21, + "endColumn": 69, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 21, + "endColumn": 69, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 70, + "endColumn": 83, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 76, + "endColumn": 89, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 27, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 17, + "endColumn": 18, + "lineCount": 1 + } + } + ], + "./app/plex/tmdb.py": [ + { + "code": "reportAssignmentType", + "range": { + "startColumn": 16, + "endColumn": 9, + "lineCount": 10 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 30, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 29, + "endColumn": 35, + "lineCount": 1 + } + } + ], + "./app/plex/watch_sync.py": [ + { + "code": "reportReturnType", + "range": { + "startColumn": 11, + "endColumn": 5, + "lineCount": 11 + } + } + ], + "./app/ratelimit.py": [ + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 14, + "endColumn": 25, + "lineCount": 1 + } + } + ], + "./app/routes/admin.py": [ + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 9, + "endColumn": 13, + "lineCount": 1 + } + } + ], + "./app/routes/admin_logs.py": [ + { + "code": "reportReturnType", + "range": { + "startColumn": 15, + "endColumn": 47, + "lineCount": 1 + } + } + ], + "./app/routes/audit.py": [ + { + "code": "reportCallIssue", + "range": { + "startColumn": 17, + "endColumn": 98, + "lineCount": 1 + } + }, + { + "code": "reportAssignmentType", + "range": { + "startColumn": 17, + "endColumn": 98, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 22, + "endColumn": 97, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 37, + "endColumn": 45, + "lineCount": 1 + } + } + ], + "./app/routes/channels.py": [ + { + "code": "reportReturnType", + "range": { + "startColumn": 11, + "endColumn": 11, + "lineCount": 6 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 8, + "endColumn": 15, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 49, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 38, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 33, + "endColumn": 39, + "lineCount": 1 + } + } + ], + "./app/routes/discover.py": [ + { + "code": "reportInvalidTypeForm", + "range": { + "startColumn": 19, + "endColumn": 33, + "lineCount": 1 + } + } + ], + "./app/routes/downloads.py": [ + { + "code": "reportArgumentType", + "range": { + "startColumn": 29, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 37, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 29, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 40, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportOptionalMemberAccess", + "range": { + "startColumn": 17, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportOptionalMemberAccess", + "range": { + "startColumn": 82, + "endColumn": 84, + "lineCount": 1 + } + }, + { + "code": "reportOptionalMemberAccess", + "range": { + "startColumn": 87, + "endColumn": 89, + "lineCount": 1 + } + }, + { + "code": "reportOptionalMemberAccess", + "range": { + "startColumn": 30, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportOptionalMemberAccess", + "range": { + "startColumn": 49, + "endColumn": 54, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 29, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 40, + "endColumn": 50, + "lineCount": 1 + } + } + ], + "./app/routes/messages.py": [ + { + "code": "reportCallIssue", + "range": { + "startColumn": 11, + "endColumn": 43, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 23, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportCallIssue", + "range": { + "startColumn": 13, + "endColumn": 5, + "lineCount": 7 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 8, + "endColumn": 15, + "lineCount": 5 + } + }, + { + "code": "reportReturnType", + "range": { + "startColumn": 15, + "endColumn": 72, + "lineCount": 1 + } + }, + { + "code": "reportCallIssue", + "range": { + "startColumn": 30, + "endColumn": 48, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 41, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportOptionalMemberAccess", + "range": { + "startColumn": 36, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportOptionalMemberAccess", + "range": { + "startColumn": 71, + "endColumn": 73, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 30, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportOptionalMemberAccess", + "range": { + "startColumn": 27, + "endColumn": 29, + "lineCount": 1 + } + } + ], + "./app/routes/playlists.py": [ + { + "code": "reportOptionalOperand", + "range": { + "startColumn": 8, + "endColumn": 9, + "lineCount": 5 + } + }, + { + "code": "reportOptionalOperand", + "range": { + "startColumn": 8, + "endColumn": 9, + "lineCount": 5 + } + }, + { + "code": "reportCallIssue", + "range": { + "startColumn": 13, + "endColumn": 5, + "lineCount": 7 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 8, + "endColumn": 15, + "lineCount": 5 + } + }, + { + "code": "reportCallIssue", + "range": { + "startColumn": 16, + "endColumn": 5, + "lineCount": 11 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 8, + "endColumn": 15, + "lineCount": 9 + } + }, + { + "code": "reportCallIssue", + "range": { + "startColumn": 13, + "endColumn": 5, + "lineCount": 5 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 8, + "endColumn": 15, + "lineCount": 3 + } + }, + { + "code": "reportCallIssue", + "range": { + "startColumn": 12, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 23, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportCallIssue", + "range": { + "startColumn": 12, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 26, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 12, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 23, + "endColumn": 27, + "lineCount": 1 + } + } + ], + "./app/routes/plex/browse.py": [ + { + "code": "reportArgumentType", + "range": { + "startColumn": 26, + "endColumn": 61, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 26, + "endColumn": 61, + "lineCount": 1 + } + }, + { + "code": "reportOptionalSubscript", + "range": { + "startColumn": 11, + "endColumn": 13, + "lineCount": 1 + } + }, + { + "code": "reportOptionalSubscript", + "range": { + "startColumn": 25, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportOptionalSubscript", + "range": { + "startColumn": 11, + "endColumn": 13, + "lineCount": 1 + } + }, + { + "code": "reportOptionalSubscript", + "range": { + "startColumn": 25, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportOptionalSubscript", + "range": { + "startColumn": 15, + "endColumn": 17, + "lineCount": 1 + } + }, + { + "code": "reportOptionalSubscript", + "range": { + "startColumn": 28, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportOptionalSubscript", + "range": { + "startColumn": 15, + "endColumn": 17, + "lineCount": 1 + } + }, + { + "code": "reportOptionalSubscript", + "range": { + "startColumn": 28, + "endColumn": 30, + "lineCount": 1 + } + } + ], + "./app/routes/plex/collections.py": [ + { + "code": "reportAssignmentType", + "range": { + "startColumn": 11, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 76, + "endColumn": 80, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 75, + "endColumn": 79, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 80, + "endColumn": 84, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 20, + "endColumn": 45, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 20, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 56, + "endColumn": 59, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 56, + "endColumn": 59, + "lineCount": 1 + } + }, + { + "code": "reportOptionalMemberAccess", + "range": { + "startColumn": 39, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 56, + "endColumn": 59, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 46, + "endColumn": 49, + "lineCount": 1 + } + } + ], + "./app/routes/plex/discover.py": [ + { + "code": "reportCallIssue", + "range": { + "startColumn": 4, + "endColumn": 63, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 18, + "endColumn": 48, + "lineCount": 1 + } + }, + { + "code": "reportCallIssue", + "range": { + "startColumn": 4, + "endColumn": 63, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 18, + "endColumn": 48, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 39, + "endColumn": 45, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 38, + "endColumn": 43, + "lineCount": 1 + } + } + ], + "./app/routes/plex/playback.py": [ + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 6, + "endColumn": 14, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 51, + "endColumn": 54, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 51, + "endColumn": 54, + "lineCount": 1 + } + } + ], + "./app/routes/plex/playlists.py": [ + { + "code": "reportArgumentType", + "range": { + "startColumn": 77, + "endColumn": 83, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 77, + "endColumn": 83, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 8, + "endColumn": 9, + "lineCount": 5 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 8, + "endColumn": 9, + "lineCount": 5 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 10, + "endColumn": 18, + "lineCount": 1 + } + } + ], + "./app/routes/public.py": [ + { + "code": "reportArgumentType", + "range": { + "startColumn": 57, + "endColumn": 71, + "lineCount": 1 + } + } + ], + "./app/routes/scheduler.py": [ + { + "code": "reportArgumentType", + "range": { + "startColumn": 42, + "endColumn": 55, + "lineCount": 1 + } + }, + { + "code": "reportOptionalMemberAccess", + "range": { + "startColumn": 25, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportOptionalMemberAccess", + "range": { + "startColumn": 25, + "endColumn": 28, + "lineCount": 1 + } + } + ], + "./app/routes/search.py": [ + { + "code": "reportArgumentType", + "range": { + "startColumn": 25, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 6, + "endColumn": 14, + "lineCount": 1 + } + } + ], + "./app/routes/sync.py": [ + { + "code": "reportArgumentType", + "range": { + "startColumn": 42, + "endColumn": 65, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 42, + "endColumn": 50, + "lineCount": 1 + } + } + ], + "./app/sync/explore.py": [ + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 6, + "endColumn": 14, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 6, + "endColumn": 14, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 6, + "endColumn": 14, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 6, + "endColumn": 14, + "lineCount": 1 + } + } + ], + "./app/sync/maintenance.py": [ + { + "code": "reportOptionalOperand", + "range": { + "startColumn": 19, + "endColumn": 42, + "lineCount": 1 + } + } + ], + "./app/sync/playlists.py": [ + { + "code": "reportArgumentType", + "range": { + "startColumn": 55, + "endColumn": 72, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 51, + "endColumn": 68, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 39, + "endColumn": 56, + "lineCount": 1 + } + } + ], + "./app/sync/runner.py": [ + { + "code": "reportAssignmentType", + "range": { + "startColumn": 19, + "endColumn": 88, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 16, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportOptionalIterable", + "range": { + "startColumn": 23, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 45, + "endColumn": 72, + "lineCount": 1 + } + }, + { + "code": "reportAssignmentType", + "range": { + "startColumn": 19, + "endColumn": 9, + "lineCount": 7 + } + }, + { + "code": "reportOptionalSubscript", + "range": { + "startColumn": 19, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportOptionalIterable", + "range": { + "startColumn": 23, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 47, + "endColumn": 55, + "lineCount": 1 + } + } + ], + "./app/sync/videos.py": [ + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 18, + "endColumn": 26, + "lineCount": 1 + } + } + ], + "./app/worker.py": [ + { + "code": "reportArgumentType", + "range": { + "startColumn": 36, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 36, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 38, + "endColumn": 43, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 35, + "endColumn": 43, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 24, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 28, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 46, + "endColumn": 52, + "lineCount": 1 + } + } + ], + "./app/youtube/rss.py": [ + { + "code": "reportGeneralTypeIssues", + "range": { + "startColumn": 10, + "endColumn": 15, + "lineCount": 1 + } + } + ] + } +} \ No newline at end of file diff --git a/backend/pyrightconfig.json b/backend/pyrightconfig.json new file mode 100644 index 00000000..89837125 --- /dev/null +++ b/backend/pyrightconfig.json @@ -0,0 +1,24 @@ +// The backend type-check lane (E27 S3) — the gate's ninth lane, and the first thing that has ever +// type-checked the ~36k lines of `app/`. Read as JSONC: pyright accepts comments here. +// +// basedpyright, not mypy: measured on this tree with the dependencies actually installed, both land +// ~190-200 errors with the same distribution, so the choice was made on the things that differ — +// basedpyright is ~2x faster, needs no MYPYPATH to resolve our `app.*` imports, and has a FIRST-CLASS +// baseline (`--writebaseline`), which is the whole ratchet. mypy would have needed a third-party tool +// for that. +// +// It runs in the pytest test image, never on the host: `requirements.lock` cannot be installed on +// Windows at all (uvloop refuses to build there), so a host run would type-check against absent +// third-party packages and report nonsense. The version is pinned in requirements-dev.txt, which the +// image installs — so the lane's version IS the pin, with no host-vs-pin check to drift. +// +// `pythonVersion` is not free-standing: `/.python-version` is the single source of truth and +// `tests/test_python_engine_contract.py` fails this gate if this file, the two Dockerfiles, the CI +// container or ruff's target-version disagree with it. +{ + "include": ["app"], + "extraPaths": ["."], + "typeCheckingMode": "basic", + "pythonVersion": "3.12", + "pythonPlatform": "Linux" +} diff --git a/backend/requirements-dev.txt b/backend/requirements-dev.txt index 6f25a4da..311a35bd 100644 --- a/backend/requirements-dev.txt +++ b/backend/requirements-dev.txt @@ -4,6 +4,10 @@ # backend lint lane runs on the host (the gate fails when the host's ruff is not this one). httpx2: # Starlette 1.x's TestClient is built on it (`import httpx2 as httpx`) and only falls back to the # app's own httpx with a deprecation warning on every run. +# basedpyright: the backend type-check lane (E27 S3). Pinned like everything else here — a type +# checker whose version floats silently changes what "clean" means, and the ratchet baseline it +# writes is keyed to the diagnostics THIS version produces. +basedpyright==1.40.1 pytest==8.4.2 ruff==0.15.21 httpx2==2.13.0