Python’s setuptools has a rather interesting feature called “entry points” which facilitates plugin discovery. I’m using this in imhotep to allow linters to live as separate packages.
So how does it work?
In your setup.py, you defined a property called
entry_points which is a dictionary mapping the entry point key to a
list of values. The values take the form of .py =
imhotep_pylint.plugin:PyLint. This is another mapping in string form
from a user-defined string to a dotted module name (and optionally, as
here, an object to import).
from setuptools import setup
setup(
# ...
entry_points={
'imhotep_linters': [
'.py = imhotep_pylint.plugin:PyLint'
],
},
# ...
)
Taking an example from imhotep_pylint, you can see that this package
defines an entry_point for “imhotep_linters”. This string is part of
the imhotep API and plugins have to register their entry_points with
that. From there, we define a list of one item, which says that the
.py string should map to the PyLint object living in
imhotep_pylint.plugin.
How do I discover these?
You can use the pkg_resources package to discover these plugins at
run-time. It looks something like this (pulled from imhotep):
def load_plugins():
tools = []
for ep in pkg_resources.iter_entry_points(group='imhotep_linters'):
klass = ep.load()
tools.append(klass(run))
return tools
This uses the group name we’ve mutually agreed upon,
imhotep_linters, to return an iterable of entry_points. From there,
we call the load() method, which will materialize the entry_point
into a class object. From there, it’s just like any other sort of
class which you can instantiate and use.
This is actually all there is to implementing a plugin system for your package. Python’s setuptools makes this quite easy! You can find [[http://pythonhosted.org/setuptools/setuptools.html#dynamic-discovery-of-services-and-plugins][more official docs here]] .