Files
gps-denied-onboard/src/gps_denied/api/deps.py
T
Yuzviak 0bb94da3c4 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
2026-05-11 09:04:56 +03:00

93 lines
3.2 KiB
Python

import logging
from typing import Annotated
from fastapi import Depends, HTTPException, Request
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from sqlalchemy.ext.asyncio import AsyncSession
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
from gps_denied.db.repository import FlightRepository
logger = logging.getLogger(__name__)
# Singleton instance of SSE Event Streamer
_sse_streamer = SSEEventStreamer()
# Singleton FlightProcessor (one per process, reused across requests)
_processor: FlightProcessor | None = None
# JWT Bearer scheme (auto_error=False — ми самі обробляємо помилки)
_bearer = HTTPBearer(auto_error=False)
async def verify_token(
credentials: HTTPAuthorizationCredentials | None = Depends(_bearer),
) -> None:
"""JWT перевірка. При AUTH_ENABLED=false — пропускає все."""
settings = get_settings()
if not settings.auth.enabled:
return # dev/SITL: автентифікація вимкнена
if credentials is None:
raise HTTPException(status_code=401, detail="Authorization header required")
try:
import jwt
jwt.decode(
credentials.credentials,
settings.auth.secret_key,
algorithms=[settings.auth.algorithm],
)
except ImportError:
logger.warning("PyJWT not installed — JWT validation skipped")
except Exception as exc:
raise HTTPException(status_code=401, detail=f"Invalid token: {exc}") from exc
def get_sse_streamer() -> SSEEventStreamer:
return _sse_streamer
async def get_repository(
session: AsyncSession = Depends(get_session),
) -> FlightRepository:
return FlightRepository(session)
async def get_flight_processor(
request: Request,
repo: FlightRepository = Depends(get_repository),
sse: SSEEventStreamer = Depends(get_sse_streamer),
) -> FlightProcessor:
global _processor
if _processor is None:
# 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
# Аліаси для зручності в роутерах
SessionDep = Annotated[AsyncSession, Depends(get_session)]
RepoDep = Annotated[FlightRepository, Depends(get_repository)]
ProcessorDep = Annotated[FlightProcessor, Depends(get_flight_processor)]
AuthDep = Annotated[None, Depends(verify_token)]