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]] .