Make rich 404 resilient to cross-service failures

Build a minimal context directly instead of relying on
get_template_context() which runs the full context processor chain
including cross-service fragment fetches. Each step (base_context,
fragments, post hydration) is independently try/excepted so the page
renders with whatever is available.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-28 18:36:11 +00:00
parent 597b0d7a2f
commit f1b7fdd37d

View File

@@ -83,53 +83,72 @@ async def _rich_error_page(errnum: str, message: str, image: str | None = None)
return None
try:
# If the app's url_value_preprocessor didn't run (no route match),
# manually extract the first path segment as a candidate slug and
# try to hydrate post data so context processors can use it.
from shared.sexp.jinja_bridge import render
from shared.sexp.helpers import full_page, call_url
# Build a minimal context — avoid get_template_context() which
# calls cross-service fragment fetches that may fail.
from shared.infrastructure.context import base_context
try:
ctx = await base_context()
except Exception:
ctx = {"base_title": "Rose Ash", "asset_url": "/static"}
# Try to fetch fragments, but don't fail if they're unreachable
try:
from shared.infrastructure.fragments import fetch_fragments
from shared.infrastructure.cart_identity import current_cart_identity
user = getattr(g, "user", None)
ident = current_cart_identity()
cart_params = {}
if ident["user_id"] is not None:
cart_params["user_id"] = ident["user_id"]
if ident["session_id"] is not None:
cart_params["session_id"] = ident["session_id"]
cart_mini_html, auth_menu_html, nav_tree_html = await fetch_fragments([
("cart", "cart-mini", cart_params or None),
("account", "auth-menu", {"email": user.email} if user else None),
("blog", "nav-tree", {"app_name": current_app.name, "path": request.path}),
])
ctx["cart_mini_html"] = cart_mini_html
ctx["auth_menu_html"] = auth_menu_html
ctx["nav_tree_html"] = nav_tree_html
except Exception:
ctx.setdefault("cart_mini_html", "")
ctx.setdefault("auth_menu_html", "")
ctx.setdefault("nav_tree_html", "")
# Try to hydrate post data from slug if not already available
segments = [s for s in request.path.strip("/").split("/") if s]
slug = segments[0] if segments else None
post_data = getattr(g, "post_data", None)
if slug and not getattr(g, "post_data", None):
# Set g.post_slug / g.page_slug so app context processors
# (inject_post, etc.) can pick it up.
from shared.infrastructure.data_client import fetch_data
raw = await fetch_data(
"blog", "post-by-slug",
params={"slug": slug},
required=False,
)
if raw:
g.post_slug = slug
g.page_slug = slug # cart uses page_slug
g.post_data = {
"post": {
"id": raw["id"],
"title": raw["title"],
"slug": raw["slug"],
"feature_image": raw.get("feature_image"),
"status": raw["status"],
"visibility": raw["visibility"],
},
}
# Also set page_post for cart app
from shared.contracts.dtos import PostDTO, dto_from_dict
post_dto = dto_from_dict(PostDTO, raw)
if post_dto and getattr(post_dto, "is_page", False):
g.page_post = post_dto
# Build template context (runs app context processors → fragments)
from shared.sexp.page import get_template_context
ctx = await get_template_context()
# Build headers: root header + post header if available
from shared.sexp.jinja_bridge import render
from shared.sexp.helpers import (
root_header_html, call_url, full_page,
)
if slug and not post_data:
try:
from shared.infrastructure.data_client import fetch_data
raw = await fetch_data(
"blog", "post-by-slug",
params={"slug": slug},
required=False,
)
if raw:
post_data = {
"post": {
"id": raw["id"],
"title": raw["title"],
"slug": raw["slug"],
"feature_image": raw.get("feature_image"),
},
}
except Exception:
pass
# Root header (site nav bar)
from shared.sexp.helpers import root_header_html
hdr = root_header_html(ctx)
post = ctx.get("post") or {}
# Post breadcrumb if we resolved a post
post = (post_data or {}).get("post") or ctx.get("post") or {}
if post.get("slug"):
title = (post.get("title") or "")[:160]
feature_image = post.get("feature_image")