def test_not_all_arguments_are_provided_issues_a_warning( pm: PluginManager) -> None: """Calling a hook without providing all arguments specified in the hook spec issues a warning.""" class Spec: @hookspec def hello(self, arg1, arg2): pass @hookspec(historic=True) def herstory(self, arg1, arg2): pass pm.add_hookspecs(Spec) with pytest.warns(UserWarning, match=r"'arg1', 'arg2'.*cannot be found.*$"): pm.hook.hello() with pytest.warns(UserWarning, match=r"'arg2'.*cannot be found.*$"): pm.hook.hello(arg1=1) with pytest.warns(UserWarning, match=r"'arg1'.*cannot be found.*$"): pm.hook.hello(arg2=2) with pytest.warns(UserWarning, match=r"'arg1', 'arg2'.*cannot be found.*$"): pm.hook.hello.call_extra([], kwargs=dict()) with pytest.warns(UserWarning, match=r"'arg1', 'arg2'.*cannot be found.*$"): pm.hook.herstory.call_historic(kwargs=dict())
def test_opt_in_args(pm: PluginManager) -> None: """Verify that two hookimpls with mutex args can serve under the same spec. """ class Api: @hookspec def hello(self, arg1, arg2, common_arg): "api hook 1" class Plugin1: @hookimpl def hello(self, arg1, common_arg): return arg1 + common_arg class Plugin2: @hookimpl def hello(self, arg2, common_arg): return arg2 + common_arg pm.add_hookspecs(Api) pm.register(Plugin1()) pm.register(Plugin2()) results = pm.hook.hello(arg1=1, arg2=2, common_arg=0) assert results == [2, 1]
def get_serializers(refresh_cache=False): """Discover and return Serializer classes from all installed plugins Serializers are cached and are not repeatedly loaded in future calls Args: refresh_cache (bool): If True, ignore any existing serializer cache and discover serializers as normal Returns: dict: Keys are the formats provided by and pointing to their respective Serializers """ global _serializer_cache if _serializer_cache is None or refresh_cache: from pluggy import PluginManager package_name = __name__.split('.')[0] pm = PluginManager(package_name) pm.load_setuptools_entrypoints(package_name + '.serializers') for mod in pm.get_plugins(): import_submodules(mod) _serializer_cache = {} for serializer_class in get_recursive_subclasses(Serializer): if not serializer_class.format: logger.warning( 'Serializer "{}" does not provide a format, will not be auto-discovered' .format(get_full_qualname(serializer_class))) continue _serializer_cache[serializer_class.format] = serializer_class return _serializer_cache
def test_hook_tracing(he_pm: PluginManager) -> None: saveindent = [] class api1: @hookimpl def he_method1(self): saveindent.append(he_pm.trace.root.indent) class api2: @hookimpl def he_method1(self): saveindent.append(he_pm.trace.root.indent) raise ValueError() he_pm.register(api1()) out: List[Any] = [] he_pm.trace.root.setwriter(out.append) undo = he_pm.enable_tracing() try: indent = he_pm.trace.root.indent he_pm.hook.he_method1(arg=1) assert indent == he_pm.trace.root.indent assert len(out) == 2 assert "he_method1" in out[0] assert "finish" in out[1] out[:] = [] he_pm.register(api2()) with pytest.raises(ValueError): he_pm.hook.he_method1(arg=1) assert he_pm.trace.root.indent == indent assert saveindent[0] > indent finally: undo()
def test_load_setuptools_instantiation(monkeypatch, pm: PluginManager) -> None: class EntryPoint: name = "myname" group = "hello" value = "myname:foo" def load(self): class PseudoPlugin: x = 42 return PseudoPlugin() class Distribution: entry_points = (EntryPoint(), ) dist = Distribution() def my_distributions(): return (dist, ) monkeypatch.setattr(importlib_metadata, "distributions", my_distributions) num = pm.load_setuptools_entrypoints("hello") assert num == 1 plugin = pm.get_plugin("myname") assert plugin is not None assert plugin.x == 42 ret = pm.list_plugin_distinfo() # poor man's `assert ret == [(plugin, mock.ANY)]` assert len(ret) == 1 assert len(ret[0]) == 2 assert ret[0][0] == plugin assert ret[0][1]._dist == dist # type: ignore[comparison-overlap] num = pm.load_setuptools_entrypoints("hello") assert num == 0 # no plugin loaded by this call
def test_warning_on_call_vs_hookspec_arg_mismatch(): """Verify that is a hook is called with less arguments then defined in the spec that a warning is emitted. """ class Spec: @hookspec def myhook(self, arg1, arg2): pass class Plugin: @hookimpl def myhook(self, arg1): pass pm = PluginManager(hookspec.project_name) pm.register(Plugin()) pm.add_hookspecs(Spec()) with warnings.catch_warnings(record=True) as warns: warnings.simplefilter('always') # calling should trigger a warning pm.hook.myhook(arg1=1) assert len(warns) == 1 warning = warns[-1] assert issubclass(warning.category, Warning) assert "Argument(s) ('arg2',)" in str(warning.message)
def hc(pm: PluginManager) -> _HookCaller: class Hooks: @hookspec def he_method1(self, arg: object) -> None: pass pm.add_hookspecs(Hooks) return pm.hook.he_method1
def test_has_plugin(pm: PluginManager) -> None: class A: pass a1 = A() pm.register(a1, "hello") assert pm.is_registered(a1) assert pm.has_plugin("hello")
def test_prefix_hookimpl_dontmatch_module(): with pytest.deprecated_call(): pm = PluginManager(hookspec.project_name, "hello_") class BadPlugin(object): hello_module = __import__("email") pm.register(BadPlugin()) pm.check_pending()
def test_only_kwargs(self): pm = PluginManager("he") class Api: def hello(self, arg): "api hook 1" pm.addhooks(Api) pytest.raises(TypeError, lambda: pm.hook.hello(3))
def _create_hook_manager() -> PluginManager: """Create a new PluginManager instance and register Kedro's hook specs. """ manager = PluginManager(HOOK_NAMESPACE) manager.add_hookspecs(NodeSpecs) manager.add_hookspecs(PipelineSpecs) manager.add_hookspecs(DataCatalogSpecs) manager.add_hookspecs(RegistrationSpecs) return manager
def test_register_dynamic_attr(he_pm: PluginManager) -> None: class A: def __getattr__(self, name): if name[0] != "_": return 42 raise AttributeError() a = A() he_pm.register(a) assert not he_pm.get_hookcallers(a)
def he_pm(request, pm: PluginManager) -> PluginManager: hookspec = HookspecMarker("example") class Hooks: @hookspec def he_method1(self, arg: int) -> int: return arg + 1 pm.add_hookspecs(request.param(Hooks)) return pm
def test_implprefix_deprecated(): with pytest.deprecated_call(): pm = PluginManager('blah', implprefix='blah_') class Plugin: def blah_myhook(self, arg1): return arg1 with pytest.deprecated_call(): pm.register(Plugin())
def test_implprefix_deprecated(): with pytest.deprecated_call(): pm = PluginManager("blah", implprefix="blah_") class Plugin: def blah_myhook(self, arg1): return arg1 with pytest.deprecated_call(): pm.register(Plugin())
def test_historic_with_subset_hook_caller(pm: PluginManager) -> None: class Hooks: @hookspec(historic=True) def he_method1(self, arg): ... pm.add_hookspecs(Hooks) out = [] class Plugin: @hookimpl def he_method1(self, arg): out.append(arg) plugin = Plugin() pm.register(plugin) class Plugin2: @hookimpl def he_method1(self, arg): out.append(arg * 10) shc = pm.subset_hook_caller("he_method1", remove_plugins=[plugin]) shc.call_historic(kwargs=dict(arg=1)) pm.register(Plugin2()) assert out == [10] pm.register(Plugin()) assert out == [10, 1]
def test_register_mismatch_arg(he_pm: PluginManager) -> None: class hello: @hookimpl def he_method1(self, qlwkje): pass plugin = hello() with pytest.raises(PluginValidationError) as excinfo: he_pm.register(plugin) assert excinfo.value.plugin is plugin
def __init__(self, driver, timeout, pm=None): self.driver = driver self.driver_adapter = IDriver(driver) self.timeout = timeout self.pm = pm if self.pm is None: self.pm = PluginManager("pypom", implprefix="pypom_") self.pm.add_hookspecs(hooks) self.pm.load_setuptools_entrypoints("pypom.plugin") self.pm.check_pending() self.wait = self.driver_adapter.wait_factory(self.timeout)
def _load_spec(self, event_dispatcher: pluggy.PluginManager) -> None: """ Load all hookspec modules """ # INFO - G.M - 2019-11-28 - get all submodules recursively, # find those with with hookspec suffix, import them and add them # to valid hookspec for hookspec_module_path in self.get_all_hookspec_module_path(): module = importlib.import_module(hookspec_module_path) event_dispatcher.add_hookspecs(module)
def test_firstresult_no_plugin(pm: PluginManager) -> None: """If no implementations/plugins have been registered for a firstresult hook the multi-call loop should return a None value. """ class Api: @hookspec(firstresult=True) def hello(self, arg): "api hook 1" pm.add_hookspecs(Api) res = pm.hook.hello(arg=3) assert res is None
def test_repr() -> None: class Plugin: @hookimpl def myhook(self): raise NotImplementedError() pm = PluginManager(hookspec.project_name) plugin = Plugin() pname = pm.register(plugin) assert repr(pm.hook.myhook.get_hookimpls()[0]) == ( f"<HookImpl plugin_name={pname!r}, plugin={plugin!r}>")
def test_repr(): class Plugin: @hookimpl def myhook(self): raise NotImplementedError() pm = PluginManager(hookspec.project_name) plugin = Plugin() pname = pm.register(plugin) assert repr(pm.hook.myhook._nonwrappers[0]) == ( "<HookImpl plugin_name=%r, plugin=%r>" % (pname, plugin))
def test_register_mismatch_method(he_pm: PluginManager) -> None: class hello: @hookimpl def he_method_notexists(self): pass plugin = hello() he_pm.register(plugin) with pytest.raises(PluginValidationError) as excinfo: he_pm.check_pending() assert excinfo.value.plugin is plugin
def test_only_kwargs(pm: PluginManager) -> None: class Api: @hookspec def hello(self, arg): "api hook 1" pm.add_hookspecs(Api) with pytest.raises(TypeError) as exc: pm.hook.hello(3) # type: ignore[call-arg] message = "__call__() takes 1 positional argument but 2 were given" assert message in str(exc.value)
class SimplePluginManager(object): """ A PluginManager class for simple, non-scanning related plugins. """ def __init__(self, project_name, entrypoint, plugin_base_class): """ Initialize this plugin manager for the fully qualified Python module name `module_qname` with plugins loaded from the setuptools `entrypoint` that must subclass `plugin_base_class`. """ self.manager = PluggyPluginManager(project_name=project_name) self.entrypoint = entrypoint self.plugin_base_class = plugin_base_class self.manager.add_hookspecs(sys.modules[project_name]) # set to True once this manager is initialized by running its setup() self.initialized = False # mapping of {plugin.name: plugin_class} for all the loaded plugins of # this manager self.plugin_classes = OrderedDict() def setup(self): """ Load and validate available plugins for this PluginManager from its assigned `entrypoint`. Raise an Exception if a plugin is not valid such that when it does not subcclass the manager `plugin_base_class`. Must be called once to initialize the plugins if this manager. Return a list of all plugin classes for this manager. """ if self.initialized: return self.plugin_classes.values() entrypoint = self.entrypoint self.manager.load_setuptools_entrypoints(entrypoint) plugin_classes = [] for name, plugin_class in self.manager.list_name_plugin(): if not issubclass(plugin_class, self.plugin_base_class): plugin_base_class = self.plugin_base_class raise Exception( 'Invalid plugin: %(name)r: %(plugin_class)r ' 'must extend %(plugin_base_class)r.' % locals()) plugin_class.name = name plugin_classes.append(plugin_class) self.plugin_classes = OrderedDict([(cls.name, cls) for cls in plugin_classes]) self.initialized = True return self.plugin_classes.values()
def get_pluginmanager(config, load_entry_points=True): pm = PluginManager("devpiweb") # support old plugins, but emit deprecation warnings pm._implprefix = "devpiweb_" pm.add_hookspecs(hookspecs) if load_entry_points: pm.load_setuptools_entrypoints("devpi_web") pm.check_pending() return pm
def test_repr(): class Plugin: @hookimpl def myhook(): raise NotImplementedError() pm = PluginManager(hookspec.project_name) plugin = Plugin() pname = pm.register(plugin) assert repr(pm.hook.myhook._nonwrappers[0]) == ( "<HookImpl plugin_name=%r, plugin=%r>" % (pname, plugin) )
def test_call_extra(pm: PluginManager) -> None: class Hooks: @hookspec def he_method1(self, arg): pass pm.add_hookspecs(Hooks) def he_method1(arg): return arg * 10 out = pm.hook.he_method1.call_extra([he_method1], dict(arg=1)) assert out == [10]
def test_register_hookwrapper_not_a_generator_function( he_pm: PluginManager) -> None: class hello: @hookimpl(hookwrapper=True) def he_method1(self): pass # pragma: no cover plugin = hello() with pytest.raises(PluginValidationError, match="generator function") as excinfo: he_pm.register(plugin) assert excinfo.value.plugin is plugin
def test_no_hookspec(pm: PluginManager) -> None: """A hook with hookimpls can still be called even if no hookspec was registered for it (and call_pending wasn't called to check against it). """ class Plugin: @hookimpl def hello(self, arg): return "Plugin.hello" pm.register(Plugin()) assert pm.hook.hello(arg=10, extra=20) == ["Plugin.hello"]
def get_pluginmanager(load_entrypoints=True): pm = PluginManager("devpiserver") # support old plugins, but emit deprecation warnings pm._implprefix = "devpiserver_" pm.add_hookspecs(hookspecs) # XXX load internal plugins here if load_entrypoints: pm.load_setuptools_entrypoints("devpi_server") pm.check_pending() return pm
def get_pluginmanager(load_entry_points=True): pm = PluginManager("devpiclient", implprefix="devpiclient_") pm.add_hookspecs(hookspecs) if load_entry_points: pm.load_setuptools_entrypoints("devpi_client") pm.check_pending() return pm
def _register_hooks(hook_manager: PluginManager, hooks: Iterable[Any]) -> None: """Register all hooks as specified in ``hooks`` with the global ``hook_manager``. Args: hook_manager: Hook manager instance to register the hooks with. hooks: Hooks that need to be registered. """ for hooks_collection in hooks: # Sometimes users might call hook registration more than once, in which # case hooks have already been registered, so we perform a simple check # here to avoid an error being raised and break user's workflow. if not hook_manager.is_registered(hooks_collection): hook_manager.register(hooks_collection)
def test_argmismatch(self): class Api: def hello(self, arg): "api hook 1" pm = PluginManager("he") pm.addhooks(Api) class Plugin: def hello(self, argwrong): pass with pytest.raises(PluginValidationError) as exc: pm.register(Plugin()) assert "argwrong" in str(exc.value)
def test_hookrelay_registration_by_specname_raises(pm: PluginManager) -> None: """Verify using specname still raises the types of errors during registration as it would have without using specname.""" class Api: @hookspec def hello(self, arg: object) -> None: "api hook 1" pm.add_hookspecs(Api) # make sure a bad signature still raises an error when using specname class Plugin: @hookimpl(specname="hello") def foo(self, arg: int, too, many, args) -> int: return arg + 1 with pytest.raises(PluginValidationError): pm.register(Plugin()) # make sure check_pending still fails if specname doesn't have a # corresponding spec. EVEN if the function name matches one. class Plugin2: @hookimpl(specname="bar") def hello(self, arg: int) -> int: return arg + 1 pm.register(Plugin2()) with pytest.raises(PluginValidationError): pm.check_pending()
def test_firstresult_definition(self): class Api: def hello(self, arg): "api hook 1" hello.firstresult = True pm = PluginManager("he") pm.addhooks(Api) class Plugin: def hello(self, arg): return arg + 1 pm.register(Plugin()) res = pm.hook.hello(arg=3) assert res == 4
def parse_hookimpl_opts(self, module_or_class, name): opts = PluginManager.parse_hookimpl_opts( self, module_or_class, name) if opts is None: if name.startswith("x1"): opts = {} return opts
def get_pluginmanager(load_entry_points=True): pm = PluginManager("devpipasswdreset", implprefix="devpipasswdreset_") pm.add_hookspecs(hookspecs) if load_entry_points: pm.load_setuptools_entrypoints("devpi_passwd_reset") pm.check_pending() return pm
def get_pluginmanager(): pm = PluginManager("devpiserver", implprefix="devpiserver_") pm.add_hookspecs(hookspecs) # XXX load internal plugins here pm.load_setuptools_entrypoints("devpi_server") pm.check_pending() return pm
def get_pluggin_manager(load_entrypoints=True): pm = PluginManager('banana', implprefix='banana_') pm.add_hookspecs(hookspecs) if load_entrypoints: pm.load_setuptools_entrypoints('banana') pm.check_pending() return pm
def test_prefix_hookimpl(self): pm = PluginManager(hookspec.project_name, "hello_") class HookSpec: @hookspec def hello_myhook(self, arg1): """ add to arg1 """ pm.add_hookspecs(HookSpec) class Plugin: def hello_myhook(self, arg1): return arg1 + 1 pm.register(Plugin()) pm.register(Plugin()) results = pm.hook.hello_myhook(arg1=17) assert results == [18, 18]
def test_plugin_getattr_raises_errors(): """Pluggy must be able to handle plugins which raise weird exceptions when getattr() gets called (#11). """ class DontTouchMe(object): def __getattr__(self, x): raise Exception('cant touch me') class Module(object): pass module = Module() module.x = DontTouchMe() pm = PluginManager(hookspec.project_name) # register() would raise an error pm.register(module, 'donttouch') assert pm.get_plugin('donttouch') is module
def test_prefix_hookimpl(include_hookspec): with pytest.deprecated_call(): pm = PluginManager(hookspec.project_name, "hello_") if include_hookspec: class HookSpec(object): @hookspec def hello_myhook(self, arg1): """ add to arg1 """ pm.add_hookspecs(HookSpec) class Plugin(object): def hello_myhook(self, arg1): return arg1 + 1 with pytest.deprecated_call(): pm.register(Plugin()) pm.register(Plugin()) results = pm.hook.hello_myhook(arg1=17) assert results == [18, 18]
def test_warn_when_deprecated_specified(recwarn): warning = DeprecationWarning("foo is deprecated") class Spec(object): @hookspec(warn_on_impl=warning) def foo(self): pass class Plugin(object): @hookimpl def foo(self): pass pm = PluginManager(hookspec.project_name) pm.add_hookspecs(Spec) with pytest.warns(DeprecationWarning) as records: pm.register(Plugin()) (record,) = records assert record.message is warning assert record.filename == Plugin.foo.__code__.co_filename assert record.lineno == Plugin.foo.__code__.co_firstlineno
def test_hapmypath(self): class Api: def hello(self, arg): "api hook 1" pm = PluginManager("he") pm.addhooks(Api) hook = pm.hook assert hasattr(hook, 'hello') assert repr(hook.hello).find("hello") != -1 class Plugin: def hello(self, arg): return arg + 1 plugin = Plugin() pm.register(plugin) l = hook.hello(arg=3) assert l == [4] assert not hasattr(hook, 'world') pm.unregister(plugin) assert hook.hello(arg=3) == []
from pluggy import PluginManager from . import hookspec, PROJECT_NAME # TODO: move to main? manager = PluginManager(PROJECT_NAME) manager.add_hookspecs(hookspec) manager.load_setuptools_entrypoints('gumby.plugin')
the --format command line option. Parameters: - `file_count`: the number of files and directories scanned. - `version`: ScanCode version - `notice`: ScanCode notice - `scanned_files`: an iterable of scan results for each file - `options`: a mapping of key by command line option to a flag True if this option was enabled. - `input`: the original input path scanned. - `output_file`: an opened, file-like object to write the output to. - `_echo`: a funtion to echo strings to stderr. This will be removedd in the future. """ pass output_plugins = PluginManager('scan_output_writer') output_plugins.add_hookspecs(sys.modules[__name__]) def initialize(): """ NOTE: this defines the entry points for use in setup.py """ output_plugins.load_setuptools_entrypoints('scancode_output_writers') def get_format_plugins(): """ Return an ordered mapping of format name --> plugin callable for all the output plugins. The mapping is ordered by sorted key. This is the main API for other code to access format plugins.
post_scan_spec = HookspecMarker('post_scan') post_scan_impl = HookimplMarker('post_scan') @post_scan_spec def post_scan_handler(active_scans, results): """ Process the scanned files and yield the modified results. Parameters: - `active_scans`: a list of scanners names requested in the current run. - `results`: an iterable of scan results for each file or directory. """ pass post_scan_plugins = PluginManager('post_scan') post_scan_plugins.add_hookspecs(sys.modules[__name__]) def initialize(): """ NOTE: this defines the entry points for use in setup.py """ post_scan_plugins.load_setuptools_entrypoints('scancode_post_scan') def get_post_scan_plugins(): """ Return an ordered mapping of "command line option name" --> "plugin callable" for all the post_scan plugins. The mapping is sorted by option name.