From e48b412c0eac631ac64c7b8da963e1e8b9f9585e Mon Sep 17 00:00:00 2001 From: Dane Urban Date: Wed, 6 May 2026 09:57:00 -0700 Subject: [PATCH 1/4] feat(health): include service version in /health response Clients can now read ``version`` from the ``/health`` payload to gate calls to functionality that's only available in newer releases. The value is sourced from the installed package metadata rather than a hardcoded string, so the FastAPI app and the health response stay in sync with the version declared in pyproject.toml. Co-Authored-By: Claude Opus 4.7 (1M context) --- code-interpreter/app/main.py | 14 +++++++++++--- code-interpreter/app/models/schemas.py | 7 +++++++ .../tests/integration_tests/test_health.py | 11 ++++++++++- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/code-interpreter/app/main.py b/code-interpreter/app/main.py index 0ac7e1c..ef3c6ac 100644 --- a/code-interpreter/app/main.py +++ b/code-interpreter/app/main.py @@ -4,7 +4,9 @@ import logging import subprocess from collections.abc import AsyncGenerator -from contextlib import asynccontextmanager, suppress +from contextlib import asynccontextmanager +from contextlib import suppress +from importlib.metadata import version as _package_version from shutil import which from typing import Final @@ -25,6 +27,8 @@ logger = logging.getLogger(__name__) +SERVICE_VERSION: Final[str] = _package_version("code-interpreter") + def _ensure_docker_image_available() -> None: """Ensure the Docker executor image is available locally. @@ -123,7 +127,7 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]: def create_app() -> FastAPI: app = FastAPI( title="Code Interpreter API", - version="0.1.0", + version=SERVICE_VERSION, docs_url="/docs", redoc_url="/redoc", openapi_url="/openapi.json", @@ -134,7 +138,11 @@ def create_app() -> FastAPI: def health() -> HealthResponse: """Health check that verifies the executor backend is operational.""" result = get_executor().check_health() - return HealthResponse(status=result.status, message=result.message) + return HealthResponse( + status=result.status, + message=result.message, + version=SERVICE_VERSION, + ) app.include_router(api_router, prefix="/v1") return app diff --git a/code-interpreter/app/models/schemas.py b/code-interpreter/app/models/schemas.py index 91ca319..85d360f 100644 --- a/code-interpreter/app/models/schemas.py +++ b/code-interpreter/app/models/schemas.py @@ -120,6 +120,13 @@ class ListFilesResponse(BaseModel): class HealthResponse(BaseModel): status: Literal["ok", "error"] message: StrictStr | None = None + version: StrictStr = Field( + ..., + description=( + "Semver of the running service. Clients can compare against a " + "required minimum to detect whether new functionality is available." + ), + ) DEFAULT_SESSION_TTL_SEC = 15 * 60 diff --git a/code-interpreter/tests/integration_tests/test_health.py b/code-interpreter/tests/integration_tests/test_health.py index baa00b6..4672305 100644 --- a/code-interpreter/tests/integration_tests/test_health.py +++ b/code-interpreter/tests/integration_tests/test_health.py @@ -7,7 +7,7 @@ import pytest from fastapi.testclient import TestClient -from app.main import create_app +from app.main import SERVICE_VERSION, create_app from app.services.executor_base import HealthCheck from app.services.executor_docker import DockerExecutor from app.services.executor_factory import get_executor @@ -29,6 +29,7 @@ def test_health_returns_ok_when_backend_healthy() -> None: body = response.json() assert body["status"] == "ok" assert body["message"] is None + assert body["version"] == SERVICE_VERSION def test_health_returns_error_when_backend_unhealthy() -> None: @@ -42,6 +43,14 @@ def test_health_returns_error_when_backend_unhealthy() -> None: body = response.json() assert body["status"] == "error" assert body["message"] == "daemon down" + assert body["version"] == SERVICE_VERSION + + +def test_health_version_matches_package_metadata() -> None: + """The version should come from the installed package, not be hardcoded.""" + from importlib.metadata import version as package_version + + assert package_version("code-interpreter") == SERVICE_VERSION def _make_completed(returncode: int, stderr: bytes = b"") -> subprocess.CompletedProcess[bytes]: From 44bbde4983522de51e7087e3db07426df84d2417 Mon Sep 17 00:00:00 2001 From: Dane Urban Date: Wed, 6 May 2026 11:33:46 -0700 Subject: [PATCH 2/4] chore: bump python packages to 0.3.3 to match helm chart The helm chart at kubernetes/code-interpreter/Chart.yaml has been on 0.3.3 since #27, but both pyproject.toml files were still reporting 0.1.0. Now that /health surfaces the package version to clients, line the pyproject versions up with the chart so a single number tracks across artifacts. Co-Authored-By: Claude Opus 4.7 (1M context) --- code-interpreter/pyproject.toml | 2 +- code-interpreter/uv.lock | 2 +- executor/pyproject.toml | 2 +- executor/uv.lock | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/code-interpreter/pyproject.toml b/code-interpreter/pyproject.toml index 3da87c9..7f45ed6 100644 --- a/code-interpreter/pyproject.toml +++ b/code-interpreter/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "code-interpreter" -version = "0.1.0" +version = "0.3.3" description = "FastAPI service to execute Python code (sync, typed)" readme = "README.md" requires-python = ">=3.11,<3.12" diff --git a/code-interpreter/uv.lock b/code-interpreter/uv.lock index dbb947e..93cd6c0 100644 --- a/code-interpreter/uv.lock +++ b/code-interpreter/uv.lock @@ -98,7 +98,7 @@ wheels = [ [[package]] name = "code-interpreter" -version = "0.1.0" +version = "0.3.3" source = { editable = "." } dependencies = [ { name = "fastapi" }, diff --git a/executor/pyproject.toml b/executor/pyproject.toml index 4ee9fd3..e0c61a9 100644 --- a/executor/pyproject.toml +++ b/executor/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "executor" -version = "0.1.0" +version = "0.3.3" description = "Dependency bundle for the code interpreter executor image" requires-python = ">=3.11,<3.12" license = { text = "MIT" } diff --git a/executor/uv.lock b/executor/uv.lock index e25bc6a..ffca250 100644 --- a/executor/uv.lock +++ b/executor/uv.lock @@ -154,7 +154,7 @@ wheels = [ [[package]] name = "executor" -version = "0.1.0" +version = "0.3.3" source = { editable = "." } dependencies = [ { name = "matplotlib" }, From 60a57c900be6235868283d7f0467a1c9ef38b3cd Mon Sep 17 00:00:00 2001 From: Dane Urban Date: Wed, 6 May 2026 11:36:34 -0700 Subject: [PATCH 3/4] test(health): assert SERVICE_VERSION matches the Helm chart version Catches drift between the Python package version (what /health reports to clients) and the chart version (what's actually deployed). If they get out of sync, a client gating on /health's version may try to use features that aren't actually shipped. Reads kubernetes/code-interpreter/Chart.yaml directly via a small regex so the test has no extra dependencies. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../tests/integration_tests/test_health.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/code-interpreter/tests/integration_tests/test_health.py b/code-interpreter/tests/integration_tests/test_health.py index 4672305..9b37243 100644 --- a/code-interpreter/tests/integration_tests/test_health.py +++ b/code-interpreter/tests/integration_tests/test_health.py @@ -1,7 +1,9 @@ from __future__ import annotations +import re import subprocess from collections.abc import Generator +from pathlib import Path from unittest.mock import patch import pytest @@ -12,6 +14,8 @@ from app.services.executor_docker import DockerExecutor from app.services.executor_factory import get_executor +CHART_YAML = Path(__file__).resolve().parents[3] / "kubernetes" / "code-interpreter" / "Chart.yaml" + @pytest.fixture(autouse=True) def _clear_executor_cache() -> Generator[None, None, None]: @@ -53,6 +57,24 @@ def test_health_version_matches_package_metadata() -> None: assert package_version("code-interpreter") == SERVICE_VERSION +def test_service_version_matches_helm_chart_version() -> None: + """Guard against drift between the Python package and the Helm chart. + + A version mismatch means clients calling /health to gate on capabilities + would see one number while the deployment artifact reports another. + """ + assert CHART_YAML.is_file(), f"Chart.yaml not found at {CHART_YAML}" + text = CHART_YAML.read_text(encoding="utf-8") + match = re.search(r"^version:\s*(\S+)\s*$", text, re.MULTILINE) + assert match is not None, f"could not find a top-level 'version:' line in {CHART_YAML}" + chart_version = match.group(1).strip("\"'") + assert chart_version == SERVICE_VERSION, ( + f"Helm chart version {chart_version!r} != Python package version " + f"{SERVICE_VERSION!r}. Bump both together so /health and the deployed " + "chart report the same number." + ) + + def _make_completed(returncode: int, stderr: bytes = b"") -> subprocess.CompletedProcess[bytes]: return subprocess.CompletedProcess(args=[], returncode=returncode, stdout=b"", stderr=stderr) From 5a050560875b61c347995ad394230b02a27e4082 Mon Sep 17 00:00:00 2001 From: Dane Urban Date: Wed, 6 May 2026 14:26:51 -0700 Subject: [PATCH 4/4] . --- code-interpreter/app/main.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/code-interpreter/app/main.py b/code-interpreter/app/main.py index ef3c6ac..e9f4c32 100644 --- a/code-interpreter/app/main.py +++ b/code-interpreter/app/main.py @@ -4,8 +4,7 @@ import logging import subprocess from collections.abc import AsyncGenerator -from contextlib import asynccontextmanager -from contextlib import suppress +from contextlib import asynccontextmanager, suppress from importlib.metadata import version as _package_version from shutil import which from typing import Final