# 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 module

> Then the plugin will be named using the Name key in the distribution `METADATA` file if one is available. Usually, this will come from having a `setup(name="distname", ...)` entry in a `setup.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))`