Files
orion/middleware/context.py
Samir Boulahtit 238c1ec9b8 refactor: modernize code quality tooling with Ruff
- Replace black, isort, and flake8 with Ruff (all-in-one linter and formatter)
- Add comprehensive pyproject.toml configuration
- Simplify Makefile code quality targets
- Configure exclusions for venv/.venv in pyproject.toml
- Auto-fix 1,359 linting issues across codebase

Benefits:
- Much faster builds (Ruff is written in Rust)
- Single tool replaces multiple tools
- More comprehensive rule set (UP, B, C4, SIM, PIE, RET, Q)
- All configuration centralized in pyproject.toml
- Better import sorting and formatting consistency

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-28 19:37:38 +01:00

208 lines
6.9 KiB
Python

# middleware/context_middleware.py
"""
Context Detection Middleware (Class-Based)
Detects the request context type (API, Admin, Vendor Dashboard, Shop, or Fallback)
and injects it into request.state for use by error handlers and other components.
MUST run AFTER vendor_context_middleware to have access to clean_path.
MUST run BEFORE theme_context_middleware (which needs context_type).
Class-based middleware provides:
- Better state management
- Easier testing
- More organized code
- Standard ASGI pattern
"""
import logging
from enum import Enum
from fastapi import Request
from starlette.middleware.base import BaseHTTPMiddleware
logger = logging.getLogger(__name__)
class RequestContext(str, Enum):
"""Request context types for the application."""
API = "api"
ADMIN = "admin"
VENDOR_DASHBOARD = "vendor"
SHOP = "shop"
FALLBACK = "fallback"
class ContextManager:
"""Manages context detection for multi-area application."""
@staticmethod
def detect_context(request: Request) -> RequestContext:
"""
Detect the request context type.
Priority order:
1. API → /api/* paths (highest priority, always JSON)
2. Admin → /admin/* paths or admin.* subdomain
3. Vendor Dashboard → /vendor/* paths (vendor management area)
4. Shop → Vendor storefront (custom domain, subdomain, or shop paths)
5. Fallback → Unknown/generic context
CRITICAL: Uses clean_path (if available) instead of original path.
This ensures correct context detection for path-based routing.
Args:
request: FastAPI request object
Returns:
RequestContext enum value
"""
# Use clean_path if available (extracted by vendor_context_middleware)
# Falls back to original path if clean_path not set
# This is critical for correct context detection with path-based routing
path = getattr(request.state, "clean_path", request.url.path)
host = request.headers.get("host", "")
# Remove port from host if present
if ":" in host:
host = host.split(":")[0]
logger.debug(
"[CONTEXT] Detecting context",
extra={
"original_path": request.url.path,
"clean_path": getattr(request.state, "clean_path", "NOT SET"),
"path_to_check": path,
"host": host,
},
)
# 1. API context (highest priority)
if path.startswith("/api/"):
logger.debug("[CONTEXT] Detected as API", extra={"path": path})
return RequestContext.API
# 2. Admin context
if ContextManager._is_admin_context(request, host, path):
logger.debug(
"[CONTEXT] Detected as ADMIN", extra={"path": path, "host": host}
)
return RequestContext.ADMIN
# 3. Vendor Dashboard context (vendor management area)
# Check both clean_path and original path for vendor dashboard
original_path = request.url.path
if ContextManager._is_vendor_dashboard_context(
path
) or ContextManager._is_vendor_dashboard_context(original_path):
logger.debug(
"[CONTEXT] Detected as VENDOR_DASHBOARD",
extra={"path": path, "original_path": original_path},
)
return RequestContext.VENDOR_DASHBOARD
# 4. Shop context (vendor storefront)
# Check if vendor context exists (set by vendor_context_middleware)
if hasattr(request.state, "vendor") and request.state.vendor:
# If we have a vendor and it's not admin or vendor dashboard, it's shop
logger.debug(
"[CONTEXT] Detected as SHOP (has vendor context)",
extra={"vendor": request.state.vendor.name},
)
return RequestContext.SHOP
# Also check shop-specific paths
if path.startswith("/shop/"):
logger.debug("[CONTEXT] Detected as SHOP (from path)", extra={"path": path})
return RequestContext.SHOP
# 5. Fallback for unknown contexts
logger.debug("[CONTEXT] Detected as FALLBACK", extra={"path": path})
return RequestContext.FALLBACK
@staticmethod
def _is_admin_context(request: Request, host: str, path: str) -> bool:
"""Check if request is in admin context."""
# Admin subdomain (admin.platform.com)
if host.startswith("admin."):
return True
# Admin path (/admin/*)
if path.startswith("/admin"):
return True
return False
@staticmethod
def _is_vendor_dashboard_context(path: str) -> bool:
"""Check if request is in vendor dashboard context."""
# Vendor dashboard paths (/vendor/{code}/*)
# Note: This is the vendor management area, not the shop
# Important: /vendors/{code}/shop/* should NOT match this
if path.startswith("/vendor/") and not path.startswith("/vendors/"):
return True
return False
class ContextMiddleware(BaseHTTPMiddleware):
"""
Middleware to detect and inject request context into request.state.
Class-based middleware provides:
- Better lifecycle management
- Easier to test and extend
- Standard ASGI pattern
- Clear separation of concerns
Runs SECOND in middleware chain (after vendor_context_middleware).
Depends on:
request.state.clean_path (set by vendor_context_middleware)
request.state.vendor (set by vendor_context_middleware)
Sets:
request.state.context_type: RequestContext enum value
"""
async def dispatch(self, request: Request, call_next):
"""
Detect context and inject into request state.
"""
# Detect context
context_type = ContextManager.detect_context(request)
# Inject into request state
request.state.context_type = context_type
# Log context detection with full details
logger.debug(
f"[CONTEXT_MIDDLEWARE] Context detected: {context_type.value}",
extra={
"path": request.url.path,
"clean_path": getattr(request.state, "clean_path", "NOT SET"),
"host": request.headers.get("host", ""),
"context": context_type.value,
"has_vendor": hasattr(request.state, "vendor")
and request.state.vendor is not None,
},
)
# Continue processing
response = await call_next(request)
return response
def get_request_context(request: Request) -> RequestContext:
"""
Helper function to get current request context.
Args:
request: FastAPI request object
Returns:
RequestContext enum value (defaults to FALLBACK if not set)
"""
return getattr(request.state, "context_type", RequestContext.FALLBACK)