mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-07-29 08:01:22 +00:00
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>
103 lines
4.8 KiB
Python
103 lines
4.8 KiB
Python
"""Shared application state — the seam that lets route modules reach core
|
|
singletons without importing ``server``.
|
|
|
|
``server.py`` is the host: it owns the FastAPI ``app``, constructs the DB
|
|
singletons, and runs the lifecycle. As routes move out into ``routers/`` (R3),
|
|
those modules need ``meta_db`` and friends — but they must not ``import
|
|
server``, or the import graph goes circular the moment ``server`` imports them
|
|
back.
|
|
|
|
So ``server`` **injects** its singletons here once, at the point it builds them::
|
|
|
|
# server.py
|
|
meta_db = MetadataDB(CONFIG_DIR)
|
|
appstate.configure(meta_db=meta_db, ...)
|
|
|
|
and a router reads them back as **module attributes, at call time**::
|
|
|
|
# routers/artists.py
|
|
import appstate
|
|
|
|
@router.get("/api/artist/{name}/page")
|
|
def artist_page(name):
|
|
return appstate.meta_db.artist_page(name)
|
|
|
|
This is the Python analogue of the injected `configureX({...})` seams the
|
|
frontend refactor uses (stems' ``configureStreaming``, studio's
|
|
``configureAudioGraph``, the editor's ``src/host.js``), and of the plugin
|
|
``setup(app, context)`` contract in Principle III: dependencies flow one way,
|
|
``server -> routers -> appstate``, and nothing imports back up.
|
|
|
|
Two properties this shape buys, both load-bearing:
|
|
|
|
* **``import appstate`` performs no IO and constructs nothing.** ``server``
|
|
still owns construction, so the ~49 test fixtures that do
|
|
``sys.modules.pop("server")`` + re-import (to rebuild ``meta_db`` under a
|
|
patched ``CONFIG_DIR``) keep working untouched — a singleton *owned* here
|
|
would survive that pop and go stale.
|
|
* **Reads are late-bound.** Routers must use ``appstate.meta_db``, never
|
|
``from appstate import meta_db`` — a ``from`` import freezes the binding at
|
|
its current value, so a later ``configure()`` (or a
|
|
``monkeypatch.setattr(appstate, "meta_db", fake)``) would not reach the
|
|
router. This is the same read-only-binding trap as ES ``import``.
|
|
|
|
Defaults are ``None`` on purpose: they are inert but *type-honest*, so a router
|
|
that runs before ``configure()`` fails loudly on ``NoneType`` instead of
|
|
quietly operating on a stand-in.
|
|
|
|
Slots are added here only when a router actually needs one — this is a seam,
|
|
not a grab-bag for everything in ``server.py``.
|
|
|
|
**Why this lives in ``lib/`` and not the repo root.** Because it constructs
|
|
nothing and does no import-time IO, it satisfies Principle V's rule for ``lib/``
|
|
modules — and ``lib/`` is the only core directory every packaging path already
|
|
copies: the Dockerfile (``COPY lib/``), ``docker-compose.yml``, and
|
|
feedback-desktop's ``bundle-slopsmith.sh`` (``cp -r lib``). All three also put
|
|
both the bundle root and ``lib/`` on ``sys.path``. A root-level module ships in
|
|
Docker but is silently dropped from the packaged desktop app, whose bundler
|
|
copies a hardcoded file list — that regression is what moved this file here.
|
|
"""
|
|
|
|
# The singletons routers may read. Every name here must also be a `_SLOTS` key.
|
|
meta_db = None
|
|
audio_effect_mappings = None
|
|
|
|
# Config paths. server.py derives these from the environment (fresh on every
|
|
# import, so the ~49 pop-and-reimport fixtures keep working) and injects them
|
|
# here. Routers read them as `appstate.config_dir` etc. — a module attribute at
|
|
# call time. NOTE: config_dir/dlc_dir are env-derived, so a `setenv`+reimport
|
|
# test reconfigures them for free; STATIC_DIR/SLOPPAK_CACHE_DIR are patched via
|
|
# `setattr(server, …)` in a few tests, so those slots (when added) need their
|
|
# tests retargeted to appstate in the same PR.
|
|
config_dir = None
|
|
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
|
|
# "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",
|
|
"static_dir", "sloppak_cache_dir", "audio_cache_dir",
|
|
})
|
|
|
|
|
|
def configure(**kwargs) -> None:
|
|
"""Publish `server`'s singletons into this module. Called once per
|
|
`server` import (and again on re-import), so it must be idempotent."""
|
|
unknown = set(kwargs) - _SLOTS
|
|
if unknown:
|
|
raise TypeError(
|
|
f"appstate.configure() got unknown slot(s): {sorted(unknown)}. "
|
|
f"Known slots: {sorted(_SLOTS)}. Add the name to _SLOTS if a router "
|
|
f"genuinely needs it."
|
|
)
|
|
globals().update(kwargs)
|