Replace sx_call() with render_to_sx() across all services
Python no longer generates s-expression strings. All SX rendering now goes through render_to_sx() which builds AST from native Python values and evaluates via async_eval_to_sx() — no SX string literals in Python. - Add render_to_sx()/render_to_html() infrastructure in shared/sx/helpers.py - Add (abort status msg) IO primitive in shared/sx/primitives_io.py - Convert all 9 services: ~650 sx_call() invocations replaced - Convert shared helpers (root_header_sx, full_page_sx, etc.) to async - Fix likes service import bug (likes.models → models) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -11,7 +11,7 @@ from typing import Any
|
||||
|
||||
from shared.sx.jinja_bridge import load_service_components
|
||||
from shared.sx.helpers import (
|
||||
sx_call, SxExpr,
|
||||
render_to_sx,
|
||||
root_header_sx, full_page_sx,
|
||||
)
|
||||
|
||||
@@ -28,9 +28,10 @@ async def render_login_page(ctx: dict) -> str:
|
||||
"""Full page: login form."""
|
||||
error = ctx.get("error", "")
|
||||
email = ctx.get("email", "")
|
||||
hdr = root_header_sx(ctx)
|
||||
content = sx_call("account-login-content", error=error or None, email=email)
|
||||
return full_page_sx(ctx, header_rows=hdr,
|
||||
hdr = await root_header_sx(ctx)
|
||||
content = await render_to_sx("account-login-content",
|
||||
error=error or None, email=email)
|
||||
return await full_page_sx(ctx, header_rows=hdr,
|
||||
content=content,
|
||||
meta_html='<title>Login \u2014 Rose Ash</title>')
|
||||
|
||||
@@ -39,18 +40,19 @@ async def render_device_page(ctx: dict) -> str:
|
||||
"""Full page: device authorization form."""
|
||||
error = ctx.get("error", "")
|
||||
code = ctx.get("code", "")
|
||||
hdr = root_header_sx(ctx)
|
||||
content = sx_call("account-device-content", error=error or None, code=code)
|
||||
return full_page_sx(ctx, header_rows=hdr,
|
||||
hdr = await root_header_sx(ctx)
|
||||
content = await render_to_sx("account-device-content",
|
||||
error=error or None, code=code)
|
||||
return await full_page_sx(ctx, header_rows=hdr,
|
||||
content=content,
|
||||
meta_html='<title>Authorize Device \u2014 Rose Ash</title>')
|
||||
|
||||
|
||||
async def render_device_approved_page(ctx: dict) -> str:
|
||||
"""Full page: device approved."""
|
||||
hdr = root_header_sx(ctx)
|
||||
content = sx_call("account-device-approved")
|
||||
return full_page_sx(ctx, header_rows=hdr,
|
||||
hdr = await root_header_sx(ctx)
|
||||
content = await render_to_sx("account-device-approved")
|
||||
return await full_page_sx(ctx, header_rows=hdr,
|
||||
content=content,
|
||||
meta_html='<title>Device Authorized \u2014 Rose Ash</title>')
|
||||
|
||||
@@ -59,10 +61,10 @@ async def render_check_email_page(ctx: dict) -> str:
|
||||
"""Full page: check email after magic link sent."""
|
||||
email = ctx.get("email", "")
|
||||
email_error = ctx.get("email_error")
|
||||
hdr = root_header_sx(ctx)
|
||||
content = sx_call("account-check-email-content",
|
||||
email=email, email_error=email_error)
|
||||
return full_page_sx(ctx, header_rows=hdr,
|
||||
hdr = await root_header_sx(ctx)
|
||||
content = await render_to_sx("account-check-email-content",
|
||||
email=email, email_error=email_error)
|
||||
return await full_page_sx(ctx, header_rows=hdr,
|
||||
content=content,
|
||||
meta_html='<title>Check your email \u2014 Rose Ash</title>')
|
||||
|
||||
@@ -71,7 +73,7 @@ async def render_check_email_page(ctx: dict) -> str:
|
||||
# Public API: Fragment renderers for POST handlers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def render_newsletter_toggle(un) -> str:
|
||||
async def render_newsletter_toggle(un) -> str:
|
||||
"""Render a newsletter toggle switch for POST response."""
|
||||
from shared.browser.app.csrf import generate_csrf_token
|
||||
|
||||
@@ -94,7 +96,7 @@ def render_newsletter_toggle(un) -> str:
|
||||
translate = "translate-x-1"
|
||||
checked = "false"
|
||||
|
||||
return sx_call(
|
||||
return await render_to_sx(
|
||||
"account-newsletter-toggle",
|
||||
id=f"nl-{nid}", url=toggle_url,
|
||||
hdrs=f'{{"X-CSRFToken": "{csrf}"}}',
|
||||
@@ -103,27 +105,3 @@ def render_newsletter_toggle(un) -> str:
|
||||
checked=checked,
|
||||
knob_cls=f"inline-block h-4 w-4 rounded-full bg-white shadow transform transition-transform {translate}",
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Internal helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _fragment_content(frag: object) -> str:
|
||||
"""Convert a fragment response to sx content string.
|
||||
|
||||
SxExpr (from text/sx responses) is embedded as-is; plain strings
|
||||
(from text/html) are wrapped in ``~rich-text``.
|
||||
"""
|
||||
from shared.sx.parser import SxExpr
|
||||
if isinstance(frag, SxExpr):
|
||||
return frag.source
|
||||
s = str(frag) if frag else ""
|
||||
if not s:
|
||||
return ""
|
||||
return f'(~rich-text :html "{_sx_escape(s)}")'
|
||||
|
||||
|
||||
def _sx_escape(s: str) -> str:
|
||||
"""Escape a string for embedding in sx string literals."""
|
||||
return s.replace("\\", "\\\\").replace('"', '\\"')
|
||||
|
||||
Reference in New Issue
Block a user