Event-driven Python
Building emitix: a zero-dependency event bus for Python
Why I built a tiny, fully-typed event bus for Python - and how the sync + async, wildcard, and priority pieces fit together.
Most of my projects reach a point where one part of the code needs to tell another part “this just happened” - without the two knowing about each other. That’s the job of an event bus. I wanted one that was tiny, typed, and didn’t drag in dependencies. So I built emitix.
The smallest useful thing
The entire API starts with one decorator and one call:
import emitix
@emitix.on("user.created")def send_welcome_email(user): print(f"Welcome, {user}!")
emitix.emit("user.created", "Ada")Arguments to emit flow straight through to every handler. No envelope, no
schema ceremony - just call it like a function.
Going async
Handlers can be coroutines. Emit them with emit_async:
@emitix.on("order.placed")async def charge_card(order): await payments.charge(order)
await emitix.emit_async("order.placed", order)Wildcards and priority
Two features that turned out to matter in real code - subscribing to a family of events, and controlling the order handlers run in:
@emitix.on("order.*") # any order.* eventdef audit(event, *args): log.info("audit", event, args)
@emitix.on("order.placed", priority=10) # runs before lower prioritiesdef reserve_stock(order): inventory.reserve(order)What I left out (on purpose)
- No broker, no network - it’s in-process by design.
- No required message classes - strings and args are enough for most apps.
If you want the full feature list - once(), off(), thread-safety - it’s in
the project write-up and on
PyPI.