Files
rose-ash/shared/sx/query_executor.py
giles 5aa13a99d1 Wire SX_USE_REF=1 switching through all route intermediaries
jinja_bridge, helpers, handlers, query_executor now conditionally
import from ref/sx_ref and ref/async_eval_ref when SX_USE_REF=1.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 22:17:26 +00:00

79 lines
2.5 KiB
Python

"""
Execute defquery / defaction definitions.
Unlike fragment handlers (which produce SX markup via ``async_eval_to_sx``),
query/action defs produce **data** (dicts, lists, scalars) that get
JSON-serialized by the calling blueprint. Uses ``async_eval()`` with
the I/O primitive pipeline so ``(service ...)`` calls are awaited inline.
"""
from __future__ import annotations
from typing import Any
from .types import QueryDef, ActionDef, NIL
async def execute_query(query_def: QueryDef, params: dict[str, str]) -> Any:
"""Execute a defquery and return a JSON-serializable result.
Parameters are bound from request query string args.
"""
from .jinja_bridge import get_component_env, _get_request_context
import os
if os.environ.get("SX_USE_REF") == "1":
from .ref.async_eval_ref import async_eval
else:
from .async_eval import async_eval
env = dict(get_component_env())
env.update(query_def.closure)
# Bind params from request args (try kebab-case and snake_case)
for param in query_def.params:
snake = param.replace("-", "_")
val = params.get(param, params.get(snake, NIL))
# Coerce type=int for common patterns
if isinstance(val, str) and val.lstrip("-").isdigit():
val = int(val)
env[param] = val
ctx = _get_request_context()
result = await async_eval(query_def.body, env, ctx)
return _normalize(result)
async def execute_action(action_def: ActionDef, payload: dict[str, Any]) -> Any:
"""Execute a defaction and return a JSON-serializable result.
Parameters are bound from the JSON request body.
"""
from .jinja_bridge import get_component_env, _get_request_context
import os
if os.environ.get("SX_USE_REF") == "1":
from .ref.async_eval_ref import async_eval
else:
from .async_eval import async_eval
env = dict(get_component_env())
env.update(action_def.closure)
# Bind params from JSON payload (try kebab-case and snake_case)
for param in action_def.params:
snake = param.replace("-", "_")
val = payload.get(param, payload.get(snake, NIL))
env[param] = val
ctx = _get_request_context()
result = await async_eval(action_def.body, env, ctx)
return _normalize(result)
def _normalize(value: Any) -> Any:
"""Ensure result is JSON-serializable (strip NIL, convert sets, etc)."""
if value is NIL or value is None:
return None
if isinstance(value, set):
return list(value)
return value