""" Blueprint factories for sx-dispatched data and action routes. Replaces per-service boilerplate in ``bp/data/routes.py`` and ``bp/actions/routes.py`` by dispatching to defquery/defaction definitions from the sx query registry. Falls back to Python ``_handlers`` dicts for queries/actions not yet converted. Usage:: from shared.infrastructure.query_blueprint import ( create_data_blueprint, create_action_blueprint, ) # In service's bp/data/routes.py: def register() -> Blueprint: bp, _handlers = create_data_blueprint("events") # Optional Python fallback handlers: # _handlers["some-query"] = _some_python_handler return bp """ from __future__ import annotations import logging from typing import Any, Callable, Awaitable from quart import Blueprint, g, jsonify, request logger = logging.getLogger("sx.query_blueprint") def create_data_blueprint( service_name: str, ) -> tuple[Blueprint, dict[str, Callable[[], Awaitable[Any]]]]: """Create a data blueprint that dispatches to sx queries with Python fallback. Returns (blueprint, python_handlers_dict) so the caller can register Python fallback handlers for queries not yet converted to sx. """ from shared.infrastructure.data_client import DATA_HEADER bp = Blueprint("data", __name__, url_prefix="/internal/data") _handlers: dict[str, Callable[[], Awaitable[Any]]] = {} @bp.before_request async def _require_data_header(): if not request.headers.get(DATA_HEADER): return jsonify({"error": "forbidden"}), 403 from shared.infrastructure.internal_auth import validate_internal_request if not validate_internal_request(): return jsonify({"error": "forbidden"}), 403 @bp.get("/") async def handle_query(query_name: str): # 1. Check sx query registry first from shared.sx.query_registry import get_query from shared.sx.query_executor import execute_query qdef = get_query(service_name, query_name) if qdef is not None: result = await execute_query(qdef, dict(request.args)) return jsonify(result) # 2. Fall back to Python handlers handler = _handlers.get(query_name) if handler is not None: result = await handler() return jsonify(result) return jsonify({"error": "unknown query"}), 404 return bp, _handlers def create_action_blueprint( service_name: str, ) -> tuple[Blueprint, dict[str, Callable[[], Awaitable[Any]]]]: """Create an action blueprint that dispatches to sx actions with Python fallback. Returns (blueprint, python_handlers_dict) so the caller can register Python fallback handlers for actions not yet converted to sx. """ from shared.infrastructure.actions import ACTION_HEADER bp = Blueprint("actions", __name__, url_prefix="/internal/actions") _handlers: dict[str, Callable[[], Awaitable[Any]]] = {} @bp.before_request async def _require_action_header(): if not request.headers.get(ACTION_HEADER): return jsonify({"error": "forbidden"}), 403 from shared.infrastructure.internal_auth import validate_internal_request if not validate_internal_request(): return jsonify({"error": "forbidden"}), 403 @bp.post("/") async def handle_action(action_name: str): # 1. Check sx action registry first from shared.sx.query_registry import get_action from shared.sx.query_executor import execute_action adef = get_action(service_name, action_name) if adef is not None: try: payload = await request.get_json(force=True) or {} result = await execute_action(adef, payload) return jsonify(result) except Exception as exc: logger.exception("SX action %s:%s failed", service_name, action_name) return jsonify({"error": str(exc)}), 500 # 2. Fall back to Python handlers handler = _handlers.get(action_name) if handler is not None: try: result = await handler() return jsonify(result) except Exception as exc: logger.exception("Action %s failed", action_name) return jsonify({"error": str(exc)}), 500 return jsonify({"error": "unknown action"}), 404 return bp, _handlers