84 lines
3.2 KiB
Python
84 lines
3.2 KiB
Python
# 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
|