An ultra simple, modern pub/sub library and blinker alternative for Python
Project description
EZPubSub
A tiny, modern alternative to Blinker – typed, thread-safe, and designed for today’s Python.
EZPubSub is a zero-dependency pub/sub library focused on one thing: making event publishing and subscribing easy, safe, and predictable. No async-first complexity, no dynamic runtime magic—just clean, synchronous pub/sub that works anywhere.
The core design is inspired by the internal signal system in Textual, refined into a standalone library built for general use.
Quick Start
from ezpubsub import Signal
data_signal = Signal[str]("data_updated")
def on_data(data: str) -> None:
print("Received:", data)
data_signal.subscribe(on_data)
data_signal.publish("Hello World")
# Output: Received: Hello World
That’s it. You create a signal, subscribe to it, and publish events.
Why Another Pub/Sub Library?
Because pub/sub in Python is either old and untyped or overengineered and async-first.
Writing a naive pub/sub system is easy—just keep a list of callbacks and fire them. Writing one that actually works in production is not. You need to handle thread safety, memory management (weak refs for bound methods), error isolation, subscription lifecycles, and type safety. Most libraries get at least one of these wrong.
The last great attempt was Blinker—15 years ago. It was excellent for its time, but Python has moved on. EZPubSub is what a pub/sub library should look like in 2025: type-safe, thread-safe, ergonomic, and designed for modern Python.
Features
- Thread-Safe by Default – Publish and subscribe safely across threads.
- Strongly Typed with Generics –
Signal[str],Signal[MyClass], or even TypedDict/dataclasses for structured events. Pyright/MyPy catches mistakes before runtime. - Synchronous First (Async Optional) – Works in any environment, including mixed sync/async projects.
- Automatic Memory Management – Bound methods are weakly referenced and auto-unsubscribed when their objects are deleted.
- No Runtime Guesswork – No
**kwargs, no stringly-typed namespaces, no dynamic channel lookups. - Lightweight & Zero Dependencies – Only what you need, nothing else.
How It Compares
EZPubSub vs Blinker
Blinker is great for simple, single-threaded Flask-style apps. But:
| Feature | EZPubSub | Blinker |
|---|---|---|
| Typing | ✅ Full static typing (Signal[T]) |
❌ Untyped (Any) |
| Thread Safety | ✅ Built-in | ❌ Single-threaded only |
| Design | ✅ Instance-based, type-safe | ⚠️ Channel-based (runtime filtering, string keys) |
| Weak Refs | ✅ Automatic | ✅ Automatic |
If you’re starting a new project in 2025, you deserve type checking and thread safety out of the box.
EZPubSub vs AioSignal
aiosignal is excellent for its niche—managing fixed async callbacks inside aiohttp—but unsuitable as a general pub/sub system:
| Limitation | Why It Matters |
|---|---|
| Async-Only | Forces you to rewrite sync code or wrap callbacks in event loop tasks. |
| Frozen Subscribers | You must freeze() before sending; no dynamic add/remove at runtime. |
| No Thread Safety | Assumes a single event loop context. |
| Loose Typing | Allows arbitrary **kwargs, undermining type safety. |
aiosignal is great if you’re writing an aiohttp extension. But if you need a general-purpose pub/sub system that works in any context, it's not the greatest fit.
Why Not Async-First Libraries?
Pub/sub is just a dispatch mechanism. Whether you await data before publishing is application logic—not the library’s job. Async-first libraries complicate what should be simple: they force you to juggle tasks, event loops, and weird APIs for no real benefit.
Synchronous first, with optional async support, is simpler and more predictable. That’s why Blinker, Celery, and PyDispatcher all share this design—and why EZPubSub does too.
Design Philosophy
Signals vs Channels
EZPubSub uses one object per signal, instead of Blinker’s “one channel, many signals” model.
Blinker (channel-based):
user_signal = Signal()
user_signal.connect(login_handler, sender=LoginService)
user_signal.send(sender=LoginService, user=user)
EZPubSub (instance-based):
login_signal = Signal[LoginEvent]("user_login")
login_signal.subscribe(login_handler)
login_signal.publish(LoginEvent(user=user))
This matters because:
- No filtering – Each signal already represents one event type.
- No runtime lookups – You never hunt down signals by string name.
- Type safety – Wrong event types are caught by your IDE/type checker.
Fewer magic strings, fewer runtime bugs, and code that reads like what it does.
Why No **kwargs?
Allowing arbitrary keyword arguments is convenient—but it destroys type safety.
# Bad: fragile, stringly typed
signal.publish(user, session_id="abc123", ip="1.2.3.4")
# Good: explicit, type-safe
@dataclass
class UserLoginEvent:
user: User
session_id: str
ip: str
signal.publish(UserLoginEvent(user, "abc123", "1.2.3.4"))
This forces better API design and catches mistakes at compile time instead of runtime. If you need flexible payloads, use TypedDicts, dataclasses, or even a Union of event types.
Installation
pip install ezpubsub
Or with UV:
uv add ezpubsub
Requires Python 3.10+.
Documentation
Full docs: Click here
License
MIT License. See LICENSE for details.
Why This Library Exists
Because the Python ecosystem needed a modern, type-safe, thread-safe pub/sub library that doesn’t suck.
EZPubSub is {167} deliberate lines of code that exist for one reason: to make event-driven Python sane again.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ezpubsub-0.2.0.tar.gz.
File metadata
- Download URL: ezpubsub-0.2.0.tar.gz
- Upload date:
- Size: 6.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bb5e445a8f079fc923637844cb552b44b52c5856a5e294f8bfd1e8eca1b3b425
|
|
| MD5 |
37db77db1f4bbcb379d6feb0bf18e189
|
|
| BLAKE2b-256 |
d55e6bb77866683e3a88d47535c32ec4b173611cec7adb4e64d353c0511d0cd5
|
Provenance
The following attestation bundles were made for ezpubsub-0.2.0.tar.gz:
Publisher:
release.yml on edward-jazzhands/ezpubsub
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ezpubsub-0.2.0.tar.gz -
Subject digest:
bb5e445a8f079fc923637844cb552b44b52c5856a5e294f8bfd1e8eca1b3b425 - Sigstore transparency entry: 319613274
- Sigstore integration time:
-
Permalink:
edward-jazzhands/ezpubsub@0a6b56a98cc522061fbd9394c8c7af2a4126c093 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/edward-jazzhands
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@0a6b56a98cc522061fbd9394c8c7af2a4126c093 -
Trigger Event:
push
-
Statement type:
File details
Details for the file ezpubsub-0.2.0-py3-none-any.whl.
File metadata
- Download URL: ezpubsub-0.2.0-py3-none-any.whl
- Upload date:
- Size: 7.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9e00708901147fbc4ada894b339e7853b74ac7380a2e400fb1ca459f6defd2e6
|
|
| MD5 |
c07db040fb90693b3284778640e5a01b
|
|
| BLAKE2b-256 |
d7d2d297114d86a643c9522bcee9e02561930a23864c21ff9627904168cbeec1
|
Provenance
The following attestation bundles were made for ezpubsub-0.2.0-py3-none-any.whl:
Publisher:
release.yml on edward-jazzhands/ezpubsub
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ezpubsub-0.2.0-py3-none-any.whl -
Subject digest:
9e00708901147fbc4ada894b339e7853b74ac7380a2e400fb1ca459f6defd2e6 - Sigstore transparency entry: 319613280
- Sigstore integration time:
-
Permalink:
edward-jazzhands/ezpubsub@0a6b56a98cc522061fbd9394c8c7af2a4126c093 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/edward-jazzhands
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@0a6b56a98cc522061fbd9394c8c7af2a4126c093 -
Trigger Event:
push
-
Statement type: