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 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_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_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 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 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_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 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_subset_hook_caller(pm: PluginManager) -> None: class Hooks: @hookspec def he_method1(self, arg): pass pm.add_hookspecs(Hooks) out = [] class Plugin1: @hookimpl def he_method1(self, arg): out.append(arg) class Plugin2: @hookimpl def he_method1(self, arg): out.append(arg * 10) class PluginNo: pass plugin1, plugin2, plugin3 = Plugin1(), Plugin2(), PluginNo() pm.register(plugin1) pm.register(plugin2) pm.register(plugin3) pm.hook.he_method1(arg=1) assert out == [10, 1] out[:] = [] hc = pm.subset_hook_caller("he_method1", [plugin1]) hc(arg=2) assert out == [20] out[:] = [] hc = pm.subset_hook_caller("he_method1", [plugin2]) hc(arg=2) assert out == [2] out[:] = [] pm.unregister(plugin1) hc(arg=2) assert out == [] out[:] = [] pm.hook.he_method1(arg=1) assert out == [10] assert repr(hc) == "<_SubsetHookCaller 'he_method1'>"
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_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_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 _register_all(plugin_manager: pluggy.PluginManager, plugins: typing.Dict[str, types.ModuleType]): # INFO - G.M - 2019-12-02 - sort plugins by name here to have predictable # order for plugin registration according to plugin_name. plugins = collections.OrderedDict(sorted(plugins.items())) for plugin_name, module in plugins.items(): plugin_manager.register(module) try: entry_point = getattr(module, PLUGIN_ENTRY_POINT_NAME) entry_point(plugin_manager) except AttributeError: logger.warning( plugin_manager, "Cannot find plugin entry point '{}' in '{}' plugin".format( PLUGIN_ENTRY_POINT_NAME, plugin_name), )
def create_app(): pm = PluginManager('fp') pm.add_hookspecs(hooks) pm.register(impl) pm.load_setuptools_entrypoints('flaskplug') app = Flask(__name__) for bp in pm.hook.fp_load_blueprints(): if isinstance(bp, dict): app.register_blueprint(**bp) elif isinstance(bp, tuple): app.register_blueprint(bp[0], **bp[1]) else: app.register_blueprint(bp) return app
def test_register_historic_incompat_hookwrapper(pm: PluginManager) -> None: class Hooks: @hookspec(historic=True) def he_method1(self, arg): pass pm.add_hookspecs(Hooks) out = [] class Plugin: @hookimpl(hookwrapper=True) def he_method1(self, arg): out.append(arg) with pytest.raises(PluginValidationError): pm.register(Plugin())
def test_call_with_too_few_args(pm: PluginManager) -> None: class Hooks: @hookspec def he_method1(self, arg): pass pm.add_hookspecs(Hooks) class Plugin1: @hookimpl def he_method1(self, arg): 0 / 0 pm.register(Plugin1()) with pytest.raises(HookCallError): with pytest.warns(UserWarning): pm.hook.he_method1()
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 test_argmismatch(pm: PluginManager) -> None: class Api: @hookspec def hello(self, arg): "api hook 1" pm.add_hookspecs(Api) class Plugin: @hookimpl def hello(self, argwrong): pass with pytest.raises(PluginValidationError) as exc: pm.register(Plugin()) assert "argwrong" in str(exc.value)
def test_pm_name(pm: PluginManager) -> None: class A: pass a1 = A() name = pm.register(a1, name="hello") assert name == "hello" pm.unregister(a1) assert pm.get_plugin("hello") is None assert not pm.is_registered(a1) assert not pm.get_plugins() name2 = pm.register(a1, name="hello") assert name2 == name pm.unregister(name="hello") assert pm.get_plugin("hello") is None assert not pm.is_registered(a1) assert not pm.get_plugins()
def test_plugin_getattr_raises_errors(): """Pluggy must be able to handle plugins which raise weird exceptions when getattr() gets called (#11). """ class DontTouchMe: def __getattr__(self, x): raise Exception('cant touch me') class Module: 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(): pm = PluginManager(hookspec.project_name, "hello_") 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 pm.register(Plugin()) pm.register(Plugin()) results = pm.hook.hello_myhook(arg1=17) assert results == [18, 18]
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_set_blocked(pm: PluginManager) -> None: class A: pass a1 = A() name = pm.register(a1) assert name is not None assert pm.is_registered(a1) assert not pm.is_blocked(name) pm.set_blocked(name) assert pm.is_blocked(name) assert not pm.is_registered(a1) pm.set_blocked("somename") assert pm.is_blocked("somename") assert not pm.register(A(), "somename") pm.unregister(name="somename") assert pm.is_blocked("somename")
def test_plugin_getattr_raises_errors() -> None: """Pluggy must be able to handle plugins which raise weird exceptions when getattr() gets called (#11). """ class DontTouchMe: def __getattr__(self, x): raise Exception("can't touch me") class Module: pass module = Module() module.x = DontTouchMe() # type: ignore[attr-defined] pm = PluginManager(hookspec.project_name) # register() would raise an error pm.register(module, "donttouch") assert pm.get_plugin("donttouch") is module
def test_firstresult_returns_none(pm: PluginManager) -> None: """If None results are returned by underlying implementations ensure the multi-call loop returns a None value. """ class Api: @hookspec(firstresult=True) def hello(self, arg): "api hook 1" pm.add_hookspecs(Api) class Plugin1: @hookimpl def hello(self, arg): return None pm.register(Plugin1()) res = pm.hook.hello(arg=3) assert res is None
def load_plugins( manager: PluginManager, plugins: Iterable[str] = (), logger: Logger = default_logger, ) -> None: """ Load plugins explicitly. Args: manager: A plugin manager to load plugins. plugins: Plugin paths to load. logger: A logger. Raises: RuntimeError: when a given plugin cannot be loaded as a module. Exception: when loading a plugin fails. """ modules = (_load_module(path, logger) for path in plugins) for module in modules: manager.register(module)
def test_pm(pm: PluginManager) -> None: """Basic registration with objects""" class A: pass a1, a2 = A(), A() pm.register(a1) assert pm.is_registered(a1) pm.register(a2, "hello") assert pm.is_registered(a2) out = pm.get_plugins() assert a1 in out assert a2 in out assert pm.get_plugin("hello") == a2 assert pm.unregister(a1) == a1 assert not pm.is_registered(a1) out2 = pm.list_name_plugin() assert len(out2) == 1 assert out2 == [("hello", a2)]
def test_with_result_memorized(pm: PluginManager, result_callback: bool) -> None: """Verify that ``_HookCaller._maybe_apply_history()` correctly applies the ``result_callback`` function, when provided, to the result from calling each newly registered hook. """ out = [] if not result_callback: callback = None else: def callback(res) -> None: out.append(res) class Hooks: @hookspec(historic=True) def he_method1(self, arg): pass pm.add_hookspecs(Hooks) class Plugin1: @hookimpl def he_method1(self, arg): return arg * 10 pm.register(Plugin1()) he_method1 = pm.hook.he_method1 he_method1.call_historic(result_callback=callback, kwargs=dict(arg=1)) class Plugin2: @hookimpl def he_method1(self, arg): return arg * 10 pm.register(Plugin2()) if result_callback: assert out == [10, 10] else: assert out == []
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(pm: PluginManager) -> None: class MyPlugin: @hookimpl def he_method1(self): ... my = MyPlugin() pm.register(my) assert pm.get_plugins() == {my} my2 = MyPlugin() pm.register(my2) assert pm.get_plugins() == {my, my2} assert pm.is_registered(my) assert pm.is_registered(my2) pm.unregister(my) assert not pm.is_registered(my) assert pm.get_plugins() == {my2} with pytest.raises(AssertionError, match=r"not registered"): pm.unregister(my)
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) == []
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_plugin_double_register(pm: PluginManager) -> None: """Registering the same plugin more then once isn't allowed""" pm.register(42, name="abc") with pytest.raises(ValueError): pm.register(42, name="abc") with pytest.raises(ValueError): pm.register(42, name="def")
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_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_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_hookrelay_registration_by_specname(pm: PluginManager) -> None: """Verify hook caller instances may also be registered by specifying a specname option to the hookimpl""" class Api: @hookspec def hello(self, arg: object) -> None: "api hook 1" pm.add_hookspecs(Api) hook = pm.hook assert hasattr(hook, "hello") assert len(pm.hook.hello.get_hookimpls()) == 0 class Plugin: @hookimpl(specname="hello") def foo(self, arg: int) -> int: return arg + 1 plugin = Plugin() pm.register(plugin) out = hook.hello(arg=3) assert out == [4]