refactor(server): extract the song routes into routers/song.py (R3) [needs device pass] - #864
Conversation
Upload/delete, the catalog-metadata write-back, user-meta, overrides, gap-fill, and the per-song info payload (11 routes) move to lib/routers/song.py with their exclusive helpers (the atomic upload commit + the song-IO lock, the upload caps, the gap-fill proposal builders). Bodies verbatim except @app->@router and the seam reads: meta_db->appstate.meta_db, art_override_paths->appstate.art_override_paths, and the scan/ingest helpers that stay in server.py (the scan lifecycle owns them) -> new appstate seam callables: kick_scan, invalidate_song_caches, stat_for_cache, and scan_status() (a getter — the underlying dict is reassigned). The gap-fill MBID/ISRC regexes are reached as enrichment.X; _MULTIPART_OVERHEAD_SLACK (shared with the staying AcoustID-identify route) moves to lib/enrichment.py beside _ACOUSTID_MAX_UPLOAD_BYTES. ROUTE ORDER: song_router mounts AFTER art_router — get_song_info's catch-all `/api/song/{filename:path}` would otherwise shadow `/api/song/{path}/art*` (Starlette matches first-registered; the :path converter is greedy). server.py: 4,478 -> 3,692 (-786). Verified: pyflakes clean; ORDERED route table preserves the specific-before-catch-all invariant; full pytest 2397 passed (incl the art/cover 304 + CAA-fetch tests that caught the shadowing before it was fixed). eslint 0. BEHAVIORAL — needs an on-device pass (upload a sloppak, edit metadata write-back, delete a song) before merge. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughThe PR extracts song upload, deletion, metadata, override, gap-fill, and information routes from ChangesSong router extraction
Estimated code review effort: 4 (Complex) | ~45 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
lib/routers/song.py (1)
659-661: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low valueSQL-injection warnings here (and at Lines 823-824) are false positives.
The interpolated
updatesfragments are built only from field names drawn from fixed allowlists (("title","artist","album","year")at Line 615;{album,year,genre}at Lines 805-811), and all values go through?placeholders. No user input reaches the query string. Consider a brief inline comment (or# noqa: S608) at both sites to suppress the recurring Ruff/OpenGrep noise.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/routers/song.py` around lines 659 - 661, Suppress the SQL-injection false positive at the UPDATE statements in the song update flow and the corresponding statement around the album/year/genre updates, using a brief inline justification or the project’s supported S608 suppression. Keep the existing fixed-field allowlists and parameterized values unchanged.Source: Linters/SAST tools
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/size-exemptions.md`:
- Around line 58-59: The size-exemption history entry in docs/size-exemptions.md
has an incorrect baseline. Change the “was 14,037” value in the MetadataDB and
AudioEffectsMappingDB extraction entry to 4,478, preserving the current 3,692
count and the surrounding explanatory text.
---
Nitpick comments:
In `@lib/routers/song.py`:
- Around line 659-661: Suppress the SQL-injection false positive at the UPDATE
statements in the song update flow and the corresponding statement around the
album/year/genre updates, using a brief inline justification or the project’s
supported S608 suppression. Keep the existing fixed-field allowlists and
parameterized values unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 2d564eb7-6597-42f0-8776-2c854a10d3fb
📒 Files selected for processing (6)
CHANGELOG.mddocs/size-exemptions.mdlib/appstate.pylib/enrichment.pylib/routers/song.pyserver.py
Upload/delete, the catalog-metadata write-back, user-meta, overrides, gap-fill, and the per-song info payload (11 routes) →
lib/routers/song.pywith their exclusive helpers (atomic upload commit + song-IO lock, upload caps, gap-fill proposal builders).Bodies verbatim except
@app→@routerand the seam reads:meta_db→appstate.meta_db,art_override_paths→appstate.art_override_paths, and the scan/ingest helpers that stay inserver.py→ newappstateseams:kick_scan,invalidate_song_caches,stat_for_cache, andscan_status()(a getter — the underlying dict is reassigned). The gap-fill MBID/ISRC regexes are reached asenrichment.X;_MULTIPART_OVERHEAD_SLACK(shared with the staying AcoustID-identify route) moves tolib/enrichment.pybeside_ACOUSTID_MAX_UPLOAD_BYTES.Route order:
song_routermounts afterart_router—get_song_info's catch-all/api/song/{filename:path}would otherwise shadow/api/song/{path}/art*(Starlette matches first-registered; the:pathconverter is greedy). The full suite's art/cover-304 + CAA-fetch tests caught this before it was fixed.server.py: 4,478 → 3,692 (−786).Verification
pyflakesclean; ordered route table preserves the specific-before-catch-all invariant.pytest2397 passed;npm run lint0; Codex 0 findings./api/song/x/art404 (not shadowed by info),/user-meta200,/gap-fill200,POST /songs/upload400 (no file), 0 tracebacks.On-device pass to run before merge
.sloppak(drag/drop or the upload button) → it lands in the library, scan picks it up.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes