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:
8
app/routes/__init__.py
Normal file
8
app/routes/__init__.py
Normal file
@@ -0,0 +1,8 @@
|
||||
"""HTTP route packages.
|
||||
|
||||
Routers live as sibling modules and are wired into the app in
|
||||
:mod:`app.main`. Phase 0 only exposes ``health``; public, admin, and auth
|
||||
routers arrive in later phases.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
69
app/routes/health.py
Normal file
69
app/routes/health.py
Normal file
@@ -0,0 +1,69 @@
|
||||
"""Liveness / version endpoint.
|
||||
|
||||
The ``/healthz`` endpoint is intentionally minimal: it must be safe to
|
||||
expose unauthenticated (Caddy + uptime checks will hit it), so it leaks
|
||||
only the app version and the build's git commit SHA — no hostnames,
|
||||
paths, config values, or environment details.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Literal
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from app import __version__
|
||||
from app.config import Settings, get_settings
|
||||
|
||||
|
||||
class HealthResponse(BaseModel):
|
||||
"""Response schema for ``GET /healthz``.
|
||||
|
||||
Kept as an explicit :class:`pydantic.BaseModel` so FastAPI publishes
|
||||
the shape in the OpenAPI schema and so any drift is caught at typing
|
||||
time rather than via a loose ``dict``.
|
||||
"""
|
||||
|
||||
status: Literal["ok"] = Field(
|
||||
default="ok",
|
||||
description="Liveness indicator; this endpoint only returns 'ok'.",
|
||||
)
|
||||
version: str = Field(description="Application semantic version.")
|
||||
commit_sha: str = Field(
|
||||
description="Git commit SHA of the running build ('unknown' in dev).",
|
||||
)
|
||||
|
||||
|
||||
# Module-level router; mounted by `app.main.create_app`. No prefix and no
|
||||
# auth dependencies — /healthz is intentionally public.
|
||||
router: APIRouter = APIRouter(tags=["health"])
|
||||
|
||||
|
||||
@router.get(
|
||||
"/healthz",
|
||||
response_model=HealthResponse,
|
||||
summary="Liveness + version probe",
|
||||
)
|
||||
def healthz(settings: Settings = Depends(get_settings)) -> HealthResponse:
|
||||
"""Return a minimal liveness envelope.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
settings:
|
||||
Injected via FastAPI's dependency system so tests can override
|
||||
configuration cleanly.
|
||||
|
||||
Returns
|
||||
-------
|
||||
HealthResponse
|
||||
``status`` is always ``"ok"``; ``version`` and ``commit_sha``
|
||||
identify the running build.
|
||||
"""
|
||||
# Intentionally does not touch the DB, filesystem, or any external
|
||||
# service. This is liveness, not readiness — a readiness probe with
|
||||
# deeper checks can be added in a later phase behind a different path.
|
||||
return HealthResponse(
|
||||
version=__version__,
|
||||
commit_sha=settings.git_commit_sha,
|
||||
)
|
||||
Reference in New Issue
Block a user