Hardening notes¶
pluginkit started as a teaching reimplementation. This page records what was done to move it past a toy, and why each item matters in a real plugin host.
Wrapper exception safety¶
The single most important fix. A wrapper holds control across its yield, so if
an inner implementation raises, the wrapper must still be resumed - otherwise any
try/finally or with it holds would leak.
The dispatch loop runs the inner implementations inside a try, then unwinds the
started wrappers in reverse, throwing the exception into each one. A wrapper
may clean up and re-raise, or swallow the exception by returning a value. Only
after every wrapper has been resumed is the exception re-raised (if still
pending). See HookCaller._teardown.
Covered by test_wrapper_cleanup_runs_when_inner_impl_raises and
test_wrapper_can_suppress_inner_exception.
Fail-fast validation¶
Two checks run at registration, turning silent misbehaviour into loud errors:
- unknown hook - an implementation whose name matches no spec raises
PluginValidationError(unless markedoptional). - unknown argument - an implementation that declares a parameter the spec does
not have raises
PluginValidationError. Without this, a typo likedef greet(self, nam)would silently never receive its value, because the kwarg filter would simply drop it.
Plugin lifecycle¶
A real host needs to remove and disable plugins, not just add them:
unregister(name)drops a plugin and all its implementations;set_blocked(name)removes it and refuses future registration (including via entry-point discovery);is_registered,get_plugin,get_name,get_hookcallers, andplugin_namescover inspection.
Resilient discovery¶
load_entrypoints isolates each plugin load. A broken third-party plugin raises a
PluginValidationError that names it, or is skipped entirely with
ignore_errors=True, so one bad package cannot block all discovery.
Thread-safe mutation¶
Registry mutations are guarded by a re-entrant lock, so plugins can be loaded from multiple threads. Hook calls are intentionally lock-free; see Lifecycle and validation for the rationale.
Spec-combination validation¶
A historic spec cannot also be firstresult (replay has no single winner); the
manager rejects the combination when specs are added, rather than failing
mysteriously at call time.
Packaging quality¶
- zero runtime dependencies - the library is standard-library only;
- ships a
py.typedmarker so downstreams get the types; - a curated public API in
pluginkit/__init__.pywith__version__; - strict
ruff+mypy+pyright, and a test suite covering the happy paths, the error paths, and the examples.
What is deliberately still out of scope¶
See Differences from pluggy for the features pluginkit does not implement and when you should reach for pluggy instead.