117 lines
3.8 KiB
Python
117 lines
3.8 KiB
Python
# Copyright (c) 2025 Patrick Motsch
|
|
# All rights reserved.
|
|
"""
|
|
Feature Registry for Plug&Play Feature Container Loading.
|
|
Dynamically discovers and loads feature containers from the features directory.
|
|
"""
|
|
|
|
import os
|
|
import glob
|
|
import importlib
|
|
import logging
|
|
from typing import List, Dict, Any
|
|
from fastapi import FastAPI
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Path to the features directory
|
|
FEATURES_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
|
|
def discoverFeatureContainers() -> List[str]:
|
|
"""
|
|
Discover all feature container directories by filename pattern.
|
|
A valid feature container has a routeFeature*.py file.
|
|
"""
|
|
containers = []
|
|
pattern = os.path.join(FEATURES_DIR, "*", "routeFeature*.py")
|
|
|
|
for filepath in glob.glob(pattern):
|
|
featureDir = os.path.basename(os.path.dirname(filepath))
|
|
if featureDir not in containers and not featureDir.startswith("_"):
|
|
containers.append(featureDir)
|
|
|
|
return sorted(containers)
|
|
|
|
|
|
def loadFeatureRouters(app: FastAPI) -> Dict[str, Any]:
|
|
"""
|
|
Dynamically load and register routers from all discovered feature containers.
|
|
"""
|
|
results = {}
|
|
pattern = os.path.join(FEATURES_DIR, "*", "routeFeature*.py")
|
|
|
|
for filepath in glob.glob(pattern):
|
|
featureDir = os.path.basename(os.path.dirname(filepath))
|
|
routerFile = os.path.basename(filepath)[:-3] # Remove .py
|
|
|
|
if featureDir.startswith("_"):
|
|
continue
|
|
|
|
try:
|
|
modulePath = f"modules.features.{featureDir}.{routerFile}"
|
|
module = importlib.import_module(modulePath)
|
|
|
|
if hasattr(module, "router"):
|
|
app.include_router(module.router)
|
|
logger.info(f"Loaded router: {featureDir}")
|
|
results[featureDir] = {"status": "loaded", "module": modulePath}
|
|
else:
|
|
logger.warning(f"No 'router' in {modulePath}")
|
|
results[featureDir] = {"status": "no_router_object"}
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to load router from {featureDir}: {e}")
|
|
results[featureDir] = {"status": "error", "error": str(e)}
|
|
|
|
return results
|
|
|
|
|
|
def loadFeatureMainModules() -> Dict[str, Any]:
|
|
"""
|
|
Dynamically load main modules from all discovered feature containers.
|
|
"""
|
|
mainModules = {}
|
|
pattern = os.path.join(FEATURES_DIR, "*", "main*.py")
|
|
|
|
for filepath in glob.glob(pattern):
|
|
filename = os.path.basename(filepath)
|
|
if filename == "__init__.py":
|
|
continue
|
|
|
|
featureDir = os.path.basename(os.path.dirname(filepath))
|
|
if featureDir.startswith("_"):
|
|
continue
|
|
|
|
mainFile = filename[:-3] # Remove .py
|
|
|
|
try:
|
|
modulePath = f"modules.features.{featureDir}.{mainFile}"
|
|
module = importlib.import_module(modulePath)
|
|
mainModules[featureDir] = module
|
|
logger.debug(f"Loaded main module: {featureDir}")
|
|
except Exception as e:
|
|
logger.error(f"Failed to load main module from {featureDir}: {e}")
|
|
|
|
return mainModules
|
|
|
|
|
|
def registerAllFeaturesInCatalog(catalogService) -> Dict[str, bool]:
|
|
"""
|
|
Register all features' RBAC objects in the catalog.
|
|
"""
|
|
mainModules = loadFeatureMainModules()
|
|
results = {}
|
|
|
|
for featureName, module in mainModules.items():
|
|
if hasattr(module, "registerFeature"):
|
|
try:
|
|
success = module.registerFeature(catalogService)
|
|
results[featureName] = success
|
|
if success:
|
|
logger.info(f"Registered RBAC objects: {featureName}")
|
|
except Exception as e:
|
|
logger.error(f"Error registering {featureName}: {e}")
|
|
results[featureName] = False
|
|
|
|
return results
|