pymediate

How it compares

A source-level comparison with Python mediator libraries, plus dated dispatch benchmarks.

This page compares two specific parts of PyMediate with other Python mediator libraries: the information preserved by the public types, and the runtime cost of dispatch. It is a dated snapshot, not a general ranking of the libraries.

Scope and method

In July 2026, the source of six Python mediator libraries was reviewed. The selection was based on GitHub stars and PyPI downloads at the time. The review covered dispatch code, registries, and type signatures rather than relying on project descriptions.

The results are reported in aggregate. This keeps the comparison focused on common API approaches rather than individual projects. It also means the table should not be read as a claim about every mediator library in Python. Because the library identities and per-library profiles are not published, readers cannot independently map each aggregate claim back to its source project. Treat the table as a maintainer-produced survey snapshot, and verify any row that is material to a library decision.

Capability comparison

CapabilityPyMediateFinding across the six surveyed libraries
Request-to-handler linkRequestHandler[PlaceOrder] links the handler to the exact request class. Dispatch uses that class as its key.Links used class-name strings, naming conventions, or a separate bind(request, handler) call. None used a type parameter as the dispatch key.
Response type at the call sitePlaceOrder(Request[OrderReceipt]) makes send(PlaceOrder(...)) return OrderReceipt to a static type checker.The return type was object, Any, or a generic that was not bound to the request.
Handler signature checksThe request parameter and response annotation are checked when Python defines the handler class.None checked these signatures at class-definition time; mismatches surfaced during dispatch.
Duplicate request handlersDefining a second handler for one request raises an error.Registration either replaced the existing handler or ignored the new one without raising.
Synchronous useThe top-level package is async; pymediate.sync provides the corresponding synchronous API.Almost all were async-only.
Notification publishingNotificationHandler[OrderPlaced] is typed and checked. Sync handlers run sequentially; async handlers run concurrently. Ordinary failures are grouped after delivery; fatal base exceptions can interrupt it.Five supported publishing. Each surveyed implementation was type-erased or async-only.
Streaming responsesStreamRequest[ChunkT] and StreamRequestHandler[StreamReqT] produce a lazy Iterator[ChunkT] or AsyncIterator[ChunkT].One supported streaming, without a typed chunk relationship.
Required runtime dependenciesThe core package has no runtime dependencies. Integration with dependency-injector is optional.Requirements varied; several required a serialization framework or dependency-injection container.

The table covers only the properties that were checked in source. It does not compare documentation, release history, framework integrations, community size, or support for older Python versions. PyMediate is an early-stage package, requires Python 3.12 or later, and may be a worse fit when one of those other factors is more important than its typing model.

Dispatch cost

A mediated call performs more work than a direct call. The benchmark measures PyMediate against the direct calls it replaces; it does not benchmark other libraries with different feature sets.

These results came from PyMediate 0.6.0 on a 2020 Intel MacBook Pro running CPython 3.13.0 and macOS 15.7.4. Each value is the median of five samples of 100,000 calls. The handlers do little work so that dispatch remains visible in the result.

ScenarioMedian per callRelative to its direct call
Sync: handler(request) — direct call0.98 µsBaseline
Sync: mediator.send(request)3.1 µs3.2x
Sync: send() plus one pipeline behavior3.6 µs3.7x
Sync: handler(notification) — direct call0.25 µsBaseline
Sync: mediator.publish(notification) — one subscriber1.1 µs4.5x
Async: await handler(request) — direct call0.92 µsBaseline
Async: await mediator.send(request)2.3 µs2.5x
Async: await handler(notification) — direct call0.85 µsBaseline
Async: await mediator.publish(notification) — one subscriber247 µs290x

On this run, synchronous send() added about 2.1 microseconds to a direct request-handler call, and asynchronous send() added about 1.4 microseconds. Absolute values vary with the machine and its current load, so use the table to judge scale rather than to predict an exact value in another application.

The async publish result includes the cost of scheduling and awaiting subscribers concurrently with asyncio.gather. That fixed cost is visible because the benchmark handler does no I/O. In an application, compare it with the work each subscriber performs and with the notification rate.

Read scripts before you run them

This applies to any script fetched from a URL, including this one. The source is committed as scripts/benchmark.py.

uv run https://pymediate.sina-al.uk/benchmark.py

The remote command runs the method against the package version it resolves and prints that version with the Python and platform details. It does not necessarily reproduce this dated 0.6.0 table after a later release. To run the same source version, check out the v0.6.0 tag and use uv run poe benchmark; absolute results will still vary by machine and load.

When a direct call is a better fit

Use an ordinary function or callable object when callers can depend on it directly and that relationship does not create repeated wiring. A direct call avoids a service provider, registration, runtime lookup, and indirect navigation. It is also the appropriate choice for code called very frequently to perform small in-memory operations.

A mediator becomes relevant when several callers need one dispatch boundary and the typed request-to-response relationship or early handler checks justify that extra mechanism. The architecture article develops that decision from a direct call, and the introduction explains the resulting API declarations.

On this page