Remove render_to_sx from public API: enforce sx_call for all service code
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m44s

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,
oob_header_sx as _oob_header_sx,
full_page_sx, oob_page_sx,
@@ -25,21 +25,21 @@ from .helpers import _markets_admin_panel_sx
# Browse page
# ---------------------------------------------------------------------------
async def _product_grid(cards_sx: str) -> str:
def _product_grid(cards_sx: str) -> str:
"""Wrap product cards in a grid as sx."""
return await render_to_sx("market-product-grid", cards=SxExpr(cards_sx))
return sx_call("market-product-grid", cards=SxExpr(cards_sx))
async def render_browse_page(ctx: dict) -> str:
"""Full page: product browse with filters."""
cards = await _product_cards_sx(ctx)
content = await _product_grid(cards)
cards = _product_cards_sx(ctx)
content = _product_grid(cards)
from shared.sx.helpers import render_to_sx_with_env
hdr = 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)))
menu = await _mobile_nav_panel_sx(ctx)
market_header=SxExpr(_market_header_sx(ctx)))
menu = _mobile_nav_panel_sx(ctx)
filter_sx = await _mobile_filter_summary_sx(ctx)
aside_sx = await _desktop_filter_sx(ctx)
@@ -49,17 +49,17 @@ async def render_browse_page(ctx: dict) -> str:
async def render_browse_oob(ctx: dict) -> str:
"""OOB response: product browse."""
cards = await _product_cards_sx(ctx)
content = await _product_grid(cards)
cards = _product_cards_sx(ctx)
content = _product_grid(cards)
oob_hdr = await _oob_header_sx("post-header-child", "market-header-child",
await _market_header_sx(ctx))
oobs = await render_to_sx("market-browse-layout-oob",
_market_header_sx(ctx))
oobs = 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")))
menu = await _mobile_nav_panel_sx(ctx)
menu = _mobile_nav_panel_sx(ctx)
filter_sx = await _mobile_filter_summary_sx(ctx)
aside_sx = await _desktop_filter_sx(ctx)
@@ -67,9 +67,9 @@ async def render_browse_oob(ctx: dict) -> str:
menu=menu, filter=filter_sx, aside=aside_sx)
async def render_browse_cards(ctx: dict) -> str:
def render_browse_cards(ctx: dict) -> str:
"""Pagination fragment: product cards -- sx wire format."""
return await _product_cards_sx(ctx)
return _product_cards_sx(ctx)
# ---------------------------------------------------------------------------
@@ -78,29 +78,29 @@ async def render_browse_cards(ctx: dict) -> str:
async def render_product_page(ctx: dict, d: dict) -> str:
"""Full page: product detail."""
content = await _product_detail_sx(d, ctx)
meta = await _product_meta_sx(d, ctx)
content = _product_detail_sx(d, ctx)
meta = _product_meta_sx(d, ctx)
from shared.sx.helpers import render_to_sx_with_env
hdr = await render_to_sx_with_env("market-product-layout-full", {},
post_header=SxExpr(await _post_header_sx(ctx)),
market_header=SxExpr(await _market_header_sx(ctx)),
product_header=SxExpr(await _product_header_sx(ctx, d)))
market_header=SxExpr(_market_header_sx(ctx)),
product_header=SxExpr(_product_header_sx(ctx, d)))
return await full_page_sx(ctx, header_rows=hdr, content=content, meta=meta)
async def render_product_oob(ctx: dict, d: dict) -> str:
"""OOB response: product detail."""
content = await _product_detail_sx(d, ctx)
content = _product_detail_sx(d, ctx)
oobs = await render_to_sx("market-oob-wrap",
parts=SxExpr("(<> " + await _market_header_sx(ctx, oob=True) + " "
oobs = sx_call("market-oob-wrap",
parts=SxExpr("(<> " + _market_header_sx(ctx, oob=True) + " "
+ await _oob_header_sx("market-header-child", "product-header-child",
await _product_header_sx(ctx, d)) + " "
_product_header_sx(ctx, d)) + " "
+ _clear_deeper_oob("post-row", "post-header-child",
"market-row", "market-header-child",
"product-row", "product-header-child") + ")"))
menu = await _mobile_nav_panel_sx(ctx)
menu = _mobile_nav_panel_sx(ctx)
return await oob_page_sx(oobs=oobs, content=content, menu=menu)
@@ -110,25 +110,25 @@ async def render_product_oob(ctx: dict, d: dict) -> str:
async def render_product_admin_page(ctx: dict, d: dict) -> str:
"""Full page: product admin."""
content = await _product_detail_sx(d, ctx)
content = _product_detail_sx(d, ctx)
from shared.sx.helpers import render_to_sx_with_env
hdr = await render_to_sx_with_env("market-product-admin-layout-full", {},
post_header=SxExpr(await _post_header_sx(ctx)),
market_header=SxExpr(await _market_header_sx(ctx)),
product_header=SxExpr(await _product_header_sx(ctx, d)),
admin_header=SxExpr(await _product_admin_header_sx(ctx, d)))
market_header=SxExpr(_market_header_sx(ctx)),
product_header=SxExpr(_product_header_sx(ctx, d)),
admin_header=SxExpr(_product_admin_header_sx(ctx, d)))
return await full_page_sx(ctx, header_rows=hdr, content=content)
async def render_product_admin_oob(ctx: dict, d: dict) -> str:
"""OOB response: product admin."""
content = await _product_detail_sx(d, ctx)
content = _product_detail_sx(d, ctx)
oobs = await render_to_sx("market-oob-wrap",
parts=SxExpr("(<> " + await _product_header_sx(ctx, d, oob=True) + " "
oobs = sx_call("market-oob-wrap",
parts=SxExpr("(<> " + _product_header_sx(ctx, d, oob=True) + " "
+ await _oob_header_sx("product-header-child", "product-admin-header-child",
await _product_admin_header_sx(ctx, d)) + " "
_product_admin_header_sx(ctx, d)) + " "
+ _clear_deeper_oob("post-row", "post-header-child",
"market-row", "market-header-child",
"product-row", "product-header-child",
@@ -149,7 +149,7 @@ async def render_markets_admin_list_panel(ctx: dict) -> str:
# Public API: POST handler fragment renderers
# ---------------------------------------------------------------------------
async def render_all_markets_cards(markets: list, has_more: bool,
def render_all_markets_cards(markets: list, has_more: bool,
page_info: dict, page: int) -> str:
"""Pagination fragment: all markets cards."""
from quart import url_for
@@ -157,10 +157,10 @@ async def render_all_markets_cards(markets: list, has_more: bool,
prefix = route_prefix()
next_url = prefix + url_for("all_markets.markets_fragment", page=page + 1)
return await _market_cards_sx(markets, page_info, page, has_more, next_url)
return _market_cards_sx(markets, page_info, page, has_more, next_url)
async def render_page_markets_cards(markets: list, has_more: bool,
def render_page_markets_cards(markets: list, has_more: bool,
page: int, post_slug: str) -> str:
"""Pagination fragment: page-scoped markets cards."""
from quart import url_for
@@ -168,11 +168,11 @@ async def render_page_markets_cards(markets: list, has_more: bool,
prefix = route_prefix()
next_url = prefix + url_for("page_markets.markets_fragment", page=page + 1)
return await _market_cards_sx(markets, {}, page, has_more, next_url,
return _market_cards_sx(markets, {}, page, has_more, next_url,
show_page_badge=False, post_slug=post_slug)
async def render_like_toggle_button(slug: str, liked: bool, *,
def render_like_toggle_button(slug: str, liked: bool, *,
like_url: str | None = None,
item_type: str = "product") -> str:
"""Render a standalone like toggle button for HTMX POST response."""
@@ -193,7 +193,7 @@ async def render_like_toggle_button(slug: str, liked: bool, *,
icon = "fa-regular fa-heart"
label = f"Like this {item_type}"
return await render_to_sx(
return sx_call(
"market-like-toggle-button",
colour=colour, action=like_url,
hx_headers=f'{{"X-CSRFToken": "{csrf}"}}',
@@ -201,7 +201,7 @@ async def render_like_toggle_button(slug: str, liked: bool, *,
)
async def render_cart_added_response(cart: list, item: Any, d: dict) -> str:
def render_cart_added_response(cart: list, item: Any, d: dict) -> str:
"""Render the HTMX response after add-to-cart.
Returns OOB fragments: cart-mini icon + product add/remove buttons + cart item row.
@@ -217,30 +217,30 @@ async def render_cart_added_response(cart: list, item: Any, d: dict) -> str:
# 1. Cart mini icon OOB
if count > 0:
cart_href = _cart_url("/")
cart_mini = await render_to_sx("market-cart-mini-count", href=cart_href, count=str(count))
cart_mini = sx_call("market-cart-mini-count", href=cart_href, count=str(count))
else:
from shared.config import config
blog_href = config().get("blog_url", "/")
logo = config().get("logo", "")
cart_mini = await render_to_sx("market-cart-mini-empty", href=blog_href, logo=logo)
cart_mini = sx_call("market-cart-mini-empty", href=blog_href, logo=logo)
# 2. Add/remove buttons OOB
action = url_for("market.browse.product.cart", product_slug=slug)
quantity = getattr(item, "quantity", 0) if item else 0
if not quantity:
cart_add = await render_to_sx(
cart_add = sx_call(
"market-cart-add-empty",
cart_id=f"cart-{slug}", action=action, csrf=csrf,
)
else:
cart_href = _cart_url("/") if callable(_cart_url) else "/"
cart_add = await render_to_sx(
cart_add = 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),
quantity=str(quantity), cart_href=cart_href,
)
add_sx = await render_to_sx(
add_sx = sx_call(
"market-cart-add-oob",
id=f"cart-add-{slug}",
inner=SxExpr(cart_add),