Files
led2/lightsync/main.py
Claude 509911f5c5 feat(03-03): integrate UDPSender into FastAPI lifespan
- Import UDPSender in main.py
- Start UDPSender on app boot, assign to app.state.udp_sender
- Stop UDPSender cleanly on shutdown (before registry.save)
- All existing lifespan behavior unchanged (beats, registry, show_store)
2026-04-06 21:51:40 +00:00

72 lines
2.3 KiB
Python

import os
from contextlib import asynccontextmanager
from pathlib import Path
from fastapi import FastAPI, Request
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
from lightsync.devices.registry import DeviceRegistry
from lightsync.store.show_store import ShowStore
from lightsync.protocol.udp_sender import UDPSender
# Module-level containers populated in lifespan
registry: DeviceRegistry | None = None
show_store: ShowStore | None = None
@asynccontextmanager
async def lifespan(app: FastAPI):
global registry, show_store
# Startup
registry = DeviceRegistry(Path("devices.json"))
await registry.load()
show_store = ShowStore(Path("shows"))
show_store.ensure_dir()
# Beat analysis cache: filepath -> {"tempo": float, "beats": [float, ...]}
app.state.beats = {}
# UDP sender for device communication (Phase 3)
udp_sender = UDPSender()
await udp_sender.start()
app.state.udp_sender = udp_sender
yield
# Shutdown
await app.state.udp_sender.stop()
await registry.save()
def create_app() -> FastAPI:
app = FastAPI(title="LightSync", lifespan=lifespan, redirect_slashes=False)
# API routes (registered before static mount — see Pattern 7)
from lightsync.api import shows, devices, ws, audio
app.include_router(shows.router, prefix="/api/shows")
app.include_router(devices.router, prefix="/api/devices")
app.include_router(ws.router)
app.include_router(audio.router, prefix="/api/audio")
# Serve uploaded audio files so <audio src=...> can load them
shows_dir = Path("/app/shows")
shows_dir.mkdir(parents=True, exist_ok=True)
app.mount("/shows", StaticFiles(directory=str(shows_dir)), name="shows")
# Static file serving — no-cache headers to prevent stale JS/CSS
frontend_dir = Path(__file__).parent / "frontend"
@app.get("/{filename:path}")
async def static_files(request: Request, filename: str):
path = frontend_dir / (filename or "index.html")
if not path.exists() or not path.is_file():
path = frontend_dir / "index.html"
response = FileResponse(path)
response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
return response
return app
app = create_app()