From 02923690fa57467d8ef02b39f3b6e85cebaf2c9d Mon Sep 17 00:00:00 2001 From: dimdano Date: Thu, 25 Sep 2025 13:54:49 +0200 Subject: [PATCH 1/6] Add initial plugin support for external backends (e.g. aie4ml) --- MANIFEST.in | 1 + README.md | 3 + docs/advanced/plugins.rst | 61 +++++++++++++++ docs/index.rst | 1 + hls4ml/backends/__init__.py | 21 ++++-- hls4ml/backends/plugin_loader.py | 124 +++++++++++++++++++++++++++++++ 6 files changed, 204 insertions(+), 7 deletions(-) create mode 100644 docs/advanced/plugins.rst create mode 100644 hls4ml/backends/plugin_loader.py diff --git a/MANIFEST.in b/MANIFEST.in index e3ee5ded3c..4a6bc93b2f 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -4,6 +4,7 @@ graft test graft contrib recursive-include hls4ml/templates * recursive-include hls4ml *.py +recursive-include hls4ml/backends *.json recursive-include hls4ml/contrib * global-exclude .git .gitmodules .gitlab-ci.yml *.pyc include hls4ml/backends/vivado_accelerator/supported_boards.json diff --git a/README.md b/README.md index 3f633a3dcc..44ad7d7734 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,9 @@ Detailed tutorials on how to use `hls4ml`'s various functionalities can be found pip install hls4ml ``` +Specialised backends are now distributed as optional plugins. Install them alongside hls4ml via +``pip install ``, for example ``pip install aie4ml`` for the AMD AIE flow. + To install the extra dependencies for profiling: ```bash diff --git a/docs/advanced/plugins.rst b/docs/advanced/plugins.rst new file mode 100644 index 0000000000..e1949b8f9d --- /dev/null +++ b/docs/advanced/plugins.rst @@ -0,0 +1,61 @@ +======================================= +External Backend and Writer Plugins +======================================= + +Starting with this release ``hls4ml`` can discover and load backend implementations from +external Python packages. This enables specialised flows—such as the AMD AIE backend—to live in +independent projects that version and iterate at their own cadence while reusing the core +conversion infrastructure. + +Discovery +========= + +Plugin packages advertise themselves through the ``hls4ml.backends`` Python entry point group. Each +entry either exposes a subclass of :class:`hls4ml.backends.backend.Backend` or a callable that +receives ``register_backend`` and ``register_writer`` helpers and performs any setup that is +required. ``hls4ml`` automatically scans for these entry points during ``hls4ml.backends`` import so +third-party backends become available without additional user configuration. + +In addition to entry points, modules listed in the ``HLS4ML_BACKEND_PLUGINS`` environment variable +are imported and treated as registration callables. The variable accepts an ``os.pathsep`` separated +list (``:`` on Linux/macOS or ``;`` on Windows): + +.. code-block:: bash + + export HLS4ML_BACKEND_PLUGINS=aie4ml.plugin:another_pkg.hls4ml_backend + +Authoring a Plugin +================== + +A minimal plugin registers both a backend and an accompanying writer. The example below +shows how the ``aie4ml`` package exposes its backend via ``pyproject.toml`` and a ``register`` +function: + +.. code-block:: toml + + [project.entry-points."hls4ml.backends"] + AIE = "aie4ml.plugin:register" + +.. code-block:: python + + # aie4ml/plugin.py + from aie4ml.aie_backend import AIEBackend + from aie4ml.writer import AIEWriter + + def register(*, register_backend, register_writer): + register_writer('AIE', AIEWriter) + register_backend('AIE', AIEBackend) + +When the plugin is installed, ``hls4ml.backends.get_available_backends()`` will report the new +backend just like the built-in FPGA toolflows. + +Packaging Data Files +==================== + +Backends often rely on firmware templates or device description files. These assets should be +packaged alongside the Python sources using the usual ``setuptools`` mechanisms (``package-data`` or +``include-package-data``) so they are available from the installed distribution. + +For an end-to-end example see the companion ``aie4ml`` [https://github.com/dimdano/aie4ml] package that ships alongside this project +as a standalone distribution; it encapsulates the existing AMD AIE backend as an installable plugin +depending on ``hls4ml``. diff --git a/docs/index.rst b/docs/index.rst index ed617a4537..f170ca6858 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -53,6 +53,7 @@ advanced/extension advanced/model_optimization advanced/bramfactor + advanced/plugins .. toctree:: :hidden: diff --git a/hls4ml/backends/__init__.py b/hls4ml/backends/__init__.py index 4a48f072cd..7b31770c84 100644 --- a/hls4ml/backends/__init__.py +++ b/hls4ml/backends/__init__.py @@ -10,11 +10,18 @@ from hls4ml.backends.catapult.catapult_backend import CatapultBackend # isort: skip from hls4ml.backends.vitis.vitis_backend import VitisBackend # isort: skip +from hls4ml.backends.plugin_loader import load_backend_plugins -register_backend('Vivado', VivadoBackend) -register_backend('VivadoAccelerator', VivadoAcceleratorBackend) -register_backend('Vitis', VitisBackend) -register_backend('Quartus', QuartusBackend) -register_backend('Catapult', CatapultBackend) -register_backend('SymbolicExpression', SymbolicExpressionBackend) -register_backend('oneAPI', OneAPIBackend) + +def _register_builtin_backends(): + register_backend('Vivado', VivadoBackend) + register_backend('VivadoAccelerator', VivadoAcceleratorBackend) + register_backend('Vitis', VitisBackend) + register_backend('Quartus', QuartusBackend) + register_backend('Catapult', CatapultBackend) + register_backend('SymbolicExpression', SymbolicExpressionBackend) + register_backend('oneAPI', OneAPIBackend) + + +_register_builtin_backends() +load_backend_plugins() diff --git a/hls4ml/backends/plugin_loader.py b/hls4ml/backends/plugin_loader.py new file mode 100644 index 0000000000..05edb3ef40 --- /dev/null +++ b/hls4ml/backends/plugin_loader.py @@ -0,0 +1,124 @@ +"""Utilities for discovering and loading external hls4ml backend plugins.""" + +from __future__ import annotations + +import inspect +import logging +import os +from collections.abc import Iterable +from importlib import import_module +from typing import Any, Callable + +try: # pragma: no cover - fall back for older Python versions + from importlib.metadata import entry_points +except ImportError: # pragma: no cover + from importlib_metadata import entry_points # type: ignore + +from hls4ml.backends.backend import Backend, register_backend +from hls4ml.writer.writers import register_writer + +ENTRY_POINT_GROUP = 'hls4ml.backends' +ENV_PLUGIN_MODULES = 'HLS4ML_BACKEND_PLUGINS' + +_plugins_loaded = False + + +def load_backend_plugins(logger: logging.Logger | None = None) -> None: + """Discover and register backend plugins. + + This function loads plugins published via Python entry points under the + ``hls4ml.backends`` group as well as modules listed in the + ``HLS4ML_BACKEND_PLUGINS`` environment variable. The environment variable + accepts a separator compatible with :data:`os.pathsep`. + + Args: + logger (logging.Logger, optional): Optional logger used for diagnostics. + When omitted, a module-local logger will be used. + """ + + global _plugins_loaded + if _plugins_loaded: + return + + logger = logger or logging.getLogger(__name__) + + _load_entry_point_plugins(logger) + _load_env_plugins(logger) + + _plugins_loaded = True + + +def _load_entry_point_plugins(logger: logging.Logger) -> None: + eps = entry_points() + + if hasattr(eps, 'select'): + group_eps = eps.select(group=ENTRY_POINT_GROUP) + else: # pragma: no cover - legacy importlib_metadata API + group_eps = eps.get(ENTRY_POINT_GROUP, []) + + for ep in group_eps: + try: + obj = ep.load() + except Exception as exc: # pragma: no cover - defensive + logger.warning('Failed to load backend plugin entry %s: %s', ep.name, exc, exc_info=logger.isEnabledFor(logging.DEBUG)) + continue + _register_plugin_object(ep.name, obj, logger) + + +def _load_env_plugins(logger: logging.Logger) -> None: + raw_modules = os.environ.get(ENV_PLUGIN_MODULES, '') + if not raw_modules: + return + + for module_name in filter(None, raw_modules.split(os.pathsep)): + try: + module = import_module(module_name) + except Exception as exc: # pragma: no cover - defensive + logger.warning('Failed to import backend plugin module %s: %s', module_name, exc, exc_info=logger.isEnabledFor(logging.DEBUG)) + continue + + register_callable: Any = getattr(module, 'register', module) + _register_plugin_object(module_name, register_callable, logger) + + +def _register_plugin_object(name: str, obj: Any, logger: logging.Logger) -> None: + """Interpret the plugin object and register provided backends.""" + + if inspect.isclass(obj) and issubclass(obj, Backend): + _safe_register_backend(name, obj, logger) + return + + if isinstance(obj, Iterable) and not isinstance(obj, (str, bytes)): + for item in obj: + _register_plugin_object(name, item, logger) + return + + if callable(obj): + _invoke_registration_callable(name, obj, logger) + return + + logger.warning('Plugin entry %s did not provide a usable backend registration (got %r)', name, obj) + + +def _invoke_registration_callable(name: str, func: Callable[..., Any], logger: logging.Logger) -> None: + try: + func(register_backend=register_backend, register_writer=register_writer) + except TypeError: + try: + func(register_backend, register_writer) + except Exception as exc: # pragma: no cover - defensive + logger.warning('Backend plugin callable %s failed: %s', name, exc, exc_info=logger.isEnabledFor(logging.DEBUG)) + else: + return + except Exception as exc: # pragma: no cover - defensive + logger.warning('Backend plugin callable %s failed: %s', name, exc, exc_info=logger.isEnabledFor(logging.DEBUG)) + return + else: + return + + +def _safe_register_backend(name: str, backend_cls: type[Backend], logger: logging.Logger) -> None: + try: + register_backend(name, backend_cls) + except Exception as exc: # pragma: no cover - defensive + logger.warning('Failed to register backend %s from plugin: %s', name, exc, exc_info=logger.isEnabledFor(logging.DEBUG)) From b5a85b9c8673c5254535e385a01b53ce30d7401b Mon Sep 17 00:00:00 2001 From: dimdano Date: Fri, 26 Sep 2025 10:56:17 +0200 Subject: [PATCH 2/6] pre-commit fixes --- README.md | 3 --- docs/advanced/plugins.rst | 4 ++-- hls4ml/backends/__init__.py | 2 +- hls4ml/backends/plugin_loader.py | 19 ++++++++++++++----- 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 44ad7d7734..3f633a3dcc 100644 --- a/README.md +++ b/README.md @@ -26,9 +26,6 @@ Detailed tutorials on how to use `hls4ml`'s various functionalities can be found pip install hls4ml ``` -Specialised backends are now distributed as optional plugins. Install them alongside hls4ml via -``pip install ``, for example ``pip install aie4ml`` for the AMD AIE flow. - To install the extra dependencies for profiling: ```bash diff --git a/docs/advanced/plugins.rst b/docs/advanced/plugins.rst index e1949b8f9d..ddb7373bfd 100644 --- a/docs/advanced/plugins.rst +++ b/docs/advanced/plugins.rst @@ -2,8 +2,8 @@ External Backend and Writer Plugins ======================================= -Starting with this release ``hls4ml`` can discover and load backend implementations from -external Python packages. This enables specialised flows—such as the AMD AIE backend—to live in +``hls4ml`` can discover and load backend implementations from +external Python packages. This enables specialised flows, such as the AMD AIE backend, to live in independent projects that version and iterate at their own cadence while reusing the core conversion infrastructure. diff --git a/hls4ml/backends/__init__.py b/hls4ml/backends/__init__.py index 7b31770c84..54a047646a 100644 --- a/hls4ml/backends/__init__.py +++ b/hls4ml/backends/__init__.py @@ -1,6 +1,7 @@ from hls4ml.backends.backend import Backend, get_available_backends, get_backend, register_backend # noqa: F401 from hls4ml.backends.fpga.fpga_backend import FPGABackend # noqa: F401 from hls4ml.backends.oneapi.oneapi_backend import OneAPIBackend +from hls4ml.backends.plugin_loader import load_backend_plugins from hls4ml.backends.quartus.quartus_backend import QuartusBackend from hls4ml.backends.symbolic.symbolic_backend import SymbolicExpressionBackend from hls4ml.backends.vivado.vivado_backend import VivadoBackend @@ -10,7 +11,6 @@ from hls4ml.backends.catapult.catapult_backend import CatapultBackend # isort: skip from hls4ml.backends.vitis.vitis_backend import VitisBackend # isort: skip -from hls4ml.backends.plugin_loader import load_backend_plugins def _register_builtin_backends(): diff --git a/hls4ml/backends/plugin_loader.py b/hls4ml/backends/plugin_loader.py index 05edb3ef40..a51ccd7e38 100644 --- a/hls4ml/backends/plugin_loader.py +++ b/hls4ml/backends/plugin_loader.py @@ -5,9 +5,9 @@ import inspect import logging import os -from collections.abc import Iterable +from collections.abc import Callable, Iterable from importlib import import_module -from typing import Any, Callable +from typing import Any try: # pragma: no cover - fall back for older Python versions from importlib.metadata import entry_points @@ -60,7 +60,9 @@ def _load_entry_point_plugins(logger: logging.Logger) -> None: try: obj = ep.load() except Exception as exc: # pragma: no cover - defensive - logger.warning('Failed to load backend plugin entry %s: %s', ep.name, exc, exc_info=logger.isEnabledFor(logging.DEBUG)) + logger.warning( + 'Failed to load backend plugin entry %s: %s', ep.name, exc, exc_info=logger.isEnabledFor(logging.DEBUG) + ) continue _register_plugin_object(ep.name, obj, logger) @@ -74,7 +76,12 @@ def _load_env_plugins(logger: logging.Logger) -> None: try: module = import_module(module_name) except Exception as exc: # pragma: no cover - defensive - logger.warning('Failed to import backend plugin module %s: %s', module_name, exc, exc_info=logger.isEnabledFor(logging.DEBUG)) + logger.warning( + 'Failed to import backend plugin module %s: %s', + module_name, + exc, + exc_info=logger.isEnabledFor(logging.DEBUG), + ) continue register_callable: Any = getattr(module, 'register', module) @@ -121,4 +128,6 @@ def _safe_register_backend(name: str, backend_cls: type[Backend], logger: loggin try: register_backend(name, backend_cls) except Exception as exc: # pragma: no cover - defensive - logger.warning('Failed to register backend %s from plugin: %s', name, exc, exc_info=logger.isEnabledFor(logging.DEBUG)) + logger.warning( + 'Failed to register backend %s from plugin: %s', name, exc, exc_info=logger.isEnabledFor(logging.DEBUG) + ) From 95fc4487745dabd198dcdbbe3fe9c06a9028847d Mon Sep 17 00:00:00 2001 From: dimdano Date: Fri, 26 Sep 2025 13:42:26 +0200 Subject: [PATCH 3/6] remove unused JSON include --- MANIFEST.in | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index 4a6bc93b2f..656a95dd71 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -4,7 +4,6 @@ graft test graft contrib recursive-include hls4ml/templates * recursive-include hls4ml *.py -recursive-include hls4ml/backends *.json recursive-include hls4ml/contrib * global-exclude .git .gitmodules .gitlab-ci.yml *.pyc -include hls4ml/backends/vivado_accelerator/supported_boards.json +include hls4ml/backends/vivado_accelerator/supported_boards.json \ No newline at end of file From 1448871f598640d0a59248e0f403dea087a62be7 Mon Sep 17 00:00:00 2001 From: dimdano Date: Fri, 26 Sep 2025 13:44:22 +0200 Subject: [PATCH 4/6] pre-commit fix --- MANIFEST.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index 656a95dd71..e3ee5ded3c 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -6,4 +6,4 @@ recursive-include hls4ml/templates * recursive-include hls4ml *.py recursive-include hls4ml/contrib * global-exclude .git .gitmodules .gitlab-ci.yml *.pyc -include hls4ml/backends/vivado_accelerator/supported_boards.json \ No newline at end of file +include hls4ml/backends/vivado_accelerator/supported_boards.json From e093e4d4e10a9ab97088f75b523c0743008fad46 Mon Sep 17 00:00:00 2001 From: dimdano Date: Tue, 11 Nov 2025 10:13:35 +0100 Subject: [PATCH 5/6] make plugin loader consistent with hls4ml --- hls4ml/backends/plugin_loader.py | 83 +++++++++++--------------------- 1 file changed, 28 insertions(+), 55 deletions(-) diff --git a/hls4ml/backends/plugin_loader.py b/hls4ml/backends/plugin_loader.py index a51ccd7e38..1feda1e192 100644 --- a/hls4ml/backends/plugin_loader.py +++ b/hls4ml/backends/plugin_loader.py @@ -3,17 +3,12 @@ from __future__ import annotations import inspect -import logging import os from collections.abc import Callable, Iterable from importlib import import_module +from importlib.metadata import entry_points from typing import Any -try: # pragma: no cover - fall back for older Python versions - from importlib.metadata import entry_points -except ImportError: # pragma: no cover - from importlib_metadata import entry_points # type: ignore - from hls4ml.backends.backend import Backend, register_backend from hls4ml.writer.writers import register_writer @@ -23,51 +18,37 @@ _plugins_loaded = False -def load_backend_plugins(logger: logging.Logger | None = None) -> None: +def load_backend_plugins() -> None: """Discover and register backend plugins. This function loads plugins published via Python entry points under the ``hls4ml.backends`` group as well as modules listed in the ``HLS4ML_BACKEND_PLUGINS`` environment variable. The environment variable accepts a separator compatible with :data:`os.pathsep`. - - Args: - logger (logging.Logger, optional): Optional logger used for diagnostics. - When omitted, a module-local logger will be used. """ - global _plugins_loaded if _plugins_loaded: return - logger = logger or logging.getLogger(__name__) - - _load_entry_point_plugins(logger) - _load_env_plugins(logger) + _load_entry_point_plugins() + _load_env_plugins() _plugins_loaded = True -def _load_entry_point_plugins(logger: logging.Logger) -> None: - eps = entry_points() - - if hasattr(eps, 'select'): - group_eps = eps.select(group=ENTRY_POINT_GROUP) - else: # pragma: no cover - legacy importlib_metadata API - group_eps = eps.get(ENTRY_POINT_GROUP, []) +def _load_entry_point_plugins() -> None: + group_eps = entry_points().select(group=ENTRY_POINT_GROUP) for ep in group_eps: try: obj = ep.load() - except Exception as exc: # pragma: no cover - defensive - logger.warning( - 'Failed to load backend plugin entry %s: %s', ep.name, exc, exc_info=logger.isEnabledFor(logging.DEBUG) - ) + except Exception as exc: + print(f'WARNING: failed to load backend plugin entry "{ep.name}": {exc}') continue - _register_plugin_object(ep.name, obj, logger) + _register_plugin_object(ep.name, obj) -def _load_env_plugins(logger: logging.Logger) -> None: +def _load_env_plugins() -> None: raw_modules = os.environ.get(ENV_PLUGIN_MODULES, '') if not raw_modules: return @@ -75,59 +56,51 @@ def _load_env_plugins(logger: logging.Logger) -> None: for module_name in filter(None, raw_modules.split(os.pathsep)): try: module = import_module(module_name) - except Exception as exc: # pragma: no cover - defensive - logger.warning( - 'Failed to import backend plugin module %s: %s', - module_name, - exc, - exc_info=logger.isEnabledFor(logging.DEBUG), - ) + except Exception as exc: + print(f'WARNING: failed to import backend plugin module "{module_name}": {exc}') continue register_callable: Any = getattr(module, 'register', module) - _register_plugin_object(module_name, register_callable, logger) + _register_plugin_object(module_name, register_callable) -def _register_plugin_object(name: str, obj: Any, logger: logging.Logger) -> None: +def _register_plugin_object(name: str, obj: Any) -> None: """Interpret the plugin object and register provided backends.""" if inspect.isclass(obj) and issubclass(obj, Backend): - _safe_register_backend(name, obj, logger) + _safe_register_backend(name, obj) return if isinstance(obj, Iterable) and not isinstance(obj, (str, bytes)): for item in obj: - _register_plugin_object(name, item, logger) + _register_plugin_object(name, item) return if callable(obj): - _invoke_registration_callable(name, obj, logger) + _invoke_registration_callable(name, obj) return - logger.warning('Plugin entry %s did not provide a usable backend registration (got %r)', name, obj) + print(f'WARNING: plugin entry "{name}" did not provide a usable backend registration (got {obj!r})') -def _invoke_registration_callable(name: str, func: Callable[..., Any], logger: logging.Logger) -> None: +def _invoke_registration_callable(name: str, func: Callable[..., Any]) -> None: try: func(register_backend=register_backend, register_writer=register_writer) + return except TypeError: try: func(register_backend, register_writer) - except Exception as exc: # pragma: no cover - defensive - logger.warning('Backend plugin callable %s failed: %s', name, exc, exc_info=logger.isEnabledFor(logging.DEBUG)) - else: return - except Exception as exc: # pragma: no cover - defensive - logger.warning('Backend plugin callable %s failed: %s', name, exc, exc_info=logger.isEnabledFor(logging.DEBUG)) - return - else: + except Exception as exc: + print(f'WARNING: backend plugin callable "{name}" failed: {exc}') + return + except Exception as exc: + print(f'WARNING: backend plugin callable "{name}" failed: {exc}') return -def _safe_register_backend(name: str, backend_cls: type[Backend], logger: logging.Logger) -> None: +def _safe_register_backend(name: str, backend_cls: type[Backend]) -> None: try: register_backend(name, backend_cls) - except Exception as exc: # pragma: no cover - defensive - logger.warning( - 'Failed to register backend %s from plugin: %s', name, exc, exc_info=logger.isEnabledFor(logging.DEBUG) - ) + except Exception as exc: + print(f'WARNING: failed to register backend "{name}" from plugin: {exc}') From 798f09ad1713289dc7ef39087478c27c8f267708 Mon Sep 17 00:00:00 2001 From: dimdano Date: Tue, 11 Nov 2025 15:18:08 +0100 Subject: [PATCH 6/6] simplify plugin loader --- docs/advanced/plugins.rst | 3 +-- hls4ml/backends/plugin_loader.py | 24 ++---------------------- 2 files changed, 3 insertions(+), 24 deletions(-) diff --git a/docs/advanced/plugins.rst b/docs/advanced/plugins.rst index ddb7373bfd..3c741adfb8 100644 --- a/docs/advanced/plugins.rst +++ b/docs/advanced/plugins.rst @@ -11,8 +11,7 @@ Discovery ========= Plugin packages advertise themselves through the ``hls4ml.backends`` Python entry point group. Each -entry either exposes a subclass of :class:`hls4ml.backends.backend.Backend` or a callable that -receives ``register_backend`` and ``register_writer`` helpers and performs any setup that is +entry exposes a callable that receives ``register_backend`` and ``register_writer`` helpers and performs any setup that is required. ``hls4ml`` automatically scans for these entry points during ``hls4ml.backends`` import so third-party backends become available without additional user configuration. diff --git a/hls4ml/backends/plugin_loader.py b/hls4ml/backends/plugin_loader.py index 1feda1e192..b7147b430e 100644 --- a/hls4ml/backends/plugin_loader.py +++ b/hls4ml/backends/plugin_loader.py @@ -1,15 +1,12 @@ """Utilities for discovering and loading external hls4ml backend plugins.""" -from __future__ import annotations - -import inspect import os -from collections.abc import Callable, Iterable +from collections.abc import Callable from importlib import import_module from importlib.metadata import entry_points from typing import Any -from hls4ml.backends.backend import Backend, register_backend +from hls4ml.backends.backend import register_backend from hls4ml.writer.writers import register_writer ENTRY_POINT_GROUP = 'hls4ml.backends' @@ -66,16 +63,6 @@ def _load_env_plugins() -> None: def _register_plugin_object(name: str, obj: Any) -> None: """Interpret the plugin object and register provided backends.""" - - if inspect.isclass(obj) and issubclass(obj, Backend): - _safe_register_backend(name, obj) - return - - if isinstance(obj, Iterable) and not isinstance(obj, (str, bytes)): - for item in obj: - _register_plugin_object(name, item) - return - if callable(obj): _invoke_registration_callable(name, obj) return @@ -97,10 +84,3 @@ def _invoke_registration_callable(name: str, func: Callable[..., Any]) -> None: except Exception as exc: print(f'WARNING: backend plugin callable "{name}" failed: {exc}') return - - -def _safe_register_backend(name: str, backend_cls: type[Backend]) -> None: - try: - register_backend(name, backend_cls) - except Exception as exc: - print(f'WARNING: failed to register backend "{name}" from plugin: {exc}')