A wheel whose top-level module is `app` collides with anything else in a user's venv, so the package that is about to be published takes the name it is published under. Only the Python package moves; the repo, the Docker WORKDIR and the compose project keep theirs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
78 lines
2.7 KiB
Python
78 lines
2.7 KiB
Python
"""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}"
|