{"id":9971,"library":"napari-plugin-engine","title":"napari Plugin Engine","description":"napari-plugin-engine is a Python library providing a generic plugin management system, forked from the widely used pluggy project. It enables applications like napari to discover, load, and execute plugins by defining clear hook specifications and allowing multiple plugin implementations. As of version 0.2.1, it provides a stable API for extending applications. Its release cadence is tied to its development within the napari ecosystem.","status":"active","version":"0.2.1","language":"en","source_language":"en","source_url":"https://github.com/napari/napari-plugin-engine","tags":["plugin","napari","extension","architecture","hook","pluggy"],"install":[{"cmd":"pip install napari-plugin-engine","lang":"bash","label":"Install stable version"}],"dependencies":[],"imports":[{"symbol":"PluginManager","correct":"from napari_plugin_engine import PluginManager"},{"symbol":"HookspecMarker","correct":"from napari_plugin_engine import HookspecMarker"},{"symbol":"HookimplMarker","correct":"from napari_plugin_engine import HookimplMarker"}],"quickstart":{"code":"from napari_plugin_engine import PluginManager, HookspecMarker, HookimplMarker\n\n# 1. Define unique markers for your project\nproject_name = \"my_app_plugins\"\nhookspec = HookspecMarker(project_name)\nhookimpl = HookimplMarker(project_name)\n\n# 2. Define your hook specifications\nclass MyAppHooks:\n    @hookspec(firstresult=True)\n    def get_data_source(self, name: str) -> str:\n        \"\"\"Get data from a named source.\"\"\"\n        pass\n\n    @hookspec\n    def process_data(self, data: str) -> str:\n        \"\"\"Process input data and return modified data.\"\"\"\n        pass\n\n# 3. Implement a plugin\nclass MyFileSystemPlugin:\n    @hookimpl\n    def get_data_source(self, name: str) -> str:\n        if name == \"filesystem\":\n            return \"Data from filesystem\"\n        return None # Crucial for firstresult hooks to allow other plugins\n\n    @hookimpl\n    def process_data(self, data: str) -> str:\n        return f\"Processed: {data.upper()}\"\n\nclass MyNetworkPlugin:\n    @hookimpl\n    def get_data_source(self, name: str) -> str:\n        if name == \"network\":\n            return \"Data from network\"\n        return None\n\n# 4. Initialize the PluginManager\npm = PluginManager(project_name)\n# Add hook specifications. Optional prefix can differentiate similar hook names.\npm.add_hookspecs(MyAppHooks, specname_prefix=f'{project_name}_')\n\n# 5. Register plugins\npm.register(MyFileSystemPlugin())\npm.register(MyNetworkPlugin())\n\n# 6. Call hooks\nprint(f\"--- Hook Calls ---\")\n\ndata_fs = pm.hook.get_data_source(name=\"filesystem\")\nprint(f\"get_data_source('filesystem'): {data_fs}\")\n\ndata_net = pm.hook.get_data_source(name=\"network\")\nprint(f\"get_data_source('network'): {data_net}\")\n\nprocessed_data_fs = pm.hook.process_data(data=data_fs)\nprint(f\"process_data(data_fs): {processed_data_fs}\")\n\nprocessed_data_net = pm.hook.process_data(data=data_net)\nprint(f\"process_data(data_net): {processed_data_net}\")","lang":"python","description":"This quickstart demonstrates how to define hook specifications, implement plugins with hook implementations, register them with a PluginManager, and then call the hooks. It showcases a 'firstresult' hook and a multi-result hook."},"warnings":[{"fix":"Ensure `PluginManager('myproject')`, `HookspecMarker('myproject')`, and `HookimplMarker('myproject')` use a globally unique string for 'myproject' within your application context.","message":"Each PluginManager instance requires a unique `project_name` string during initialization and for its `HookspecMarker` and `HookimplMarker`. Reusing project names across different logical plugin systems or in different parts of an application can lead to unexpected plugin interactions or discovery issues.","severity":"gotcha","affected_versions":"All versions"},{"fix":"Always ensure the arguments of a `@hookimpl` function precisely match (or are a compatible superset of) its corresponding `@hookspec` function. Pay close attention to default values and `*args`/`**kwargs`.","message":"Hook specification (hookspec) and hook implementation (hookimpl) signatures must match, or the hookimpl must be compatible (e.g., accepting `**kwargs` for extra arguments). Mismatches will result in `TypeError` when the hook is called, or the hookimpl might not be recognized.","severity":"gotcha","affected_versions":"All versions"},{"fix":"Plugin implementations for `firstresult` hooks should explicitly return `None` if they cannot handle the specific request, allowing other plugins a chance. Otherwise, return the actual result.","message":"When using `firstresult=True` on a hookspec, only the first hook implementation that returns a non-`None` value will have its result used. If all registered hookimpls return `None`, the hook call itself will return `None` (or the default set in the hookspec).","severity":"gotcha","affected_versions":"All versions"}],"env_vars":null,"last_verified":"2026-04-17T00:00:00.000Z","next_check":"2026-07-16T00:00:00.000Z","problems":[{"fix":"Ensure you have called `pm.add_hookspecs(MyHooksClass)` with the class containing your `@hookspec` definitions. Also, check that the `specname_prefix` used when adding hookspecs matches any prefix you might be expecting for the hook name.","cause":"The PluginManager's hook relay cannot find the specified hook. This usually means the hook specification (with `@hookspec`) was not added to the PluginManager, or the `specname_prefix` did not match.","error":"AttributeError: 'HookRelay' object has no attribute 'my_hook'"},{"fix":"Review both the `@hookspec` definition and the `@hookimpl` definition for `my_hook`. Ensure that the `hookimpl` accepts all arguments defined in the `hookspec` and that all required arguments are passed when calling `pm.hook.my_hook(...)`.","cause":"A hook implementation's signature does not match the hook specification or the arguments provided during the hook call.","error":"TypeError: my_hook() missing 1 required positional argument: 'arg1'"},{"fix":"Install the package using pip: `pip install napari-plugin-engine`.","cause":"The `napari-plugin-engine` package is not installed in your current Python environment.","error":"ModuleNotFoundError: No module named 'napari_plugin_engine'"}]}