Auto-mount defpages: eliminate Python route stubs across all 9 services
Some checks failed
Build and Deploy / build-and-deploy (push) Failing after 16s
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:
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user