# app/main.py — Sparrow FastAPI application factory from __future__ import annotations import logging import os from contextlib import asynccontextmanager from pathlib import Path from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from app.api.routes import router from app.db.store import get_connection, run_migrations logger = logging.getLogger(__name__) @asynccontextmanager async def lifespan(app: FastAPI): # Read env at startup — not at import time — so tests can inject values db_path = os.environ.get("SPARROW_DB_PATH", "data/sparrow.db") data_dir = os.environ.get("SPARROW_DATA_DIR", "data") env = os.environ.get("SPARROW_ENV", "development") Path(db_path).parent.mkdir(parents=True, exist_ok=True) Path(data_dir).mkdir(parents=True, exist_ok=True) conn = get_connection(db_path) run_migrations(conn) app.state.conn = conn app.state.data_dir = data_dir logger.info("Sparrow started (env=%s, db=%s, data=%s)", env, db_path, data_dir) yield conn.close() logger.info("Sparrow shut down.") def create_app() -> FastAPI: env = os.environ.get("SPARROW_ENV", "development") app = FastAPI( title="Sparrow", description="AI music continuation — branching chain editor", version="0.1.0", lifespan=lifespan, ) # CORS: open in dev, tighten in production via env origins = ( ["*"] if env == "development" else os.environ.get("SPARROW_CORS_ORIGINS", "").split(",") ) app.add_middleware( CORSMiddleware, allow_origins=origins, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) app.include_router(router) return app app = create_app()