# 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 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"] = f.label fields.append(fd) result.append({ "connectorType": connectorType, "label": 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