Open-source project
pytest-dev/pluggy avatar
pytest-dev/pluggy

pluggy: Python Hooks and Plugin Registration

A minimalist production ready plugin system

1,705 stars192 forksPythonMIT

At a glance

What is it?
pluggy gives Python applications a small hook-based plugin system based on specifications, decorated implementations, and a central PluginManager. It fits library maintainers that need controlled extension points without building a larger framework, but it does not handle package installation or automatic discovery by itself.
Who is it for?
Library maintainers should consider pluggy when a stable extension API, ordered multi-plugin calls, and explicit registration matter. Projects that need automatic package discovery, configuration loading, or dependency installation should account for those missing layers.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 2 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

pluggy's Specific Role in a Python Extension API

pluggy solves the problem of adding extension points to a Python library without requiring every caller to know every concrete plugin in advance. A library declares the functions it wants extensions to implement, while separate objects register implementations for those functions. This division is useful for tools such as test runners, configuration managers, and extensible developer utilities. The README identifies pytest, tox, and devpi as core users, which places pluggy below the application layer rather than beside them.

The library is deliberately narrow. It manages hook specifications, plugin registration, and hook invocation. It does not present itself as a package installer, configuration system, dependency resolver, or automatic discovery service. That boundary matters because an application still needs a policy for which plugins are enabled and when they are loaded. pluggy supplies the calling mechanism once those decisions reach its PluginManager.

Its audience is therefore narrower than users of general application frameworks. Python library maintainers are the primary candidates, especially maintainers who need a public extension contract and deterministic collaboration among several implementations. A small application with one fixed integration path gains little from adding this abstraction.

Hook Specifications, Implementations, and PluginManager Data Flow

The core design uses two markers. HookspecMarker creates a decorator for the library's extension contract, while HookimplMarker identifies functions supplied by plugins. The namespace passed to each marker, such as "myproject", belongs to the project whose hooks are being defined. A specification class contains methods decorated with the hook specification marker. Plugin classes contain methods decorated with the hook implementation marker.

The runtime flow starts with PluginManager("myproject"). The specification class is added with pm.add_hookspecs(MySpec), after which concrete plugin objects are passed to pm.register(). Calling pm.hook.myhook(arg1=1, arg2=2) resolves the named hook and invokes registered implementations. In the README example, two implementations receive the same arguments and return different values.

The example also documents an observable ordering constraint: Plugin_2.myhook() prints before Plugin_1.myhook(), and the result list contains their return values in that order. That is important for pluggy because a hook is not simply a one-to-one callback. It is a collection point that can gather multiple results. Consumers should therefore inspect the hook specification and project documentation when call order or result ordering affects correctness.

Registration is explicit in the demonstrated flow. No automatic scan, package metadata rule, or entry-point lookup appears in the README example. Applications remain responsible for deciding which plugin instances exist and passing them to the manager.

A First pluggy Hook in a Local Example

The README does not provide an installation command, so it gives no supported pip or setuptools invocation to copy. It points users to the documentation and links the project through PyPI and conda-forge badges. The repository packaging metadata identifies the distribution as "pluggy", requires Python 3.10 or later, and places its import package under src/.

The following first use is the complete example supplied by the repository. Save it as toy-example.py if the repository documentation is available locally. It defines one hook specification and two implementations, registers both, and calls the hook.

python
import pluggy

hookspec = pluggy.HookspecMarker("myproject")
hookimpl = pluggy.HookimplMarker("myproject")

class MySpec:
    @hookspec
    def myhook(self, arg1, arg2):
        """My special little hook that you can customize."""

The plugin classes implement that same method name. Their return values are deliberately different, which makes the aggregation visible when the example runs.

python
class Plugin_1:
    @hookimpl
    def myhook(self, arg1, arg2):
        print("inside Plugin_1.myhook()")
        return arg1 + arg2

class Plugin_2:
    @hookimpl
    def myhook(self, arg1, arg2):
        print("inside Plugin_2.myhook()")
        return arg1 - arg2

Create the manager, add the specification, and register both plugin objects. Registration occurs before the hook call, so implementations added afterward are not part of that documented invocation.

python
pm = pluggy.PluginManager("myproject")
pm.add_hookspecs(MySpec)
pm.register(Plugin_1())
pm.register(Plugin_2())
results = pm.hook.myhook(arg1=1, arg2=2)
print(results)

Run the file using the command printed by the README:

bash
python docs/examples/toy-example.py

The README states that the output lines appear in Plugin_2 then Plugin_1 order, followed by a list containing the subtraction result and addition result:

text
inside Plugin_2.myhook()
inside Plugin_1.myhook()
[-1, 3]

That result comes directly from Plugin_2 returning 1 - 2 and Plugin_1 returning 1 + 2. The example establishes the basic integration contract, but the README does not document configuration, unregistering, tracing, or result validation for this toy program.

Explicit Registration Boundaries and Failure Cases

The clearest limitation is that pluggy does not decide which plugins an application should load. The demonstrated code constructs Plugin_1 and Plugin_2 directly before registering them. In a larger application, somebody must translate configuration, installed packages, or command-line options into plugin objects. The README does not document a pluggy-managed discovery layer, so treating registration as automatic discovery would go beyond the documented behavior.

Naming also carries architectural weight. A hook contract depends on matching method names and compatible arguments. Changing a specification can affect every registered implementation, which means the specification is effectively part of the library's extension API. pluggy can provide the mechanism for calling implementations, but it cannot decide whether a breaking signature change is acceptable to downstream plugins.

The multiple-result behavior is another boundary. The example returns [-1, 3], not a single merged object. Code that expects one callback result must still define how it should interpret multiple results. The README does not document built-in result filtering, exception policy, or first-result selection, so those behaviors should not be inferred from this example.

pluggy is also the wrong layer for applications whose only extension requirement is one optional callable. A direct function call or ordinary object composition already provides that relationship without a hook specification and plugin manager. The added abstraction becomes more relevant when several independently registered implementations need to participate in the same named operation.

Direct Python Composition Compared with pluggy Hooks

A direct integration passes a callable or object to the code that needs it. The receiving component knows its collaborator's interface, and the application decides explicitly when to invoke it. This approach has little registration machinery and is easy to inspect when there is only one extension or when control flow remains fixed.

pluggy changes the organization of that relationship. The owning library first declares a hook specification. Plugins then implement methods with matching names, and the PluginManager gathers their results when the hook is called. The library does not need each caller to coordinate a sequence of optional callbacks. Instead, the manager becomes the common dispatch point for independently registered implementations.

The practical difference is therefore not performance, which the README does not establish, but API shape. Direct composition makes dependencies visible in ordinary Python calls. pluggy formalizes an extension surface, supports a shared manager for multiple plugins, and returns a collection of implementation results in the documented example. In exchange, developers must learn markers, namespaces, specifications, registration, and invocation through pm.hook.

This comparison also exposes when pluggy adds little. If an application has no independent extensions, a direct call is simpler. If it needs automatic package discovery, pluggy's registration model still requires an external policy for constructing and registering plugin objects.

Packaging, Licence, and Maintenance Boundaries

The pyproject.toml file defines pluggy as an MIT-licensed Python package and requires Python 3.10 or later. Its classifiers list CPython and PyPy implementations, along with Python versions 3.10 through 3.14. The project metadata marks the license as "MIT" and links the full licence through the repository's LICENSE file. MIT licensing permits reuse and modification under that licence's conditions, but the licence file remains the authority for its exact obligations.

The package uses setuptools and setuptools-scm for building. Version information is dynamic, and the repository includes RELEASING.rst plus a changelog directory. Those files indicate that versioning and release work are part of the repository process. However, the repository has no GitHub releases, and the README does not describe pluggy's compatibility policy or a stable release cadence.

The repository is not archived, and its last push was on 2026-10-15. That is recent relative to the current date, but recency alone does not resolve upgrade risk for an application. Before pinning a version, inspect CHANGELOG.rst and the project documentation for changes affecting the hook features and plugin contracts the application uses. The `testing` dependency group includes pytest, pytest-benchmark, and coverage, while the `dev` group includes pre-commit. These entries describe development dependencies rather than runtime requirements.

Editorial conclusion

Library maintainers should consider pluggy when a stable extension API, ordered multi-plugin calls, and explicit registration matter. Projects that need automatic package discovery, configuration loading, or dependency installation should account for those missing layers. Before adoption, verify the documented hook order and result handling for the exact extension pattern, then confirm that Python 3.10 or later matches the supported runtime range.

Frequently asked questions

How do I install pluggy?

The README does not give a pip or conda installation command. It points to the documentation and includes PyPI and conda-forge badges; pyproject.toml identifies the package as pluggy and requires Python 3.10 or later.

How does pluggy call multiple plugin implementations?

Implementations decorated with a HookimplMarker are registered with a PluginManager. In the README example, calling pm.hook.myhook invokes both registered methods and returns both results in a list.

Does pluggy automatically discover and load plugins?

The README example does not show automatic discovery. It creates plugin instances and passes them to pm.register(), so the application remains responsible for deciding which plugins to construct and register.

Official sources

  1. Issues
  2. License: MIT
  3. Project website
  4. pytest-dev/pluggy on GitHub
  5. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/pytest-dev-pluggy.svg)](https://hysenlabs.com/projects/pytest-dev-pluggy)

Was this useful?