Full FastAPI backend for the AI music continuation editor:
Services
- chain.py: chain + node CRUD, commit/discard, recursive CTE spine query
- musicgen.py: MusicGenClient with cf-orch allocation + mock mode (CF_MUSICGEN_MOCK=1)
- stems.py: Demucs 4-stem separation subprocess wrapper + mock mode
- export.py: ffmpeg concat demuxer to stitch committed spine into WAV/MP3
API endpoints
- chains: CRUD, multipart audio upload (WAV/MP3/FLAC/OGG/M4A/AIFF)
- nodes: branch creation (202 + BackgroundTasks), commit, discard, audio stream
- gpu: cf-orch capacity status; session allocation stubbed pending cf-orch#43
- stems: Paid-tier stem separation (Demucs, gated via tiers.py)
- export: POST /{chain_id}/export → FileResponse download
- events: SSE stream (node-status events) per chain via asyncio Queue pub/sub
Infrastructure
- lifespan: reads SPARROW_DB_PATH/DATA_DIR at startup (not import time)
- events_store: subscribe/unsubscribe/broadcast pattern for SSE
- CORS: open in dev, SPARROW_CORS_ORIGINS in production
- Background generation opens its own DB connection (WAL-safe)
Tests: 30/30 passing across service units and API integration
66 lines
1.7 KiB
Python
66 lines
1.7 KiB
Python
# 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()
|