Remove render_to_sx from public API: enforce sx_call for all service code

Replace ~250 render_to_sx calls across all services with sync sx_call,
converting many async functions to sync where no other awaits remained.
Make render_to_sx/render_to_sx_with_env private (_render_to_sx).
Add (post-header-ctx) IO primitive and shared post/post-admin defmacros.
Convert built-in post/post-admin layouts from Python to register_sx_layout
with .sx defcomps. Remove dead post_admin_mobile_nav_sx.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-04 19:30:45 +00:00
parent 57e0d0c341
commit 959e63d440
61 changed files with 1352 additions and 1208 deletions

View File

@@ -5,7 +5,7 @@ from typing import Any
from shared.sx.parser import SxExpr
from shared.sx.helpers import (
render_to_sx,
sx_call,
post_header_sx as _post_header_sx,
post_admin_header_sx,
oob_header_sx as _oob_header_sx,
@@ -19,7 +19,7 @@ from .utils import _set_prices, _price_str, _clear_deeper_oob
# Header helpers
# ---------------------------------------------------------------------------
async def _market_header_sx(ctx: dict, *, oob: bool = False) -> str:
def _market_header_sx(ctx: dict, *, oob: bool = False) -> str:
"""Build the market-level header row as sx call string."""
from quart import url_for
@@ -28,7 +28,7 @@ async def _market_header_sx(ctx: dict, *, oob: bool = False) -> str:
sub_slug = ctx.get("sub_slug", "")
hx_select_search = ctx.get("hx_select_search", "#main-panel")
label_sx = await render_to_sx(
label_sx = sx_call(
"market-shop-label",
title=market_title, top_slug=top_slug or "",
sub_div=sub_slug or None,
@@ -39,9 +39,9 @@ async def _market_header_sx(ctx: dict, *, oob: bool = False) -> str:
# Build desktop nav from categories
categories = ctx.get("categories", {})
qs = ctx.get("qs", "")
nav_sx = await _desktop_category_nav_sx(ctx, categories, qs, hx_select_search)
nav_sx = _desktop_category_nav_sx(ctx, categories, qs, hx_select_search)
return await render_to_sx(
return sx_call(
"menu-row-sx",
id="market-row", level=2,
link_href=link_href, link_label_content=SxExpr(label_sx),
@@ -50,7 +50,7 @@ async def _market_header_sx(ctx: dict, *, oob: bool = False) -> str:
)
async def _desktop_category_nav_sx(ctx: dict, categories: dict, qs: str,
def _desktop_category_nav_sx(ctx: dict, categories: dict, qs: str,
hx_select: str) -> str:
"""Build desktop category navigation links as sx."""
from quart import url_for
@@ -63,7 +63,7 @@ async def _desktop_category_nav_sx(ctx: dict, categories: dict, qs: str,
all_href = prefix + url_for("market.browse.browse_all") + qs
all_active = (category_label == "All Products")
link_parts = [await render_to_sx(
link_parts = [sx_call(
"market-category-link",
href=all_href, hx_select=hx_select, active=all_active,
select_colours=select_colours, label="All",
@@ -72,7 +72,7 @@ async def _desktop_category_nav_sx(ctx: dict, categories: dict, qs: str,
for cat, data in categories.items():
cat_href = prefix + url_for("market.browse.browse_top", top_slug=data["slug"]) + qs
cat_active = (cat == category_label)
link_parts.append(await render_to_sx(
link_parts.append(sx_call(
"market-category-link",
href=cat_href, hx_select=hx_select, active=cat_active,
select_colours=select_colours, label=cat,
@@ -83,14 +83,14 @@ async def _desktop_category_nav_sx(ctx: dict, categories: dict, qs: str,
admin_sx = ""
if rights and rights.get("admin"):
admin_href = prefix + url_for("defpage_market_admin")
admin_sx = await render_to_sx("market-admin-link", href=admin_href, hx_select=hx_select)
admin_sx = sx_call("market-admin-link", href=admin_href, hx_select=hx_select)
return await render_to_sx("market-desktop-category-nav",
return sx_call("market-desktop-category-nav",
links=SxExpr(links_sx),
admin=SxExpr(admin_sx) if admin_sx else None)
async def _product_header_sx(ctx: dict, d: dict, *, oob: bool = False) -> str:
def _product_header_sx(ctx: dict, d: dict, *, oob: bool = False) -> str:
"""Build the product-level header row as sx call string."""
from quart import url_for
from shared.browser.app.csrf import generate_csrf_token
@@ -100,21 +100,21 @@ async def _product_header_sx(ctx: dict, d: dict, *, oob: bool = False) -> str:
hx_select_search = ctx.get("hx_select_search", "#main-panel")
link_href = url_for("market.browse.product.product_detail", product_slug=slug)
label_sx = await render_to_sx("market-product-label", title=title)
label_sx = sx_call("market-product-label", title=title)
# Prices in nav area
pr = _set_prices(d)
cart = ctx.get("cart", [])
prices_nav = await _prices_header_sx(d, pr, cart, slug, ctx)
prices_nav = _prices_header_sx(d, pr, cart, slug, ctx)
rights = ctx.get("rights", {})
nav_parts = [prices_nav]
if rights and rights.get("admin"):
admin_href = url_for("market.browse.product.admin", product_slug=slug)
nav_parts.append(await render_to_sx("market-admin-link", href=admin_href, hx_select=hx_select_search))
nav_parts.append(sx_call("market-admin-link", href=admin_href, hx_select=hx_select_search))
nav_sx = "(<> " + " ".join(nav_parts) + ")"
return await render_to_sx(
return sx_call(
"menu-row-sx",
id="product-row", level=3,
link_href=link_href, link_label_content=SxExpr(label_sx),
@@ -122,7 +122,7 @@ async def _product_header_sx(ctx: dict, d: dict, *, oob: bool = False) -> str:
)
async def _prices_header_sx(d: dict, pr: dict, cart: list, slug: str, ctx: dict) -> str:
def _prices_header_sx(d: dict, pr: dict, cart: list, slug: str, ctx: dict) -> str:
"""Build prices + add-to-cart for product header row as sx."""
from quart import url_for
from shared.browser.app.csrf import generate_csrf_token
@@ -133,20 +133,20 @@ async def _prices_header_sx(d: dict, pr: dict, cart: list, slug: str, ctx: dict)
# Add-to-cart button
quantity = sum(ci.quantity for ci in cart if ci.product.slug == slug) if cart else 0
add_sx = await _cart_add_sx(slug, quantity, cart_action, csrf, cart_url_fn)
add_sx = _cart_add_sx(slug, quantity, cart_action, csrf, cart_url_fn)
parts = [add_sx]
sp_val, rp_val = pr.get("sp_val"), pr.get("rp_val")
if sp_val:
parts.append(await render_to_sx("market-header-price-special-label"))
parts.append(await render_to_sx("market-header-price-special",
parts.append(sx_call("market-header-price-special-label"))
parts.append(sx_call("market-header-price-special",
price=_price_str(sp_val, pr["sp_raw"], pr["sp_cur"])))
if rp_val:
parts.append(await render_to_sx("market-header-price-strike",
parts.append(sx_call("market-header-price-strike",
price=_price_str(rp_val, pr["rp_raw"], pr["rp_cur"])))
elif rp_val:
parts.append(await render_to_sx("market-header-price-regular-label"))
parts.append(await render_to_sx("market-header-price-regular",
parts.append(sx_call("market-header-price-regular-label"))
parts.append(sx_call("market-header-price-regular",
price=_price_str(rp_val, pr["rp_raw"], pr["rp_cur"])))
# RRP
@@ -155,23 +155,23 @@ async def _prices_header_sx(d: dict, pr: dict, cart: list, slug: str, ctx: dict)
case_size = d.get("case_size_count") or 1
if rrp_raw and rrp_val:
rrp_str = f"{rrp_raw[0]}{rrp_val * case_size:.2f}"
parts.append(await render_to_sx("market-header-rrp", rrp=rrp_str))
parts.append(sx_call("market-header-rrp", rrp=rrp_str))
inner_sx = "(<> " + " ".join(parts) + ")"
return await render_to_sx("market-prices-row", inner=SxExpr(inner_sx))
return sx_call("market-prices-row", inner=SxExpr(inner_sx))
async def _cart_add_sx(slug: str, quantity: int, action: str, csrf: str,
def _cart_add_sx(slug: str, quantity: int, action: str, csrf: str,
cart_url_fn: Any = None) -> str:
"""Build add-to-cart button or quantity controls as sx."""
if not quantity:
return await render_to_sx(
return sx_call(
"market-cart-add-empty",
cart_id=f"cart-{slug}", action=action, csrf=csrf,
)
cart_href = cart_url_fn("/") if callable(cart_url_fn) else "/"
return await render_to_sx(
return sx_call(
"market-cart-add-quantity",
cart_id=f"cart-{slug}", action=action, csrf=csrf,
minus_val=str(quantity - 1), plus_val=str(quantity + 1),
@@ -183,7 +183,7 @@ async def _cart_add_sx(slug: str, quantity: int, action: str, csrf: str,
# Mobile nav panel
# ---------------------------------------------------------------------------
async def _mobile_nav_panel_sx(ctx: dict) -> str:
def _mobile_nav_panel_sx(ctx: dict) -> str:
"""Build mobile nav panel with category accordion as sx."""
from quart import url_for
from shared.utils import route_prefix
@@ -199,7 +199,7 @@ async def _mobile_nav_panel_sx(ctx: dict) -> str:
all_href = prefix + url_for("market.browse.browse_all") + qs
all_active = (category_label == "All Products")
item_parts = [await render_to_sx(
item_parts = [sx_call(
"market-mobile-all-link",
href=all_href, hx_select=hx_select, active=all_active,
select_colours=select_colours,
@@ -211,10 +211,10 @@ async def _mobile_nav_panel_sx(ctx: dict) -> str:
cat_href = prefix + url_for("market.browse.browse_top", top_slug=cat_slug) + qs
bg_cls = " bg-stone-900 text-white hover:bg-stone-900" if cat_active else ""
chevron_sx = await render_to_sx("market-mobile-chevron")
chevron_sx = sx_call("market-mobile-chevron")
cat_count = data.get("count", 0)
summary_sx = await render_to_sx(
summary_sx = sx_call(
"market-mobile-cat-summary",
bg_cls=bg_cls, href=cat_href, hx_select=hx_select,
select_colours=select_colours, cat_name=cat,
@@ -231,19 +231,19 @@ async def _mobile_nav_panel_sx(ctx: dict) -> str:
sub_active = (cat_active and sub_slug == sub.get("slug"))
sub_label = sub.get("html_label") or sub.get("name", "")
sub_count = sub.get("count", 0)
sub_link_parts.append(await render_to_sx(
sub_link_parts.append(sx_call(
"market-mobile-sub-link",
select_colours=select_colours, active=sub_active,
href=sub_href, hx_select=hx_select, label=sub_label,
count_label=f"{sub_count} products", count_str=str(sub_count),
))
sub_links_sx = "(<> " + " ".join(sub_link_parts) + ")"
subs_sx = await render_to_sx("market-mobile-subs-panel", links=SxExpr(sub_links_sx))
subs_sx = sx_call("market-mobile-subs-panel", links=SxExpr(sub_links_sx))
else:
view_href = prefix + url_for("market.browse.browse_top", top_slug=cat_slug) + qs
subs_sx = await render_to_sx("market-mobile-view-all", href=view_href, hx_select=hx_select)
subs_sx = sx_call("market-mobile-view-all", href=view_href, hx_select=hx_select)
item_parts.append(await render_to_sx(
item_parts.append(sx_call(
"market-mobile-cat-details",
open=cat_active or None,
summary=SxExpr(summary_sx),
@@ -251,20 +251,20 @@ async def _mobile_nav_panel_sx(ctx: dict) -> str:
))
items_sx = "(<> " + " ".join(item_parts) + ")"
return await render_to_sx("market-mobile-nav-wrapper", items=SxExpr(items_sx))
return sx_call("market-mobile-nav-wrapper", items=SxExpr(items_sx))
# ---------------------------------------------------------------------------
# Product admin header
# ---------------------------------------------------------------------------
async def _product_admin_header_sx(ctx: dict, d: dict, *, oob: bool = False) -> str:
def _product_admin_header_sx(ctx: dict, d: dict, *, oob: bool = False) -> str:
"""Build product admin header row as sx."""
from quart import url_for
slug = d.get("slug", "")
link_href = url_for("market.browse.product.admin", product_slug=slug)
return await render_to_sx(
return sx_call(
"menu-row-sx",
id="product-admin-row", level=4,
link_href=link_href, link_label="admin!!", icon="fa fa-cog",
@@ -296,21 +296,21 @@ async def _market_full(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import render_to_sx_with_env
return await render_to_sx_with_env("market-browse-layout-full", {},
post_header=SxExpr(await _post_header_sx(ctx)),
market_header=SxExpr(await _market_header_sx(ctx)))
market_header=SxExpr(_market_header_sx(ctx)))
async def _market_oob(ctx: dict, **kw: Any) -> str:
oob_hdr = await _oob_header_sx("post-header-child", "market-header-child",
await _market_header_sx(ctx))
return await render_to_sx("market-browse-layout-oob",
_market_header_sx(ctx))
return sx_call("market-browse-layout-oob",
oob_header=SxExpr(oob_hdr),
post_header_oob=SxExpr(await _post_header_sx(ctx, oob=True)),
clear_oob=SxExpr(_clear_deeper_oob("post-row", "post-header-child",
"market-row", "market-header-child")))
async def _market_mobile(ctx: dict, **kw: Any) -> str:
return await _mobile_nav_panel_sx(ctx)
def _market_mobile(ctx: dict, **kw: Any) -> str:
return _mobile_nav_panel_sx(ctx)
async def _market_admin_full(ctx: dict, **kw: Any) -> str:
@@ -318,14 +318,14 @@ async def _market_admin_full(ctx: dict, **kw: Any) -> str:
selected = kw.get("selected", "")
return await render_to_sx_with_env("market-admin-layout-full", {},
post_header=SxExpr(await _post_header_sx(ctx)),
market_header=SxExpr(await _market_header_sx(ctx)),
market_header=SxExpr(_market_header_sx(ctx)),
admin_header=SxExpr(await _market_admin_header_sx(ctx, selected=selected)))
async def _market_admin_oob(ctx: dict, **kw: Any) -> str:
selected = kw.get("selected", "")
return await render_to_sx("market-admin-layout-oob",
market_header_oob=SxExpr(await _market_header_sx(ctx, oob=True)),
return sx_call("market-admin-layout-oob",
market_header_oob=SxExpr(_market_header_sx(ctx, oob=True)),
admin_oob_header=SxExpr(await _oob_header_sx("market-header-child", "market-admin-header-child",
await _market_admin_header_sx(ctx, selected=selected))),
clear_oob=SxExpr(_clear_deeper_oob("post-row", "post-header-child",