Eggs are being more and more used in Python land. One thing Python developers can do easily with them is plugins cooking. They are used in Paste*, TurboGears and Trac for instance, so i wanted to see how to design a simple mini plugins based system.
The main requirement here is setuptools (i link directly to PyPI because Peak’s wiki lags quite a bit currently). Setuptools supply a mechanism to plug systems together called entry points, so in your framework you can search plugins by entrypoint, and each egg supplying the entry point would be found.
For the lazy people i made a little archive, holding the few lines of code showed in this article. We’ll use an entry point called “my.plugins”. Create a new tree structure which will be our first plugin base:
foo |-- foo_plugin | |-- __init__.py | `-- foo.py `-- setup.py
In the setup.py, declare the setup() and don’t forget the entry points!
from setuptools import setup, find_packages setup( name="Foo", version="0.0", description="""Foo plugin""", author="Phil", packages=['foo_plugin'], entry_points=""" [my.plugins] myFoo = foo_plugin.foo:Foo """)
Basically we have one entry point labelled myFoo in the “category” called “my.plugins”. The entry point points to the Foo class of the module foo_plugin.foo. That’s quite simple, no ? Next step, in the foo_plugin/foo.py, declare your Foo class:
print 'Foo loading!' class Foo: def echo(self, message): """ sample method, returning its argument """ return message
The next step is to package your foo plugin to a full fledged egg and to install it using easy_install:
$ cd foo $ python setup.py bdist_egg $ sudo easy_install dist/Foo-0.0-py2.3.egg
One alternate, and simpler way to go when you are developing your plugin:
$ cd foo $ sudo python setup.py develop
Thus, you don’t need to re-compile the egg each time you modify your code, setuptools directly access to it via a link (as i understood it, i may be wrong here). Now the code to search and load our plugins, create a file called load_plugins.py with following code included:
import pkg_resources for entrypoint in pkg_resources.iter_entry_points("my.plugins"): plugin_class = entrypoint.load() print entrypoint.name, plugin_class
Execute it and here we go, the foo plugin magically pops up:
$ python load_plugins.py Foo loading! foo_plugin.foo.Foo myFoo
After that, the developer can instantiate the class, and play with the plugin. That’s all for now, i could have used interfaces to infer the plugin API, but i’m too lazy for now ;-) The interested user should read the Trac Egg Cooking tutorial which explains how to design trac plugins using the trac components architecture based on interfaces. This document is really worth looking at, Trac is a good piece of code, really well-designed IMHO.