refactor: switch to full auto-discovery for module API routes
- Enhanced route discovery system with ROUTE_CONFIG support for custom
prefix, tags, and priority
- Added get_admin_api_routes() and get_vendor_api_routes() helpers that
return routes sorted by priority
- Added fallback discovery for routes/{frontend}.py when routes/api/
doesn't exist
- Updated CMS module with ROUTE_CONFIG (prefix: /content-pages,
priority: 100) to register last for catch-all routes
- Moved customers routes from routes/ to routes/api/ directory
- Updated orders module to aggregate exception routers into main routers
- Removed manual module router imports from admin and vendor API init
files, replaced with auto-discovery loop
Modules now auto-discovered: billing, inventory, orders, marketplace,
cms, customers, analytics, loyalty, messaging, monitoring, dev-tools
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -22,11 +22,20 @@ Usage:
|
||||
tags=route_info["tags"],
|
||||
include_in_schema=route_info.get("include_in_schema", True),
|
||||
)
|
||||
|
||||
Route Configuration:
|
||||
Modules can export a ROUTE_CONFIG dict to customize route registration:
|
||||
|
||||
ROUTE_CONFIG = {
|
||||
"prefix": "/content-pages", # Custom prefix (replaces default)
|
||||
"tags": ["admin-content-pages"], # Custom tags
|
||||
"priority": 100, # Higher = registered later (for catch-all routes)
|
||||
}
|
||||
"""
|
||||
|
||||
import importlib
|
||||
import logging
|
||||
from dataclasses import dataclass
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
@@ -47,6 +56,8 @@ class RouteInfo:
|
||||
module_code: str = ""
|
||||
route_type: str = "" # "api" or "pages"
|
||||
frontend: str = "" # "admin", "vendor", "shop"
|
||||
priority: int = 0 # Higher = registered later (for catch-all routes)
|
||||
custom_prefix: str = "" # Custom prefix from ROUTE_CONFIG
|
||||
|
||||
|
||||
def discover_module_routes() -> list[RouteInfo]:
|
||||
@@ -113,9 +124,10 @@ def _discover_module_routes(module_code: str) -> list[RouteInfo]:
|
||||
if not routes_path.exists():
|
||||
return routes
|
||||
|
||||
# Discover API routes
|
||||
# Discover API routes (with fallback to routes/ for legacy modules)
|
||||
api_routes = _discover_routes_in_dir(
|
||||
module_code, dir_name, routes_path / "api", "api"
|
||||
module_code, dir_name, routes_path / "api", "api",
|
||||
fallback_dir=routes_path # Allow routes/admin.py as fallback
|
||||
)
|
||||
routes.extend(api_routes)
|
||||
|
||||
@@ -129,7 +141,8 @@ def _discover_module_routes(module_code: str) -> list[RouteInfo]:
|
||||
|
||||
|
||||
def _discover_routes_in_dir(
|
||||
module_code: str, dir_name: str, routes_dir: Path, route_type: str
|
||||
module_code: str, dir_name: str, routes_dir: Path, route_type: str,
|
||||
fallback_dir: Path | None = None
|
||||
) -> list[RouteInfo]:
|
||||
"""
|
||||
Discover routes in a specific directory (api/ or pages/).
|
||||
@@ -139,15 +152,14 @@ def _discover_routes_in_dir(
|
||||
dir_name: Directory name (module_code with _ instead of -)
|
||||
routes_dir: Path to routes/api/ or routes/pages/
|
||||
route_type: "api" or "pages"
|
||||
fallback_dir: Optional fallback directory (e.g., routes/ for modules
|
||||
with routes/admin.py instead of routes/api/admin.py)
|
||||
|
||||
Returns:
|
||||
List of RouteInfo for discovered routes
|
||||
"""
|
||||
routes: list[RouteInfo] = []
|
||||
|
||||
if not routes_dir.exists():
|
||||
return routes
|
||||
|
||||
# Look for admin.py, vendor.py, shop.py
|
||||
frontends = {
|
||||
"admin": {
|
||||
@@ -168,13 +180,26 @@ def _discover_routes_in_dir(
|
||||
}
|
||||
|
||||
for frontend, config in frontends.items():
|
||||
route_file = routes_dir / f"{frontend}.py"
|
||||
if not route_file.exists():
|
||||
# Check primary location first, then fallback
|
||||
route_file = routes_dir / f"{frontend}.py" if routes_dir.exists() else None
|
||||
use_fallback = False
|
||||
|
||||
if route_file is None or not route_file.exists():
|
||||
if fallback_dir and fallback_dir.exists():
|
||||
fallback_file = fallback_dir / f"{frontend}.py"
|
||||
if fallback_file.exists():
|
||||
route_file = fallback_file
|
||||
use_fallback = True
|
||||
|
||||
if route_file is None or not route_file.exists():
|
||||
continue
|
||||
|
||||
try:
|
||||
# Import the module
|
||||
import_path = f"app.modules.{dir_name}.routes.{route_type}.{frontend}"
|
||||
if use_fallback:
|
||||
import_path = f"app.modules.{dir_name}.routes.{frontend}"
|
||||
else:
|
||||
import_path = f"app.modules.{dir_name}.routes.{route_type}.{frontend}"
|
||||
route_module = importlib.import_module(import_path)
|
||||
|
||||
# Get the router (try common names)
|
||||
@@ -188,14 +213,23 @@ def _discover_routes_in_dir(
|
||||
logger.warning(f"No router found in {import_path}")
|
||||
continue
|
||||
|
||||
# Read ROUTE_CONFIG if present
|
||||
route_config = getattr(route_module, "ROUTE_CONFIG", {})
|
||||
custom_prefix = route_config.get("prefix", "")
|
||||
custom_tags = route_config.get("tags", [])
|
||||
priority = route_config.get("priority", 0)
|
||||
|
||||
# Determine prefix based on route type
|
||||
if route_type == "api":
|
||||
prefix = config["api_prefix"]
|
||||
else:
|
||||
prefix = config["pages_prefix"]
|
||||
|
||||
# Build tags
|
||||
tags = [f"{module_code}-{frontend}-{route_type}"]
|
||||
# Build tags - use custom tags if provided, otherwise default
|
||||
if custom_tags:
|
||||
tags = custom_tags
|
||||
else:
|
||||
tags = [f"{frontend}-{module_code}"]
|
||||
|
||||
route_info = RouteInfo(
|
||||
router=router,
|
||||
@@ -205,6 +239,8 @@ def _discover_routes_in_dir(
|
||||
module_code=module_code,
|
||||
route_type=route_type,
|
||||
frontend=frontend,
|
||||
priority=priority,
|
||||
custom_prefix=custom_prefix,
|
||||
)
|
||||
routes.append(route_info)
|
||||
|
||||
@@ -238,6 +274,36 @@ def get_admin_page_routes() -> list[RouteInfo]:
|
||||
return [r for r in discover_module_routes() if r.route_type == "pages" and r.frontend == "admin"]
|
||||
|
||||
|
||||
def get_admin_api_routes() -> list[RouteInfo]:
|
||||
"""
|
||||
Get admin API routes from modules, sorted by priority.
|
||||
|
||||
Returns routes sorted by priority (lower first, higher last).
|
||||
This ensures catch-all routes (priority 100+) are registered after
|
||||
specific routes.
|
||||
"""
|
||||
routes = [
|
||||
r for r in discover_module_routes()
|
||||
if r.route_type == "api" and r.frontend == "admin"
|
||||
]
|
||||
return sorted(routes, key=lambda r: r.priority)
|
||||
|
||||
|
||||
def get_vendor_api_routes() -> list[RouteInfo]:
|
||||
"""
|
||||
Get vendor API routes from modules, sorted by priority.
|
||||
|
||||
Returns routes sorted by priority (lower first, higher last).
|
||||
This ensures catch-all routes (priority 100+) are registered after
|
||||
specific routes.
|
||||
"""
|
||||
routes = [
|
||||
r for r in discover_module_routes()
|
||||
if r.route_type == "api" and r.frontend == "vendor"
|
||||
]
|
||||
return sorted(routes, key=lambda r: r.priority)
|
||||
|
||||
|
||||
__all__ = [
|
||||
"RouteInfo",
|
||||
"discover_module_routes",
|
||||
@@ -245,4 +311,6 @@ __all__ = [
|
||||
"get_page_routes",
|
||||
"get_vendor_page_routes",
|
||||
"get_admin_page_routes",
|
||||
"get_admin_api_routes",
|
||||
"get_vendor_api_routes",
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user