ktx/python/ktx-daemon/src/ktx_daemon/app.py

234 lines
7.8 KiB
Python
Raw Normal View History

2026-05-10 23:51:24 +02:00
"""FastAPI app factory for the KTX daemon semantic compute server."""
2026-05-10 23:12:26 +02:00
from __future__ import annotations
import logging
feat: npm-managed Python runtime for @kaelio/ktx (#7) * docs: add npm managed python runtime design * build: add bundled python runtime wheel builder * build: make local embedding dependencies optional * build: bundle python runtime wheel in cli artifacts * build: track bundled python runtime release artifact * test: verify bundled python runtime wheel * docs: add plan for bundled python runtime wheel * test: cover managed python runtime lifecycle * feat: add managed python runtime installer * feat: add runtime command runner * feat: expose runtime management commands * test: verify managed python runtime commands * docs: add plan for managed python runtime installer * feat: add managed python command helper * feat: use managed runtime for sl query compute * feat: route sl query managed runtime policy * docs: add plan for managed runtime sl query integration * feat: add managed runtime daemon metadata * feat: manage python daemon lifecycle * feat: add runtime daemon start stop commands * fix: verify managed runtime daemon lifecycle * docs: add plan for managed runtime daemon lifecycle * feat: add managed local embeddings config marker * feat: add managed local embeddings daemon helper * feat: use managed runtime for local embedding setup * feat: pass managed runtime policy through setup * docs: add plan for managed local embeddings runtime * feat: read CLI package metadata dynamically * feat: assemble public kaelio ktx npm package * feat: release one public kaelio ktx npm artifact * test: cover public kaelio ktx package invocations * chore: verify public kaelio ktx package artifacts * docs: add plan for public kaelio ktx npm package * test: verify managed runtime in public package smoke * test: finalize managed runtime release smoke * docs: add plan for managed runtime release smoke * test: specify local embeddings release smoke * feat: add local embeddings runtime smoke * chore: register local embeddings smoke * fix: verify local embeddings smoke * fix: restore artifact smoke python env helper * docs: add plan for managed local embeddings release smoke * refactor: share managed runtime install policy parsing * feat: use managed runtime for agent semantic queries * feat: use managed runtime for MCP semantic compute * docs: add plan for managed agent and MCP semantic runtime * feat(cli): add managed daemon HTTP helpers * feat(cli): route local adapters through managed daemon * feat(cli): use managed daemon for ingest helpers * feat(cli): pass managed daemon options to scan * feat(context): pass MCP ingest pull config options * feat(cli): pass managed daemon options to serve ingest * test: verify managed local ingest daemon runtime * docs: add plan for managed local ingest daemon runtime * docs: align managed runtime examples * docs: add plan for managed runtime docs cleanup * test: cover published package runtime smoke commands * test: validate published package smoke outputs * docs: add plan for published package runtime smoke * build: stamp public npm package version * release: add npm public release policy * release: add guarded npm publish script * release: document public npm release handoff * docs: add plan for public npm release handoff * test: cover managed runtime prune in package smoke * docs: document managed runtime prune * docs: add plan for managed runtime prune smoke and docs * chore: encode uv runtime prerequisite policy * fix: clarify missing uv runtime error * docs: document uv runtime prerequisite * docs: add plan for uv runtime prerequisite contract * refactor: limit release artifacts to public package runtime * chore: align release policy with bundled runtime wheel * docs: describe single public runtime artifact surface * test: verify single public runtime artifact contract * docs: add plan for single public runtime artifact cleanup * fix: align local embeddings smoke with public version * docs: add plan for local embeddings smoke public version * release: soft-launch as @kaelio/ktx@0.1.0-rc.0 on next tag Publish target moves to the pre-release version 0.1.0-rc.0 under the next dist-tag so npm install @kaelio/ktx (which resolves to latest) does not pick up the soft-launch build. Users opt in via @kaelio/ktx@next. * Fix release script boundary checks * Remove PostHog from public package bundle
2026-05-11 15:50:34 +02:00
import os
2026-05-10 23:12:26 +02:00
from collections.abc import Callable
from typing import Any
from fastapi import FastAPI, HTTPException
from fastapi.responses import Response
2026-05-10 23:51:24 +02:00
from ktx_daemon.code_execution import (
2026-05-10 23:12:26 +02:00
ExecuteCodeRequest,
ExecuteCodeResponse,
dumps_numpy_json,
execute_code_response,
)
2026-05-10 23:51:24 +02:00
from ktx_daemon.database_introspection import (
2026-05-10 23:12:26 +02:00
DatabaseIntrospectionRequest,
DatabaseIntrospectionResponse,
introspect_database_response,
)
2026-05-10 23:51:24 +02:00
from ktx_daemon.embeddings import (
2026-05-10 23:12:26 +02:00
ComputeEmbeddingBulkRequest,
ComputeEmbeddingBulkResponse,
ComputeEmbeddingRequest,
ComputeEmbeddingResponse,
EmbeddingProvider,
compute_embedding_bulk_response,
compute_embedding_response,
)
2026-05-10 23:51:24 +02:00
from ktx_daemon.lookml import (
2026-05-10 23:12:26 +02:00
ParseLookMLRequest,
ParseLookMLResponse,
parse_lookml_project,
)
2026-05-10 23:51:24 +02:00
from ktx_daemon.semantic_layer import (
2026-05-10 23:12:26 +02:00
SemanticLayerQueryRequest,
SemanticLayerQueryResponse,
ValidateSourcesRequest,
ValidateSourcesResponse,
query_semantic_layer,
validate_semantic_layer,
)
2026-05-10 23:51:24 +02:00
from ktx_daemon.source_generation import (
2026-05-10 23:12:26 +02:00
GenerateSourcesRequest,
GenerateSourcesResponse,
generate_sources_response,
)
2026-05-10 23:51:24 +02:00
from ktx_daemon.table_identifier import (
2026-05-10 23:12:26 +02:00
ParseTableIdentifierBatchRequest,
ParseTableIdentifierBatchResponse,
parse_table_identifier_response,
)
logger = logging.getLogger(__name__)
class NumpyORJSONResponse(Response):
media_type = "application/json"
def render(self, content: Any) -> bytes:
return dumps_numpy_json(content)
def create_app(
*,
embedding_provider: EmbeddingProvider | None = None,
database_introspector: Callable[
[DatabaseIntrospectionRequest], DatabaseIntrospectionResponse
]
| None = None,
enable_code_execution: bool = False,
) -> FastAPI:
app = FastAPI(
2026-05-10 23:51:24 +02:00
title="KTX Daemon",
description="Stateless portable compute server for KTX.",
2026-05-10 23:12:26 +02:00
version="0.1.0",
)
@app.get("/health")
async def health() -> dict[str, str]:
feat: npm-managed Python runtime for @kaelio/ktx (#7) * docs: add npm managed python runtime design * build: add bundled python runtime wheel builder * build: make local embedding dependencies optional * build: bundle python runtime wheel in cli artifacts * build: track bundled python runtime release artifact * test: verify bundled python runtime wheel * docs: add plan for bundled python runtime wheel * test: cover managed python runtime lifecycle * feat: add managed python runtime installer * feat: add runtime command runner * feat: expose runtime management commands * test: verify managed python runtime commands * docs: add plan for managed python runtime installer * feat: add managed python command helper * feat: use managed runtime for sl query compute * feat: route sl query managed runtime policy * docs: add plan for managed runtime sl query integration * feat: add managed runtime daemon metadata * feat: manage python daemon lifecycle * feat: add runtime daemon start stop commands * fix: verify managed runtime daemon lifecycle * docs: add plan for managed runtime daemon lifecycle * feat: add managed local embeddings config marker * feat: add managed local embeddings daemon helper * feat: use managed runtime for local embedding setup * feat: pass managed runtime policy through setup * docs: add plan for managed local embeddings runtime * feat: read CLI package metadata dynamically * feat: assemble public kaelio ktx npm package * feat: release one public kaelio ktx npm artifact * test: cover public kaelio ktx package invocations * chore: verify public kaelio ktx package artifacts * docs: add plan for public kaelio ktx npm package * test: verify managed runtime in public package smoke * test: finalize managed runtime release smoke * docs: add plan for managed runtime release smoke * test: specify local embeddings release smoke * feat: add local embeddings runtime smoke * chore: register local embeddings smoke * fix: verify local embeddings smoke * fix: restore artifact smoke python env helper * docs: add plan for managed local embeddings release smoke * refactor: share managed runtime install policy parsing * feat: use managed runtime for agent semantic queries * feat: use managed runtime for MCP semantic compute * docs: add plan for managed agent and MCP semantic runtime * feat(cli): add managed daemon HTTP helpers * feat(cli): route local adapters through managed daemon * feat(cli): use managed daemon for ingest helpers * feat(cli): pass managed daemon options to scan * feat(context): pass MCP ingest pull config options * feat(cli): pass managed daemon options to serve ingest * test: verify managed local ingest daemon runtime * docs: add plan for managed local ingest daemon runtime * docs: align managed runtime examples * docs: add plan for managed runtime docs cleanup * test: cover published package runtime smoke commands * test: validate published package smoke outputs * docs: add plan for published package runtime smoke * build: stamp public npm package version * release: add npm public release policy * release: add guarded npm publish script * release: document public npm release handoff * docs: add plan for public npm release handoff * test: cover managed runtime prune in package smoke * docs: document managed runtime prune * docs: add plan for managed runtime prune smoke and docs * chore: encode uv runtime prerequisite policy * fix: clarify missing uv runtime error * docs: document uv runtime prerequisite * docs: add plan for uv runtime prerequisite contract * refactor: limit release artifacts to public package runtime * chore: align release policy with bundled runtime wheel * docs: describe single public runtime artifact surface * test: verify single public runtime artifact contract * docs: add plan for single public runtime artifact cleanup * fix: align local embeddings smoke with public version * docs: add plan for local embeddings smoke public version * release: soft-launch as @kaelio/ktx@0.1.0-rc.0 on next tag Publish target moves to the pre-release version 0.1.0-rc.0 under the next dist-tag so npm install @kaelio/ktx (which resolves to latest) does not pick up the soft-launch build. Users opt in via @kaelio/ktx@next. * Fix release script boundary checks * Remove PostHog from public package bundle
2026-05-11 15:50:34 +02:00
response = {"status": "healthy"}
version = os.environ.get("KTX_DAEMON_VERSION")
if version:
response["version"] = version
return response
2026-05-10 23:12:26 +02:00
@app.post("/database/introspect", response_model=DatabaseIntrospectionResponse)
async def database_introspect(
request: DatabaseIntrospectionRequest,
) -> DatabaseIntrospectionResponse:
try:
introspector = database_introspector or introspect_database_response
return introspector(request)
except ValueError as error:
logger.warning("Database introspection rejected: %s", error)
raise HTTPException(status_code=400, detail=str(error)) from error
except Exception as error:
logger.exception("Database introspection failed: %s", error)
raise HTTPException(
status_code=500,
detail=f"Database introspection failed: {error}",
) from error
@app.post("/embeddings/compute", response_model=ComputeEmbeddingResponse)
async def embedding_compute(
request: ComputeEmbeddingRequest,
) -> ComputeEmbeddingResponse:
try:
return compute_embedding_response(
request,
provider=embedding_provider,
)
except ValueError as error:
logger.warning("Embedding compute rejected: %s", error)
raise HTTPException(status_code=400, detail=str(error)) from error
except Exception as error:
logger.exception("Embedding compute failed: %s", error)
raise HTTPException(
status_code=500,
detail=f"Embedding compute failed: {error}",
) from error
@app.post(
"/embeddings/compute-bulk",
response_model=ComputeEmbeddingBulkResponse,
)
async def embedding_compute_bulk(
request: ComputeEmbeddingBulkRequest,
) -> ComputeEmbeddingBulkResponse:
try:
return compute_embedding_bulk_response(
request,
provider=embedding_provider,
)
except ValueError as error:
logger.warning("Bulk embedding compute rejected: %s", error)
raise HTTPException(status_code=400, detail=str(error)) from error
except Exception as error:
logger.exception("Bulk embedding compute failed: %s", error)
raise HTTPException(
status_code=500,
detail=f"Bulk embedding compute failed: {error}",
) from error
if enable_code_execution:
@app.post(
"/code/execute",
response_model=ExecuteCodeResponse,
response_class=NumpyORJSONResponse,
)
async def code_execute(request: ExecuteCodeRequest) -> ExecuteCodeResponse:
try:
return execute_code_response(
request,
nest_api_url=None,
auth_header=None,
)
except Exception as error:
logger.exception("Code execution failed: %s", error)
raise HTTPException(
status_code=500,
detail=f"Code execution failed: {error}",
) from error
@app.post("/lookml/parse", response_model=ParseLookMLResponse)
async def lookml_parse(request: ParseLookMLRequest) -> ParseLookMLResponse:
try:
return parse_lookml_project(request)
except Exception as error:
logger.exception("LookML parsing failed: %s", error)
raise HTTPException(
status_code=500,
detail=f"LookML parsing failed: {error}",
) from error
@app.post(
"/sql/parse-table-identifier",
response_model=ParseTableIdentifierBatchResponse,
)
async def sql_parse_table_identifier(
request: ParseTableIdentifierBatchRequest,
) -> ParseTableIdentifierBatchResponse:
try:
return parse_table_identifier_response(request)
except Exception as error:
logger.exception("Table identifier parsing failed: %s", error)
raise HTTPException(
status_code=500,
detail=f"Table identifier parsing failed: {error}",
) from error
@app.post(
"/semantic-layer/generate-sources", response_model=GenerateSourcesResponse
)
async def semantic_generate_sources(
request: GenerateSourcesRequest,
) -> GenerateSourcesResponse:
try:
return generate_sources_response(request)
except Exception as error:
logger.exception("Semantic source generation failed: %s", error)
raise HTTPException(
status_code=500,
detail=f"Semantic source generation failed: {error}",
) from error
@app.post("/semantic-layer/query", response_model=SemanticLayerQueryResponse)
async def semantic_query(
request: SemanticLayerQueryRequest,
) -> SemanticLayerQueryResponse:
try:
return query_semantic_layer(request)
except ValueError as error:
logger.warning("Semantic query rejected: %s", error)
raise HTTPException(status_code=400, detail=str(error)) from error
except Exception as error:
logger.exception("Semantic query failed: %s", error)
raise HTTPException(
status_code=500,
detail=f"Semantic layer query failed: {error}",
) from error
@app.post("/semantic-layer/validate", response_model=ValidateSourcesResponse)
async def semantic_validate(
request: ValidateSourcesRequest,
) -> ValidateSourcesResponse:
return validate_semantic_layer(request)
return app