4.9 KiB
Loading the Plugins
There are several different enabling and invocation patterns for consumers of plugins, depending on your needs.
Loading Drivers
The most common way plugins are used is as individual drivers. In
this case, there may be many plugin options to choose from, but only one
needs to be loaded and called. The ~stevedore.driver.DriverManager
class supports this
pattern.
This example program uses a DriverManager
to load a formatter defined in the
examples for stevedore. It then uses the formatter to convert a data
structure to a text format, which it can print.
../../../../stevedore/example/load_as_driver.py
The manager takes the plugin namespace and name as arguments, and
uses them to find the plugin. Then, because invoke_on_load
is true, it calls the object loaded. In this case that object is the
plugin class registered as a formatter. The invoke_args
are
positional arguments passed to the class constructor, and are used to
set the maximum width parameter.
../../../../stevedore/example/load_as_driver.py
After the manager is created, it holds a reference to a single object
returned by calling the code registered for the plugin. That object is
the actual driver, in this case an instance of the formatter class from
the plugin. The single driver can be accessed via the driver
property of the
manager, and then its methods can be called directly.
../../../../stevedore/example/load_as_driver.py
Running the example program produces this output:
driver_output.txt
Loading Extensions
Another common use case is to load several extensions at one time,
and do something with all of them. Several of the other manager classes
support this invocation pattern, including ~stevedore.extension.ExtensionManager
, ~stevedore.named.NamedExtensionManager
, and ~stevedore.enabled.EnabledExtensionManager
.
../../../../stevedore/example/load_as_extension.py
The ExtensionManager
is created slightly differently
from the DriverManager
because it does not need to know in
advance which plugin to load. It loads all of the plugins it finds.
../../../../stevedore/example/load_as_extension.py
To call the plugins, use the map
method, passing a callable to be invoked for each
extension. The format_data
function used with map
in this example takes
two arguments, the ~stevedore.extension.Extension
and the data argument
given to map
.
../../../../stevedore/example/load_as_extension.py
The Extension
passed format_data
is
a class defined by stevedore that wraps the plugin. It includes the name
of the plugin, the EntryPoint
returned by importlib.metadata
, and the
plugin itself (the named object referenced by the plugin definition).
When invoke_on_load
is true, the Extension
will also have an
obj
attribute
containing the value returned when the plugin was invoked.
map
returns a
sequence of the values returned by the callback function. In this case,
format_data
returns a
tuple containing the extension name and the iterable that produces the
text to print. As the results are processed, the name of each plugin is
printed and then the formatted data.
../../../../stevedore/example/load_as_extension.py
The order the plugins are loaded is undefined, and depends on the
order packages are found on the import path as well as the way the
metadata files are read. If the order extensions are used matters, try
the ~stevedore.named.NamedExtensionManager
.
extension_output.txt
Why Not Call Plugins Directly?
Using a separate callable argument to map
, rather than just invoking the plugin directly
introduces a separation between your application code and the plugins.
The benefits of this separation manifest in the application code design
and in the plugin API design.
If map
called the
plugin directly, each plugin would have to be a callable. That would
mean a separate namespace for what is really just a method of the
plugin. By using a separate callable argument, the plugin API does not
need to match exactly any particular use case in the application. This
frees you to create a finer-grained API, with more individual methods
that can be called in different ways to achieve different goals.
../patterns_loading
../patterns_enabling