fix: resolve cross-module import violations with lazy import pattern
- Convert core→optional imports to lazy imports with try/except fallbacks - cms/media_service: use TYPE_CHECKING for ProductMedia type hints - customers/customer_service: wrap Order imports in try/except - tenancy/admin_platform_users: wrap stats_service import in try/except - Enhance validate_architecture.py to recognize lazy import patterns - Add module_dependency_graph.py script for dependency visualization The lazy import pattern allows optional modules to be truly optional while maintaining type safety through TYPE_CHECKING blocks. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -16,10 +16,14 @@ import shutil
|
||||
import uuid
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from sqlalchemy import or_
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from app.modules.catalog.models import ProductMedia
|
||||
|
||||
from app.modules.cms.exceptions import (
|
||||
MediaNotFoundException,
|
||||
MediaUploadException,
|
||||
@@ -28,7 +32,6 @@ from app.modules.cms.exceptions import (
|
||||
MediaFileTooLargeException,
|
||||
)
|
||||
from app.modules.cms.models import MediaFile
|
||||
from app.modules.catalog.models import ProductMedia
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -454,7 +457,7 @@ class MediaService:
|
||||
product_id: int,
|
||||
usage_type: str = "gallery",
|
||||
display_order: int = 0,
|
||||
) -> ProductMedia:
|
||||
) -> "ProductMedia | None":
|
||||
"""
|
||||
Attach a media file to a product.
|
||||
|
||||
@@ -467,8 +470,14 @@ class MediaService:
|
||||
display_order: Order for galleries
|
||||
|
||||
Returns:
|
||||
Created ProductMedia association
|
||||
Created ProductMedia association, or None if catalog module unavailable
|
||||
"""
|
||||
try:
|
||||
from app.modules.catalog.models import ProductMedia
|
||||
except ImportError:
|
||||
logger.warning("Catalog module not available for media-product attachment")
|
||||
return None
|
||||
|
||||
# Verify media belongs to vendor
|
||||
media = self.get_media(db, vendor_id, media_id)
|
||||
|
||||
@@ -524,8 +533,14 @@ class MediaService:
|
||||
usage_type: Specific usage type to remove (None = all)
|
||||
|
||||
Returns:
|
||||
True if detached
|
||||
True if detached, False if catalog module unavailable
|
||||
"""
|
||||
try:
|
||||
from app.modules.catalog.models import ProductMedia
|
||||
except ImportError:
|
||||
logger.warning("Catalog module not available for media-product detachment")
|
||||
return False
|
||||
|
||||
# Verify media belongs to vendor
|
||||
media = self.get_media(db, vendor_id, media_id)
|
||||
|
||||
|
||||
@@ -329,25 +329,30 @@ class CustomerService:
|
||||
Raises:
|
||||
CustomerNotFoundException: If customer not found
|
||||
"""
|
||||
from app.modules.orders.models import Order
|
||||
|
||||
# Verify customer belongs to vendor
|
||||
self.get_customer(db, vendor_id, customer_id)
|
||||
|
||||
# Get customer orders
|
||||
query = (
|
||||
db.query(Order)
|
||||
.filter(
|
||||
Order.customer_id == customer_id,
|
||||
Order.vendor_id == vendor_id,
|
||||
try:
|
||||
from app.modules.orders.models import Order
|
||||
|
||||
# Get customer orders
|
||||
query = (
|
||||
db.query(Order)
|
||||
.filter(
|
||||
Order.customer_id == customer_id,
|
||||
Order.vendor_id == vendor_id,
|
||||
)
|
||||
.order_by(Order.created_at.desc())
|
||||
)
|
||||
.order_by(Order.created_at.desc())
|
||||
)
|
||||
|
||||
total = query.count()
|
||||
orders = query.offset(skip).limit(limit).all()
|
||||
total = query.count()
|
||||
orders = query.offset(skip).limit(limit).all()
|
||||
|
||||
return orders, total
|
||||
return orders, total
|
||||
except ImportError:
|
||||
# Orders module not available
|
||||
logger.warning("Orders module not available for customer orders")
|
||||
return [], 0
|
||||
|
||||
def get_customer_statistics(
|
||||
self, db: Session, vendor_id: int, customer_id: int
|
||||
@@ -363,34 +368,43 @@ class CustomerService:
|
||||
Returns:
|
||||
Dict with customer statistics
|
||||
"""
|
||||
from sqlalchemy import func
|
||||
|
||||
from app.modules.orders.models import Order
|
||||
|
||||
customer = self.get_customer(db, vendor_id, customer_id)
|
||||
|
||||
# Get order statistics
|
||||
order_stats = (
|
||||
db.query(
|
||||
func.count(Order.id).label("total_orders"),
|
||||
func.sum(Order.total_cents).label("total_spent_cents"),
|
||||
func.avg(Order.total_cents).label("avg_order_cents"),
|
||||
func.max(Order.created_at).label("last_order_date"),
|
||||
)
|
||||
.filter(Order.customer_id == customer_id)
|
||||
.first()
|
||||
)
|
||||
# Get order statistics if orders module is available
|
||||
try:
|
||||
from sqlalchemy import func
|
||||
|
||||
total_orders = order_stats.total_orders or 0
|
||||
total_spent_cents = order_stats.total_spent_cents or 0
|
||||
avg_order_cents = order_stats.avg_order_cents or 0
|
||||
from app.modules.orders.models import Order
|
||||
|
||||
order_stats = (
|
||||
db.query(
|
||||
func.count(Order.id).label("total_orders"),
|
||||
func.sum(Order.total_cents).label("total_spent_cents"),
|
||||
func.avg(Order.total_cents).label("avg_order_cents"),
|
||||
func.max(Order.created_at).label("last_order_date"),
|
||||
)
|
||||
.filter(Order.customer_id == customer_id)
|
||||
.first()
|
||||
)
|
||||
|
||||
total_orders = order_stats.total_orders or 0
|
||||
total_spent_cents = order_stats.total_spent_cents or 0
|
||||
avg_order_cents = order_stats.avg_order_cents or 0
|
||||
last_order_date = order_stats.last_order_date
|
||||
except ImportError:
|
||||
# Orders module not available
|
||||
logger.warning("Orders module not available for customer statistics")
|
||||
total_orders = 0
|
||||
total_spent_cents = 0
|
||||
avg_order_cents = 0
|
||||
last_order_date = None
|
||||
|
||||
return {
|
||||
"customer_id": customer_id,
|
||||
"total_orders": total_orders,
|
||||
"total_spent": total_spent_cents / 100, # Convert to euros
|
||||
"average_order_value": avg_order_cents / 100 if avg_order_cents else 0.0,
|
||||
"last_order_date": order_stats.last_order_date,
|
||||
"last_order_date": last_order_date,
|
||||
"member_since": customer.created_at,
|
||||
"is_active": customer.is_active,
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ from sqlalchemy.orm import Session
|
||||
from app.api.deps import get_current_admin_api
|
||||
from app.core.database import get_db
|
||||
from app.modules.tenancy.services.admin_service import admin_service
|
||||
from app.modules.analytics.services.stats_service import stats_service
|
||||
from models.schema.auth import UserContext
|
||||
from models.schema.auth import (
|
||||
UserCreate,
|
||||
@@ -111,7 +110,19 @@ def get_user_statistics(
|
||||
current_admin: UserContext = Depends(get_current_admin_api),
|
||||
):
|
||||
"""Get user statistics for admin dashboard (Admin only)."""
|
||||
return stats_service.get_user_statistics(db)
|
||||
try:
|
||||
from app.modules.analytics.services.stats_service import stats_service
|
||||
|
||||
return stats_service.get_user_statistics(db)
|
||||
except ImportError:
|
||||
# Analytics module not available - return empty stats
|
||||
logger.warning("Analytics module not available for user statistics")
|
||||
return {
|
||||
"total_users": 0,
|
||||
"active_users": 0,
|
||||
"inactive_users": 0,
|
||||
"admin_users": 0,
|
||||
}
|
||||
|
||||
|
||||
@admin_platform_users_router.get("/search", response_model=UserSearchResponse)
|
||||
|
||||
Reference in New Issue
Block a user