Entry points as extensions
How pyproject.toml entry-points register plugins at install time and how importlib.metadata discovers them at runtime.
- Explain how [project.entry-points."group"] in pyproject.toml registers a plugin at install time
- Use importlib.metadata.entry_points(group=...) to discover all installed plugins for a group
- Describe how pytest, Sphinx, and Click use this same mechanism
A plugin system lets other packages extend your tool without modifying its source code. The question is how the host tool discovers extensions it has never seen. The answer is already built into Python's packaging infrastructure: entry points.
What entry points are
An entry point is metadata a package writes into its wheel at build time. When
pip installs the package, that metadata lands in the dist-info directory
alongside the code. It is not loaded — it is just a structured record that says
"this package exposes the dotted name mypackage.processors:UppercaseProcessor
under the group mytool.processors with the name uppercase."
The declaration lives in pyproject.toml:
[project.entry-points."mytool.processors"]
uppercase = "mypackage.processors:UppercaseProcessor"
filter-empty = "mypackage.processors:FilterEmptyProcessor"The key on the left (uppercase) is the plugin name. The value
(mypackage.processors:UppercaseProcessor) is a dotted import path followed
by a colon and the attribute to load. No file scanning, no import at install
time — just metadata.
Discovering plugins at runtime
importlib.metadata reads that installed metadata and hands back a list of
entry point objects:
from importlib.metadata import entry_points
plugins = entry_points(group="mytool.processors")
for ep in plugins:
print(ep.name, ep.value)
cls = ep.load() # imports the module and returns UppercaseProcessor
instance = cls()ep.load() does the actual import on demand. If a plugin package is broken,
only that plugin fails — the rest load normally.
The group string is a namespace that belongs to the tool author. Choose
something specific enough to avoid collisions: mytool.processors rather than
just processors.
How popular tools use this mechanism
Pytest, Sphinx, and Click all ship their own plugin systems on top of entry points, because the pattern works at any scale:
- pytest discovers plugins registered under
pytest11. Every package that ships a conftest or hook simply declares[project.entry-points.pytest11]in itspyproject.toml. Runningpytest --coloads them all without any configuration from the user. - Sphinx uses
sphinx.buildersandsphinx.rolesgroups. Third-party themes and extensions register their components here. - Click plugin groups let CLI suites split commands across separate installable packages that merge into one top-level command.
The common thread: the host tool calls entry_points(group=...) once at
startup and gets back every registered plugin, regardless of who wrote it or
when it was installed.
Entry points require the plugin package to be installed — they are not
discovered by scanning directories or by adding paths to sys.path. This is
a feature, not a limitation. It means plugins are explicit (installed with
pip), versioned, and removable without touching the host tool's files.
What this gives you
The entry point mechanism separates three concerns that are often tangled:
- Authorship — a third party writes the plugin in their own repository.
- Distribution — they publish it to PyPI with the entry point declaration.
- Discovery — your tool finds it automatically after
pip install.
No configuration files to edit, no import hooks to register, no directory conventions to document. Install the package and it just works.
Where to go next
Next: designing a plugin API — defining a stable interface using Protocol
so that every plugin your tool discovers is guaranteed to be callable in the
same way.
Lab: TUI log viewer
Build a Textual TUI that displays a live-updating log table, supports keyboard navigation, and exports the current view to a file.
Designing a plugin API
Use Protocol or ABC to define a stable plugin interface, version it to avoid breaking changes, and document the contract every plugin must fulfill.