finish tutorial section on loading plugins
Signed-off-by: Doug Hellmann <doug.hellmann@dreamhost.com>
This commit is contained in:
parent
712877f3cc
commit
6535133e88
@ -1,10 +0,0 @@
|
||||
=====================
|
||||
Calling the Plugins
|
||||
=====================
|
||||
|
||||
.. explain the args to map()
|
||||
|
||||
.. explain why a separate callable is used to combine app & plugin (by
|
||||
not passing Ext the callable directly the app use of stevedore does
|
||||
not dictate the plugin API, and map() can do more than one thing
|
||||
with a plugin
|
31
docs/source/tutorial/extension_output.txt
Normal file
31
docs/source/tutorial/extension_output.txt
Normal file
@ -0,0 +1,31 @@
|
||||
$ python -m stevedore.example.load_as_extension --width 30
|
||||
Formatter: simple
|
||||
a = A
|
||||
b = B
|
||||
long = word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word
|
||||
|
||||
Formatter: field
|
||||
: a : A
|
||||
: b : B
|
||||
: long : word word word word
|
||||
word word word word word
|
||||
word word word word word
|
||||
word word word word word
|
||||
word word word word word
|
||||
word word word word word
|
||||
word word word word word
|
||||
word word word word word
|
||||
word word word word word
|
||||
word word word word word
|
||||
word word word word word
|
||||
word word word word word
|
||||
word word word word word
|
||||
word word word word word
|
||||
word word word word word
|
||||
word word word word word
|
||||
word
|
||||
|
||||
Formatter: plain
|
||||
a = A
|
||||
b = B
|
||||
long = word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word
|
@ -12,7 +12,6 @@ application.
|
||||
naming
|
||||
creating_plugins
|
||||
loading
|
||||
calling
|
||||
testing
|
||||
|
||||
.. seealso::
|
||||
|
@ -20,7 +20,6 @@ convert a data structure to a text format, which it can print.
|
||||
|
||||
.. literalinclude:: ../../../stevedore/example/load_as_driver.py
|
||||
:language: python
|
||||
:linenos:
|
||||
:prepend: # stevedore/example/load_as_driver.py
|
||||
|
||||
The manager takes the plugin namespace and name as arguments, and uses
|
||||
@ -49,12 +48,78 @@ Running the example program produces this output:
|
||||
|
||||
.. literalinclude:: driver_output.txt
|
||||
|
||||
Loading Extensions
|
||||
==================
|
||||
|
||||
.. talk about when to do this, and that it should be done as few times
|
||||
as possible (on app startup, rather than on each event)
|
||||
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`.
|
||||
|
||||
.. explain invoke_on_load use case
|
||||
.. 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::
|
||||
|
||||
|
39
stevedore/example/load_as_extension.py
Normal file
39
stevedore/example/load_as_extension.py
Normal file
@ -0,0 +1,39 @@
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
|
||||
from stevedore import extension
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
'--width',
|
||||
default=60,
|
||||
type=int,
|
||||
help='maximum output width for text',
|
||||
)
|
||||
parsed_args = parser.parse_args()
|
||||
|
||||
data = {
|
||||
'a': 'A',
|
||||
'b': 'B',
|
||||
'long': 'word ' * 80,
|
||||
}
|
||||
|
||||
mgr = extension.ExtensionManager(
|
||||
namespace='stevedore.example.formatter',
|
||||
invoke_on_load=True,
|
||||
invoke_args=(parsed_args.width,),
|
||||
)
|
||||
|
||||
def format_data(ext, data):
|
||||
return (ext.name, ext.obj.format(data))
|
||||
|
||||
results = mgr.map(format_data, data)
|
||||
|
||||
for name, result in results:
|
||||
print('Formatter: {0}'.format(name))
|
||||
for chunk in result:
|
||||
print(chunk, end='')
|
||||
print('')
|
@ -12,6 +12,11 @@ LOG = logging.getLogger(__name__)
|
||||
class Extension(object):
|
||||
"""Book-keeping object for tracking extensions.
|
||||
|
||||
The arguments passed to the constructor are saved as attributes of
|
||||
the instance using the same names, and can be accessed by the
|
||||
callables passed to :meth:`map` or when iterating over an
|
||||
:class:`ExtensionManager` directly.
|
||||
|
||||
:param name: The entry point name.
|
||||
:type name: str
|
||||
:param entry_point: The EntryPoint instance returned by
|
||||
@ -20,6 +25,7 @@ class Extension(object):
|
||||
:param plugin: The value returned by entry_point.load()
|
||||
:param obj: The object returned by ``plugin(*args, **kwds)`` if the
|
||||
manager invoked the extension on load.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, name, entry_point, plugin, obj):
|
||||
|
Loading…
Reference in New Issue
Block a user