124 lines
3.8 KiB
Python
124 lines
3.8 KiB
Python
|
|
"""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
|