Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions code-interpreter/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import subprocess
from collections.abc import AsyncGenerator
from contextlib import asynccontextmanager, suppress
from importlib.metadata import version as _package_version
from shutil import which
from typing import Final

Expand All @@ -25,6 +26,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.
Expand Down Expand Up @@ -123,7 +126,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",
Expand All @@ -134,7 +137,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
Expand Down
7 changes: 7 additions & 0 deletions code-interpreter/app/models/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion code-interpreter/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
33 changes: 32 additions & 1 deletion code-interpreter/tests/integration_tests/test_health.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
from __future__ import annotations

import re
import subprocess
from collections.abc import Generator
from pathlib import Path
from unittest.mock import patch

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

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


@pytest.fixture(autouse=True)
def _clear_executor_cache() -> Generator[None, None, None]:
Expand All @@ -29,6 +33,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:
Expand All @@ -42,6 +47,32 @@ 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 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]:
Expand Down
2 changes: 1 addition & 1 deletion code-interpreter/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion executor/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand Down
2 changes: 1 addition & 1 deletion executor/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading