From 4de164c457f7707ddbf8119d0f0fad1f5e3e3df8 Mon Sep 17 00:00:00 2001 From: Leo Vasanko Date: Fri, 18 Sep 2026 19:00:13 +0000 Subject: [PATCH] Use fastapi-vue RuntimeConfig passing, simplifying code. --- paskia/__main__.py | 40 ++++++++++++++++----------------------- paskia/fastapi/mainapp.py | 13 +++++++++---- paskia/util/runtime.py | 8 +++++--- tests/test_cli.py | 16 +++++++++++++--- 4 files changed, 43 insertions(+), 34 deletions(-) diff --git a/paskia/__main__.py b/paskia/__main__.py index 78a9ed9..152ca7b 100644 --- a/paskia/__main__.py +++ b/paskia/__main__.py @@ -190,18 +190,6 @@ def cmd_migrate(args: argparse.Namespace) -> None: print(f"✅ {action} {db_file_path()} (domains: {', '.join(rp_ids)})") -def _save_listen(db_path: Path, listen: list[str] | None) -> None: - """Persist the listen endpoints to the stored configuration.""" - kanta = Kanta(str(db_path), DB()) - - async def _write() -> None: - async with kanta: - with kanta.transaction("serve:save_listen"): - kanta.data.config.listen = listen - - asyncio.run(_write()) - - def cmd_serve(args: argparse.Namespace) -> None: """Open the combined database and serve all configured domains.""" db_path = db_file_path() @@ -214,14 +202,20 @@ def cmd_serve(args: argparse.Namespace) -> None: ) raise SystemExit(f"Database {db_path} not found — run 'paskia init' first.") - if args.save and args.listen is not None: - # '--listen ""' clears the stored endpoints (back to the default) - _save_listen(db_path, _split_multi(args.listen) or None) - config = _load_stored_config(db_path) - listen = _split_multi(args.listen) or config.listen - configure_domains(listen=listen) + # Effective serve parameters, teleported to the server process(es); the + # app persists the listen endpoints to the database when save is set. + cfg = serve_config() + cfg.save = bool(args.save and args.listen is not None) + if cfg.save: + # '--listen ""' clears the stored endpoints (back to the default) + cfg.listen = _split_multi(args.listen) or None + else: + cfg.listen = _split_multi(args.listen) or config.listen + teleport() # Serialize bound config before spawning workers + + configure_domains(listen=cfg.listen) try: registry = build_registry(config) except ValueError as e: @@ -229,18 +223,16 @@ def cmd_serve(args: argparse.Namespace) -> None: # Sanitization warnings (serving is best-effort; fixing the stored config # is the admin's job via the admin interface) are logged by build(). - # Pass process-global serve parameters to the server process(es) - serve_config().listen = listen - teleport() # Serialize bound config before spawning workers - - startupbox.print_startup_config(registry, listen=listen, default_port=DEFAULT_PORT) + startupbox.print_startup_config( + registry, listen=cfg.listen, default_port=DEFAULT_PORT + ) # Run the server (spawns processes in dev mode) # tracerite, access logging and log config are handled by fastapi_vue.server; # we print our own startup config box, so disable the built-in one. server.run( "paskia.fastapi.mainapp:app", - listen=listen, + listen=cfg.listen, default_port=DEFAULT_PORT, server_header=False, startup_box=None, diff --git a/paskia/fastapi/mainapp.py b/paskia/fastapi/mainapp.py index c2e4a2b..f2296ed 100644 --- a/paskia/fastapi/mainapp.py +++ b/paskia/fastapi/mainapp.py @@ -29,10 +29,12 @@ _EXAMPLES_DIR = Path(__file__).parent.parent.parent / "examples" async def lifespan(app: FastAPI): # pragma: no cover - startup path """Application lifespan: open the combined database and build the domain registry. - Process-global serve parameters (listen endpoints) are passed via the - PASKIA_CONFIG JSON env variable (set by the CLI entrypoint) so that - uvicorn reload / multiprocess workers derive site URLs the same way. - Domain configuration is read from the database. + Process-global serve parameters (listen endpoints, save flag) are passed + via the PASKIA_CONFIG JSON env variable (set by the CLI entrypoint) so + that uvicorn reload / multiprocess workers derive site URLs the same + way. With the save flag set, the listen endpoints are persisted here — + the CLI never opens the database read-write. Domain configuration is + read from the database. """ cfg = serve_config() domains.configure(listen=cfg.listen) @@ -41,6 +43,9 @@ async def lifespan(app: FastAPI): # pragma: no cover - startup path Path(kanta.filename).parent.mkdir, parents=True, exist_ok=True ) async with kanta: + if cfg.save: + with kanta.transaction("serve:save_listen"): + db.data().config.listen = cfg.listen try: domains.init_registry(db.data().config) await remoteauth.init() diff --git a/paskia/util/runtime.py b/paskia/util/runtime.py index fbe7e45..df45d01 100644 --- a/paskia/util/runtime.py +++ b/paskia/util/runtime.py @@ -2,9 +2,10 @@ Domain configuration lives in the database (``Config.domains``); the ``PASKIA_CONFIG`` environment variable only carries the effective listen -endpoints so that child processes (uvicorn reload / workers) derive site -URLs the same way the parent did. The CLI entry point mutates the bound -object before ``server.run()`` calls ``teleport()`` to pass it on. +endpoints and whether to persist them, so that child processes (uvicorn +reload / workers) derive site URLs the same way the parent did. The CLI +entry point mutates the bound object before ``server.run()`` calls +``teleport()`` to pass it on. """ import msgspec @@ -15,6 +16,7 @@ class ServeConfig(msgspec.Struct): """Process-global serve parameters.""" listen: list[str] | None = None + save: bool = False # Persist listen to the stored config on startup def serve_config() -> ServeConfig: diff --git a/tests/test_cli.py b/tests/test_cli.py index 190be6a..3b55000 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -172,6 +172,7 @@ def test_serve_uses_stored_config(run_cli, tmp_path): assert calls["listen"] is None # stored listen (None) used serve = msgspec.json.decode(os.environ["PASKIA_CONFIG"].encode(), type=ServeConfig) assert serve.listen is None + assert serve.save is False def test_serve_listen_override_not_persisted(run_cli, tmp_path): @@ -181,24 +182,33 @@ def test_serve_listen_override_not_persisted(run_cli, tmp_path): assert calls["listen"] == ["4403"] serve = msgspec.json.decode(os.environ["PASKIA_CONFIG"].encode(), type=ServeConfig) assert serve.listen == ["4403"] + assert serve.save is False # Stored config keeps the original listen value assert stored_config(tmp_path).listen == ["4402"] def test_serve_listen_save_persists(run_cli, tmp_path): + """--save teleports the save flag; the app persists, the CLI is read-only.""" run_cli("init", "--listen", "4402") calls = run_cli("--listen", "4403", "--save") assert calls["listen"] == ["4403"] - assert stored_config(tmp_path).listen == ["4403"] + serve = msgspec.json.decode(os.environ["PASKIA_CONFIG"].encode(), type=ServeConfig) + assert serve.listen == ["4403"] + assert serve.save is True + # The CLI itself does not write the database + assert stored_config(tmp_path).listen == ["4402"] def test_serve_listen_save_clear(run_cli, tmp_path): - """--listen "" --save clears the stored endpoints (back to default).""" + """--listen "" --save teleports a clear (back to default) for the app.""" run_cli("init", "--listen", "4402") run_cli("--listen", "", "--save") - assert stored_config(tmp_path).listen is None + serve = msgspec.json.decode(os.environ["PASKIA_CONFIG"].encode(), type=ServeConfig) + assert serve.listen is None + assert serve.save is True + assert stored_config(tmp_path).listen == ["4402"] def test_serve_suggests_migrate_when_legacy_present(run_cli, tmp_path):