Skip to content

Commit eb7c26d

Browse files
authored
Merge pull request #33 from onyx-dot-app/dane/health-version
feat(health): include service version in /health response
2 parents 8d21be9 + 5a05056 commit eb7c26d

7 files changed

Lines changed: 52 additions & 7 deletions

File tree

code-interpreter/app/main.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import subprocess
66
from collections.abc import AsyncGenerator
77
from contextlib import asynccontextmanager, suppress
8+
from importlib.metadata import version as _package_version
89
from shutil import which
910
from typing import Final
1011

@@ -25,6 +26,8 @@
2526

2627
logger = logging.getLogger(__name__)
2728

29+
SERVICE_VERSION: Final[str] = _package_version("code-interpreter")
30+
2831

2932
def _ensure_docker_image_available() -> None:
3033
"""Ensure the Docker executor image is available locally.
@@ -123,7 +126,7 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
123126
def create_app() -> FastAPI:
124127
app = FastAPI(
125128
title="Code Interpreter API",
126-
version="0.1.0",
129+
version=SERVICE_VERSION,
127130
docs_url="/docs",
128131
redoc_url="/redoc",
129132
openapi_url="/openapi.json",
@@ -134,7 +137,11 @@ def create_app() -> FastAPI:
134137
def health() -> HealthResponse:
135138
"""Health check that verifies the executor backend is operational."""
136139
result = get_executor().check_health()
137-
return HealthResponse(status=result.status, message=result.message)
140+
return HealthResponse(
141+
status=result.status,
142+
message=result.message,
143+
version=SERVICE_VERSION,
144+
)
138145

139146
app.include_router(api_router, prefix="/v1")
140147
return app

code-interpreter/app/models/schemas.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,13 @@ class ListFilesResponse(BaseModel):
120120
class HealthResponse(BaseModel):
121121
status: Literal["ok", "error"]
122122
message: StrictStr | None = None
123+
version: StrictStr = Field(
124+
...,
125+
description=(
126+
"Semver of the running service. Clients can compare against a "
127+
"required minimum to detect whether new functionality is available."
128+
),
129+
)
123130

124131

125132
DEFAULT_SESSION_TTL_SEC = 15 * 60

code-interpreter/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "code-interpreter"
7-
version = "0.1.0"
7+
version = "0.3.3"
88
description = "FastAPI service to execute Python code (sync, typed)"
99
readme = "README.md"
1010
requires-python = ">=3.11,<3.12"

code-interpreter/tests/integration_tests/test_health.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
from __future__ import annotations
22

3+
import re
34
import subprocess
45
from collections.abc import Generator
6+
from pathlib import Path
57
from unittest.mock import patch
68

79
import pytest
810
from fastapi.testclient import TestClient
911

10-
from app.main import create_app
12+
from app.main import SERVICE_VERSION, create_app
1113
from app.services.executor_base import HealthCheck
1214
from app.services.executor_docker import DockerExecutor
1315
from app.services.executor_factory import get_executor
1416

17+
CHART_YAML = Path(__file__).resolve().parents[3] / "kubernetes" / "code-interpreter" / "Chart.yaml"
18+
1519

1620
@pytest.fixture(autouse=True)
1721
def _clear_executor_cache() -> Generator[None, None, None]:
@@ -29,6 +33,7 @@ def test_health_returns_ok_when_backend_healthy() -> None:
2933
body = response.json()
3034
assert body["status"] == "ok"
3135
assert body["message"] is None
36+
assert body["version"] == SERVICE_VERSION
3237

3338

3439
def test_health_returns_error_when_backend_unhealthy() -> None:
@@ -42,6 +47,32 @@ def test_health_returns_error_when_backend_unhealthy() -> None:
4247
body = response.json()
4348
assert body["status"] == "error"
4449
assert body["message"] == "daemon down"
50+
assert body["version"] == SERVICE_VERSION
51+
52+
53+
def test_health_version_matches_package_metadata() -> None:
54+
"""The version should come from the installed package, not be hardcoded."""
55+
from importlib.metadata import version as package_version
56+
57+
assert package_version("code-interpreter") == SERVICE_VERSION
58+
59+
60+
def test_service_version_matches_helm_chart_version() -> None:
61+
"""Guard against drift between the Python package and the Helm chart.
62+
63+
A version mismatch means clients calling /health to gate on capabilities
64+
would see one number while the deployment artifact reports another.
65+
"""
66+
assert CHART_YAML.is_file(), f"Chart.yaml not found at {CHART_YAML}"
67+
text = CHART_YAML.read_text(encoding="utf-8")
68+
match = re.search(r"^version:\s*(\S+)\s*$", text, re.MULTILINE)
69+
assert match is not None, f"could not find a top-level 'version:' line in {CHART_YAML}"
70+
chart_version = match.group(1).strip("\"'")
71+
assert chart_version == SERVICE_VERSION, (
72+
f"Helm chart version {chart_version!r} != Python package version "
73+
f"{SERVICE_VERSION!r}. Bump both together so /health and the deployed "
74+
"chart report the same number."
75+
)
4576

4677

4778
def _make_completed(returncode: int, stderr: bytes = b"") -> subprocess.CompletedProcess[bytes]:

code-interpreter/uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

executor/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "executor"
7-
version = "0.1.0"
7+
version = "0.3.3"
88
description = "Dependency bundle for the code interpreter executor image"
99
requires-python = ">=3.11,<3.12"
1010
license = { text = "MIT" }

executor/uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)