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:
66
app/main.py
Normal file
66
app/main.py
Normal 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()
|
||||
Reference in New Issue
Block a user