98 lines
3.2 KiB
Python
98 lines
3.2 KiB
Python
from typing import TYPE_CHECKING
|
|
if TYPE_CHECKING:
|
|
from .noSysCore import NoSysCore
|
|
|
|
import sys
|
|
import importlib
|
|
import logging
|
|
from typing import Dict, Tuple, Any
|
|
|
|
from .events import Events
|
|
from .noSysModule import NoSysModule
|
|
from libs.app.common.logging import get_logger
|
|
from libs.fspn.protocol.security import Pmc
|
|
|
|
logger = get_logger("moduleManager", buffer=True)
|
|
|
|
class ModuleManager:
|
|
"""Loads, unloads, and manages modules dynamically."""
|
|
|
|
def __init__(self, nosys_core: "NoSysCore"):
|
|
self.nosys_core = nosys_core
|
|
self.modules: Dict[str, NoSysModule] = {}
|
|
from libs.api.api import Api
|
|
self.api:Api = None
|
|
self.pmc:Pmc = None
|
|
|
|
self.load_from_config()
|
|
|
|
def get(self, package_id, module_id) -> NoSysModule:
|
|
return self.modules.get((package_id, module_id))
|
|
|
|
def load_from_config(self):
|
|
libs = self.nosys_core.config.get("app", "libs")
|
|
for lib in libs:
|
|
if lib.get("enabled", True):
|
|
for module in self.nosys_core.config.get(lib.get("id"), "info", "modules"):
|
|
self.load(lib.get("id"), module.get("id"))
|
|
|
|
def load(self, package_id: str, module_id: str):
|
|
"""Dynamically load a module and initialize it."""
|
|
logger.debug(f"Importing module: {package_id}.{module_id}")
|
|
|
|
key = (package_id, module_id)
|
|
if key in self.modules:
|
|
return self.modules[key]
|
|
|
|
path = f"libs.{package_id}.{module_id}"
|
|
|
|
try:
|
|
if path in sys.modules:
|
|
module = importlib.reload(sys.modules[path])
|
|
else:
|
|
module = importlib.import_module(path)
|
|
|
|
# Prefer explicit "get_module_class" function if provided
|
|
if hasattr(module, "get_module_class"):
|
|
clss = module.get_module_class()
|
|
else:
|
|
# Fallback to naming convention: Uppercase the first letter of the module
|
|
class_name = module_id[:1].upper()+module_id[1:]
|
|
clss = getattr(module, class_name)
|
|
|
|
if not issubclass(clss, NoSysModule):
|
|
raise TypeError(f"{clss} must inherit from NoSysModule")
|
|
|
|
instance = clss(self.nosys_core)
|
|
self.modules[key] = instance
|
|
|
|
logger.debug(f"Module loaded: {path} ({clss.__name__})")
|
|
return instance
|
|
except Exception as e:
|
|
logger.exception(f"Failed to load module {path}: {e}")
|
|
return None
|
|
|
|
def unload(self, package_id: str, module_id: str):
|
|
"""Unload a module and call its teardown hook if available."""
|
|
key = (package_id, module_id)
|
|
if key not in self.modules:
|
|
return
|
|
|
|
module:NoSysModule = self.modules.pop(key)
|
|
|
|
try:
|
|
module.teardown_module()
|
|
except Exception as e:
|
|
logger.warning(f"Error during teardown of {key}: {e}")
|
|
|
|
logger.debug(f"Module unloaded: {key}")
|
|
|
|
def reload(self, package_id: str, module_id: str):
|
|
"""Reload a module (unload + load)."""
|
|
self.unload(package_id, module_id)
|
|
return self.load(package_id, module_id)
|
|
|
|
def setup(self):
|
|
for module in self.modules.values():
|
|
module.setup()
|