feat: phase 0 foundation — FastAPI scaffold + /healthz

Scaffold app/ package, pinned requirements.txt, multi-stage Dockerfile,
docker-compose.yml, and .env.example. Add typed pydantic-settings loader
with full env contract and a production validator that refuses the
dev-sentinel SECRET_KEY. Wire structlog with an APP_ENV-driven renderer
(console in dev, JSON in prod). Ship a minimal unauthenticated /healthz
returning {status, version, commit_sha} with commit SHA fed through a
GIT_COMMIT_SHA build arg.

Also mark Phase 0 complete in docs/ROADMAP.md.
This commit is contained in:
2026-04-21 14:44:41 -05:00
parent 22f357f3e8
commit 78dd1ac243
19 changed files with 653 additions and 9 deletions

66
app/main.py Normal file
View File

@@ -0,0 +1,66 @@
"""FastAPI application factory and module-level app instance.
The factory pattern (``create_app``) keeps test setup straightforward and
lets us swap in alternate configurations without module-level side
effects. ``app = create_app()`` at import time is what Uvicorn references
via ``app.main:app``.
"""
from __future__ import annotations
import structlog
from fastapi import FastAPI
from app import __version__
from app.config import get_settings
from app.logging_config import configure_logging
from app.routes.health import router as health_router
def create_app() -> FastAPI:
"""Build and return the FastAPI application.
Responsibilities:
- Load validated configuration via :func:`get_settings`.
- Initialize structured logging *before* any logger is used.
- Instantiate FastAPI with canonical title + version.
- Register routers (Phase 0: only ``/healthz``).
- Emit a single ``app_started`` structured log event.
"""
# Parse + validate configuration first so a bad environment fails fast
# with a clear pydantic error before we touch logging / FastAPI.
settings = get_settings()
# Configure structlog *before* acquiring any logger in the app so the
# very first log line already flows through our processor chain.
configure_logging(settings.app_env)
application = FastAPI(
title="Chicken Babies R Us",
version=__version__,
# Docs stay on for Phase 0; a later phase can gate these behind
# admin auth or disable them entirely in production.
docs_url="/docs",
redoc_url="/redoc",
)
# Register routers. Kept explicit (no dynamic discovery) so the set of
# mounted endpoints is trivially auditable.
application.include_router(health_router)
# Single structured startup event. Do NOT include secret material.
logger = structlog.get_logger(__name__)
logger.info(
"app_started",
app_env=settings.app_env,
version=__version__,
commit_sha=settings.git_commit_sha,
)
return application
# Module-level ASGI handle. Uvicorn / gunicorn import this as
# ``app.main:app``. Building it at import time is intentional: it fails
# loudly at container start if configuration is invalid.
app: FastAPI = create_app()