4.8 KiB
Creating Plugins
After a lot of trial and error, the easiest way I have found to define an API is to follow these steps:
- Use the abc module to create a base abstract class to define the behaviors required of plugins of the API. Developers don't have to subclass from the base class, but it provides a convenient way to document the API, and using an abstract base class keeps you honest.
- Create plugins by subclassing the base class and implementing the required methods.
- Define a unique namespace for each API by combining the name of the application (or library) and a name of the API. Keep it shallow. For example, "cliff.formatters" or "ceilometer.pollsters.compute".
Example Plugin Set
The example program in this tutorial will create a plugin set with several data formatters, like what might be used by a command line program to prepare data to be printed to the console. Each formatter will take as input a dictionary with string keys and built-in data types as values. It will return as output an iterator that produces the string with the data structure formatted based on the rules of the specific formatter being used. The formatter's constructor lets the caller specify the maximum width the output should have.
A Plugin Base Class
Step 1 above is to define an abstract base class for the API that needs to be implemented by each plugin.
../../../stevedore/example/base.py
The constructor is a concrete method because subclasses do not need
to override it, but the format
method does not do anything useful because
there is no "default" implementation available.
Concrete Plugins
The next step is to create a couple of plugin classes with concrete
implementations of format
. A simple example formatter produces output
with each variable name and value on a single line.
../../../stevedore/example/simple.py
An alternate implementation produces a reStructuredText field list.
../../../stevedore/example/fields.py
There are plenty of other formatting options, but these two examples will give us enough to work with to demonstrate registering and using pluins.
Registering the Plugins
To use setuptools entry points, you must package your application or library using setuptools. The build and packaging process generates metadata which is available after installation to find the plugins provided by each python distribution.
The entry points must be declared as belonging to a specific namespace, so we need to pick one before going any further. These plugins are formatters from the stevedore examples, so I will use the namespace "stevedore.example.formatter". Now it is possible to provide all of the necessary information in the packaging instructions:
../../../stevedore/example/setup.py
The important lines are 38-44. The entry_points
argument
to setup
is a
dictionary mapping the namespace for the plugins to a list of their
definitions. Each item in the list should be a string with
name = module:importable
where name is the
user-visible name for the plugin, module is the Python import
reference for the module, and importable is the name of
something that can be imported from inside the module.
../../../stevedore/example/setup.py
In this case, there are three plugins registered. The "simple" and "field" plugins defined above, and a "plain" plugin, which is just an alias for the simple plugin.
setuptools Metadata
During the build, setuptools copies entry point definitions to a file
in the ".egg-info" directory for the package. For example, the file for
stevedore is located in
stevedore.egg-info/entry_points.txt
:
[stevedore.example.formatter]
simple = stevedore.example.simple:Simple
field = stevedore.example.fields:FieldList
plain = stevedore.example.simple:Simple
[stevedore.test.extension]
t2 = stevedore.tests.test_extension:FauxExtension
t1 = stevedore.tests.test_extension:FauxExtension
pkg_resources
uses
the entry_points.txt
file from all of the installed
packages on the import path to find plugins. You should not modify these
files, except by changing the list of entry points in
setup.py
.