Use fastapi-vue RuntimeConfig passing, simplifying code.
This commit is contained in:
+16
-24
@@ -190,18 +190,6 @@ def cmd_migrate(args: argparse.Namespace) -> None:
|
|||||||
print(f"✅ {action} {db_file_path()} (domains: {', '.join(rp_ids)})")
|
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:
|
def cmd_serve(args: argparse.Namespace) -> None:
|
||||||
"""Open the combined database and serve all configured domains."""
|
"""Open the combined database and serve all configured domains."""
|
||||||
db_path = db_file_path()
|
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.")
|
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)
|
config = _load_stored_config(db_path)
|
||||||
|
|
||||||
listen = _split_multi(args.listen) or config.listen
|
# Effective serve parameters, teleported to the server process(es); the
|
||||||
configure_domains(listen=listen)
|
# 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:
|
try:
|
||||||
registry = build_registry(config)
|
registry = build_registry(config)
|
||||||
except ValueError as e:
|
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
|
# Sanitization warnings (serving is best-effort; fixing the stored config
|
||||||
# is the admin's job via the admin interface) are logged by build().
|
# is the admin's job via the admin interface) are logged by build().
|
||||||
|
|
||||||
# Pass process-global serve parameters to the server process(es)
|
startupbox.print_startup_config(
|
||||||
serve_config().listen = listen
|
registry, listen=cfg.listen, default_port=DEFAULT_PORT
|
||||||
teleport() # Serialize bound config before spawning workers
|
)
|
||||||
|
|
||||||
startupbox.print_startup_config(registry, listen=listen, default_port=DEFAULT_PORT)
|
|
||||||
|
|
||||||
# Run the server (spawns processes in dev mode)
|
# Run the server (spawns processes in dev mode)
|
||||||
# tracerite, access logging and log config are handled by fastapi_vue.server;
|
# 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.
|
# we print our own startup config box, so disable the built-in one.
|
||||||
server.run(
|
server.run(
|
||||||
"paskia.fastapi.mainapp:app",
|
"paskia.fastapi.mainapp:app",
|
||||||
listen=listen,
|
listen=cfg.listen,
|
||||||
default_port=DEFAULT_PORT,
|
default_port=DEFAULT_PORT,
|
||||||
server_header=False,
|
server_header=False,
|
||||||
startup_box=None,
|
startup_box=None,
|
||||||
|
|||||||
@@ -29,10 +29,12 @@ _EXAMPLES_DIR = Path(__file__).parent.parent.parent / "examples"
|
|||||||
async def lifespan(app: FastAPI): # pragma: no cover - startup path
|
async def lifespan(app: FastAPI): # pragma: no cover - startup path
|
||||||
"""Application lifespan: open the combined database and build the domain registry.
|
"""Application lifespan: open the combined database and build the domain registry.
|
||||||
|
|
||||||
Process-global serve parameters (listen endpoints) are passed via the
|
Process-global serve parameters (listen endpoints, save flag) are passed
|
||||||
PASKIA_CONFIG JSON env variable (set by the CLI entrypoint) so that
|
via the PASKIA_CONFIG JSON env variable (set by the CLI entrypoint) so
|
||||||
uvicorn reload / multiprocess workers derive site URLs the same way.
|
that uvicorn reload / multiprocess workers derive site URLs the same
|
||||||
Domain configuration is read from the database.
|
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()
|
cfg = serve_config()
|
||||||
domains.configure(listen=cfg.listen)
|
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
|
Path(kanta.filename).parent.mkdir, parents=True, exist_ok=True
|
||||||
)
|
)
|
||||||
async with kanta:
|
async with kanta:
|
||||||
|
if cfg.save:
|
||||||
|
with kanta.transaction("serve:save_listen"):
|
||||||
|
db.data().config.listen = cfg.listen
|
||||||
try:
|
try:
|
||||||
domains.init_registry(db.data().config)
|
domains.init_registry(db.data().config)
|
||||||
await remoteauth.init()
|
await remoteauth.init()
|
||||||
|
|||||||
@@ -2,9 +2,10 @@
|
|||||||
|
|
||||||
Domain configuration lives in the database (``Config.domains``); the
|
Domain configuration lives in the database (``Config.domains``); the
|
||||||
``PASKIA_CONFIG`` environment variable only carries the effective listen
|
``PASKIA_CONFIG`` environment variable only carries the effective listen
|
||||||
endpoints so that child processes (uvicorn reload / workers) derive site
|
endpoints and whether to persist them, so that child processes (uvicorn
|
||||||
URLs the same way the parent did. The CLI entry point mutates the bound
|
reload / workers) derive site URLs the same way the parent did. The CLI
|
||||||
object before ``server.run()`` calls ``teleport()`` to pass it on.
|
entry point mutates the bound object before ``server.run()`` calls
|
||||||
|
``teleport()`` to pass it on.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import msgspec
|
import msgspec
|
||||||
@@ -15,6 +16,7 @@ class ServeConfig(msgspec.Struct):
|
|||||||
"""Process-global serve parameters."""
|
"""Process-global serve parameters."""
|
||||||
|
|
||||||
listen: list[str] | None = None
|
listen: list[str] | None = None
|
||||||
|
save: bool = False # Persist listen to the stored config on startup
|
||||||
|
|
||||||
|
|
||||||
def serve_config() -> ServeConfig:
|
def serve_config() -> ServeConfig:
|
||||||
|
|||||||
+13
-3
@@ -172,6 +172,7 @@ def test_serve_uses_stored_config(run_cli, tmp_path):
|
|||||||
assert calls["listen"] is None # stored listen (None) used
|
assert calls["listen"] is None # stored listen (None) used
|
||||||
serve = msgspec.json.decode(os.environ["PASKIA_CONFIG"].encode(), type=ServeConfig)
|
serve = msgspec.json.decode(os.environ["PASKIA_CONFIG"].encode(), type=ServeConfig)
|
||||||
assert serve.listen is None
|
assert serve.listen is None
|
||||||
|
assert serve.save is False
|
||||||
|
|
||||||
|
|
||||||
def test_serve_listen_override_not_persisted(run_cli, tmp_path):
|
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"]
|
assert calls["listen"] == ["4403"]
|
||||||
serve = msgspec.json.decode(os.environ["PASKIA_CONFIG"].encode(), type=ServeConfig)
|
serve = msgspec.json.decode(os.environ["PASKIA_CONFIG"].encode(), type=ServeConfig)
|
||||||
assert serve.listen == ["4403"]
|
assert serve.listen == ["4403"]
|
||||||
|
assert serve.save is False
|
||||||
# Stored config keeps the original listen value
|
# Stored config keeps the original listen value
|
||||||
assert stored_config(tmp_path).listen == ["4402"]
|
assert stored_config(tmp_path).listen == ["4402"]
|
||||||
|
|
||||||
|
|
||||||
def test_serve_listen_save_persists(run_cli, tmp_path):
|
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")
|
run_cli("init", "--listen", "4402")
|
||||||
calls = run_cli("--listen", "4403", "--save")
|
calls = run_cli("--listen", "4403", "--save")
|
||||||
|
|
||||||
assert calls["listen"] == ["4403"]
|
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):
|
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("init", "--listen", "4402")
|
||||||
run_cli("--listen", "", "--save")
|
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):
|
def test_serve_suggests_migrate_when_legacy_present(run_cli, tmp_path):
|
||||||
|
|||||||
Reference in New Issue
Block a user