refactor(server): extract the loops routes into routers/loops.py (R3) - #839
Conversation
Third router. Practice loops (saved A/B regions per song): GET/POST/DELETE /api/loops, meta_db-only (0 setattr targets, 0 helpers to relocate per router_scan.py). Bodies verbatim; @app -> @router, meta_db -> appstate.meta_db. include_router at the original site; 143-route table identical to origin/main. server.py: 9,337 -> 9,301. No test retargeting (test_demo_mode only names the paths in middleware regexes). Verified: pyflakes clean; route table identical; pytest 2398 passed; packaging guard green; eslint 0; boot smoke drives POST (auto-names "Loop N") / GET / DELETE / missing-fields error, and demo mode 403s both writes while allowing the read. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughThe Loops API was extracted from ChangesLoops router extraction
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related PRs
🚥 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: 2
🤖 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 `@lib/routers/loops.py`:
- Around line 18-21: The loops list query currently accesses the shared SQLite
connection without synchronization. In the relevant list handler, wrap both the
`appstate.meta_db.conn.execute(...)` call and its `.fetchall()` in `with
appstate.meta_db._lock:` so the entire read is protected from concurrent
POST/DELETE transactions.
- Around line 34-43: Make loop name generation atomic in the unnamed-loop
handling code: acquire appstate.meta_db._lock before executing the COUNT query,
then keep the same lock held through name assignment, INSERT, and commit.
Consolidate the existing lock scopes so the count and persistence occur within
one critical section.
🪄 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: cec452fc-86f7-41c5-9684-f4bde2cbbe31
📒 Files selected for processing (4)
CHANGELOG.mddocs/size-exemptions.mdlib/routers/loops.pyserver.py
|
Both findings are real, and both are pre-existing — the Landing both in a small follow-up: move the |
|
Follow-up fix opened: #840 — locks the count+insert and the list read, with a barrier-based concurrency test (negative-checked 5/5). |
…840) Two pre-existing races in the loops routes, flagged by CodeRabbit on #839 (the verbatim extraction moved them unchanged from server.py, so they correctly weren't fixed there): 1. save_loop computed `COUNT(*)` OUTSIDE meta_db._lock, then inserted inside it. Two simultaneous unnamed POSTs read the same count and both mint "Loop N". Fix: one lock scope around COUNT + INSERT. 2. list_loops read the shared single connection (check_same_thread=False) with no lock, so it could overlap a POST/DELETE commit. Fix: read under the lock, like every writer. Low severity in context — FeedBack is single-user (Principle I), so concurrent unnamed-loop POSTs essentially can't happen — but each fix is one lock scope. tests/test_loops_concurrency.py pins both with a threading.Barrier that releases 16 workers into save_loop at once. Negative-checked: reverting the COUNT back outside the lock fails the uniqueness assertion 5/5 runs; the fix passes 3/3. pytest 2400 passed; on-device two unnamed POSTs -> ['Loop 1','Loop 2']. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Third router in the R3 train (after audio-effects #834, artist-aliases #838). Move-only.
Practice loops — saved A/B regions per song.
router_scan.pyranks it at 0setattrtargets, 0 helpers to relocate;meta_db-only, own banner, contiguous.GET /api/loops·POST /api/loops·DELETE /api/loops/{loop_id}Bodies verbatim;
@app→@router,meta_db→appstate.meta_db.include_routerat the original site.server.py: 9,337 → 9,302.Verification
origin/main(143, paths/methods/order).test_demo_modeonly names the paths in middleware regexes).POSTauto-namesLoop N,GETreads back,DELETEremoves, missing fields → error; demo mode403s both writes, allows the read.pytest2398 passed; packaging guard green;pyflakesclean;npm run lint0 errors; Codex 0 findings.Next candidates (
router_scan.py):wanted/savedneed_clean_str(shared by 10 callers) relocated to alib/home first — a small shared-helpers decision before those move.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation