Files
orion/app/modules/service.py
Samir Boulahtit 1cb659e3a5
All checks were successful
CI / ruff (push) Successful in 10s
CI / pytest (push) Successful in 37m52s
CI / validate (push) Successful in 25s
CI / dependency-scanning (push) Successful in 33s
CI / docs (push) Successful in 43s
CI / deploy (push) Successful in 56s
perf: fix all 77 performance validator warnings
Refactor 10 db.add() loops to db.add_all() in services (menu, admin,
orders, dev_tools), suppress 65 in tests/seeds/complex patterns with
noqa: PERF006, suppress 2 polling interval warnings with noqa: PERF062,
and add JS comment noqa support to base validator.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 20:00:06 +01:00

685 lines
21 KiB
Python

# app/modules/service.py
"""
Module service for platform module operations.
Provides methods to check module enablement, get enabled modules,
and filter menu items based on module configuration.
Module configuration is stored in the PlatformModule junction table,
which provides auditability, per-module config, and explicit state tracking.
"""
import logging
from datetime import UTC, datetime
from sqlalchemy.orm import Session
from app.modules.base import ModuleDefinition
from app.modules.enums import FrontendType
from app.modules.registry import (
MODULES,
get_core_module_codes,
get_menu_item_module,
get_module,
)
from app.modules.tenancy.models import Platform, PlatformModule
logger = logging.getLogger(__name__)
class ModuleService:
"""
Service for platform module operations.
Handles module enablement checking, module listing, and menu item filtering
based on enabled modules.
Module configuration is stored in the PlatformModule junction table,
which provides auditability, per-module config, and explicit state tracking.
If no PlatformModule records exist for a platform, no optional modules are
enabled (only core modules). Use seed scripts or the admin API to configure
module enablement for each platform.
Example PlatformModule records:
PlatformModule(platform_id=1, module_code="billing", is_enabled=True, config={"stripe_mode": "live"})
PlatformModule(platform_id=1, module_code="inventory", is_enabled=True, config={"low_stock_threshold": 10})
"""
# =========================================================================
# Module Enablement
# =========================================================================
def get_platform_modules(
self,
db: Session,
platform_id: int,
) -> list[ModuleDefinition]:
"""
Get all enabled modules for a platform.
Args:
db: Database session
platform_id: Platform ID
Returns:
List of enabled ModuleDefinition objects (always includes core)
"""
enabled_codes = self._get_enabled_module_codes(db, platform_id)
return [MODULES[code] for code in enabled_codes if code in MODULES]
def get_enabled_module_codes(
self,
db: Session,
platform_id: int,
) -> set[str]:
"""
Get set of enabled module codes for a platform.
Args:
db: Database session
platform_id: Platform ID
Returns:
Set of enabled module codes (always includes core modules)
"""
return self._get_enabled_module_codes(db, platform_id)
def is_module_enabled(
self,
db: Session,
platform_id: int,
module_code: str,
) -> bool:
"""
Check if a specific module is enabled for a platform.
Core modules are always enabled.
Args:
db: Database session
platform_id: Platform ID
module_code: Module code to check
Returns:
True if module is enabled
"""
# Core modules are always enabled
if module_code in get_core_module_codes():
return True
enabled_codes = self._get_enabled_module_codes(db, platform_id)
return module_code in enabled_codes
def _get_enabled_module_codes(
self,
db: Session,
platform_id: int,
) -> set[str]:
"""
Get enabled module codes for a platform.
Uses the PlatformModule junction table exclusively. If no records exist,
returns only core modules (empty set of optional modules).
Always includes core modules.
Args:
db: Database session
platform_id: Platform ID
Returns:
Set of enabled module codes
"""
platform = db.query(Platform).filter(Platform.id == platform_id).first()
if not platform:
logger.warning(f"Platform {platform_id} not found, returning core modules only")
return get_core_module_codes()
# Query junction table for enabled modules
platform_modules = (
db.query(PlatformModule)
.filter(PlatformModule.platform_id == platform_id)
.all()
)
enabled_set = {pm.module_code for pm in platform_modules if pm.is_enabled}
# Always include core modules
core_codes = get_core_module_codes()
enabled_set = enabled_set | core_codes
# Resolve dependencies - add required modules
enabled_set = self._resolve_dependencies(enabled_set)
logger.debug(
f"[MODULES] Platform '{platform.code}' (id={platform_id}) has "
f"{len(enabled_set)} modules enabled: {sorted(enabled_set)}"
)
return enabled_set
def _resolve_dependencies(self, enabled_codes: set[str]) -> set[str]:
"""
Resolve module dependencies by adding required modules.
If module A requires module B, and A is enabled, B must also be enabled.
Args:
enabled_codes: Set of explicitly enabled module codes
Returns:
Set of enabled module codes including dependencies
"""
resolved = set(enabled_codes)
changed = True
while changed:
changed = False
for code in list(resolved):
module = get_module(code)
if module:
for required in module.requires:
if required not in resolved:
resolved.add(required)
changed = True
logger.debug(
f"Module '{code}' requires '{required}', auto-enabling"
)
return resolved
# =========================================================================
# Menu Item Filtering
# =========================================================================
def get_module_menu_items(
self,
db: Session,
platform_id: int,
frontend_type: FrontendType,
) -> set[str]:
"""
Get all menu item IDs available for enabled modules.
Args:
db: Database session
platform_id: Platform ID
frontend_type: Which frontend (admin or store)
Returns:
Set of menu item IDs from enabled modules
"""
enabled_modules = self.get_platform_modules(db, platform_id)
menu_items = set()
for module in enabled_modules:
menu_items.update(module.get_menu_items(frontend_type))
return menu_items
def is_menu_item_module_enabled(
self,
db: Session,
platform_id: int,
menu_item_id: str,
frontend_type: FrontendType,
) -> bool:
"""
Check if the module providing a menu item is enabled.
Args:
db: Database session
platform_id: Platform ID
menu_item_id: Menu item ID
frontend_type: Which frontend (admin or store)
Returns:
True if the module providing this menu item is enabled,
or if menu item is not associated with any module.
"""
module_code = get_menu_item_module(menu_item_id, frontend_type)
# If menu item isn't associated with any module, allow it
if module_code is None:
return True
return self.is_module_enabled(db, platform_id, module_code)
def filter_menu_items_by_modules(
self,
db: Session,
platform_id: int,
menu_item_ids: set[str],
frontend_type: FrontendType,
) -> set[str]:
"""
Filter menu items to only those from enabled modules.
Args:
db: Database session
platform_id: Platform ID
menu_item_ids: Set of menu item IDs to filter
frontend_type: Which frontend (admin or store)
Returns:
Filtered set of menu item IDs
"""
available_items = self.get_module_menu_items(db, platform_id, frontend_type)
# Items that are in available_items, OR items not associated with any module
filtered = set()
for item_id in menu_item_ids:
module_code = get_menu_item_module(item_id, frontend_type)
if module_code is None or item_id in available_items:
filtered.add(item_id)
return filtered
# =========================================================================
# Module Configuration
# =========================================================================
def get_module_config(
self,
db: Session,
platform_id: int,
module_code: str,
) -> dict:
"""
Get module-specific configuration for a platform.
Uses the PlatformModule junction table for configuration storage.
Args:
db: Database session
platform_id: Platform ID
module_code: Module code
Returns:
Module configuration dict (empty if not configured)
"""
platform_module = (
db.query(PlatformModule)
.filter(
PlatformModule.platform_id == platform_id,
PlatformModule.module_code == module_code,
)
.first()
)
if platform_module:
return platform_module.config or {}
return {}
def set_module_config(
self,
db: Session,
platform_id: int,
module_code: str,
config: dict,
) -> bool:
"""
Set module-specific configuration for a platform.
Uses junction table for persistence. Creates record if doesn't exist.
Args:
db: Database session
platform_id: Platform ID
module_code: Module code
config: Configuration dict to set
Returns:
True if successful
"""
if module_code not in MODULES:
logger.error(f"Unknown module: {module_code}")
return False
platform = db.query(Platform).filter(Platform.id == platform_id).first()
if not platform:
logger.error(f"Platform {platform_id} not found")
return False
# Get or create junction table record
platform_module = (
db.query(PlatformModule)
.filter(
PlatformModule.platform_id == platform_id,
PlatformModule.module_code == module_code,
)
.first()
)
if platform_module:
platform_module.config = config
else:
# Create new record with config
platform_module = PlatformModule(
platform_id=platform_id,
module_code=module_code,
is_enabled=True, # Default to enabled
config=config,
)
db.add(platform_module)
logger.info(f"Updated config for module '{module_code}' on platform {platform_id}")
return True
def set_enabled_modules(
self,
db: Session,
platform_id: int,
module_codes: list[str],
user_id: int | None = None,
) -> bool:
"""
Set the enabled modules for a platform.
Core modules are automatically included.
Dependencies are automatically resolved.
Uses junction table for auditability.
Args:
db: Database session
platform_id: Platform ID
module_codes: List of module codes to enable
user_id: ID of user making the change (for audit)
Returns:
True if successful, False if platform not found
"""
platform = db.query(Platform).filter(Platform.id == platform_id).first()
if not platform:
logger.error(f"Platform {platform_id} not found")
return False
# Validate module codes
valid_codes = set(MODULES.keys())
invalid = [code for code in module_codes if code not in valid_codes]
if invalid:
logger.warning(f"Invalid module codes ignored: {invalid}")
module_codes = [code for code in module_codes if code in valid_codes]
# Always include core modules
core_codes = get_core_module_codes()
enabled_set = set(module_codes) | core_codes
# Resolve dependencies
enabled_set = self._resolve_dependencies(enabled_set)
now = datetime.now(UTC)
# Update junction table for all modules
for code in MODULES:
platform_module = (
db.query(PlatformModule)
.filter(
PlatformModule.platform_id == platform_id,
PlatformModule.module_code == code,
)
.first()
)
should_enable = code in enabled_set
if platform_module:
# Update existing record
if should_enable and not platform_module.is_enabled:
platform_module.is_enabled = True
platform_module.enabled_at = now
platform_module.enabled_by_user_id = user_id
elif not should_enable and platform_module.is_enabled:
platform_module.is_enabled = False
platform_module.disabled_at = now
platform_module.disabled_by_user_id = user_id
else:
# Create new record
platform_module = PlatformModule(
platform_id=platform_id,
module_code=code,
is_enabled=should_enable,
enabled_at=now if should_enable else None,
enabled_by_user_id=user_id if should_enable else None,
disabled_at=None if should_enable else now,
disabled_by_user_id=None if should_enable else user_id,
config={},
)
db.add(platform_module) # noqa: PERF006
logger.info(
f"Updated enabled modules for platform {platform_id}: {sorted(enabled_set)}"
)
return True
def enable_module(
self,
db: Session,
platform_id: int,
module_code: str,
user_id: int | None = None,
) -> bool:
"""
Enable a single module for a platform.
Also enables required dependencies.
Uses the PlatformModule junction table for auditability.
Args:
db: Database session
platform_id: Platform ID
module_code: Module code to enable
user_id: ID of user enabling the module (for audit)
Returns:
True if successful
"""
if module_code not in MODULES:
logger.error(f"Unknown module: {module_code}")
return False
platform = db.query(Platform).filter(Platform.id == platform_id).first()
if not platform:
logger.error(f"Platform {platform_id} not found")
return False
now = datetime.now(UTC)
# Enable this module and its dependencies
modules_to_enable = {module_code}
module = get_module(module_code)
if module:
for required in module.requires:
modules_to_enable.add(required)
for code in modules_to_enable:
# Check if junction table record exists
platform_module = (
db.query(PlatformModule)
.filter(
PlatformModule.platform_id == platform_id,
PlatformModule.module_code == code,
)
.first()
)
if platform_module:
# Update existing record
platform_module.is_enabled = True
platform_module.enabled_at = now
platform_module.enabled_by_user_id = user_id
else:
# Create new record
platform_module = PlatformModule(
platform_id=platform_id,
module_code=code,
is_enabled=True,
enabled_at=now,
enabled_by_user_id=user_id,
config={},
)
db.add(platform_module) # noqa: PERF006
logger.info(f"Enabled module '{module_code}' for platform {platform_id}")
return True
def disable_module(
self,
db: Session,
platform_id: int,
module_code: str,
user_id: int | None = None,
) -> bool:
"""
Disable a single module for a platform.
Core modules cannot be disabled.
Also disables modules that depend on this one.
Uses the PlatformModule junction table for auditability.
Args:
db: Database session
platform_id: Platform ID
module_code: Module code to disable
user_id: ID of user disabling the module (for audit)
Returns:
True if successful, False if core or not found
"""
if module_code not in MODULES:
logger.error(f"Unknown module: {module_code}")
return False
module = MODULES[module_code]
if module.is_core:
logger.warning(f"Cannot disable core module: {module_code}")
return False
platform = db.query(Platform).filter(Platform.id == platform_id).first()
if not platform:
logger.error(f"Platform {platform_id} not found")
return False
now = datetime.now(UTC)
# Get modules to disable (this one + dependents)
modules_to_disable = {module_code}
dependents = self._get_dependent_modules(module_code)
modules_to_disable.update(dependents)
for code in modules_to_disable:
# Check if junction table record exists
platform_module = (
db.query(PlatformModule)
.filter(
PlatformModule.platform_id == platform_id,
PlatformModule.module_code == code,
)
.first()
)
if platform_module:
# Update existing record
platform_module.is_enabled = False
platform_module.disabled_at = now
platform_module.disabled_by_user_id = user_id
else:
# Create disabled record for tracking
platform_module = PlatformModule(
platform_id=platform_id,
module_code=code,
is_enabled=False,
disabled_at=now,
disabled_by_user_id=user_id,
config={},
)
db.add(platform_module) # noqa: PERF006
if code != module_code:
logger.info(
f"Also disabled '{code}' (depends on '{module_code}')"
)
logger.info(f"Disabled module '{module_code}' for platform {platform_id}")
return True
def _get_dependent_modules(self, module_code: str) -> set[str]:
"""
Get modules that depend on a given module.
Args:
module_code: Module code to find dependents for
Returns:
Set of module codes that require the given module
"""
dependents = set()
for code, module in MODULES.items():
if module_code in module.requires:
dependents.add(code)
# Recursively find dependents of dependents
dependents.update(self._get_dependent_modules(code))
return dependents
# =========================================================================
# Platform Code Helpers
# =========================================================================
def get_platform_modules_by_code(
self,
db: Session,
platform_code: str,
) -> list[ModuleDefinition]:
"""
Get enabled modules for a platform by code.
Args:
db: Database session
platform_code: Platform code (e.g., "oms", "loyalty")
Returns:
List of enabled ModuleDefinition objects
"""
platform = db.query(Platform).filter(Platform.code == platform_code).first()
if not platform:
logger.warning(f"Platform '{platform_code}' not found, returning core modules only")
core_codes = get_core_module_codes()
return [MODULES[code] for code in core_codes if code in MODULES]
return self.get_platform_modules(db, platform.id)
def is_module_enabled_by_code(
self,
db: Session,
platform_code: str,
module_code: str,
) -> bool:
"""
Check if a module is enabled for a platform by code.
Args:
db: Database session
platform_code: Platform code (e.g., "oms", "loyalty")
module_code: Module code to check
Returns:
True if module is enabled
"""
platform = db.query(Platform).filter(Platform.code == platform_code).first()
if not platform:
logger.warning(f"Platform '{platform_code}' not found, assuming enabled")
return True
return self.is_module_enabled(db, platform.id, module_code)
# Singleton instance
module_service = ModuleService()
__all__ = [
"ModuleService",
"module_service",
]