feat: implement module-driven context providers for dynamic page context
Introduces a module-driven context provider system that allows modules to dynamically contribute template context variables without hardcoding imports. Key changes: - Add context_providers field to ModuleDefinition in app/modules/base.py - Create unified get_context_for_frontend() that queries enabled modules only - Add context providers to CMS module (PLATFORM, STOREFRONT) - Add context providers to billing module (PLATFORM) - Fix SQLAlchemy cross-module relationship resolution (Order, AdminMenuConfig, MarketplaceImportJob) by ensuring models are imported before referencing - Document the entire system in docs/architecture/module-system.md Benefits: - Zero coupling: adding/removing modules requires no route handler changes - Lazy loading: module code only imported when that module is enabled - Per-platform customization: each platform loads only what it needs - Graceful degradation: one failing module doesn't break entire page Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -371,6 +371,26 @@ class ModuleDefinition:
|
||||
tasks_path: str | None = None # Python import path, e.g., "app.modules.billing.tasks"
|
||||
scheduled_tasks: list[ScheduledTask] = field(default_factory=list)
|
||||
|
||||
# =========================================================================
|
||||
# Context Providers (Module-Driven Page Context)
|
||||
# =========================================================================
|
||||
# Callables that provide context data for page templates per frontend type.
|
||||
# Each provider receives (request, db, platform) and returns a dict.
|
||||
# This enables modules to contribute context only when enabled for a platform.
|
||||
#
|
||||
# Example:
|
||||
# def get_billing_platform_context(request, db, platform):
|
||||
# from app.modules.billing.models import TIER_LIMITS
|
||||
# return {"tiers": get_tiers_data()}
|
||||
#
|
||||
# billing_module = ModuleDefinition(
|
||||
# code="billing",
|
||||
# context_providers={
|
||||
# FrontendType.PLATFORM: get_billing_platform_context,
|
||||
# },
|
||||
# )
|
||||
context_providers: dict[FrontendType, Callable[..., dict[str, Any]]] = field(default_factory=dict)
|
||||
|
||||
# =========================================================================
|
||||
# Menu Item Methods (Legacy - uses menu_items dict of IDs)
|
||||
# =========================================================================
|
||||
@@ -695,6 +715,46 @@ class ModuleDefinition:
|
||||
else:
|
||||
return "optional"
|
||||
|
||||
# =========================================================================
|
||||
# Context Provider Methods
|
||||
# =========================================================================
|
||||
|
||||
def has_context_provider(self, frontend_type: FrontendType) -> bool:
|
||||
"""Check if this module has a context provider for a frontend type."""
|
||||
return frontend_type in self.context_providers
|
||||
|
||||
def get_context_contribution(
|
||||
self,
|
||||
frontend_type: FrontendType,
|
||||
request: Any,
|
||||
db: Any,
|
||||
platform: Any,
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Get context contribution from this module for a frontend type.
|
||||
|
||||
Args:
|
||||
frontend_type: The frontend type (PLATFORM, ADMIN, VENDOR, STOREFRONT)
|
||||
request: FastAPI Request object
|
||||
db: Database session
|
||||
platform: Platform object (may be None for some contexts)
|
||||
|
||||
Returns:
|
||||
Dict of context variables, or empty dict if no provider
|
||||
|
||||
Note:
|
||||
Exceptions are caught and logged by the caller. This method
|
||||
may raise if the provider fails.
|
||||
"""
|
||||
provider = self.context_providers.get(frontend_type)
|
||||
if provider is None:
|
||||
return {}
|
||||
return provider(request, db, platform)
|
||||
|
||||
def get_supported_frontend_types(self) -> list[FrontendType]:
|
||||
"""Get list of frontend types this module provides context for."""
|
||||
return list(self.context_providers.keys())
|
||||
|
||||
# =========================================================================
|
||||
# Magic Methods
|
||||
# =========================================================================
|
||||
|
||||
Reference in New Issue
Block a user