mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-08-13 04:09:26 +00:00
refactor(server): extract the highway WebSocket into routers/ws_highway.py (R3) (#844)
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>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
0dcc9136b6
commit
514461167e
+1
-1
@@ -26,7 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
next root-level module can't ship broken.
|
next root-level module can't ship broken.
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- **`routers/` — extracting `server.py`'s route layer, cheapest-first (R3).** Each PR moves a cohesive route group into a `fastapi.APIRouter` under `lib/routers/`, mounted with `app.include_router(...)` at its original site (FastAPI matches in registration order; the full route table stays byte-identical). Bodies are verbatim — only the decorator receiver (`@app` → `@router`) and singleton reads (`meta_db` → `appstate.meta_db`, resolved at call time) change. So far: `audio_effects` (5), `artist_aliases` (5), `loops` (3), `playlists` (12 + custom covers). The DLC library-path resolution (`_get_dlc_dir`, pure `_resolve_dlc_path`) moved to `lib/dlc_paths.py`, reading paths through the seam; `config_dir`/`dlc_dir`/`dlc_dir_env` now ride the `appstate` seam (env-derived, so the pop-and-reimport fixtures reconfigure it for free), and the shared request-field sanitizer `_clean_str` moved to `lib/reqfields.py`. The next cut is picked by a dependency-closure scan that ranks groups by how many `monkeypatch.setattr(server, …)` targets they'd drag along.
|
- **`routers/` — extracting `server.py`'s route layer, cheapest-first (R3).** Each PR moves a cohesive route group into a `fastapi.APIRouter` under `lib/routers/`, mounted with `app.include_router(...)` at its original site (FastAPI matches in registration order; the full route table stays byte-identical). Bodies are verbatim — only the decorator receiver (`@app` → `@router`) and singleton reads (`meta_db` → `appstate.meta_db`, resolved at call time) change. So far: `audio_effects` (5), `artist_aliases` (5), `loops` (3), `playlists` (12 + covers), `ws_highway` (the 902-line highway chart WebSocket — the single largest handler). The DLC library-path resolution (`_get_dlc_dir`, pure `_resolve_dlc_path`) moved to `lib/dlc_paths.py`, reading paths through the seam; `config_dir`/`dlc_dir`/`dlc_dir_env` now ride the `appstate` seam (env-derived, so the pop-and-reimport fixtures reconfigure it for free), and the shared request-field sanitizer `_clean_str` moved to `lib/reqfields.py`. The next cut is picked by a dependency-closure scan that ranks groups by how many `monkeypatch.setattr(server, …)` targets they'd drag along.
|
||||||
- **`routers/` — the first extracted route module (R3).** The five audio-effects mapping
|
- **`routers/` — the first extracted route module (R3).** The five audio-effects mapping
|
||||||
endpoints move out of `server.py` into `lib/routers/audio_effects.py` as a
|
endpoints move out of `server.py` into `lib/routers/audio_effects.py` as a
|
||||||
`fastapi.APIRouter`, mounted with `app.include_router(...)` **at the point in the file
|
`fastapi.APIRouter`, mounted with `app.include_router(...)` **at the point in the file
|
||||||
|
|||||||
@@ -55,8 +55,8 @@ without a *signed* exemption" is unenforceable.
|
|||||||
## Planned, NOT exempt (owned by split plans — listed so nothing falls between states)
|
## Planned, NOT exempt (owned by split plans — listed so nothing falls between states)
|
||||||
|
|
||||||
core `static/app.js` (11,852) · `static/highway.js` (4,168, whole file) · `server.py`
|
core `static/app.js` (11,852) · `static/highway.js` (4,168, whole file) · `server.py`
|
||||||
(9,008 — was 14,037; ratcheted by the R3 `MetadataDB` + `AudioEffectsMappingDB`
|
(8,003 — was 14,037; ratcheted by the R3 `MetadataDB` + `AudioEffectsMappingDB`
|
||||||
extractions and four `routers/` modules) ·
|
extractions and five `routers/` modules) ·
|
||||||
`lib/metadata_db.py` (4,373 — new in R3; the `MetadataDB` class alone is 4,018 lines
|
`lib/metadata_db.py` (4,373 — new in R3; the `MetadataDB` class alone is 4,018 lines
|
||||||
and is a monolith in its own right, to be split per-table once the router train
|
and is a monolith in its own right, to be split per-table once the router train
|
||||||
lands) · `static/v3/songs.js` (4,134) · `static/capabilities/audio-session.js`
|
lands) · `static/v3/songs.js` (4,134) · `static/capabilities/audio-session.js`
|
||||||
|
|||||||
+13
-1
@@ -73,8 +73,20 @@ config_dir = None
|
|||||||
dlc_dir = None # the DLC_DIR env value as a Path (Path("") if unset)
|
dlc_dir = None # the DLC_DIR env value as a Path (Path("") if unset)
|
||||||
dlc_dir_env = None # the raw DLC_DIR env string, "" if unset — distinguishes
|
dlc_dir_env = None # the raw DLC_DIR env string, "" if unset — distinguishes
|
||||||
# "unset" from Path("")→"." (see dlc_paths._get_dlc_dir)
|
# "unset" from Path("")→"." (see dlc_paths._get_dlc_dir)
|
||||||
|
# Cache/asset dirs. static_dir + sloppak_cache_dir are patched via
|
||||||
|
# `setattr(server, …)` in a few tests, so a router reading them here needs those
|
||||||
|
# setattr sites retargeted to `setattr(appstate, …)` in the same PR (ws_highway
|
||||||
|
# retargets the 3 test_highway_ws_* SLOPPAK sites). config_dir-derived dirs are
|
||||||
|
# reconfigured for free on a setenv+reimport.
|
||||||
|
static_dir = None
|
||||||
|
sloppak_cache_dir = None
|
||||||
|
audio_cache_dir = None
|
||||||
|
|
||||||
_SLOTS = frozenset({"meta_db", "audio_effect_mappings", "config_dir", "dlc_dir", "dlc_dir_env"})
|
_SLOTS = frozenset({
|
||||||
|
"meta_db", "audio_effect_mappings",
|
||||||
|
"config_dir", "dlc_dir", "dlc_dir_env",
|
||||||
|
"static_dir", "sloppak_cache_dir", "audio_cache_dir",
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
def configure(**kwargs) -> None:
|
def configure(**kwargs) -> None:
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -16,6 +16,8 @@ import pytest
|
|||||||
import yaml
|
import yaml
|
||||||
from fastapi.testclient import TestClient
|
from fastapi.testclient import TestClient
|
||||||
|
|
||||||
|
from routers.ws_highway import _sanitize_authors
|
||||||
|
|
||||||
|
|
||||||
# ── _sanitize_authors unit tests ────────────────────────────────────────────
|
# ── _sanitize_authors unit tests ────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -35,7 +37,7 @@ def server_mod(monkeypatch, tmp_path):
|
|||||||
|
|
||||||
|
|
||||||
def test_sanitize_authors_valid(server_mod):
|
def test_sanitize_authors_valid(server_mod):
|
||||||
out = server_mod._sanitize_authors(
|
out = _sanitize_authors(
|
||||||
{
|
{
|
||||||
"authors": [
|
"authors": [
|
||||||
{"name": "Azure", "role": "charter", "email": "a@b.c", "url": "x"},
|
{"name": "Azure", "role": "charter", "email": "a@b.c", "url": "x"},
|
||||||
@@ -53,7 +55,7 @@ def test_sanitize_authors_valid(server_mod):
|
|||||||
|
|
||||||
|
|
||||||
def test_sanitize_authors_skips_malformed(server_mod):
|
def test_sanitize_authors_skips_malformed(server_mod):
|
||||||
out = server_mod._sanitize_authors(
|
out = _sanitize_authors(
|
||||||
{
|
{
|
||||||
"authors": [
|
"authors": [
|
||||||
{"name": ""}, # blank name → skipped
|
{"name": ""}, # blank name → skipped
|
||||||
@@ -69,7 +71,7 @@ def test_sanitize_authors_skips_malformed(server_mod):
|
|||||||
|
|
||||||
@pytest.mark.parametrize("manifest", [None, {}, {"authors": None}, {"authors": "x"}, "nope"])
|
@pytest.mark.parametrize("manifest", [None, {}, {"authors": None}, {"authors": "x"}, "nope"])
|
||||||
def test_sanitize_authors_absent_or_nonlist(server_mod, manifest):
|
def test_sanitize_authors_absent_or_nonlist(server_mod, manifest):
|
||||||
assert server_mod._sanitize_authors(manifest) == []
|
assert _sanitize_authors(manifest) == []
|
||||||
|
|
||||||
|
|
||||||
# ── song_info WS integration ────────────────────────────────────────────────
|
# ── song_info WS integration ────────────────────────────────────────────────
|
||||||
@@ -118,6 +120,9 @@ def make_client(tmp_path, monkeypatch):
|
|||||||
monkeypatch.setattr(server, "load_plugins", lambda *a, **kw: None)
|
monkeypatch.setattr(server, "load_plugins", lambda *a, **kw: None)
|
||||||
monkeypatch.setattr(server, "startup_scan", lambda: None)
|
monkeypatch.setattr(server, "startup_scan", lambda: None)
|
||||||
monkeypatch.setattr(server, "SLOPPAK_CACHE_DIR", tmp_path / "cache")
|
monkeypatch.setattr(server, "SLOPPAK_CACHE_DIR", tmp_path / "cache")
|
||||||
|
# ws_highway reads the cache dir through the appstate seam now.
|
||||||
|
import appstate as _appstate
|
||||||
|
monkeypatch.setattr(_appstate, "sloppak_cache_dir", tmp_path / "cache")
|
||||||
return server
|
return server
|
||||||
|
|
||||||
(tmp_path / "dlc").mkdir()
|
(tmp_path / "dlc").mkdir()
|
||||||
|
|||||||
@@ -96,6 +96,9 @@ def make_client(tmp_path, monkeypatch):
|
|||||||
monkeypatch.setattr(server, "load_plugins", lambda *a, **kw: None)
|
monkeypatch.setattr(server, "load_plugins", lambda *a, **kw: None)
|
||||||
monkeypatch.setattr(server, "startup_scan", lambda: None)
|
monkeypatch.setattr(server, "startup_scan", lambda: None)
|
||||||
monkeypatch.setattr(server, "SLOPPAK_CACHE_DIR", tmp_path / "cache")
|
monkeypatch.setattr(server, "SLOPPAK_CACHE_DIR", tmp_path / "cache")
|
||||||
|
# ws_highway reads the cache dir through the appstate seam now.
|
||||||
|
import appstate as _appstate
|
||||||
|
monkeypatch.setattr(_appstate, "sloppak_cache_dir", tmp_path / "cache")
|
||||||
return server
|
return server
|
||||||
|
|
||||||
(tmp_path / "dlc").mkdir()
|
(tmp_path / "dlc").mkdir()
|
||||||
|
|||||||
@@ -123,6 +123,9 @@ def make_client(tmp_path, monkeypatch):
|
|||||||
monkeypatch.setattr(server, "load_plugins", lambda *a, **kw: None)
|
monkeypatch.setattr(server, "load_plugins", lambda *a, **kw: None)
|
||||||
monkeypatch.setattr(server, "startup_scan", lambda: None)
|
monkeypatch.setattr(server, "startup_scan", lambda: None)
|
||||||
monkeypatch.setattr(server, "SLOPPAK_CACHE_DIR", tmp_path / "cache")
|
monkeypatch.setattr(server, "SLOPPAK_CACHE_DIR", tmp_path / "cache")
|
||||||
|
# ws_highway reads the cache dir through the appstate seam now.
|
||||||
|
import appstate as _appstate
|
||||||
|
monkeypatch.setattr(_appstate, "sloppak_cache_dir", tmp_path / "cache")
|
||||||
return server
|
return server
|
||||||
|
|
||||||
(tmp_path / "dlc").mkdir()
|
(tmp_path / "dlc").mkdir()
|
||||||
|
|||||||
Reference in New Issue
Block a user