"""Loading connector plugins from installed packages. A connector is an ordinary Python package that advertises its node class under the ``fluksio.node_types`` entry point group. The entry point's name is the node type as it appears on the canvas, and the package's own metadata — name and version — is the manifest; there is no second format to keep in step. Discovery happens once, at startup: a connector's code is imported, and Python does not re-import a changed module, so a rescan would promise more than it delivers. Installing or upgrading one means restarting the engine. """ from __future__ import annotations import logging from importlib.metadata import entry_points from fluksio.flow.connector import CONTRACT_VERSION, ConnectorNode logger = logging.getLogger(__name__) ENTRY_POINT_GROUP = "fluksio.node_types" def load_plugins() -> list[str]: """Register every installed connector. Returns the type names it added.""" from fluksio.flow.controller import NODE_TYPES, NodeType, _schema_of added: list[str] = [] for entry in entry_points(group=ENTRY_POINT_GROUP): if entry.name in NODE_TYPES: logger.warning( "Ignoring connector '%s': that node type already exists", entry.name ) continue try: cls = entry.load() except Exception: logger.exception("Could not load connector '%s'", entry.name) continue if not (isinstance(cls, type) and issubclass(cls, ConnectorNode)): logger.warning( "Ignoring connector '%s': %r is not a ConnectorNode", entry.name, cls ) continue # Checked on the class itself: inheriting a version from a base class # would let a connector written against an older contract look current. if cls.__dict__.get("contract") != CONTRACT_VERSION: logger.warning( "Ignoring connector '%s': written for contract version %s, " "this engine speaks %s", entry.name, cls.__dict__.get("contract"), CONTRACT_VERSION, ) continue NODE_TYPES[entry.name] = NodeType( title=cls.title or entry.name, description=cls.description, cls=cls, params_schema=_schema_of(cls), plugin=_provenance(entry), ) added.append(entry.name) logger.info("Loaded connector '%s' from %s", entry.name, _provenance(entry)) return added def _provenance(entry: object) -> str: """Which package this node type came from, for the editor to show.""" dist = getattr(entry, "dist", None) if dist is None: return "unknown" return f"{dist.name} {dist.version}"