All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 2m5s
The inter-service data layer (fetch_data/call_action) was the least structured part of the codebase — Python _handlers dicts with ad-hoc param extraction scattered across 16 route files. This replaces them with declarative .sx query/action definitions that make the entire inter-service protocol self-describing and greppable. Infrastructure: - defquery/defaction special forms in the sx evaluator - Query/action registry with load, lookup, and schema introspection - Query executor using async_eval with I/O primitives - Blueprint factories (create_data_blueprint/create_action_blueprint) with sx-first dispatch and Python fallback - /internal/schema endpoint on every service - parse-datetime and split-ids primitives for type coercion Service extractions: - LikesService (toggle, is_liked, liked_slugs, liked_ids) - PageConfigService (ensure, get_by_container, get_by_id, get_batch, update) - RelationsService (wraps module-level functions) - AccountDataService (user_by_email, newsletters) - CartItemsService, MarketDataService (raw SQLAlchemy lookups) 50 of 54 handlers converted to sx, 4 Python fallbacks remain (ghost-sync/push-member, clear-cart-for-order, create-order). Net: -1,383 lines Python, +251 lines modified. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
181 lines
5.8 KiB
Python
181 lines
5.8 KiB
Python
"""
|
|
Registry for defquery / defaction definitions.
|
|
|
|
Mirrors the pattern in ``handlers.py`` but for inter-service data queries
|
|
and action endpoints. Each service loads its ``.sx`` files at startup,
|
|
and the registry makes them available for dispatch by the query blueprint.
|
|
|
|
Usage::
|
|
|
|
from shared.sx.query_registry import load_query_file, get_query
|
|
|
|
load_query_file("events/queries.sx", "events")
|
|
qdef = get_query("events", "pending-entries")
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import os
|
|
from typing import Any
|
|
|
|
from .types import QueryDef, ActionDef
|
|
|
|
logger = logging.getLogger("sx.query_registry")
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Registry — service → name → QueryDef / ActionDef
|
|
# ---------------------------------------------------------------------------
|
|
|
|
_QUERY_REGISTRY: dict[str, dict[str, QueryDef]] = {}
|
|
_ACTION_REGISTRY: dict[str, dict[str, ActionDef]] = {}
|
|
|
|
|
|
def register_query(service: str, qdef: QueryDef) -> None:
|
|
if service not in _QUERY_REGISTRY:
|
|
_QUERY_REGISTRY[service] = {}
|
|
_QUERY_REGISTRY[service][qdef.name] = qdef
|
|
logger.debug("Registered query %s:%s", service, qdef.name)
|
|
|
|
|
|
def register_action(service: str, adef: ActionDef) -> None:
|
|
if service not in _ACTION_REGISTRY:
|
|
_ACTION_REGISTRY[service] = {}
|
|
_ACTION_REGISTRY[service][adef.name] = adef
|
|
logger.debug("Registered action %s:%s", service, adef.name)
|
|
|
|
|
|
def get_query(service: str, name: str) -> QueryDef | None:
|
|
return _QUERY_REGISTRY.get(service, {}).get(name)
|
|
|
|
|
|
def get_action(service: str, name: str) -> ActionDef | None:
|
|
return _ACTION_REGISTRY.get(service, {}).get(name)
|
|
|
|
|
|
def get_all_queries(service: str) -> dict[str, QueryDef]:
|
|
return dict(_QUERY_REGISTRY.get(service, {}))
|
|
|
|
|
|
def get_all_actions(service: str) -> dict[str, ActionDef]:
|
|
return dict(_ACTION_REGISTRY.get(service, {}))
|
|
|
|
|
|
def clear(service: str | None = None) -> None:
|
|
if service is None:
|
|
_QUERY_REGISTRY.clear()
|
|
_ACTION_REGISTRY.clear()
|
|
else:
|
|
_QUERY_REGISTRY.pop(service, None)
|
|
_ACTION_REGISTRY.pop(service, None)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Loading — parse .sx files and collect QueryDef / ActionDef instances
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def load_query_file(filepath: str, service_name: str) -> list[QueryDef]:
|
|
"""Parse an .sx file and register any defquery definitions."""
|
|
from .parser import parse_all
|
|
from .evaluator import _eval
|
|
from .jinja_bridge import get_component_env
|
|
|
|
with open(filepath, encoding="utf-8") as f:
|
|
source = f.read()
|
|
|
|
env = dict(get_component_env())
|
|
exprs = parse_all(source)
|
|
queries: list[QueryDef] = []
|
|
|
|
for expr in exprs:
|
|
_eval(expr, env)
|
|
|
|
for val in env.values():
|
|
if isinstance(val, QueryDef):
|
|
register_query(service_name, val)
|
|
queries.append(val)
|
|
|
|
return queries
|
|
|
|
|
|
def load_action_file(filepath: str, service_name: str) -> list[ActionDef]:
|
|
"""Parse an .sx file and register any defaction definitions."""
|
|
from .parser import parse_all
|
|
from .evaluator import _eval
|
|
from .jinja_bridge import get_component_env
|
|
|
|
with open(filepath, encoding="utf-8") as f:
|
|
source = f.read()
|
|
|
|
env = dict(get_component_env())
|
|
exprs = parse_all(source)
|
|
actions: list[ActionDef] = []
|
|
|
|
for expr in exprs:
|
|
_eval(expr, env)
|
|
|
|
for val in env.values():
|
|
if isinstance(val, ActionDef):
|
|
register_action(service_name, val)
|
|
actions.append(val)
|
|
|
|
return actions
|
|
|
|
|
|
def load_query_dir(directory: str, service_name: str) -> list[QueryDef]:
|
|
"""Load all .sx files from a directory and register queries."""
|
|
import glob as glob_mod
|
|
queries: list[QueryDef] = []
|
|
for filepath in sorted(glob_mod.glob(os.path.join(directory, "*.sx"))):
|
|
queries.extend(load_query_file(filepath, service_name))
|
|
return queries
|
|
|
|
|
|
def load_action_dir(directory: str, service_name: str) -> list[ActionDef]:
|
|
"""Load all .sx files from a directory and register actions."""
|
|
import glob as glob_mod
|
|
actions: list[ActionDef] = []
|
|
for filepath in sorted(glob_mod.glob(os.path.join(directory, "*.sx"))):
|
|
actions.extend(load_action_file(filepath, service_name))
|
|
return actions
|
|
|
|
|
|
def load_service_protocols(service_name: str, base_dir: str) -> None:
|
|
"""Load queries.sx and actions.sx from a service's base directory."""
|
|
queries_path = os.path.join(base_dir, "queries.sx")
|
|
actions_path = os.path.join(base_dir, "actions.sx")
|
|
if os.path.exists(queries_path):
|
|
load_query_file(queries_path, service_name)
|
|
logger.info("Loaded queries for %s from %s", service_name, queries_path)
|
|
if os.path.exists(actions_path):
|
|
load_action_file(actions_path, service_name)
|
|
logger.info("Loaded actions for %s from %s", service_name, actions_path)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Schema — introspection for /internal/schema
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def schema_for_service(service: str) -> dict[str, Any]:
|
|
"""Return a JSON-serializable schema of all queries and actions."""
|
|
queries = []
|
|
for qdef in _QUERY_REGISTRY.get(service, {}).values():
|
|
queries.append({
|
|
"name": qdef.name,
|
|
"params": list(qdef.params),
|
|
"doc": qdef.doc,
|
|
})
|
|
actions = []
|
|
for adef in _ACTION_REGISTRY.get(service, {}).values():
|
|
actions.append({
|
|
"name": adef.name,
|
|
"params": list(adef.params),
|
|
"doc": adef.doc,
|
|
})
|
|
return {
|
|
"service": service,
|
|
"queries": sorted(queries, key=lambda q: q["name"]),
|
|
"actions": sorted(actions, key=lambda a: a["name"]),
|
|
}
|