jmri_core.plugins

Entry-point-based plugin discovery for connections and extensions.

JMRI's Java code uses Reflections to scan the classpath for Manager implementations and ConnectionConfig classes. In Python we do this declaratively via importlib.metadata entry points:

[project.entry-points."jmri.connection"]
loconet  = "jmri_loconet:LocoNetConnection"
nce      = "jmri_nce:NceConnection"
internal = "jmri_core.hal.internal:InternalConnection"

Each entry point name is the connection prefix string the user sees in panels and rosters. The loaded value must be a subclass of jmri_core.hal.connection.SystemConnection.

 1"""Entry-point-based plugin discovery for connections and extensions.
 2
 3JMRI's Java code uses Reflections to scan the classpath for ``Manager``
 4implementations and ``ConnectionConfig`` classes. In Python we do this
 5declaratively via ``importlib.metadata`` entry points:
 6
 7    [project.entry-points."jmri.connection"]
 8    loconet  = "jmri_loconet:LocoNetConnection"
 9    nce      = "jmri_nce:NceConnection"
10    internal = "jmri_core.hal.internal:InternalConnection"
11
12Each entry point name is the connection prefix string the user sees in
13panels and rosters. The loaded value must be a subclass of
14:class:`jmri_core.hal.connection.SystemConnection`.
15"""
16from __future__ import annotations
17
18from importlib.metadata import entry_points
19from typing import TYPE_CHECKING
20
21if TYPE_CHECKING:
22    from jmri_core.hal.connection import SystemConnection
23
24CONNECTION_GROUP = "jmri.connection"
25
26
27def load_connections() -> dict[str, type[SystemConnection]]:
28    """Return ``{prefix: connection_class}`` for every registered driver.
29
30    Failures to load a single entry point are silently dropped — a broken
31    driver should never crash the core's discovery pass. Use
32    :func:`load_connections_strict` if you want failures to surface.
33    """
34    result: dict[str, type[SystemConnection]] = {}
35    for ep in entry_points(group=CONNECTION_GROUP):
36        try:
37            cls = ep.load()
38        except Exception:  # noqa: BLE001 — third-party plugin code
39            continue
40        result[ep.name] = cls
41    return result
42
43
44def load_connections_strict() -> dict[str, type[SystemConnection]]:
45    """Like :func:`load_connections` but raises on the first failed entry."""
46    return {ep.name: ep.load() for ep in entry_points(group=CONNECTION_GROUP)}
CONNECTION_GROUP = 'jmri.connection'
def load_connections() -> dict[str, type[jmri_core.hal.SystemConnection]]:
28def load_connections() -> dict[str, type[SystemConnection]]:
29    """Return ``{prefix: connection_class}`` for every registered driver.
30
31    Failures to load a single entry point are silently dropped — a broken
32    driver should never crash the core's discovery pass. Use
33    :func:`load_connections_strict` if you want failures to surface.
34    """
35    result: dict[str, type[SystemConnection]] = {}
36    for ep in entry_points(group=CONNECTION_GROUP):
37        try:
38            cls = ep.load()
39        except Exception:  # noqa: BLE001 — third-party plugin code
40            continue
41        result[ep.name] = cls
42    return result

Return {prefix: connection_class} for every registered driver.

Failures to load a single entry point are silently dropped — a broken driver should never crash the core's discovery pass. Use load_connections_strict() if you want failures to surface.

def load_connections_strict() -> dict[str, type[jmri_core.hal.SystemConnection]]:
45def load_connections_strict() -> dict[str, type[SystemConnection]]:
46    """Like :func:`load_connections` but raises on the first failed entry."""
47    return {ep.name: ep.load() for ep in entry_points(group=CONNECTION_GROUP)}

Like load_connections() but raises on the first failed entry.