# `api/app.py` — FastAPI service _(no submodules)_ ## Purpose `api/app.py` wraps the `AudioFingerprinter` core into a FastAPI HTTP service. It is the network surface the TypeScript `PythonAudioService` client talks to from browsers (via the React-tier sibling) and from server-side JavaScript runtimes. The application exposes a health-check endpoint and two operational endpoints (`POST /fingerprint`, `POST /match`) that accept multipart file uploads and return JSON. It is intentionally a thin REST shell: every business decision (chromagram parameters, peak threshold, fan value, min matches) is driven by client-supplied form fields. The service is deployment-tier code — it runs under `uvicorn` and is intended to live behind a reverse proxy in production. CORS is pre-configured for the two local development origins (`http://localhost:3000` and `http://localhost:5173`); add production origins via your deployment-time `CORSMiddleware` override. ## Features - `GET /` health-check returning `{"status": "ok", "service": "audio-fingerprinting"}`. - `POST /fingerprint` accepting an audio file, optional `peak_threshold` and `fan_value`, returning fingerprint hashes and statistics. - `POST /match` accepting two audio files, optional `peak_threshold`, `fan_value`, `min_matches`, returning the match decision, count, confidence, and time offset. - Temporary-file lifecycle: each upload is persisted to `NamedTemporaryFile`, processed, and unlinked in a `finally` block — including on the failure path. - Uniform HTTP-500 error envelope: pipeline exceptions are caught and re-raised as `HTTPException(status_code=500, detail="Failed to ... : ")`. - CORS pre-configured for `localhost:3000` and `localhost:5173`. ## Usage: Basic ```bash # Start the service micromamba run -n suaveplan-audio-fingerprint \ uvicorn src.api.app:app --host 0.0.0.0 --port 8000 # Health check curl http://localhost:8000/ # Fingerprint a single file curl -X POST http://localhost:8000/fingerprint -F "file=@track.mp3" ``` ## Usage: Advanced ```bash # Tune the algorithm parameters at request time curl -X POST http://localhost:8000/fingerprint \ -F "file=@track.flac" \ -F "peak_threshold=0.6" \ -F "fan_value=20" # Match two files with a stricter min_matches threshold curl -X POST http://localhost:8000/match \ -F "query_file=@query.wav" \ -F "reference_file=@reference.wav" \ -F "peak_threshold=0.5" \ -F "fan_value=15" \ -F "min_matches=25" ``` ```python # Programmatic in-process testing via httpx ASGITransport from httpx import ASGITransport, AsyncClient from src.api.app import app async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac: response = await ac.get("/") assert response.json() == {"status": "ok", "service": "audio-fingerprinting"} ``` ## API Reference | Method | Path | Form fields | Response | |---|---|---|---| | `GET` | `/` | — | `{"status": "ok", "service": "audio-fingerprinting"}` | | `POST` | `/fingerprint` | `file` (UploadFile, required), `peak_threshold` (float, default 0.5), `fan_value` (int, default 15) | `{filename, duration, peaks_count, fingerprints_count, fingerprints[{hash,time,delta}]}` | | `POST` | `/match` | `query_file` (UploadFile, required), `reference_file` (UploadFile, required), `peak_threshold` (float, default 0.5), `fan_value` (int, default 15), `min_matches` (int, default 10) | `{query_file, reference_file, query_duration, reference_duration, matched, match_count, confidence, time_offset}` | ### Failure paths - Missing required upload(s) -> HTTP 422 (FastAPI validation error envelope). - Pipeline failure (unreadable audio, decode error) -> HTTP 500 with `detail` starting with `Failed to process audio file:` (for `/fingerprint`) or `Failed to match audio files:` (for `/match`). ## Implementation Notes - A single module-level `AudioFingerprinter()` instance is reused across requests. It has no mutable per-request state; only its analysis parameters are shared, and those are configuration-stable. - `tempfile.NamedTemporaryFile(delete=False)` is used so the descriptor can be closed while the file is still on disk for librosa to open by path. The unlink lives in a `finally` clause and uses `missing_ok=True` so the cleanup is idempotent even if librosa already moved or removed the file. - The `match` endpoint persists both uploads inside a single nested `with` block; this guarantees both temp paths are bound before either pipeline call runs, simplifying the cleanup in the `finally` clause. - `CORSMiddleware` is wired with `allow_credentials=True`, `allow_methods=["*"]`, `allow_headers=["*"]`. The TestClient suite exercises the preflight (`OPTIONS`) path for `http://localhost:3000` to ensure the configuration is live. - The `version="0.1.0"` field on the `FastAPI(...)` constructor matches the `pyproject.toml` and `package.json` versions; bump all three in lockstep at graduation to `1.0.0`.