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:
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user