"""Plugins module. Uses ``pkg_resources`` to detect installed plugins and loads them ascategories."""importtypesfromcollectionsimportdefaultdictfromimportlib.metadataimportentry_pointsfromitertoolsimportchainimporterrrfrom.exceptionsimportPluginError# Before 3.10 `importlib.metadata` was provisional, and didn't have `select` yet.class_EntryPointsPatch(dict):defselect(self,*,group=None):returnself.get(group,[])classmutdict(dict):passclassmutlist(list):pass
[docs]defdiscover(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()ifnothasattr(eps,"select"):eps=_EntryPointsPatch(eps)forentryinchain(eps.select(group="bsb."+category),_unittest_plugins[category]):try:advert=entry.load()ifhasattr(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.ifisinstance(advert,types.FunctionType):advert=advert()advert=_decorate_advert(advert,entry)registry[entry.name]=advertexceptExceptionase:# pragma: nocovererrr.wrap(PluginError,e,entry,prepend="Could not instantiate the `%plugin.name%` plugin:\n",)returnregistry
def_decorate_advert(advert,entry):iftype(advert)islist:advert=mutlist(advert)eliftype(advert)isdict:advert=mutdict(advert)advert._bsb_entry_point=entryreturnadvert# Registry to insert plugins without having to install them, intended for testing purposes._unittest_plugins=defaultdict(list)__all__=["discover"]