feat(01-08): rewire app.py lifespan and deps.py to use build_pipeline

- app.py: replace inline component wiring with build_pipeline(env=cfg.env)
  - Store processor as app.state.processor (and backwards-compat pipeline_components)
  - RuntimeConfig replaces get_settings(); MAVLink stop() on shutdown
- deps.py: get_flight_processor prefers app.state.processor from lifespan
  - Falls back to build_pipeline() for test contexts without lifespan
  - Per-request repo/streamer swap preserved
This commit is contained in:
Yuzviak
2026-05-11 09:04:56 +03:00
parent 3a2e91439e
commit 0bb94da3c4
2 changed files with 45 additions and 56 deletions
+17 -8
View File
@@ -5,7 +5,7 @@ from fastapi import Depends, HTTPException, Request
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from sqlalchemy.ext.asyncio import AsyncSession
from gps_denied.config import get_settings
from gps_denied.config import RuntimeConfig, get_settings
from gps_denied.core.processor import FlightProcessor
from gps_denied.core.sse import SSEEventStreamer
from gps_denied.db.engine import get_session
@@ -65,14 +65,23 @@ async def get_flight_processor(
) -> FlightProcessor:
global _processor
if _processor is None:
eskf_config = getattr(request.app.state, "eskf_config", None)
_processor = FlightProcessor(repo, sse, eskf_config=eskf_config)
# Підключаємо pipeline компоненти з lifespan
components = getattr(request.app.state, "pipeline_components", None)
if components:
_processor.attach_components(**components)
# Оновлюємо repo (нова сесія на кожен запит)
# Prefer the processor already built by lifespan (via build_pipeline)
lifespan_processor = getattr(request.app.state, "processor", None)
if lifespan_processor is not None:
_processor = lifespan_processor
else:
# Fallback: build pipeline directly (e.g. in tests without lifespan)
from gps_denied.pipeline import build_pipeline
_settings = RuntimeConfig()
_processor = build_pipeline(
env=_settings.env,
config=_settings,
repository=repo,
streamer=sse,
)
# Оновлюємо repo та streamer (нова сесія на кожен запит)
_processor.repository = repo
_processor.streamer = sse
return _processor
+28 -48
View File
@@ -7,70 +7,50 @@ from fastapi import FastAPI
from gps_denied import __version__
from gps_denied.api.routers import flights
from gps_denied.config import get_settings
from gps_denied.config import RuntimeConfig
from gps_denied.pipeline import build_pipeline
logger = logging.getLogger(__name__)
@asynccontextmanager
async def lifespan(app: FastAPI):
"""Initialise core pipeline components on startup."""
from gps_denied.core.chunk_manager import RouteChunkManager
from gps_denied.core.coordinates import CoordinateTransformer
from gps_denied.core.gpr import GlobalPlaceRecognition
from gps_denied.core.graph import FactorGraphOptimizer
from gps_denied.core.mavlink import MAVLinkBridge
from gps_denied.core.metric import MetricRefinement
from gps_denied.core.models import ModelManager
from gps_denied.core.recovery import FailureRecoveryCoordinator
from gps_denied.core.rotation import ImageRotationManager
from gps_denied.core.satellite import SatelliteDataManager
from gps_denied.core.vo import create_vo_backend
from gps_denied.schemas.eskf import ESKFConfig
from gps_denied.schemas.graph import FactorGraphConfig
"""Initialise core pipeline components on startup via build_pipeline."""
cfg = RuntimeConfig()
processor = build_pipeline(env=cfg.env, config=cfg)
settings = get_settings()
# Retrieve MAVLink bridge from processor internals for lifecycle management
mavlink = processor._mavlink
mm = ModelManager(engine_dir=str(settings.models.weights_dir))
vo = create_vo_backend(model_manager=mm)
gpr = GlobalPlaceRecognition(mm)
metric = MetricRefinement(mm)
graph = FactorGraphOptimizer(FactorGraphConfig())
chunk_mgr = RouteChunkManager(graph)
recovery = FailureRecoveryCoordinator(chunk_mgr, gpr, metric)
rotation = ImageRotationManager(mm)
coord = CoordinateTransformer()
satellite = SatelliteDataManager(tile_dir=settings.satellite.tile_dir)
mavlink = MAVLinkBridge(
connection_string=settings.mavlink.connection,
output_hz=settings.mavlink.output_hz,
telemetry_hz=settings.mavlink.telemetry_hz,
)
# ESKF config from env vars (per-airframe tuning)
eskf_config = ESKFConfig(**settings.eskf.model_dump())
# Store on app.state so deps can access them
app.state.processor = processor
app.state.config = cfg
# Keep backwards-compat key so any code reading pipeline_components still works
app.state.pipeline_components = {
"vo": vo, "gpr": gpr, "metric": metric,
"graph": graph, "recovery": recovery,
"chunk_mgr": chunk_mgr, "rotation": rotation,
"coord": coord, "satellite": satellite,
"vo": processor._vo,
"gpr": processor._gpr,
"metric": processor._metric,
"graph": processor._graph,
"recovery": processor._recovery,
"chunk_mgr": processor._chunk_mgr,
"rotation": processor._rotation,
"coord": processor._coord,
"satellite": processor._satellite,
"mavlink": mavlink,
}
app.state.eskf_config = eskf_config
app.state.eskf_config = processor._eskf_config
logger.info(
"Pipeline ready — MAVLink: %s, tiles: %s",
settings.mavlink.connection, settings.satellite.tile_dir,
"Pipeline ready — env=%s, MAVLink: %s, tiles: %s",
cfg.env, cfg.mavlink.connection, cfg.satellite.tile_dir,
)
yield
# Cleanup
try:
await mavlink.stop()
except Exception:
pass
# Cleanup MAVLink on shutdown
if mavlink is not None:
try:
await mavlink.stop()
except Exception:
pass
app.state.pipeline_components = None