Eliminate Python page helpers from orders — pure .sx defpages with IO primitives

Orders defpages now fetch data via (service ...) and generate URLs via
(url-for ...) and (route-prefix) directly in .sx. No Python middleman.

- Add url-for, route-prefix IO primitives to shared/sx/primitives_io.py
- Add generic register()/\_\_getattr\_\_ to ServiceRegistry for dynamic services
- Create OrdersPageService with list_page_data/detail_page_data methods
- Rewrite orders.sx defpages to use IO primitives + defcomp calls
- Remove ~320 lines of Python page helpers from orders/sxc/pages/__init__.py
- Convert :data env merge to use kebab-case keys for SX symbol access

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-04 01:50:15 +00:00
parent 50b33ab08e
commit 63b895afd8
7 changed files with 240 additions and 335 deletions

View File

@@ -15,6 +15,8 @@ Usage::
"""
from __future__ import annotations
from typing import Any
from shared.contracts.protocols import (
CalendarService,
MarketService,
@@ -36,6 +38,7 @@ class _ServiceRegistry:
self._market: MarketService | None = None
self._cart: CartService | None = None
self._federation: FederationService | None = None
self._extra: dict[str, Any] = {}
# -- calendar -------------------------------------------------------------
@property
@@ -81,10 +84,27 @@ class _ServiceRegistry:
def federation(self, impl: FederationService) -> None:
self._federation = impl
# -- generic registration --------------------------------------------------
def register(self, name: str, impl: Any) -> None:
"""Register a service by name (for services without typed properties)."""
self._extra[name] = impl
def __getattr__(self, name: str) -> Any:
# Fallback to _extra dict for dynamically registered services
try:
extra = object.__getattribute__(self, "_extra")
if name in extra:
return extra[name]
except AttributeError:
pass
raise AttributeError(f"No service registered as: {name}")
# -- introspection --------------------------------------------------------
def has(self, name: str) -> bool:
"""Check whether a domain service is registered."""
return getattr(self, f"_{name}", None) is not None
if getattr(self, f"_{name}", None) is not None:
return True
return name in self._extra
# Module-level singleton — import this everywhere.

View File

@@ -184,7 +184,9 @@ async def execute_page(
if page_def.data_expr is not None:
data_result = await async_eval(page_def.data_expr, env, ctx)
if isinstance(data_result, dict):
env.update(data_result)
# Merge with kebab-case keys so SX symbols can reference them
for k, v in data_result.items():
env[k.replace("_", "-")] = v
# Render content slot (required)
content_sx = await _eval_slot(page_def.content_expr, env, ctx)

View File

@@ -43,6 +43,8 @@ IO_PRIMITIVES: frozenset[str] = frozenset({
"g",
"csrf-token",
"abort",
"url-for",
"route-prefix",
})
@@ -345,6 +347,34 @@ async def _io_abort(
abort(status, message)
async def _io_url_for(
args: list[Any], kwargs: dict[str, Any], ctx: RequestContext
) -> str:
"""``(url-for "endpoint" :key val ...)`` → url_for(endpoint, **kwargs).
Generates a URL for the given endpoint. Keyword args become URL
parameters (kebab-case converted to snake_case).
"""
if not args:
raise ValueError("url-for requires an endpoint name")
from quart import url_for
endpoint = str(args[0])
clean = {k.replace("-", "_"): v for k, v in _clean_kwargs(kwargs).items()}
# Convert numeric values for int URL params
for k, v in clean.items():
if isinstance(v, str) and v.isdigit():
clean[k] = int(v)
return url_for(endpoint, **clean)
async def _io_route_prefix(
args: list[Any], kwargs: dict[str, Any], ctx: RequestContext
) -> str:
"""``(route-prefix)`` → current route prefix string."""
from shared.utils import route_prefix
return route_prefix()
_IO_HANDLERS: dict[str, Any] = {
"frag": _io_frag,
"query": _io_query,
@@ -359,4 +389,6 @@ _IO_HANDLERS: dict[str, Any] = {
"g": _io_g,
"csrf-token": _io_csrf_token,
"abort": _io_abort,
"url-for": _io_url_for,
"route-prefix": _io_route_prefix,
}