loom.rest.fastapi.sql

Generic per-connection SQL endpoint mounting and envelope encoding.

Implements the optional REST surface of the SQL subsystem (specs/sql_api_clickhouse_spec.md §3/§4): one POST route per connection that opted in with sql_endpoint.enabled and an explicit sql_endpoint.auth value (double opt-in, B2). The request body only admits {sql, roles?, parameters?, limit?, offset?} — backend settings are rejected by schema — and the response is the single SqlQueryResult envelope encoded in one pass by a module-level msgspec encoder.

Roles are bound to the authenticated identity: when the configured authentication mechanism binds roles to the caller, the effective roles are the ones that identity holds intersected with the allowlist, and the body roles can only narrow them. The endpoint never learns which mechanism that was.

Errors reuse the framework standard body (code/message/trace_id) through HttpErrorMapper, exactly as the router runtime does.

Functions

_connection_mechanism(connection, mechanism)

Narrow the application-wide mechanism to what this connection binds.

_declared_content_length(request)

Return the Content-Length header as an int, or None when unusable.

_decode_request(body, *, max_sql_bytes)

Decode and validate the request body at the input edge.

_effective_roles_label(roles, connection)

Label the roles the query actually runs with.

_encode_exotic(obj)

Encode backend types msgspec does not handle natively (spec §3 matrix).

_invalid_request(field, message)

Build a 422 with the framework standard error body.

_make_sql_handler(service, name, connection, ...)

Build the async handler serving SQL queries for one connection.

_mount_endpoint(app, *, service, name, ...)

Register the POST route for name and emit the startup WARNING (§4).

_payload_too_large(max_bytes)

Build a 413 with the framework standard error body.

_read_body_capped(request, *, max_bytes)

Read the request body without ever buffering more than max_bytes.

_require_identity_binding(name, connection, ...)

Refuse to mount a multi-role endpoint whose roles are not bound to an identity.

_role_exposure_notice(mechanism, ...)

State plainly which roles a caller of this endpoint can obtain.

_roles_mechanism(authenticator)

Return the name of the mechanism binding roles, or None when none does.

_unexpected_error_response()

Replicate the router runtime generic 500 body without leaking internals.

bind_sql_endpoints(app, *, service, config)

Mount one generic SQL endpoint per opted-in connection.

Classes

_SqlJSONResponse(*args, **kwargs)

Envelope response encoded once by the module-level SQL encoder.

_SqlQueryRequest(*, sql[, roles, ...])

Body accepted by the SQL endpoint — never backend settings (spec §3).

loom.rest.fastapi.sql.bind_sql_endpoints(app, *, service, config, authenticator=None, observability_runtime=None)[source]

Mount one generic SQL endpoint per opted-in connection.

Only connections with sql_endpoint.enabled and an explicit sql_endpoint.auth mount a route (double opt-in, B2 resolved); every mounted endpoint is announced with a WARNING carrying its security-relevant state. Connections without endpoint expose no HTTP surface at all.

Parameters:
  • app (fastapi.FastAPI) – FastAPI application to mount the routes on.

  • service (SqlQueryService) – Policy-applying SQL query service shared by every endpoint.

  • config (SqlConfig) – Parsed sql: section with the named connections.

  • authenticator (Authenticator | None) – Mechanism authenticating callers of the application. Its provides_roles flag decides whether roles are bound to the identity; None (or a mechanism binding no role) means no binding, and the endpoint is then single-role by config.

  • observability_runtime (ObservabilityRuntime | None) – Runtime emitting one span per request, with the same labels the router runtime uses. None falls back to a no-op runtime.

Return type:

None

Example:

service = SqlQueryService(executors=executors, config=sql_config)
bind_sql_endpoints(app, service=service, config=sql_config)