summaryrefslogtreecommitdiff
path: root/docs/source/tutorial/creating_plugins.rst
diff options
context:
space:
mode:
Diffstat (limited to 'docs/source/tutorial/creating_plugins.rst')
-rw-r--r--docs/source/tutorial/creating_plugins.rst130
1 files changed, 130 insertions, 0 deletions
diff --git a/docs/source/tutorial/creating_plugins.rst b/docs/source/tutorial/creating_plugins.rst
new file mode 100644
index 0000000..f2b46ea
--- /dev/null
+++ b/docs/source/tutorial/creating_plugins.rst
@@ -0,0 +1,130 @@
+==================
+ 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.
+
+.. literalinclude:: ../../../stevedore/example/base.py
+ :language: python
+ :linenos:
+ :prepend: # stevedore/example/base.py
+
+The constructor is a concrete method because subclasses do not need to
+override it, but the :func:`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 :func:`format`. A simple example formatter produces
+output with each variable name and value on a single line.
+
+.. literalinclude:: ../../../stevedore/example/simple.py
+ :language: python
+ :linenos:
+ :prepend: # stevedore/example/simple.py
+
+An alternate implementation produces a reStructuredText `field list`_.
+
+.. literalinclude:: ../../../stevedore/example/fields.py
+ :language: python
+ :linenos:
+ :prepend: # 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:
+
+.. literalinclude:: ../../../stevedore/example/setup.py
+ :language: python
+ :linenos:
+ :emphasize-lines: 38-44
+ :prepend: # stevedore/example/setup.py
+
+The important lines are 38-44. The ``entry_points`` argument to
+:func:`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.
+
+.. literalinclude:: ../../../stevedore/example/setup.py
+ :language: python
+ :lines: 37-43
+
+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
+
+:mod:`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``.
+
+.. _abc module: http://docs.python.org/2/library/abc.html
+.. _field list: http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html#field-lists