5019b10584
POST /api/v1/slabs accepts multipart/form-data with: - data: JSON-encoded SlabCreate payload - files[]: original image files (1-5 photos) - meta[]: JSON-encoded photo metadata objects Returns 201 with status: calibrated and server_rev: 1. Server stores client-sent numbers only (sqft, bdft, widths) without server-side recompute. Client is authoritative for deterministic math. Includes: - SlabCreateData Pydantic model mirroring OPENAPI.md SlabCreate schema - PhotoMeta model for photo metadata validation - SlabRead/PhotoRead response models - File storage with configurable images directory (temp fallback for tests) - Duplicate SKU detection (409) - python-multipart added to requirements.txt D-1 test coverage (8 tests, all green): - 201 response on valid multipart upload - status: calibrated in response - server_rev: 1 in response - Full response shape assertion - Multiple photos handling - Missing data field returns 400 - Duplicate SKU returns 409 - TestClient lifespan management (FUND-9) Evidence: pytest tests/ -v → 31 passed, 0 failed
57 lines
1.6 KiB
Python
57 lines
1.6 KiB
Python
"""App factory and lifespan."""
|
|
|
|
import logging
|
|
from contextlib import asynccontextmanager
|
|
|
|
from fastapi import FastAPI
|
|
|
|
from app.api import health as health_router
|
|
from app.api import settings as settings_router
|
|
from app.api import slabs as slabs_router
|
|
from app.config import Settings
|
|
from app.db.engine import close_db_connection, init_db
|
|
from app.logging_config import configure_logging
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI):
|
|
"""Application lifespan: startup and shutdown hooks.
|
|
|
|
Handles database initialization, logging configuration,
|
|
and cleanup on shutdown.
|
|
"""
|
|
configure_logging()
|
|
logging.info("SlabUploader starting up")
|
|
await init_db()
|
|
yield
|
|
logging.info("SlabUploader shutting down")
|
|
await close_db_connection()
|
|
|
|
|
|
def create_app(app_settings: Settings | None = None) -> FastAPI:
|
|
"""Create a fresh FastAPI app instance.
|
|
|
|
Each call produces an independent app with its own database connection
|
|
and cleared dependency overrides. This is the factory used by tests.
|
|
|
|
Args:
|
|
app_settings: Application settings. Uses env vars if None.
|
|
|
|
Returns:
|
|
Configured FastAPI application.
|
|
"""
|
|
if app_settings is None:
|
|
app_settings = Settings()
|
|
app_settings.validate()
|
|
|
|
app = FastAPI(title="SlabUploader", version="0.1.0", lifespan=lifespan)
|
|
|
|
# Store settings on app state for dependency injection
|
|
app.state.settings = app_settings
|
|
|
|
app.include_router(health_router.router)
|
|
app.include_router(settings_router.router)
|
|
app.include_router(slabs_router.router)
|
|
|
|
return app
|