Usage Overview¶
Create a plugin manager¶
A PluginManager
is the main object that registers and organizes
plugins. It is instantiated with a project_name
:
plugin_manager = PluginManager('my_project')
All hook specifications and hook
implementations must use the same project_name
if
they are to be recognized by this plugin_manager
instance.
Add some hook specifications¶
You add hook specifications which outline the function signatures plugins may implement:
plugin_manager.add_hookspecs(some_class_or_module)
… where “some_class_or_module
” is any namespace
object (such as a class or module) that has some functions that have been
decorated as hook specifications for 'my_project'
using a
HookSpecificationMarker
decorator.
# some_class_or_module.py
from napari_plugin_engine import HookSpecificationMarker
my_project_hook_specification = HookSpecificationMarker('my_project')
@my_project_hook_specification
def do_something(arg1: int, arg2: int): -> int:
"""Take two integers and return one integer."""
After calling add_hookspecs()
, your plugin_manager
instance will have a new HookCaller
instance created under the
plugin_manager.hooks
namespace, for each hook specification discovered.
In this case, there will be a new one at plugin_manager.hooks.do_something
.
(Plugins) write hook implementations¶
Plugins may then provide implementations for your hook specifications, by
creating classes or modules that contain functions that are decorated with an
instance of a HookImplementationMarker
that has been created using the same
project name (in this example: 'my_project'
)
# some_plugin.py
from napari_plugin_engine import HookImplementationMarker
my_project_hook_implementation = HookImplementationMarker('my_project')
@my_project_hook_implementation
def do_something(arg1, arg2):
return arg1 + arg2
Register plugins¶
You may directly register these modules with the plugin_manager
…
import some_plugin
plugin_manager.register(some_plugin)
Autodiscover plugins in the environment¶
However, it is more often the case that you will want to discover plugins in
your environment. napari-plugin-engine
provides two ways to discover
plugins via two different conventions:
1. Using package metadata:
looking for distributions that declare a specific entry_point
in their setup.py
file.
2. Using naming convention: looking for modules that begin with a specific prefix.
You can look for either or both, in single call to
discover()
, which will import any modules or entry_points
that follow one of the aforementioned conventions, and search them for
functions decorated with the appropriate HookImplementationMarker
(as
shown above in some_plugin.py
)
plugin_manager.discover(
entry_point='my_project.plugin', prefix='my_project_'
)
Use (call) the plugin implementations¶
Your HookCaller
should now be populated with any of the
implementations found in plugins, as HookImplementation
objects on the
HookCaller
.
# show all implementations for do_something
plugin_manager.hooks.do_something.get_hookimpls()
Finally, you can call some or all of the plugin implementation functions by
directly calling the HookCaller
object:
result = plugin_manager.hooks.do_something(arg1=2, arg2=7)
# assuming only some_plugin.py from above is registered:
print(result) # [9]
By default, all plugin implementations are called, and all non-None
results are returned in a list. However, this is configurable and depends on
how the @my_project_hook_specification
was used, and how the
HookCaller
was called
How the plugin_name
is chosen¶
1. If plugin discovery via entry_points is used
(e.g. plugin_manager.discover(entry_point='app.plugin')
), then plugins
will be named using the name of the entry_point provided by each plugin. Note,
a single package may provide multiple plugins via entry points. For example,
if a package had the following entry_points
declared in their setup.py
file:
# setup.py
setup(
...
entry_points={'app.plugin': ['plugin1 = module_a', 'plugin2 = module_b']},
...
)
… then manager.discover(entry_point='app.plugin')
would register two
plugins, named "plugin1"
(which would inspect module_a
for
implementations) and "plugin2"
(which would inspect module_b
for
implementations).
2. If plugin discovery via naming convention is used
(e.g. plugin_manager.discover(prefix='app_')
), then…
2a. If a
dist-info
folder is found for the moduleThen the plugin will be named using the Name key in the distribution
METADATA
file if one is available. Usually, this will come from having asetup(name="distname", ...)
entry in asetup.py
file. See Core metadata specifications and PEP 566 for details.2a. If no distribution metadata can be located
The the plugin will be named using the name of the module itself.
3. If a plugin is directly registered
(e.g. plugin_manager.register(object, name)
), then if a name
argument
is provided to the PluginManager.register()
method, it will be used as
the plugin_name
, otherwise, the string form of the object is used:
str(id(object))