pluginkit vs pluggy¶
pluggy is the mature, widely-used hook framework (it powers pytest, tox, and datasette). pluginkit is a smaller, strictly-typed alternative for Python 3.13+ codebases that want hook calls their type checker actually understands. This page is the honest comparison.
The headline: typed hooks¶
In pluggy, pm.hook.foo(...) returns Any - the type checker sees neither the
hook's arguments nor its result. pluginkit derives both from the spec:
greetings = pm.caller(Specs.greeting)(name="Ada") # list[str]
cup = pm.caller(Specs.choose_cup)(size="L") # str | None (firstresult)
total = pm.caller(Specs.fold)(value=v) # R (pipeline)
The return type is mode-correct - list[R] for collecting, R | None for
firstresult, R for pipeline - and checked by mypy and pyright, not asserted by
hand. This is the main reason to reach for pluginkit, and it is practical precisely
because it targets only Python 3.13 (PEP 695 generics + ParamSpec).
Also beyond pluggy¶
- Pipeline dispatch - a fold/middleware mode where each impl transforms the previous result. pluggy has no built-in equivalent.
- Async dispatch - an
AsyncPluginManagerthat awaits coroutine implementations. In pluggy this needs the separateapluggypackage. - Zero runtime dependencies and a
py.typedmarker, in a few readable files.
Behavioural differences¶
| Area | pluginkit | pluggy |
|---|---|---|
| Order within a bucket | first-registered-first (FIFO) | last-registered-first (LIFO) |
| Dispatch core | plain Python loop | optimised, with a C-accelerated multicall |
| Argument passing | filtered per call via inspect.signature |
arg lists precomputed once per impl |
The ordering tie-break is the one you might actually notice: pluginkit calls
same-priority implementations in registration order; pluggy calls them in reverse.
Both honour tryfirst/trylast identically. Dispatch speed is competitive and fast
enough in practice; performance is not a reason to pick one over the other.
What pluggy has that pluginkit does not¶
- broad Python-version support (pluginkit is 3.13+ only);
- a C-accelerated multicall, a large test suite, and years of edge-case hardening;
- the legacy
hookwrapperstyle (pluginkit has only the modernwrapper=True); - spec enforcement of required arguments, warn-on-impl options, call monitoring /
tracing,
subset_hook_caller, and other advanced relay manipulation.
pluginkit shares the core surface otherwise: collecting and firstresult specs,
tryfirst/trylast, optional, target, generator wrappers with exception
safety, historic hooks, call_extra, unregister/blocking/lookup, registration- and
call-time validation, and entry-point discovery.
When to use which¶
- pluginkit - you are on Python 3.13+, you want typed hooks with correct return types, and a small dependency-free plugin layer you fully control.
- pluggy - you need broad Python-version support, maximum maturity, or the wider feature surface above.