Auto-mount fragment handlers: eliminate fragment blueprint boilerplate across all 8 services
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 16m38s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 16m38s
Fragment read API is now fully declarative — every handler is a defhandler s-expression dispatched through one shared auto_mount_fragment_handlers() function. Replaces 8 near-identical blueprint files (~35 lines each) with a single function call per service. Events Python handlers (container-cards, account-page) extracted to a standalone module. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -8,7 +8,7 @@ from jinja2 import FileSystemLoader, ChoiceLoader
|
|||||||
|
|
||||||
from shared.infrastructure.factory import create_base_app
|
from shared.infrastructure.factory import create_base_app
|
||||||
|
|
||||||
from bp import register_account_bp, register_auth_bp, register_fragments
|
from bp import register_account_bp, register_auth_bp
|
||||||
|
|
||||||
|
|
||||||
async def account_context() -> dict:
|
async def account_context() -> dict:
|
||||||
@@ -86,7 +86,8 @@ def create_app() -> "Quart":
|
|||||||
from shared.sx.pages import auto_mount_pages
|
from shared.sx.pages import auto_mount_pages
|
||||||
auto_mount_pages(app, "account")
|
auto_mount_pages(app, "account")
|
||||||
|
|
||||||
app.register_blueprint(register_fragments())
|
from shared.sx.handlers import auto_mount_fragment_handlers
|
||||||
|
auto_mount_fragment_handlers(app, "account")
|
||||||
|
|
||||||
from bp.actions.routes import register as register_actions
|
from bp.actions.routes import register as register_actions
|
||||||
app.register_blueprint(register_actions())
|
app.register_blueprint(register_actions())
|
||||||
|
|||||||
@@ -1,3 +1,2 @@
|
|||||||
from .account.routes import register as register_account_bp
|
from .account.routes import register as register_account_bp
|
||||||
from .auth.routes import register as register_auth_bp
|
from .auth.routes import register as register_auth_bp
|
||||||
from .fragments import register_fragments
|
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
from .routes import register as register_fragments
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
"""Account app fragment endpoints.
|
|
||||||
|
|
||||||
Exposes sx fragments at ``/internal/fragments/<type>`` for consumption
|
|
||||||
by other coop apps via the fragment client.
|
|
||||||
|
|
||||||
All handlers are defined declaratively in .sx files under
|
|
||||||
``account/sx/handlers/`` and dispatched via the sx handler registry.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from quart import Blueprint, Response, request
|
|
||||||
|
|
||||||
from shared.infrastructure.fragments import FRAGMENT_HEADER
|
|
||||||
from shared.sx.handlers import get_handler, execute_handler
|
|
||||||
|
|
||||||
|
|
||||||
def register():
|
|
||||||
bp = Blueprint("fragments", __name__, url_prefix="/internal/fragments")
|
|
||||||
|
|
||||||
@bp.before_request
|
|
||||||
async def _require_fragment_header():
|
|
||||||
if not request.headers.get(FRAGMENT_HEADER):
|
|
||||||
return Response("", status=403)
|
|
||||||
|
|
||||||
@bp.get("/<fragment_type>")
|
|
||||||
async def get_fragment(fragment_type: str):
|
|
||||||
handler_def = get_handler("account", fragment_type)
|
|
||||||
if handler_def is not None:
|
|
||||||
result = await execute_handler(
|
|
||||||
handler_def, "account", args=dict(request.args),
|
|
||||||
)
|
|
||||||
return Response(result, status=200, content_type="text/sx")
|
|
||||||
return Response("", status=200, content_type="text/sx")
|
|
||||||
|
|
||||||
return bp
|
|
||||||
@@ -16,7 +16,6 @@ from bp import (
|
|||||||
register_admin,
|
register_admin,
|
||||||
register_menu_items,
|
register_menu_items,
|
||||||
register_snippets,
|
register_snippets,
|
||||||
register_fragments,
|
|
||||||
register_data,
|
register_data,
|
||||||
register_actions,
|
register_actions,
|
||||||
)
|
)
|
||||||
@@ -108,7 +107,9 @@ def create_app() -> "Quart":
|
|||||||
app.register_blueprint(register_admin("/settings"))
|
app.register_blueprint(register_admin("/settings"))
|
||||||
app.register_blueprint(register_menu_items())
|
app.register_blueprint(register_menu_items())
|
||||||
app.register_blueprint(register_snippets())
|
app.register_blueprint(register_snippets())
|
||||||
app.register_blueprint(register_fragments())
|
from shared.sx.handlers import auto_mount_fragment_handlers
|
||||||
|
auto_mount_fragment_handlers(app, "blog")
|
||||||
|
|
||||||
app.register_blueprint(register_data())
|
app.register_blueprint(register_data())
|
||||||
app.register_blueprint(register_actions())
|
app.register_blueprint(register_actions())
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,5 @@ from .blog.routes import register as register_blog_bp
|
|||||||
from .admin.routes import register as register_admin
|
from .admin.routes import register as register_admin
|
||||||
from .menu_items.routes import register as register_menu_items
|
from .menu_items.routes import register as register_menu_items
|
||||||
from .snippets.routes import register as register_snippets
|
from .snippets.routes import register as register_snippets
|
||||||
from .fragments import register_fragments
|
|
||||||
from .data import register_data
|
from .data import register_data
|
||||||
from .actions.routes import register as register_actions
|
from .actions.routes import register as register_actions
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
from .routes import register as register_fragments
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
"""Blog app fragment endpoints.
|
|
||||||
|
|
||||||
Exposes sx fragments at ``/internal/fragments/<type>`` for consumption
|
|
||||||
by other coop apps via the fragment client.
|
|
||||||
|
|
||||||
All handlers are defined declaratively in .sx files under
|
|
||||||
``blog/sx/handlers/`` and dispatched via the sx handler registry.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from quart import Blueprint, Response, request
|
|
||||||
|
|
||||||
from shared.infrastructure.fragments import FRAGMENT_HEADER
|
|
||||||
from shared.sx.handlers import get_handler, execute_handler
|
|
||||||
|
|
||||||
|
|
||||||
def register():
|
|
||||||
bp = Blueprint("fragments", __name__, url_prefix="/internal/fragments")
|
|
||||||
|
|
||||||
@bp.before_request
|
|
||||||
async def _require_fragment_header():
|
|
||||||
if not request.headers.get(FRAGMENT_HEADER):
|
|
||||||
return Response("", status=403)
|
|
||||||
|
|
||||||
@bp.get("/<fragment_type>")
|
|
||||||
async def get_fragment(fragment_type: str):
|
|
||||||
handler_def = get_handler("blog", fragment_type)
|
|
||||||
if handler_def is not None:
|
|
||||||
result = await execute_handler(
|
|
||||||
handler_def, "blog", args=dict(request.args),
|
|
||||||
)
|
|
||||||
return Response(result, status=200, content_type="text/sx")
|
|
||||||
return Response("", status=200, content_type="text/sx")
|
|
||||||
|
|
||||||
return bp
|
|
||||||
@@ -17,7 +17,6 @@ from bp import (
|
|||||||
register_page_cart,
|
register_page_cart,
|
||||||
register_cart_global,
|
register_cart_global,
|
||||||
register_page_admin,
|
register_page_admin,
|
||||||
register_fragments,
|
|
||||||
register_actions,
|
register_actions,
|
||||||
register_data,
|
register_data,
|
||||||
register_inbox,
|
register_inbox,
|
||||||
@@ -141,7 +140,9 @@ def create_app() -> "Quart":
|
|||||||
app.jinja_env.globals["cart_quantity_url"] = lambda product_id: f"/quantity/{product_id}/"
|
app.jinja_env.globals["cart_quantity_url"] = lambda product_id: f"/quantity/{product_id}/"
|
||||||
app.jinja_env.globals["cart_delete_url"] = lambda product_id: f"/delete/{product_id}/"
|
app.jinja_env.globals["cart_delete_url"] = lambda product_id: f"/delete/{product_id}/"
|
||||||
|
|
||||||
app.register_blueprint(register_fragments())
|
from shared.sx.handlers import auto_mount_fragment_handlers
|
||||||
|
auto_mount_fragment_handlers(app, "cart")
|
||||||
|
|
||||||
app.register_blueprint(register_actions())
|
app.register_blueprint(register_actions())
|
||||||
app.register_blueprint(register_data())
|
app.register_blueprint(register_data())
|
||||||
app.register_blueprint(register_inbox())
|
app.register_blueprint(register_inbox())
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ from .cart.overview_routes import register as register_cart_overview
|
|||||||
from .cart.page_routes import register as register_page_cart
|
from .cart.page_routes import register as register_page_cart
|
||||||
from .cart.global_routes import register as register_cart_global
|
from .cart.global_routes import register as register_cart_global
|
||||||
from .page_admin.routes import register as register_page_admin
|
from .page_admin.routes import register as register_page_admin
|
||||||
from .fragments import register_fragments
|
|
||||||
from .actions import register_actions
|
from .actions import register_actions
|
||||||
from .data import register_data
|
from .data import register_data
|
||||||
from .inbox import register_inbox
|
from .inbox import register_inbox
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
from .routes import register as register_fragments
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
"""Cart app fragment endpoints.
|
|
||||||
|
|
||||||
Exposes sx fragments at ``/internal/fragments/<type>`` for consumption
|
|
||||||
by other coop apps via the fragment client.
|
|
||||||
|
|
||||||
All handlers are defined declaratively in .sx files under
|
|
||||||
``cart/sx/handlers/`` and dispatched via the sx handler registry.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from quart import Blueprint, Response, request
|
|
||||||
|
|
||||||
from shared.infrastructure.fragments import FRAGMENT_HEADER
|
|
||||||
from shared.sx.handlers import get_handler, execute_handler
|
|
||||||
|
|
||||||
|
|
||||||
def register():
|
|
||||||
bp = Blueprint("fragments", __name__, url_prefix="/internal/fragments")
|
|
||||||
|
|
||||||
@bp.before_request
|
|
||||||
async def _require_fragment_header():
|
|
||||||
if not request.headers.get(FRAGMENT_HEADER):
|
|
||||||
return Response("", status=403)
|
|
||||||
|
|
||||||
@bp.get("/<fragment_type>")
|
|
||||||
async def get_fragment(fragment_type: str):
|
|
||||||
handler_def = get_handler("cart", fragment_type)
|
|
||||||
if handler_def is not None:
|
|
||||||
result = await execute_handler(
|
|
||||||
handler_def, "cart", args=dict(request.args),
|
|
||||||
)
|
|
||||||
return Response(result, status=200, content_type="text/sx")
|
|
||||||
return Response("", status=200, content_type="text/sx")
|
|
||||||
|
|
||||||
return bp
|
|
||||||
@@ -9,7 +9,7 @@ from jinja2 import FileSystemLoader, ChoiceLoader
|
|||||||
|
|
||||||
from shared.infrastructure.factory import create_base_app
|
from shared.infrastructure.factory import create_base_app
|
||||||
|
|
||||||
from bp import register_all_events, register_calendar, register_calendars, register_markets, register_page, register_fragments, register_actions, register_data
|
from bp import register_all_events, register_calendar, register_calendars, register_markets, register_page, register_actions, register_data
|
||||||
|
|
||||||
|
|
||||||
async def events_context() -> dict:
|
async def events_context() -> dict:
|
||||||
@@ -112,7 +112,12 @@ def create_app() -> "Quart":
|
|||||||
url_prefix="/<slug>/markets",
|
url_prefix="/<slug>/markets",
|
||||||
)
|
)
|
||||||
|
|
||||||
app.register_blueprint(register_fragments())
|
from shared.sx.handlers import auto_mount_fragment_handlers
|
||||||
|
from bp.fragments.python_handlers import container_cards_handler, account_page_handler
|
||||||
|
add_fragment_handler = auto_mount_fragment_handlers(app, "events")
|
||||||
|
add_fragment_handler("container-cards", container_cards_handler, content_type="text/html")
|
||||||
|
add_fragment_handler("account-page", account_page_handler)
|
||||||
|
|
||||||
app.register_blueprint(register_actions())
|
app.register_blueprint(register_actions())
|
||||||
app.register_blueprint(register_data())
|
app.register_blueprint(register_data())
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,5 @@ from .calendar.routes import register as register_calendar
|
|||||||
from .calendars.routes import register as register_calendars
|
from .calendars.routes import register as register_calendars
|
||||||
from .markets.routes import register as register_markets
|
from .markets.routes import register as register_markets
|
||||||
from .page.routes import register as register_page
|
from .page.routes import register as register_page
|
||||||
from .fragments import register_fragments
|
|
||||||
from .actions import register_actions
|
from .actions import register_actions
|
||||||
from .data import register_data
|
from .data import register_data
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
from .routes import register as register_fragments
|
|
||||||
|
|||||||
58
events/bp/fragments/python_handlers.py
Normal file
58
events/bp/fragments/python_handlers.py
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
"""Python fragment handlers for events.
|
||||||
|
|
||||||
|
These handlers call domain services and use sx_call() for rendering,
|
||||||
|
so they can't be expressed as declarative .sx handlers.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from quart import g, request
|
||||||
|
|
||||||
|
from shared.services.registry import services
|
||||||
|
|
||||||
|
|
||||||
|
async def container_cards_handler():
|
||||||
|
"""Container-cards fragment: entries for blog listing cards.
|
||||||
|
|
||||||
|
Returns text/html with <!-- card-widget:POST_ID --> comment markers
|
||||||
|
so the blog consumer can split per-post fragments.
|
||||||
|
"""
|
||||||
|
from sx.sx_components import render_fragment_container_cards
|
||||||
|
|
||||||
|
post_ids_raw = request.args.get("post_ids", "")
|
||||||
|
post_slugs_raw = request.args.get("post_slugs", "")
|
||||||
|
post_ids = [int(x) for x in post_ids_raw.split(",") if x.strip()]
|
||||||
|
post_slugs = [x.strip() for x in post_slugs_raw.split(",") if x.strip()]
|
||||||
|
if not post_ids:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
slug_map = {}
|
||||||
|
for i, pid in enumerate(post_ids):
|
||||||
|
slug_map[pid] = post_slugs[i] if i < len(post_slugs) else ""
|
||||||
|
|
||||||
|
batch = await services.calendar.confirmed_entries_for_posts(g.s, post_ids)
|
||||||
|
return render_fragment_container_cards(batch, post_ids, slug_map)
|
||||||
|
|
||||||
|
|
||||||
|
async def account_page_handler():
|
||||||
|
"""Account-page fragment: tickets or bookings panel.
|
||||||
|
|
||||||
|
Returns text/sx — the account app embeds this as sx source.
|
||||||
|
"""
|
||||||
|
from sx.sx_components import (
|
||||||
|
render_fragment_account_tickets,
|
||||||
|
render_fragment_account_bookings,
|
||||||
|
)
|
||||||
|
|
||||||
|
slug = request.args.get("slug", "")
|
||||||
|
user_id = request.args.get("user_id", type=int)
|
||||||
|
if not user_id:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
if slug == "tickets":
|
||||||
|
tickets = await services.calendar.user_tickets(g.s, user_id=user_id)
|
||||||
|
return render_fragment_account_tickets(tickets)
|
||||||
|
elif slug == "bookings":
|
||||||
|
bookings = await services.calendar.user_bookings(g.s, user_id=user_id)
|
||||||
|
return render_fragment_account_bookings(bookings)
|
||||||
|
return ""
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
"""Events app fragment endpoints.
|
|
||||||
|
|
||||||
Exposes sx fragments at ``/internal/fragments/<type>`` for consumption
|
|
||||||
by other coop apps via the fragment client.
|
|
||||||
|
|
||||||
All handlers are defined declaratively in .sx files under
|
|
||||||
``events/sx/handlers/`` and dispatched via the sx handler registry.
|
|
||||||
|
|
||||||
container-cards and account-page remain as Python handlers because they
|
|
||||||
call domain service methods and return batched/conditional content, but
|
|
||||||
they use sx_call() for rendering (no Jinja templates).
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from quart import Blueprint, Response, g, request
|
|
||||||
|
|
||||||
from shared.infrastructure.fragments import FRAGMENT_HEADER
|
|
||||||
from shared.services.registry import services
|
|
||||||
from shared.sx.handlers import get_handler, execute_handler
|
|
||||||
|
|
||||||
|
|
||||||
def register():
|
|
||||||
bp = Blueprint("fragments", __name__, url_prefix="/internal/fragments")
|
|
||||||
|
|
||||||
_handlers: dict[str, object] = {}
|
|
||||||
|
|
||||||
# Fragment types that return HTML (comment-delimited batch)
|
|
||||||
_html_types = {"container-cards"}
|
|
||||||
|
|
||||||
@bp.before_request
|
|
||||||
async def _require_fragment_header():
|
|
||||||
if not request.headers.get(FRAGMENT_HEADER):
|
|
||||||
return Response("", status=403)
|
|
||||||
|
|
||||||
@bp.get("/<fragment_type>")
|
|
||||||
async def get_fragment(fragment_type: str):
|
|
||||||
# 1. Check Python handlers first
|
|
||||||
handler = _handlers.get(fragment_type)
|
|
||||||
if handler is not None:
|
|
||||||
result = await handler()
|
|
||||||
ct = "text/html" if fragment_type in _html_types else "text/sx"
|
|
||||||
return Response(result, status=200, content_type=ct)
|
|
||||||
|
|
||||||
# 2. Check sx handler registry
|
|
||||||
handler_def = get_handler("events", fragment_type)
|
|
||||||
if handler_def is not None:
|
|
||||||
result = await execute_handler(
|
|
||||||
handler_def, "events", args=dict(request.args),
|
|
||||||
)
|
|
||||||
return Response(result, status=200, content_type="text/sx")
|
|
||||||
|
|
||||||
return Response("", status=200, content_type="text/sx")
|
|
||||||
|
|
||||||
# --- container-cards fragment: entries for blog listing cards -----------
|
|
||||||
# Returns text/html with <!-- card-widget:POST_ID --> comment markers
|
|
||||||
# so the blog consumer can split per-post fragments.
|
|
||||||
|
|
||||||
async def _container_cards_handler():
|
|
||||||
from sx.sx_components import render_fragment_container_cards
|
|
||||||
|
|
||||||
post_ids_raw = request.args.get("post_ids", "")
|
|
||||||
post_slugs_raw = request.args.get("post_slugs", "")
|
|
||||||
post_ids = [int(x) for x in post_ids_raw.split(",") if x.strip()]
|
|
||||||
post_slugs = [x.strip() for x in post_slugs_raw.split(",") if x.strip()]
|
|
||||||
if not post_ids:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
slug_map = {}
|
|
||||||
for i, pid in enumerate(post_ids):
|
|
||||||
slug_map[pid] = post_slugs[i] if i < len(post_slugs) else ""
|
|
||||||
|
|
||||||
batch = await services.calendar.confirmed_entries_for_posts(g.s, post_ids)
|
|
||||||
return render_fragment_container_cards(batch, post_ids, slug_map)
|
|
||||||
|
|
||||||
_handlers["container-cards"] = _container_cards_handler
|
|
||||||
|
|
||||||
# --- account-page fragment: tickets or bookings panel ------------------
|
|
||||||
# Returns text/sx — the account app embeds this as sx source.
|
|
||||||
|
|
||||||
async def _account_page_handler():
|
|
||||||
from sx.sx_components import (
|
|
||||||
render_fragment_account_tickets,
|
|
||||||
render_fragment_account_bookings,
|
|
||||||
)
|
|
||||||
|
|
||||||
slug = request.args.get("slug", "")
|
|
||||||
user_id = request.args.get("user_id", type=int)
|
|
||||||
if not user_id:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
if slug == "tickets":
|
|
||||||
tickets = await services.calendar.user_tickets(g.s, user_id=user_id)
|
|
||||||
return render_fragment_account_tickets(tickets)
|
|
||||||
elif slug == "bookings":
|
|
||||||
bookings = await services.calendar.user_bookings(g.s, user_id=user_id)
|
|
||||||
return render_fragment_account_bookings(bookings)
|
|
||||||
return ""
|
|
||||||
|
|
||||||
_handlers["account-page"] = _account_page_handler
|
|
||||||
|
|
||||||
bp._fragment_handlers = _handlers
|
|
||||||
|
|
||||||
return bp
|
|
||||||
@@ -12,7 +12,6 @@ from shared.services.registry import services
|
|||||||
from bp import (
|
from bp import (
|
||||||
register_identity_bp,
|
register_identity_bp,
|
||||||
register_social_bp,
|
register_social_bp,
|
||||||
register_fragments,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -99,7 +98,8 @@ def create_app() -> "Quart":
|
|||||||
from shared.sx.pages import auto_mount_pages
|
from shared.sx.pages import auto_mount_pages
|
||||||
auto_mount_pages(app, "federation")
|
auto_mount_pages(app, "federation")
|
||||||
|
|
||||||
app.register_blueprint(register_fragments())
|
from shared.sx.handlers import auto_mount_fragment_handlers
|
||||||
|
auto_mount_fragment_handlers(app, "federation")
|
||||||
|
|
||||||
# --- home page ---
|
# --- home page ---
|
||||||
@app.get("/")
|
@app.get("/")
|
||||||
|
|||||||
@@ -1,3 +1,2 @@
|
|||||||
from .identity.routes import register as register_identity_bp
|
from .identity.routes import register as register_identity_bp
|
||||||
from .social.routes import register as register_social_bp
|
from .social.routes import register as register_social_bp
|
||||||
from .fragments import register_fragments
|
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
from .routes import register as register_fragments
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
"""Federation app fragment endpoints.
|
|
||||||
|
|
||||||
Exposes sx fragments at ``/internal/fragments/<type>`` for consumption
|
|
||||||
by other coop apps via the fragment client.
|
|
||||||
|
|
||||||
All handlers are defined declaratively in .sx files under
|
|
||||||
``federation/sx/handlers/`` and dispatched via the sx handler registry.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from quart import Blueprint, Response, request
|
|
||||||
|
|
||||||
from shared.infrastructure.fragments import FRAGMENT_HEADER
|
|
||||||
from shared.sx.handlers import get_handler, execute_handler
|
|
||||||
|
|
||||||
|
|
||||||
def register():
|
|
||||||
bp = Blueprint("fragments", __name__, url_prefix="/internal/fragments")
|
|
||||||
|
|
||||||
@bp.before_request
|
|
||||||
async def _require_fragment_header():
|
|
||||||
if not request.headers.get(FRAGMENT_HEADER):
|
|
||||||
return Response("", status=403)
|
|
||||||
|
|
||||||
@bp.get("/<fragment_type>")
|
|
||||||
async def get_fragment(fragment_type: str):
|
|
||||||
handler_def = get_handler("federation", fragment_type)
|
|
||||||
if handler_def is not None:
|
|
||||||
result = await execute_handler(
|
|
||||||
handler_def, "federation", args=dict(request.args),
|
|
||||||
)
|
|
||||||
return Response(result, status=200, content_type="text/sx")
|
|
||||||
return Response("", status=200, content_type="text/sx")
|
|
||||||
|
|
||||||
return bp
|
|
||||||
@@ -11,7 +11,7 @@ from sqlalchemy import select
|
|||||||
from shared.infrastructure.factory import create_base_app
|
from shared.infrastructure.factory import create_base_app
|
||||||
from shared.config import config
|
from shared.config import config
|
||||||
|
|
||||||
from bp import register_market_bp, register_all_markets, register_page_markets, register_page_admin, register_fragments, register_actions, register_data
|
from bp import register_market_bp, register_all_markets, register_page_markets, register_page_admin, register_actions, register_data
|
||||||
|
|
||||||
|
|
||||||
async def market_context() -> dict:
|
async def market_context() -> dict:
|
||||||
@@ -126,7 +126,9 @@ def create_app() -> "Quart":
|
|||||||
url_prefix="/<page_slug>/<market_slug>",
|
url_prefix="/<page_slug>/<market_slug>",
|
||||||
)
|
)
|
||||||
|
|
||||||
app.register_blueprint(register_fragments())
|
from shared.sx.handlers import auto_mount_fragment_handlers
|
||||||
|
auto_mount_fragment_handlers(app, "market")
|
||||||
|
|
||||||
app.register_blueprint(register_actions())
|
app.register_blueprint(register_actions())
|
||||||
app.register_blueprint(register_data())
|
app.register_blueprint(register_data())
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,5 @@ from .product.routes import register as register_product
|
|||||||
from .all_markets.routes import register as register_all_markets
|
from .all_markets.routes import register as register_all_markets
|
||||||
from .page_markets.routes import register as register_page_markets
|
from .page_markets.routes import register as register_page_markets
|
||||||
from .page_admin.routes import register as register_page_admin
|
from .page_admin.routes import register as register_page_admin
|
||||||
from .fragments import register_fragments
|
|
||||||
from .actions import register_actions
|
from .actions import register_actions
|
||||||
from .data import register_data
|
from .data import register_data
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
from .routes import register as register_fragments
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
"""Market app fragment endpoints.
|
|
||||||
|
|
||||||
Exposes sx fragments at ``/internal/fragments/<type>`` for consumption
|
|
||||||
by other coop apps via the fragment client.
|
|
||||||
|
|
||||||
All handlers are defined declaratively in .sx files under
|
|
||||||
``market/sx/handlers/`` and dispatched via the sx handler registry.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from quart import Blueprint, Response, request
|
|
||||||
|
|
||||||
from shared.infrastructure.fragments import FRAGMENT_HEADER
|
|
||||||
from shared.sx.handlers import get_handler, execute_handler
|
|
||||||
|
|
||||||
|
|
||||||
def register():
|
|
||||||
bp = Blueprint("fragments", __name__, url_prefix="/internal/fragments")
|
|
||||||
|
|
||||||
@bp.before_request
|
|
||||||
async def _require_fragment_header():
|
|
||||||
if not request.headers.get(FRAGMENT_HEADER):
|
|
||||||
return Response("", status=403)
|
|
||||||
|
|
||||||
@bp.get("/<fragment_type>")
|
|
||||||
async def get_fragment(fragment_type: str):
|
|
||||||
handler_def = get_handler("market", fragment_type)
|
|
||||||
if handler_def is not None:
|
|
||||||
result = await execute_handler(
|
|
||||||
handler_def, "market", args=dict(request.args),
|
|
||||||
)
|
|
||||||
return Response(result, status=200, content_type="text/sx")
|
|
||||||
return Response("", status=200, content_type="text/sx")
|
|
||||||
|
|
||||||
return bp
|
|
||||||
@@ -14,7 +14,6 @@ from bp import (
|
|||||||
register_orders,
|
register_orders,
|
||||||
register_order,
|
register_order,
|
||||||
register_checkout,
|
register_checkout,
|
||||||
register_fragments,
|
|
||||||
register_actions,
|
register_actions,
|
||||||
register_data,
|
register_data,
|
||||||
)
|
)
|
||||||
@@ -77,7 +76,9 @@ def create_app() -> "Quart":
|
|||||||
from sxc.pages import setup_orders_pages
|
from sxc.pages import setup_orders_pages
|
||||||
setup_orders_pages()
|
setup_orders_pages()
|
||||||
|
|
||||||
app.register_blueprint(register_fragments())
|
from shared.sx.handlers import auto_mount_fragment_handlers
|
||||||
|
auto_mount_fragment_handlers(app, "orders")
|
||||||
|
|
||||||
app.register_blueprint(register_actions())
|
app.register_blueprint(register_actions())
|
||||||
app.register_blueprint(register_data())
|
app.register_blueprint(register_data())
|
||||||
|
|
||||||
|
|||||||
@@ -3,4 +3,3 @@ from .orders.routes import register as register_orders
|
|||||||
from .checkout.routes import register as register_checkout
|
from .checkout.routes import register as register_checkout
|
||||||
from .data.routes import register as register_data
|
from .data.routes import register as register_data
|
||||||
from .actions.routes import register as register_actions
|
from .actions.routes import register as register_actions
|
||||||
from .fragments.routes import register as register_fragments
|
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
"""Orders app fragment endpoints.
|
|
||||||
|
|
||||||
Exposes sx fragments at ``/internal/fragments/<type>`` for consumption
|
|
||||||
by other coop apps via the fragment client.
|
|
||||||
|
|
||||||
All handlers are defined declaratively in .sx files under
|
|
||||||
``orders/sx/handlers/`` and dispatched via the sx handler registry.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from quart import Blueprint, Response, request
|
|
||||||
|
|
||||||
from shared.infrastructure.fragments import FRAGMENT_HEADER
|
|
||||||
from shared.sx.handlers import get_handler, execute_handler
|
|
||||||
|
|
||||||
|
|
||||||
def register():
|
|
||||||
bp = Blueprint("fragments", __name__, url_prefix="/internal/fragments")
|
|
||||||
|
|
||||||
@bp.before_request
|
|
||||||
async def _require_fragment_header():
|
|
||||||
if not request.headers.get(FRAGMENT_HEADER):
|
|
||||||
return Response("", status=403)
|
|
||||||
|
|
||||||
@bp.get("/<fragment_type>")
|
|
||||||
async def get_fragment(fragment_type: str):
|
|
||||||
handler_def = get_handler("orders", fragment_type)
|
|
||||||
if handler_def is not None:
|
|
||||||
result = await execute_handler(
|
|
||||||
handler_def, "orders", args=dict(request.args),
|
|
||||||
)
|
|
||||||
return Response(result, status=200, content_type="text/sx")
|
|
||||||
return Response("", status=200, content_type="text/sx")
|
|
||||||
|
|
||||||
return bp
|
|
||||||
@@ -4,7 +4,7 @@ import sx.sx_components as sx_components # noqa: F401 # ensure Hypercorn --rel
|
|||||||
|
|
||||||
from shared.infrastructure.factory import create_base_app
|
from shared.infrastructure.factory import create_base_app
|
||||||
|
|
||||||
from bp import register_actions, register_data, register_fragments
|
from bp import register_actions, register_data
|
||||||
from services import register_domain_services
|
from services import register_domain_services
|
||||||
|
|
||||||
|
|
||||||
@@ -16,7 +16,9 @@ def create_app() -> "Quart":
|
|||||||
|
|
||||||
app.register_blueprint(register_actions())
|
app.register_blueprint(register_actions())
|
||||||
app.register_blueprint(register_data())
|
app.register_blueprint(register_data())
|
||||||
app.register_blueprint(register_fragments())
|
|
||||||
|
from shared.sx.handlers import auto_mount_fragment_handlers
|
||||||
|
auto_mount_fragment_handlers(app, "relations")
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,2 @@
|
|||||||
from .data.routes import register as register_data
|
from .data.routes import register as register_data
|
||||||
from .actions.routes import register as register_actions
|
from .actions.routes import register as register_actions
|
||||||
from .fragments.routes import register as register_fragments
|
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
"""Relations app fragment endpoints.
|
|
||||||
|
|
||||||
Exposes sx fragments at ``/internal/fragments/<type>`` for consumption
|
|
||||||
by other coop apps via the fragment client.
|
|
||||||
|
|
||||||
All handlers are defined declaratively in .sx files under
|
|
||||||
``relations/sx/handlers/`` and dispatched via the sx handler registry.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from quart import Blueprint, Response, request
|
|
||||||
|
|
||||||
from shared.infrastructure.fragments import FRAGMENT_HEADER
|
|
||||||
from shared.sx.handlers import get_handler, execute_handler
|
|
||||||
|
|
||||||
|
|
||||||
def register():
|
|
||||||
bp = Blueprint("fragments", __name__, url_prefix="/internal/fragments")
|
|
||||||
|
|
||||||
@bp.before_request
|
|
||||||
async def _require_fragment_header():
|
|
||||||
if not request.headers.get(FRAGMENT_HEADER):
|
|
||||||
return Response("", status=403)
|
|
||||||
|
|
||||||
@bp.get("/<fragment_type>")
|
|
||||||
async def get_fragment(fragment_type: str):
|
|
||||||
handler_def = get_handler("relations", fragment_type)
|
|
||||||
if handler_def is not None:
|
|
||||||
result = await execute_handler(
|
|
||||||
handler_def, "relations", args=dict(request.args),
|
|
||||||
)
|
|
||||||
return Response(result, status=200, content_type="text/sx")
|
|
||||||
return Response("", status=200, content_type="text/sx")
|
|
||||||
|
|
||||||
return bp
|
|
||||||
@@ -204,3 +204,42 @@ def create_handler_blueprint(service_name: str) -> Any:
|
|||||||
bp._python_handlers = _python_handlers # type: ignore[attr-defined]
|
bp._python_handlers = _python_handlers # type: ignore[attr-defined]
|
||||||
|
|
||||||
return bp
|
return bp
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Direct app mount — replaces per-service fragment blueprint boilerplate
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def auto_mount_fragment_handlers(app: Any, service_name: str) -> Callable:
|
||||||
|
"""Mount ``/internal/fragments/<type>`` directly on the app.
|
||||||
|
|
||||||
|
Returns an ``add_handler(name, fn, content_type)`` function for
|
||||||
|
registering Python handler overrides (checked before SX handlers).
|
||||||
|
"""
|
||||||
|
from quart import Response, request
|
||||||
|
from shared.infrastructure.fragments import FRAGMENT_HEADER
|
||||||
|
|
||||||
|
python_handlers: dict[str, Callable[[], Awaitable[str]]] = {}
|
||||||
|
html_types: set[str] = set()
|
||||||
|
|
||||||
|
@app.get("/internal/fragments/<fragment_type>")
|
||||||
|
async def _fragment_dispatch(fragment_type: str):
|
||||||
|
if not request.headers.get(FRAGMENT_HEADER):
|
||||||
|
return Response("", status=403)
|
||||||
|
py = python_handlers.get(fragment_type)
|
||||||
|
if py is not None:
|
||||||
|
result = await py()
|
||||||
|
ct = "text/html" if fragment_type in html_types else "text/sx"
|
||||||
|
return Response(result, status=200, content_type=ct)
|
||||||
|
hdef = get_handler(service_name, fragment_type)
|
||||||
|
if hdef is not None:
|
||||||
|
result = await execute_handler(hdef, service_name, args=dict(request.args))
|
||||||
|
return Response(result, status=200, content_type="text/sx")
|
||||||
|
return Response("", status=200, content_type="text/sx")
|
||||||
|
|
||||||
|
def add_handler(name: str, fn: Callable[[], Awaitable[str]], content_type: str = "text/sx") -> None:
|
||||||
|
python_handlers[name] = fn
|
||||||
|
if content_type == "text/html":
|
||||||
|
html_types.add(name)
|
||||||
|
|
||||||
|
return add_handler
|
||||||
|
|||||||
Reference in New Issue
Block a user