"""Versioned FastAPI service skeleton. The service layer is intentionally thin: route handlers translate HTTP requests into service/runtime contracts and must not own domain behavior. """ from __future__ import annotations from dataclasses import dataclass, field from importlib import metadata from typing import Any from kontextual_engine.adapters.memory import InMemoryAssetRegistryRepository from kontextual_engine.core import utc_now from kontextual_engine.ports import AssetRegistryRepository API_VERSION = "v1" OPENAPI_VERSION = "1.0.0" @dataclass class ServiceRuntime: repository: AssetRegistryRepository = field(default_factory=InMemoryAssetRegistryRepository) api_version: str = API_VERSION service_name: str = "kontextual-engine" started_at: str = field(default_factory=lambda: utc_now().isoformat()) @property def package_version(self) -> str: try: return metadata.version("kontextual-engine") except metadata.PackageNotFoundError: return "0.1.0" def health(self) -> dict[str, Any]: return { "status": "ok", "service": self.service_name, "api_version": self.api_version, "package_version": self.package_version, "started_at": self.started_at, } def readiness(self) -> dict[str, Any]: checks: dict[str, dict[str, Any]] = {} try: asset_count = len(self.repository.list_assets()) checks["asset_registry"] = { "status": "ok", "repository": type(self.repository).__name__, "asset_count": asset_count, } except Exception as exc: checks["asset_registry"] = { "status": "error", "repository": type(self.repository).__name__, "error_type": type(exc).__name__, "message": str(exc), } ready = all(item["status"] == "ok" for item in checks.values()) return { "status": "ready" if ready else "not_ready", "ready": ready, "service": self.service_name, "api_version": self.api_version, "checks": checks, } def version(self) -> dict[str, Any]: return { "service": self.service_name, "api_version": self.api_version, "package_version": self.package_version, "openapi_version": OPENAPI_VERSION, } def create_app(runtime: ServiceRuntime | None = None): try: from fastapi import FastAPI except ImportError as exc: # pragma: no cover - exercised when optional extra is absent raise RuntimeError( "FastAPI service dependencies are not installed. Install kontextual-engine[service]." ) from exc runtime = runtime or ServiceRuntime() app = FastAPI( title="Kontextual Engine Service API", version=OPENAPI_VERSION, openapi_url="/openapi.json", docs_url="/docs", redoc_url="/redoc", ) app.state.kontextual_runtime = runtime @app.get("/health", tags=["system"]) def health() -> dict[str, Any]: return runtime.health() @app.get("/ready", tags=["system"]) def ready() -> dict[str, Any]: return runtime.readiness() @app.get("/version", tags=["system"]) def version() -> dict[str, Any]: return runtime.version() prefix = f"/api/{runtime.api_version}" @app.get(f"{prefix}/health", tags=["system"]) def versioned_health() -> dict[str, Any]: return runtime.health() @app.get(f"{prefix}/ready", tags=["system"]) def versioned_ready() -> dict[str, Any]: return runtime.readiness() @app.get(f"{prefix}/version", tags=["system"]) def versioned_version() -> dict[str, Any]: return runtime.version() return app