Skip to main content

PyMediate logo

A type-safe request mediator for Python 3.12+

PyPI version Python versions MIT License Documentation
Tests Coverage Checked with mypy (strict) OpenSSF Scorecard SLSA Build Level 2


Features

  • Type safe. Full runtime validation with mypy support.
  • Events. One-to-many publish() alongside one-to-one send() — same type-safe, validated-at-import design.
  • Async/await support. First-class async handlers and mediators via pymediate.aio.
  • DI ready. Built-in dependency-injector integration.
  • Well tested. Comprehensive test suite.

Wondering how this stacks up against other Python mediator libraries — and what send() and publish() cost over direct calls? See How it compares, a source-level survey of the ecosystem plus a reproducible micro-benchmark you can run against the latest release with uv run https://pymediate.sina-al.uk/benchmark.py (read it first, as with any script from the network).

Quick example

from dataclasses import dataclass
from pymediate import Request, RequestHandler, Mediator, Services

# Define response and request as pure dataclasses
@dataclass
class UserCreated:
    user_id: int
    username: str

@dataclass
class CreateUser(Request[UserCreated]):
    username: str
    email: str

# RequestHandler automatically linked by type
class CreateUserHandler(RequestHandler[CreateUser]):
    def __call__(self, req: CreateUser) -> UserCreated:
        return UserCreated(user_id=1, username=req.username)

# Set up and use
services = Services()
services.add(CreateUserHandler())
provider = services.provider()
mediator = Mediator(provider)

response = mediator.send(CreateUser(username="alice", email="alice@example.com"))
print(f"User {response.username} created with ID {response.user_id}")

Async support

PyMediate provides first-class async/await support through the pymediate.aio package:

import asyncio
from dataclasses import dataclass
from pymediate import Request, Services
from pymediate.aio import RequestHandler, Mediator

@dataclass
class UserCreated:
    user_id: int
    username: str

@dataclass
class CreateUser(Request[UserCreated]):
    username: str
    email: str

class CreateUserHandler(RequestHandler[CreateUser]):
    async def __call__(self, req: CreateUser) -> UserCreated:
        # Perform async operations
        await asyncio.sleep(0.1)  # Simulate async database call
        return UserCreated(user_id=1, username=req.username)

async def main():
    services = Services()
    services.add(CreateUserHandler())
    provider = services.provider()
    mediator = Mediator(provider)

    response = await mediator.send(CreateUser(username="alice", email="alice@example.com"))
    print(f"User {response.username} created with ID {response.user_id}")

asyncio.run(main())

Key differences for async:

  • Import from pymediate.aio instead of pymediate.
  • The handler's __call__ method must be async def.
  • Use await mediator.send(...) instead of mediator.send(...).
  • Supports concurrent request handling with asyncio.gather().

Pipeline behaviors

PyMediate supports pipeline behaviors (middleware) that automatically wrap request processing for cross-cutting concerns like logging, validation, caching, and more:

from pymediate import Request, PipelineBehavior

# Universal behavior - applies to all requests
class LoggingBehavior(PipelineBehavior[Request]):
    def __call__(self, request, next):
        print(f"Handling: {type(request).__name__}")
        response = next()
        print(f"Completed: {type(request).__name__}")
        return response

# Selective behavior - only applies to CreateUser requests
class ValidationBehavior(PipelineBehavior[CreateUser]):
    def __call__(self, request, next):
        # Validate before processing
        if not request.username:
            raise ValueError("Username is required")
        return next()

# Register behaviors and handlers
services = Services()
services.add(LoggingBehavior())       # Applied to all requests
services.add(ValidationBehavior())    # Only applied to CreateUser
services.add(CreateUserHandler())

mediator = Mediator(services.provider())

# Behaviors automatically wrap matching requests
response = mediator.send(CreateUser(username="alice", email="alice@example.com"))
# Output:
# Handling: CreateUser
# Completed: CreateUser

Behaviors can be universal (PipelineBehavior[Request]) or selective (PipelineBehavior[SpecificRequest]), applying only to matching request types or mixins. They're resolved per request and work with any dependency-injector provider lifetime — Factory, Singleton, or a scoped variant like ContextLocalSingleton. See the Pipeline behaviors guide for more examples.

Events

send() routes one request to its one handler. publish() is the one-to-many counterpart: announce a fact once, and every subscribed EventHandler reacts — the publisher never knows who's listening:

from dataclasses import dataclass
from pymediate import Event, EventHandler, Mediator, Services

@dataclass
class OrderPlaced(Event):
    order_id: int

class SendConfirmation(EventHandler[OrderPlaced]):
    def __call__(self, event: OrderPlaced) -> None:
        print(f"Confirming order {event.order_id}")

class UpdateAnalytics(EventHandler[OrderPlaced]):
    def __call__(self, event: OrderPlaced) -> None:
        print(f"Recording order {event.order_id}")

services = Services()
services.add(SendConfirmation()).add(UpdateAnalytics())
mediator = Mediator(services.provider())

mediator.publish(OrderPlaced(order_id=42))
# Output:
# Confirming order 42
# Recording order 42

Handlers run in registration order (concurrently via asyncio.gather in the async API), zero subscribers is a no-op, and a raising handler never stops the others — failures aggregate into an ExceptionGroup. See the Events guide.

Installation

# Core package
pip install pymediate

# With dependency injection support
pip install pymediate[di]

Documentation

📚 Full documentation

Development

Quick start

# Clone and install
git clone https://github.com/sina-al/pymediate.git
cd pymediate
uv sync --all-extras --group test

# Run tests
poe test

# Run all checks
poe check:all

# See all available tasks
poe

Available commands

PyMediate uses Poe the Poet for task running. Run poe to see all commands, or check tasks.toml.

Note: uv sync alone only installs the default dev dependency group (ruff, mypy, poethepoet). Test dependencies (pytest and friends) live in the separate test group and won't be installed unless you pass --group test (or --all-groups) — otherwise poe test fails with Failed to spawn: pytest.

Requirements

  • Python 3.12+.
  • Optional: dependency-injector>=4.41.0 for DI support.

Versioning

PyMediate follows ZeroVer — the major version stays at 0 indefinitely, with no planned 1.0. Expect the public API to keep evolving: a minor release (0.X.0) can include breaking changes, while a patch release (0.1.X) is backward-compatible.

Contributing

Contributions are welcome. See CONTRIBUTING.md for guidelines.

License

MIT License — see LICENSE for details.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pymediate-0.4.0.tar.gz (1.1 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pymediate-0.4.0-py3-none-any.whl (46.6 kB view details)

Uploaded Python 3

File details

Details for the file pymediate-0.4.0.tar.gz.

File metadata

  • Download URL: pymediate-0.4.0.tar.gz
  • Upload date:
  • Size: 1.1 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for pymediate-0.4.0.tar.gz
Algorithm Hash digest
SHA256 0429ed8fbfe4e977c4377a5c18456f94805214bf54a260196b4e36ef9dc0ad9f
MD5 d97a5edd587d8c421071fca284a4d8a1
BLAKE2b-256 f4c69c31b89baf9ac21ef56364037c186d0f05efe8553e841440bbc21f94685b

See more details on using hashes here.

Provenance

The following attestation bundles were made for pymediate-0.4.0.tar.gz:

Publisher: release.yml on sina-al/pymediate

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pymediate-0.4.0-py3-none-any.whl.

File metadata

  • Download URL: pymediate-0.4.0-py3-none-any.whl
  • Upload date:
  • Size: 46.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for pymediate-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 3d5f457b5daf44fb5888071cf2e669bf99713f27500db2a099392d5fe2164203
MD5 265f39be634aa7f4ad01161c9d72f499
BLAKE2b-256 5b63366af409e72520d2fe2f704471fdee877528330c9b3e3abc8840fe961f2f

See more details on using hashes here.

Provenance

The following attestation bundles were made for pymediate-0.4.0-py3-none-any.whl:

Publisher: release.yml on sina-al/pymediate

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.10.0

2 files

0.9.0

2 files

0.8.0

2 files

0.7.1

2 files

0.6.0

2 files

0.5.0

2 files

This release

0.4.0 This release

2 files

0.3.1

2 files

0.3.0

2 files

0.2.0

2 files

0.1.5

2 files

0.1.4

2 files

0.1.3

2 files

0.1.2

2 files

0.1.1

2 files

0.1.0

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page