Code of the Day
AdvancedPlugin Systems

Lab: Plugin system

Add a plugin system to a CLI by defining a Processor Protocol, writing two built-in processors, registering them via entry points, and chaining them at runtime.

Lab · optionalUtilitiesAdvanced35 min
Recommended first
By the end of this lesson you will be able to:
  • Define a Processor Protocol with a process(lines) method and an api_version attribute
  • Write FilterEmpty and ToUppercase processors that satisfy the Protocol
  • Register both processors in pyproject.toml under a shared entry-points group
  • Write a discovery function that loads all registered processors and chains them

This lab guides you through adding a real plugin system to a minimal CLI tool. You will define the interface, write two built-in processors, register them as entry points, and verify the discovery-and-chaining pipeline works end-to-end. By the end, any third-party package that declares the right entry point will automatically be picked up — without touching your tool's source code.

The runnable cells below simulate the discovery step without actual package installation. The pyproject.toml snippets are the configuration you would use in a real project — copy them when you build this locally.

Step 1 — Define the Processor Protocol

The Protocol is the public contract. Keep it minimal: one method and one versioning attribute.

Python — editable, runs in your browser

@runtime_checkable is what makes isinstance() work here. Without it, Protocol is only used for static type-checking by tools like mypy or pyright and cannot be checked at runtime.

Step 2 — Write the built-in processors

The two built-in processors live in the tool's own package and are registered as entry points alongside any third-party ones.

Python — editable, runs in your browser

Step 3 — Register via pyproject.toml

In your real project, add this section to pyproject.toml:

[project.entry-points."mytool.processors"]
filter-empty = "mytool.processors:FilterEmpty"
to-uppercase = "mytool.processors:ToUppercase"

After pip install -e . (or pip install mytool), these two processors will appear when importlib.metadata.entry_points(group="mytool.processors") is called.

Third-party authors add their own packages with the same declaration:

[project.entry-points."mytool.processors"]
deduplicate = "myplugin.processors:DeduplicateProcessor"

No changes to mytool's source code — just installing the third-party package is enough.

Step 4 — Discovery and chaining

Python — editable, runs in your browser

The output should be ["HELLO", "WORLD", "FOO"]. Blank and whitespace-only lines are removed first, then every remaining line is uppercased.

Step 5 — Verify a third-party processor integrates cleanly

Add a processor that is not in the built-in list and confirm it slots in without any changes to the discovery code:

Python — editable, runs in your browser

Three processors, three transformations, zero changes to discovery code. The PrefixLineNumber processor is treated identically to the built-in ones because it satisfies the same Protocol and declares the same api_version.

What you built

You assembled a plugin system from its constituent parts:

  • A Protocol that is the complete, versioned public contract.
  • Two built-in processors that satisfy it.
  • A pyproject.toml entry point declaration that registers them.
  • A discovery function that loads, version-checks, and instantiates everything.
  • A pipeline runner that chains processors in order.

The same pattern scales from two built-in processors to dozens of community- maintained ones. The only coordination required between plugin authors and the tool is the Protocol definition and the entry point group name.

Where to go next

Next module: Cross-Platform — making Python tools work correctly on Linux, macOS, and Windows without platform-specific branches.

Finished reading? Mark it complete to track your progress.

On this page