Source code for bsb.plugins
"""
Plugins module. Uses ``pkg_resources`` to detect installed plugins and loads them as
categories.
"""
import types
from collections import defaultdict
from importlib.metadata import entry_points
from itertools import chain
import errr
from .exceptions import PluginError
# Before 3.10 `importlib.metadata` was provisional, and didn't have `select` yet.
class _EntryPointsPatch(dict):
def select(self, *, group=None):
return self.get(group, [])
class mutdict(dict):
pass
class mutlist(list):
pass
[docs]
def discover(category):
"""
Discover all plugins for a given category.
:param category: Plugin category (e.g. ``adapters`` to load all ``bsb.adapters``)
:type category: str
:returns: Loaded plugins by name.
:rtype: dict
"""
registry = {}
eps = entry_points()
if not hasattr(eps, "select"):
eps = _EntryPointsPatch(eps)
for entry in chain(eps.select(group="bsb." + category), _unittest_plugins[category]):
try:
advert = entry.load()
if hasattr(advert, "__plugin__"):
advert = advert.__plugin__
# Use `types.FunctionType` over `callable` as `callable` might confuse plugin
# objects that have a `__call__` method with plugin factory functions.
if isinstance(advert, types.FunctionType):
advert = advert()
advert = _decorate_advert(advert, entry)
registry[entry.name] = advert
except Exception as e: # pragma: nocover
errr.wrap(
PluginError,
e,
entry,
prepend="Could not instantiate the `%plugin.name%` plugin:\n",
)
return registry
def _decorate_advert(advert, entry):
if type(advert) is list:
advert = mutlist(advert)
elif type(advert) is dict:
advert = mutdict(advert)
advert._bsb_entry_point = entry
return advert
# Registry to insert plugins without having to install them, intended for testing purposes.
_unittest_plugins = defaultdict(list)
__all__ = ["discover"]