"""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``. """ # The singletons routers may read. Every name here must also be a `_SLOTS` key. meta_db = None audio_effect_mappings = None # Declared up front so `configure()` can reject a typo'd or stale keyword # instead of silently creating a new global that nothing ever reads. A seam # whose wiring can no-op undetected is worse than no seam. _SLOTS = frozenset({"meta_db", "audio_effect_mappings"}) 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)