Skip to content

refactor(server): extract the highway WebSocket into routers/ws_highway.py (R3) - #844

Merged
byrongamatos merged 1 commit into
mainfrom
refactor/r3-router-ws-highway
Jul 10, 2026
Merged

refactor(server): extract the highway WebSocket into routers/ws_highway.py (R3)#844
byrongamatos merged 1 commit into
mainfrom
refactor/r3-router-ws-highway

Conversation

@byrongamatos

@byrongamatos byrongamatos commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

The single largest handler in server.py — the 902-line /ws/highway/{filename} chart streamer — plus its 3 exclusive helpers. server.py: 9,008 → 8,003 (−1,005, the biggest single R3 cut).

Why it's a clean move despite the size

The handler's only server-module dependencies are: the 4 path constants, 3 exclusive helpers (_pick_smart_arrangement, _sanitize_authors, _sanitized_song_offset), app, and log. Everything else it touches is either nested inside the handler (_evict_audio_cache, _fill_scale_degree, _manifest_entries, _tone_names, _xml_rank, _send_keepalives) or imported from the shared lib modules (song, audio, sloppak, drums, notation, dlc_paths, metadata_db). No shared server.py function is called.

What changed

  • Path constants via the seam: added static_dir / sloppak_cache_dir / audio_cache_dir slots (config_dir already there); server.py configures them. Otherwise verbatim — @app.websocket@router.websocket, PATHSappstate.*, log → module logger.
  • sloppak_cache_dir is setattr-patched, so the 3 test_highway_ws_* suites now also setattr(appstate, "sloppak_cache_dir", …) next to their existing server patch. The _sanitize_authors unit tests import it from routers.ws_highway (it moved). No other test churn.
  • Removed 23 now-dead imports from server.py — diffed against the origin/main unused-import baseline so only newly-dead ones went (song/audio/drums/notation/bisect/contextvars/structlog/WebSocket*/_arr_smart_sort_key).
  • owns_tmp (assigned, never read) moved verbatim — pre-existing dead on origin/main too; left as-is to keep the move faithful.

Verification

  • Route table identical to origin/main (143, paths/methods/order); handler body verbatim spot-checked.
  • pyflakes clean (server has no new undefined/dead); pytest 2400 passed; packaging guard 53; npm run lint 0.
  • WS boot smoke: the highway WebSocket streams the full chart (song_info / beats / sections / notes / chords / notation / anchors / drum_tabready) with the same message sequence as origin/main across arrangements 0/1/2, zero tracebacks. (Confirmed against a side-by-side origin/main worktree.)
  • Codex: 0 findings.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added the Highway WebSocket experience for streaming song data, including notes, chords, beats, sections, lyrics, notation, tempo, and tone changes.
    • Added support for loading songs from loose folders, archives, and sloppak bundles.
    • Improved arrangement selection using explicit choices, instrument routing, preferences, and intelligent fallback.
    • Added playable audio conversion and caching, including stem and original-audio support where available.
    • Added song metadata such as credits, tuning, capo, timing, and arrangement details.

…ay.py (R3)

The single largest handler in server.py — the 902-line /ws/highway/{filename}
chart streamer — plus its 3 exclusive helpers (_pick_smart_arrangement,
_sanitize_authors, _sanitized_song_offset). server.py: 9,008 -> 8,003 (-1,005,
the biggest single R3 cut).

Clean move despite the size: the handler's only server-module deps are the 4
path constants + 3 exclusive helpers + app + log. Everything else it uses is
either NESTED inside the handler (_evict_audio_cache, _fill_scale_degree,
_manifest_entries, _tone_names, _xml_rank, _send_keepalives) or imported from
the shared lib modules (song/audio/sloppak/drums/notation/dlc_paths/metadata_db).

- Path constants read through the appstate seam: added static_dir /
  sloppak_cache_dir / audio_cache_dir slots (config_dir already there);
  server.py configures them. Bodies otherwise verbatim (@app.websocket ->
  @router.websocket, PATHS -> appstate.*, log -> module logger).
- sloppak_cache_dir IS setattr-patched, so the 3 test_highway_ws_* suites now
  also `setattr(appstate, "sloppak_cache_dir", ...)` next to their existing
  server patch. _sanitize_authors unit tests import it from routers.ws_highway
  (it moved). No other test churn.
- Removed 23 now-dead imports from server.py (song/audio/drums/notation/
  bisect/contextvars/structlog/WebSocket*/_arr_smart_sort_key) — diffed against
  the origin/main unused-import baseline so only NEWLY-dead ones went.

owns_tmp (assigned, never read) moved verbatim — it's pre-existing dead on
origin/main too; left as-is to keep the move faithful.

Verified: route table identical to origin/main (143, paths/methods/order);
handler body verbatim spot-checked; pyflakes clean (server has no new
undefined/dead); pytest 2400 passed; packaging guard 53; eslint 0. Boot smoke:
the highway WS streams the full chart (song_info/beats/sections/notes/chords/
notation/anchors/drum_tab -> ready) BYTE-for-byte the same message sequence as
origin/main across arrangements 0/1/2, zero tracebacks.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The highway WebSocket implementation moves from server.py into lib/routers/ws_highway.py. Shared cache paths become injectable through appstate, while the router loads sources, selects arrangements, prepares audio, streams song data, and handles socket lifecycle events.

Changes

Highway WebSocket router extraction

Layer / File(s) Summary
Router extraction and app-state wiring
lib/appstate.py, lib/routers/ws_highway.py, server.py, CHANGELOG.md, docs/size-exemptions.md
The highway route and helper logic move into ws_highway.py; server.py includes the router and passes static and cache directories through appstate.
Source selection and audio preparation
lib/routers/ws_highway.py
The router resolves loose, archive, and sloppak sources, selects arrangements, sends loading keepalives, and prepares cached or sloppak-backed audio URLs.
Streamed song payload and socket lifecycle
lib/routers/ws_highway.py, tests/test_highway_ws_authors.py, tests/test_highway_ws_instrument_routing.py, tests/test_highway_ws_notation.py
The router streams song metadata and performance data, sends readiness, handles control messages and errors, and tests use the new router and app-state seams.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant WebSocketClient
  participant highway_ws
  participant appstate
  participant DLCSource
  WebSocketClient->>highway_ws: Connect to /ws/highway/{filename}
  highway_ws->>appstate: Read configured directories
  highway_ws->>DLCSource: Load source and select arrangement
  DLCSource-->>highway_ws: Return song and audio data
  highway_ws-->>WebSocketClient: Stream song_info and playback data
  highway_ws-->>WebSocketClient: Send ready
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: extracting the highway WebSocket into routers/ws_highway.py.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/r3-router-ws-highway

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
lib/routers/ws_highway.py (2)

214-217: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Remove the dead archive branches lib/routers/ws_highway.py:217, 451-467, 516 — any non-sloppak/non-loose song already raises at line 217, so the not is_slop and not is_loose audio path and "format": "archive" case are unreachable. Drop the stale branch and format value.

🤖 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/ws_highway.py` around lines 214 - 217, The unsupported-format
guard already rejects non-sloppak and non-loose songs, so remove the unreachable
archive handling. Delete the `not is_slop and not is_loose` audio branch and
remove the `"format": "archive"` value from the relevant response logic in
`ws_highway.py`, while preserving sloppak and loose-folder behavior.

663-701: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Use defusedxml for the song XML walkers here and below
User-supplied song XML is parsed with stdlib xml.etree.ElementTree; switch these ET.parse(...) calls to defusedxml.ElementTree.parse(...) (or another safe parser) to avoid entity-expansion DoS on malicious content.

🤖 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/ws_highway.py` around lines 663 - 701, Replace the stdlib XML
parser import and every song XML parsing call in this section and the related
XML walkers with defusedxml.ElementTree, ensuring calls such as ET.parse in the
lyrics-loading logic use the safe parser while preserving existing traversal and
error handling.

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 `@lib/routers/ws_highway.py`:
- Around line 585-660: Wrap the full streaming/send sequence in the WebSocket
handler with a dedicated outer try/except WebSocketDisconnect that returns
quietly, covering sends such as beats, sections, keys, tempos, time_signatures,
anchors, chord_templates, lyrics, tone_changes, notes, chords, handshapes,
phrases, and ready. Place this handler before the existing blanket except
Exception block, while retaining the existing local handling in the drum and
notation sections.

---

Nitpick comments:
In `@lib/routers/ws_highway.py`:
- Around line 214-217: The unsupported-format guard already rejects non-sloppak
and non-loose songs, so remove the unreachable archive handling. Delete the `not
is_slop and not is_loose` audio branch and remove the `"format": "archive"`
value from the relevant response logic in `ws_highway.py`, while preserving
sloppak and loose-folder behavior.
- Around line 663-701: Replace the stdlib XML parser import and every song XML
parsing call in this section and the related XML walkers with
defusedxml.ElementTree, ensuring calls such as ET.parse in the lyrics-loading
logic use the safe parser while preserving existing traversal and error
handling.
🪄 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: 551014c7-2344-43dd-8203-7b2ec6bfa642

📥 Commits

Reviewing files that changed from the base of the PR and between 0dcc913 and dbe4317.

📒 Files selected for processing (8)
  • CHANGELOG.md
  • docs/size-exemptions.md
  • lib/appstate.py
  • lib/routers/ws_highway.py
  • server.py
  • tests/test_highway_ws_authors.py
  • tests/test_highway_ws_instrument_routing.py
  • tests/test_highway_ws_notation.py

Comment thread lib/routers/ws_highway.py
@byrongamatos

Copy link
Copy Markdown
Contributor Author

Real and pre-existing: the inner except WebSocketDisconnect: pass only wraps the post-ready keep-alive receive_text() loop. A client that disconnects during streaming (before ready) raises WebSocketDisconnect, which isn't caught there and falls through to the outer except Exception as e:log.exception("highway_ws unhandled error"). So a routine tab-close mid-load logs an error. The structure is byte-identical to origin/main:server.py, so this move correctly didn't change it.

Follow-up incoming (same pattern as the loops/playlists follow-ups): add except WebSocketDisconnect: return before the blanket handler, matching the two localized guards already in the streaming body and the lib/ coding guideline. Will link it and resolve this thread.

@byrongamatos
byrongamatos merged commit 5144611 into main Jul 10, 2026
5 checks passed
@byrongamatos

Copy link
Copy Markdown
Contributor Author

Follow-up fix opened: #845 — adds except WebSocketDisconnect: return to the outer handler, with a negative-checked regression test.

byrongamatos added a commit that referenced this pull request Jul 10, 2026
* fix(ws_highway): swallow a mid-stream WebSocketDisconnect quietly

Pre-existing (byte-identical to origin/main), flagged by CodeRabbit on #844: the
outer try's only guard was `except Exception as e: log.exception("highway_ws
unhandled error")`. The inner `except WebSocketDisconnect` covers ONLY the
post-`ready` keep-alive loop, and the drum_tab/notation sends have their own
localized guards — but the ~13 other streamed sends (beats, sections, notes,
chords, anchors, …) fall through to the blanket handler. Since
WebSocketDisconnect is an Exception subclass, a routine tab-close mid-load was
logged as an error at whichever send was in flight.

Fix: a dedicated `except WebSocketDisconnect: return` before the blanket handler,
matching the two localized guards and the lib/ coding guideline.

tests/test_ws_highway_disconnect.py drives highway_ws with a fake websocket that
raises WebSocketDisconnect on the first streamed send (`loading`), over a real
minimal sloppak. Negative-checked: removing the guard makes it fail (the
disconnect is logged as `highway_ws unhandled error`); the fix passes. Uses a
raw handler on `feedBack.server` rather than caplog, since configure_logging()
reroutes that logger through structlog where caplog doesn't observe it.

Full suite green. Closes the review thread on #844.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(ws_highway): assert streaming stops after disconnect (exactly one send) — CodeRabbit

The stub now raises only on the first send and the test asserts ws.sends == 1,
so a handler that caught the disconnect and kept streaming would fail. Still
negative-checked: removing the guard fails the test.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant