Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a0230c-b06c-7641-808a-e191b6d1da49
107 lines
3.9 KiB
Python
107 lines
3.9 KiB
Python
from __future__ import annotations
|
|
|
|
from contextlib import asynccontextmanager
|
|
|
|
from fastapi import FastAPI, Response, status
|
|
|
|
from hub_core import __version__
|
|
from hub_core.runtime.config import RuntimeSettings
|
|
from hub_core.runtime.compat import SQLCompatibilityStore, create_compatibility_router
|
|
from hub_core.runtime.models import HealthResponse, ReadinessResponse
|
|
from hub_core.runtime.ports import create_ports_router
|
|
from hub_core.runtime.repository_navigation import (
|
|
ProjectionRejected,
|
|
RepoProjectionClient,
|
|
RepositoryNavigationService,
|
|
)
|
|
from hub_core.runtime.store import InMemoryPortStore, PortStore
|
|
from hub_core.runtime.validation import ContractValidator
|
|
|
|
|
|
def create_app(
|
|
*,
|
|
settings: RuntimeSettings | None = None,
|
|
port_store: PortStore | None = None,
|
|
repo_projection_client: RepoProjectionClient | None = None,
|
|
) -> FastAPI:
|
|
resolved_settings = settings or RuntimeSettings.from_env()
|
|
resolved_store = port_store or _create_store(resolved_settings)
|
|
owns_store = port_store is None
|
|
repository_navigation = RepositoryNavigationService(
|
|
client=repo_projection_client,
|
|
store=resolved_store,
|
|
)
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(_: FastAPI):
|
|
if repo_projection_client is not None:
|
|
try:
|
|
await repository_navigation.refresh()
|
|
except ProjectionRejected:
|
|
# The readiness dependency reports the rejected or absent
|
|
# projection while the API remains available for diagnosis.
|
|
pass
|
|
yield
|
|
if owns_store and (closer := getattr(resolved_store, "aclose", None)):
|
|
await closer()
|
|
|
|
app = FastAPI(
|
|
title="Hub Core Runtime",
|
|
version=__version__,
|
|
description="HelixForge hub framework and named-port runtime.",
|
|
lifespan=lifespan,
|
|
)
|
|
app.state.settings = resolved_settings
|
|
app.state.port_store = resolved_store
|
|
app.state.compat_store = (
|
|
SQLCompatibilityStore(resolved_store)
|
|
if resolved_store.backend_name == "postgresql"
|
|
else None
|
|
)
|
|
app.state.contract_validator = ContractValidator()
|
|
app.state.repository_navigation = repository_navigation
|
|
|
|
@app.get("/healthz", response_model=HealthResponse, tags=["system"])
|
|
async def healthz() -> HealthResponse:
|
|
return HealthResponse(
|
|
service="core-hub" if resolved_settings.legacy_health else "hub-core",
|
|
version=__version__,
|
|
)
|
|
|
|
@app.get("/readyz", response_model=ReadinessResponse, tags=["system"])
|
|
async def readyz(response: Response) -> ReadinessResponse:
|
|
dependency_checks = {
|
|
**await resolved_store.readiness_checks(),
|
|
**await repository_navigation.readiness_checks(),
|
|
}
|
|
ready = resolved_settings.is_ready(resolved_store.backend_name) and all(
|
|
value in {"ok", "not_applicable"} for value in dependency_checks.values()
|
|
)
|
|
if not ready:
|
|
response.status_code = status.HTTP_503_SERVICE_UNAVAILABLE
|
|
return ReadinessResponse(
|
|
status="ok" if ready else "degraded",
|
|
checks={
|
|
**resolved_settings.readiness_checks(resolved_store.backend_name),
|
|
**dependency_checks,
|
|
},
|
|
)
|
|
|
|
app.include_router(create_ports_router())
|
|
app.include_router(create_compatibility_router())
|
|
return app
|
|
|
|
|
|
def _create_store(settings: RuntimeSettings) -> PortStore:
|
|
if settings.backend == "memory":
|
|
return InMemoryPortStore()
|
|
if settings.backend == "postgresql":
|
|
if not settings.database_url:
|
|
raise RuntimeError("HUB_CORE_DATABASE_URL is required for PostgreSQL backend")
|
|
from hub_core.runtime.postgres_store import PostgresPortStore
|
|
|
|
return PostgresPortStore.from_url(settings.database_url)
|
|
raise RuntimeError(f"Unsupported HUB_CORE_BACKEND '{settings.backend}'")
|
|
|
|
|
|
app = create_app()
|