49 lines
1.6 KiB
Python
49 lines
1.6 KiB
Python
"""FastAPI application factory for the embedded radar web UI.
|
|
|
|
The controller (the Qt bridge that forwards to the AppWindow) is created and
|
|
owned by the host process and injected here. The app's only owned resource is the
|
|
:class:`RingBroadcaster` polling task, created and torn down by the lifespan. The
|
|
static single-page frontend is mounted at ``/`` and the JSON/WS API under
|
|
``/api`` and ``/ws``.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from contextlib import asynccontextmanager
|
|
import logging
|
|
from pathlib import Path
|
|
|
|
from fastapi import FastAPI
|
|
from fastapi.staticfiles import StaticFiles
|
|
|
|
from python_app.webui.controller import WebController
|
|
from python_app.webui.routes import router
|
|
from python_app.webui.streaming import RingBroadcaster
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
_STATIC_DIR = Path(__file__).resolve().parent / "static"
|
|
|
|
|
|
def create_app(controller: WebController) -> FastAPI:
|
|
"""Build the FastAPI app that serves and streams for ``controller``."""
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI):
|
|
broadcaster = RingBroadcaster(controller)
|
|
app.state.controller = controller
|
|
app.state.broadcaster = broadcaster
|
|
broadcaster.start()
|
|
logger.info("Web UI application started")
|
|
try:
|
|
yield
|
|
finally:
|
|
await broadcaster.stop()
|
|
logger.info("Web UI application stopped")
|
|
|
|
app = FastAPI(title="Radar Web UI", lifespan=lifespan)
|
|
app.include_router(router)
|
|
# Mount the SPA last so the API routes above always take precedence.
|
|
app.mount("/", StaticFiles(directory=_STATIC_DIR, html=True), name="static")
|
|
return app
|