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