# Copyright (c) 2025 Patrick Motsch # All rights reserved. """Plugin-discovery registry for accounting connectors (analogous to aicoreModelRegistry).""" import glob import importlib import inspect import logging import os from typing import Dict, List, Optional from .accountingConnectorBase import BaseAccountingConnector from modules.shared.i18nRegistry import resolveText logger = logging.getLogger(__name__) class AccountingRegistry: """Discovers and manages accounting connector plugins.""" _connectors: Dict[str, BaseAccountingConnector] = {} _discovered: bool = False def discoverConnectors(self) -> int: """Scan connectors/ for accountingConnector*.py files and register them.""" if self._discovered: return len(self._connectors) connectorsDir = os.path.join(os.path.dirname(__file__), "connectors") pattern = os.path.join(connectorsDir, "accountingConnector*.py") for filePath in glob.glob(pattern): moduleName = os.path.splitext(os.path.basename(filePath))[0] fullModuleName = f"modules.features.trustee.accounting.connectors.{moduleName}" try: module = importlib.import_module(fullModuleName) for _name, obj in inspect.getmembers(module, inspect.isclass): if issubclass(obj, BaseAccountingConnector) and obj is not BaseAccountingConnector: instance = obj() connectorType = instance.getConnectorType() self._connectors[connectorType] = instance logger.info(f"Registered accounting connector: {connectorType}") except Exception as e: logger.error(f"Failed to load accounting connector from {moduleName}: {e}") self._discovered = True logger.info(f"Discovered {len(self._connectors)} accounting connector(s)") return len(self._connectors) def getConnector(self, connectorType: str) -> Optional[BaseAccountingConnector]: """Return the connector instance for the given type.""" if not self._discovered: self.discoverConnectors() return self._connectors.get(connectorType) def getAvailableConnectors(self) -> List[Dict]: """Return metadata for all available connectors (for frontend dropdown).""" if not self._discovered: self.discoverConnectors() result = [] for connectorType, connector in self._connectors.items(): fields = [] for f in connector.getRequiredConfigFields(): fd = f.model_dump() fd["label"] = resolveText(f.label) fields.append(fd) result.append({ "connectorType": connectorType, "label": resolveText(connector.getConnectorLabel()), "configFields": fields, }) return result _registryInstance: Optional[AccountingRegistry] = None def _getAccountingRegistry() -> AccountingRegistry: """Singleton access to the accounting registry.""" global _registryInstance if _registryInstance is None: _registryInstance = AccountingRegistry() _registryInstance.discoverConnectors() return _registryInstance