feat(api): /VERSION single source read at startup + GET /version
This commit is contained in:
@@ -17,8 +17,9 @@ from .canon_log import validate_canon_log
|
||||
from .narrate import run as narrate_run
|
||||
from .npc import UnknownNpc, run as npc_run
|
||||
from .ollama_client import ModelError
|
||||
from .version import VERSION
|
||||
|
||||
app = FastAPI(title="coc-rpg proxy", version="0.0.1")
|
||||
app = FastAPI(title="coc-rpg proxy", version=VERSION)
|
||||
|
||||
|
||||
@app.exception_handler(RequestValidationError)
|
||||
@@ -65,6 +66,11 @@ def health() -> dict:
|
||||
return {"status": "ok"}
|
||||
|
||||
|
||||
@app.get("/version")
|
||||
def version() -> dict:
|
||||
return {"version": VERSION}
|
||||
|
||||
|
||||
# ── Role endpoints (charter §4) ──────────────────────────────────────────────
|
||||
# The client knows these paths and nothing about which model or prompt serves
|
||||
# them. Bodies are validated against the canon log contract. /dm/narrate is
|
||||
|
||||
33
api/app/version.py
Normal file
33
api/app/version.py
Normal file
@@ -0,0 +1,33 @@
|
||||
"""Single source of truth for the app version (charter §4: client and API share
|
||||
one version). Reads the repo-root /VERSION file — the ONLY place a human edits the
|
||||
version. Mirrors canon_log._schema_dir's env-override + walk-up so it resolves in
|
||||
a local checkout and (once /VERSION is bundled) an image alike.
|
||||
"""
|
||||
import os
|
||||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
|
||||
LAST_RESORT = "0.0.0-dev"
|
||||
|
||||
|
||||
def _version_file() -> Path | None:
|
||||
env = os.environ.get("COC_VERSION_FILE")
|
||||
if env:
|
||||
return Path(env)
|
||||
here = Path(__file__).resolve()
|
||||
for parent in here.parents:
|
||||
candidate = parent / "VERSION"
|
||||
if candidate.is_file():
|
||||
return candidate
|
||||
return None
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def get_version() -> str:
|
||||
path = _version_file()
|
||||
if path is None or not path.is_file():
|
||||
return LAST_RESORT
|
||||
return path.read_text(encoding="utf-8").strip() or LAST_RESORT
|
||||
|
||||
|
||||
VERSION = get_version()
|
||||
22
api/tests/test_version.py
Normal file
22
api/tests/test_version.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from app.main import app
|
||||
from app.version import VERSION, get_version
|
||||
|
||||
client = TestClient(app)
|
||||
ROOT_VERSION = Path(__file__).resolve().parents[2] / "VERSION"
|
||||
|
||||
|
||||
def test_get_version_matches_file():
|
||||
assert ROOT_VERSION.is_file()
|
||||
assert get_version() == ROOT_VERSION.read_text(encoding="utf-8").strip()
|
||||
|
||||
|
||||
def test_app_version_matches():
|
||||
assert app.version == VERSION == get_version()
|
||||
|
||||
|
||||
def test_version_endpoint():
|
||||
assert client.get("/version").json() == {"version": VERSION}
|
||||
Reference in New Issue
Block a user