summaryrefslogtreecommitdiff
path: root/doc/source/user/tutorial/loading.rst
blob: 1f598c4077b7a6444c2efb2b925d50709a7af4d3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
=====================
 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
:class:`~stevedore.driver.DriverManager` class supports this pattern.

This example program uses a :class:`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.

.. literalinclude:: ../../../../stevedore/example/load_as_driver.py
   :language: python
   :prepend: # 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.

.. literalinclude:: ../../../../stevedore/example/load_as_driver.py
   :language: python
   :lines: 30-35

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
:attr:`driver` property of the manager, and then its methods can be
called directly.

.. literalinclude:: ../../../../stevedore/example/load_as_driver.py
   :language: python
   :lines: 36-37

Running the example program produces this output:

.. literalinclude:: 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
:class:`~stevedore.extension.ExtensionManager`,
:class:`~stevedore.named.NamedExtensionManager`, and
:class:`~stevedore.enabled.EnabledExtensionManager`.

.. literalinclude:: ../../../../stevedore/example/load_as_extension.py
   :language: python
   :prepend: # stevedore/example/load_as_extension.py

The :class:`ExtensionManager` is created slightly differently from the
:class:`DriverManager` because it does not need to know in advance
which plugin to load. It loads all of the plugins it finds.

.. literalinclude:: ../../../../stevedore/example/load_as_extension.py
   :language: python
   :lines: 24-28

To call the plugins, use the :meth:`map` method, passing a callable to
be invoked for each extension. The :func:`format_data` function used
with :meth:`map` in this example takes two arguments, the
:class:`~stevedore.extension.Extension` and the data argument given to
:meth:`map`.

.. literalinclude:: ../../../../stevedore/example/load_as_extension.py
   :language: python
   :lines: 30-33

The :class:`Extension` passed :func:`format_data` is a class defined
by stevedore that wraps the plugin. It includes the name of the
plugin, the :class:`EntryPoint` returned by :mod:`pkg_resources`, and
the plugin itself (the named object referenced by the plugin
definition). When ``invoke_on_load`` is true, the :class:`Extension`
will also have an :attr:`obj` attribute containing the value returned
when the plugin was invoked.

:meth:`map` returns a sequence of the values returned by the callback
function. In this case, :func:`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.

.. literalinclude:: ../../../../stevedore/example/load_as_extension.py
   :language: python
   :lines: 35-39

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 :class:`~stevedore.named.NamedExtensionManager`.

.. literalinclude:: extension_output.txt

Why Not Call Plugins Directly?
==============================

Using a separate callable argument to :meth:`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 :meth:`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.

.. seealso::

   * :doc:`../patterns_loading`
   * :doc:`../patterns_enabling`