pymediate
Getting Started

Core concepts

The vocabulary of PyMediate — requests, handlers, the mediator, pipeline behaviors, and the service provider.

PyMediate has five moving parts. This page introduces each one and shows how they fit together.

Requests

A request represents an intention — something you want your application to do. It's a plain data container that also declares, via its type parameter, what response it expects back:

from dataclasses import dataclass
from pymediate import Request

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

Responses

A response is whatever the handler produces. It has no PyMediate type at all — any class (usually a dataclass) works:

from datetime import datetime

@dataclass
class UserCreated:
    user_id: int
    username: str
    created_at: datetime

Handlers

A handler holds the business logic for exactly one request type. Subclassing Handler[CreateUser] is what links it to its request — PyMediate discovers the relationship through type inspection, not naming conventions:

from pymediate import Handler

class CreateUserHandler(Handler[CreateUser]):
    def __call__(self, request: CreateUser) -> UserCreated:
        # business logic here
        return UserCreated(...)

The mediator

The mediator is the single dispatch point. It receives a request, finds the handler registered for that request type, and returns the handler's response — typed:

response = mediator.send(CreateUser(username="alice", email="alice@example.com"))
# response: UserCreated

Callers depend only on the mediator, never on individual handlers.

Pipeline behaviors

Pipeline behaviors are middleware that wrap request processing. They handle cross-cutting concerns — logging, validation, caching, transactions — without touching handler code.

Without behaviors, you'd repeat that logic in every handler, and it would drift. A behavior is written once and applies automatically:

from pymediate import Request, PipelineBehavior, Services, Mediator

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

services = Services()
services.add(LoggingBehavior())   # discovered by the mediator automatically
services.add(CreateUserHandler())
services.add(GetUserHandler())

mediator = Mediator(services.provider())

Behaviors can be universal (PipelineBehavior[Request], applying to everything) or selective (PipelineBehavior[CreateUser], applying to one request type or a mixin).

How the chain executes

Behaviors nest around the handler, each calling next() to continue:

Request → Logging → Validation → Timing → Handler

Response ← Logging ← Validation ← Timing ← Handler
  1. The request enters the outermost behavior.
  2. Each behavior runs its pre-processing, then calls next().
  3. The handler executes and returns a response.
  4. Each behavior's post-processing runs in reverse order.

A behavior can also modify the request or response, short-circuit the pipeline by not calling next(), or handle and wrap errors.

The service provider

The service provider resolves registered instances — handlers, behaviors, and anything else — for the mediator. The built-in flow is Services (mutable registration) → provider() (immutable resolution):

from pymediate import Services, Mediator

services = Services()
services.add(CreateUserHandler(database))
mediator = Mediator(services.provider())

ServiceProvider is a protocol, so you can substitute any implementation — including the optional dependency-injector integration.

Everything together

from dataclasses import dataclass
from pymediate import Request, Handler, PipelineBehavior, Services, Mediator

# 1. Request and response
@dataclass
class UserCreated:
    user_id: int

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

# 2. Handler
class CreateUserHandler(Handler[CreateUser]):
    def __call__(self, request: CreateUser) -> UserCreated:
        return UserCreated(user_id=1)

# 3. Optional behavior — wraps every request automatically
class LoggingBehavior(PipelineBehavior[Request]):
    def __call__(self, request, next):
        print(f"Before: {type(request).__name__}")
        response = next()
        print(f"After: {type(request).__name__}")
        return response

# 4. Wire it up
services = Services()
services.add(LoggingBehavior())
services.add(CreateUserHandler())
mediator = Mediator(services.provider())

# 5. Send
response = mediator.send(CreateUser(username="alice"))
# Output: Before: CreateUser
#         After: CreateUser

Next steps

On this page