mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-04-23 01:56:37 +00:00
fix: P0+P1 audit — memory leak, hardcoded camera/GPS, lifespan init, background processing, batch validation, ABC interfaces
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
from collections.abc import AsyncGenerator
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import Depends
|
||||
from fastapi import Depends, Request
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from gps_denied.core.processor import FlightProcessor
|
||||
@@ -12,17 +12,32 @@ from gps_denied.db.repository import FlightRepository
|
||||
# Singleton instance of SSE Event Streamer
|
||||
_sse_streamer = SSEEventStreamer()
|
||||
|
||||
# Singleton FlightProcessor (one per process, reused across requests)
|
||||
_processor: FlightProcessor | None = None
|
||||
|
||||
|
||||
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:
|
||||
return FlightProcessor(repo, sse)
|
||||
global _processor
|
||||
if _processor is None:
|
||||
_processor = FlightProcessor(repo, sse)
|
||||
# Attach pipeline components from lifespan (P1#4)
|
||||
components = getattr(request.app.state, "pipeline_components", None)
|
||||
if components:
|
||||
_processor.attach_components(**components)
|
||||
# Always update repo (new session per request)
|
||||
_processor.repository = repo
|
||||
return _processor
|
||||
|
||||
|
||||
# Type aliases for cleaner router definitions
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
from typing import Annotated
|
||||
|
||||
@@ -115,11 +116,30 @@ async def upload_image_batch(
|
||||
if not f_info:
|
||||
raise HTTPException(status_code=404, detail="Flight not found")
|
||||
|
||||
if not (10 <= len(images) <= 50):
|
||||
# Allow fewer for small tests, but raise bad request based on spec typically
|
||||
pass
|
||||
# P1#6: Batch size validation (allow 1-50 for dev, spec says 10-50)
|
||||
if len(images) < 1 or len(images) > 50:
|
||||
raise HTTPException(
|
||||
status_code=422,
|
||||
detail=f"Batch must contain 1-50 images, got {len(images)}",
|
||||
)
|
||||
|
||||
res = await processor.queue_images(flight_id, meta_obj, len(images))
|
||||
|
||||
# P1#5: Spawn background task to process each frame
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
async def _process_batch():
|
||||
for idx, upload in enumerate(images):
|
||||
raw = await upload.read()
|
||||
arr = np.frombuffer(raw, dtype=np.uint8)
|
||||
img = cv2.imdecode(arr, cv2.IMREAD_COLOR)
|
||||
if img is not None:
|
||||
frame_id = meta_obj.start_frame_id + idx
|
||||
await processor.process_frame(flight_id, frame_id, img)
|
||||
|
||||
asyncio.create_task(_process_batch())
|
||||
|
||||
await session.commit()
|
||||
return res
|
||||
|
||||
|
||||
Reference in New Issue
Block a user