Fix NIL leaking into Python service calls, add mobile navigation menu
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 4m10s

Strip NIL values at I/O primitive boundaries (frag, query, action, service)
to prevent _Nil objects from reaching Python code that expects None. Add
mobile_nav_sx() helper that auto-populates the hamburger menu from nav_tree
and auth_menu context fragments when no menu slot is provided.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-03 10:45:52 +00:00
parent a8c0741f54
commit 5b4cacaf19
2 changed files with 38 additions and 5 deletions

View File

@@ -107,6 +107,12 @@ async def execute_io(
# Individual handlers
# ---------------------------------------------------------------------------
def _clean_kwargs(kwargs: dict[str, Any]) -> dict[str, Any]:
"""Strip None and NIL values from kwargs for Python interop."""
from .types import NIL
return {k: v for k, v in kwargs.items() if v is not None and v is not NIL}
async def _io_frag(
args: list[Any], kwargs: dict[str, Any], ctx: RequestContext
) -> str:
@@ -115,7 +121,7 @@ async def _io_frag(
raise ValueError("frag requires service and fragment type")
service = str(args[0])
frag_type = str(args[1])
params = {k: v for k, v in kwargs.items() if v is not None}
params = _clean_kwargs(kwargs)
from shared.infrastructure.fragments import fetch_fragment
return await fetch_fragment(service, frag_type, params=params or None)
@@ -129,7 +135,7 @@ async def _io_query(
raise ValueError("query requires service and query name")
service = str(args[0])
query_name = str(args[1])
params = {k: v for k, v in kwargs.items() if v is not None}
params = _clean_kwargs(kwargs)
from shared.infrastructure.data_client import fetch_data
return await fetch_data(service, query_name, params=params or None)
@@ -143,7 +149,7 @@ async def _io_action(
raise ValueError("action requires service and action name")
service = str(args[0])
action_name = str(args[1])
payload = {k: v for k, v in kwargs.items() if v is not None}
payload = _clean_kwargs(kwargs)
from shared.infrastructure.actions import call_action
return await call_action(service, action_name, payload=payload or None)
@@ -195,8 +201,12 @@ async def _io_service(
if method is None:
raise RuntimeError(f"Service has no method: {method_name}")
# Convert kwarg keys from kebab-case to snake_case
clean_kwargs = {k.replace("-", "_"): v for k, v in kwargs.items()}
# Convert kwarg keys from kebab-case to snake_case, NIL → None
from .types import NIL
clean_kwargs = {
k.replace("-", "_"): (None if v is NIL else v)
for k, v in kwargs.items()
}
from quart import g
result = await method(g.s, **clean_kwargs)