plugkit
A plugin and dependency-injection kernel for Python.
Your components are plain classes. plugkit constructs them in dependency order, passes each one what it asked for, and makes them reachable by name. When a component unloads, everything it registered is removed — including registrations it made inside other components.
That last part is the one thing no other Python framework does, and everything else here follows from it.
pip install plugkitfrom plugkit import Context, provide
class Greeter: # a plain class. imports nothing.
def __init__(self, database, prefix="hello"):
self.database, self.prefix = database, prefix
def hello(self, name):
return f"{self.prefix} {name}"
root = Context()
await root.plugin(provide(Database, "database"))
await root.plugin(provide(Greeter, "greeter", needs=["database"]))
root.greeter.hello("world") # "hello world"Start here
| Why plugkit exists | the route that outlives the component that added it, and the invariant that fixes it |
| Your first plugin | inject, effects, and watching unload be total |
| Plain components | provide(), and a ctx your type checker understands |
| Tools and permission | the five-stage pipeline, and why a guard has no allow |
| Extension points | many plugins filling one role, and the rebinding rule |
| Config and reactivity | values that change while the program runs |
| Composition from a file | an application as a YAML list |
| Testing | what plain components buy you |
Before you adopt it
plugkit ships fewer features than dependency-injector, pluggy or iPOPO. It replaces one thing in each: who owns a component’s side effects. What plugkit does not replace compares them feature by feature.
The one idea
A component adds a route to a shared server, then unloads. The route stays, because nothing recorded who added it. That is not a missing feature, it is a missing invariant — so plugkit has one: a registration returns its undo, and the fiber that owns your plugin’s lifetime holds it.
Hot reload, dependency-driven activation, and safe implementation swapping are all consequences of that, not separate features.
Provenance
The kernel in src/plugkit/cordis/ derives from Cordis, the plugin framework underneath DeepSeek Harness, by way of geohotstan/cordis-py. Both are MIT.
What is plugkit’s own: binding.py (provide() and @plugin), Protocol-driven injection, the signals library, supervision, the tool pipeline, the file loader, subscript access, and the conformance suite whose assertions are traced to the TypeScript rather than to either implementation.
src/plugkit/VENDORED.md lists every local change to the vendored kernel and the reason for it.