Auto-mount defpages: eliminate Python route stubs across all 9 services
Some checks failed
Build and Deploy / build-and-deploy (push) Failing after 16s

Defpages are now declared with absolute paths in .sx files and auto-mounted
directly on the Quart app, removing ~850 lines of blueprint mount_pages calls,
before_request hooks, and g.* wrapper boilerplate. A new page = one defpage
declaration, nothing else.

Infrastructure:
- async_eval awaits coroutine results from callable dispatch
- auto_mount_pages() mounts all registered defpages on the app
- g._defpage_ctx pattern passes helper data to layout context

Migrated: sx, account, orders, federation, cart, market, events, blog

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-03 19:03:15 +00:00
parent 4ba63bda17
commit 293f7713d6
63 changed files with 1340 additions and 1216 deletions

View File

@@ -19,6 +19,7 @@ Usage::
from __future__ import annotations
import inspect
from typing import Any
from .types import Component, Keyword, Lambda, Macro, NIL, Symbol
@@ -114,7 +115,10 @@ async def async_eval(expr: Any, env: dict[str, Any], ctx: RequestContext) -> Any
args = [await async_eval(a, env, ctx) for a in expr[1:]]
if callable(fn) and not isinstance(fn, (Lambda, Component)):
return fn(*args)
result = fn(*args)
if inspect.iscoroutine(result):
return await result
return result
if isinstance(fn, Lambda):
return await _async_call_lambda(fn, args, env, ctx)
if isinstance(fn, Component):
@@ -369,6 +373,8 @@ async def _asf_thread_first(expr, env, ctx):
args = [result]
if callable(fn) and not isinstance(fn, (Lambda, Component)):
result = fn(*args)
if inspect.iscoroutine(result):
result = await result
elif isinstance(fn, Lambda):
result = await _async_call_lambda(fn, args, env, ctx)
else:
@@ -418,7 +424,8 @@ async def _aho_map(expr, env, ctx):
if isinstance(fn, Lambda):
results.append(await _async_call_lambda(fn, [item], env, ctx))
elif callable(fn):
results.append(fn(item))
r = fn(item)
results.append(await r if inspect.iscoroutine(r) else r)
else:
raise EvalError(f"map requires callable, got {type(fn).__name__}")
return results
@@ -432,7 +439,8 @@ async def _aho_map_indexed(expr, env, ctx):
if isinstance(fn, Lambda):
results.append(await _async_call_lambda(fn, [i, item], env, ctx))
elif callable(fn):
results.append(fn(i, item))
r = fn(i, item)
results.append(await r if inspect.iscoroutine(r) else r)
else:
raise EvalError(f"map-indexed requires callable, got {type(fn).__name__}")
return results
@@ -447,6 +455,8 @@ async def _aho_filter(expr, env, ctx):
val = await _async_call_lambda(fn, [item], env, ctx)
elif callable(fn):
val = fn(item)
if inspect.iscoroutine(val):
val = await val
else:
raise EvalError(f"filter requires callable, got {type(fn).__name__}")
if val:
@@ -459,7 +469,12 @@ async def _aho_reduce(expr, env, ctx):
acc = await async_eval(expr[2], env, ctx)
coll = await async_eval(expr[3], env, ctx)
for item in coll:
acc = await _async_call_lambda(fn, [acc, item], env, ctx) if isinstance(fn, Lambda) else fn(acc, item)
if isinstance(fn, Lambda):
acc = await _async_call_lambda(fn, [acc, item], env, ctx)
else:
acc = fn(acc, item)
if inspect.iscoroutine(acc):
acc = await acc
return acc
@@ -467,7 +482,12 @@ async def _aho_some(expr, env, ctx):
fn = await async_eval(expr[1], env, ctx)
coll = await async_eval(expr[2], env, ctx)
for item in coll:
result = await _async_call_lambda(fn, [item], env, ctx) if isinstance(fn, Lambda) else fn(item)
if isinstance(fn, Lambda):
result = await _async_call_lambda(fn, [item], env, ctx)
else:
result = fn(item)
if inspect.iscoroutine(result):
result = await result
if result:
return result
return NIL
@@ -477,7 +497,13 @@ async def _aho_every(expr, env, ctx):
fn = await async_eval(expr[1], env, ctx)
coll = await async_eval(expr[2], env, ctx)
for item in coll:
if not (await _async_call_lambda(fn, [item], env, ctx) if isinstance(fn, Lambda) else fn(item)):
if isinstance(fn, Lambda):
val = await _async_call_lambda(fn, [item], env, ctx)
else:
val = fn(item)
if inspect.iscoroutine(val):
val = await val
if not val:
return False
return True
@@ -489,7 +515,9 @@ async def _aho_for_each(expr, env, ctx):
if isinstance(fn, Lambda):
await _async_call_lambda(fn, [item], env, ctx)
elif callable(fn):
fn(item)
r = fn(item)
if inspect.iscoroutine(r):
await r
return NIL
@@ -782,7 +810,10 @@ async def _arsf_map(expr, env, ctx):
if isinstance(fn, Lambda):
parts.append(await _arender_lambda(fn, (item,), env, ctx))
elif callable(fn):
parts.append(await _arender(fn(item), env, ctx))
r = fn(item)
if inspect.iscoroutine(r):
r = await r
parts.append(await _arender(r, env, ctx))
else:
parts.append(await _arender(item, env, ctx))
return "".join(parts)
@@ -796,7 +827,10 @@ async def _arsf_map_indexed(expr, env, ctx):
if isinstance(fn, Lambda):
parts.append(await _arender_lambda(fn, (i, item), env, ctx))
elif callable(fn):
parts.append(await _arender(fn(i, item), env, ctx))
r = fn(i, item)
if inspect.iscoroutine(r):
r = await r
parts.append(await _arender(r, env, ctx))
else:
parts.append(await _arender(item, env, ctx))
return "".join(parts)
@@ -815,7 +849,10 @@ async def _arsf_for_each(expr, env, ctx):
if isinstance(fn, Lambda):
parts.append(await _arender_lambda(fn, (item,), env, ctx))
elif callable(fn):
parts.append(await _arender(fn(item), env, ctx))
r = fn(item)
if inspect.iscoroutine(r):
r = await r
parts.append(await _arender(r, env, ctx))
else:
parts.append(await _arender(item, env, ctx))
return "".join(parts)
@@ -956,7 +993,10 @@ async def _aser(expr: Any, env: dict[str, Any], ctx: RequestContext) -> Any:
args = [await async_eval(a, env, ctx) for a in expr[1:]]
if callable(fn) and not isinstance(fn, (Lambda, Component)):
return fn(*args)
result = fn(*args)
if inspect.iscoroutine(result):
return await result
return result
if isinstance(fn, Lambda):
return await _async_call_lambda(fn, args, env, ctx)
if isinstance(fn, Component):
@@ -1151,7 +1191,8 @@ async def _asho_ser_map(expr, env, ctx):
local[p] = v
results.append(await _aser(fn.body, local, ctx))
elif callable(fn):
results.append(fn(item))
r = fn(item)
results.append(await r if inspect.iscoroutine(r) else r)
else:
raise EvalError(f"map requires callable, got {type(fn).__name__}")
return results
@@ -1169,7 +1210,8 @@ async def _asho_ser_map_indexed(expr, env, ctx):
local[fn.params[1]] = item
results.append(await _aser(fn.body, local, ctx))
elif callable(fn):
results.append(fn(i, item))
r = fn(i, item)
results.append(await r if inspect.iscoroutine(r) else r)
else:
raise EvalError(f"map-indexed requires callable, got {type(fn).__name__}")
return results
@@ -1191,7 +1233,8 @@ async def _asho_ser_for_each(expr, env, ctx):
local[fn.params[0]] = item
results.append(await _aser(fn.body, local, ctx))
elif callable(fn):
results.append(fn(item))
r = fn(item)
results.append(await r if inspect.iscoroutine(r) else r)
return results

View File

@@ -290,6 +290,18 @@ async def execute_page(
# Blueprint mounting
# ---------------------------------------------------------------------------
def auto_mount_pages(app: Any, service_name: str) -> None:
"""Auto-mount all registered defpages for a service directly on the app.
Pages must have absolute paths (from the service URL root).
Called once per service in app.py after setup_*_pages().
"""
pages = get_all_pages(service_name)
for page_def in pages.values():
_mount_one_page(app, service_name, page_def)
logger.info("Auto-mounted %d defpages for %s", len(pages), service_name)
def mount_pages(bp: Any, service_name: str,
names: set[str] | list[str] | None = None) -> None:
"""Mount registered PageDef routes onto a Quart Blueprint.