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: strResponses
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: datetimeHandlers
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: UserCreatedCallers 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- The request enters the outermost behavior.
- Each behavior runs its pre-processing, then calls
next(). - The handler executes and returns a response.
- 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: CreateUserNext steps
- Deep-dive into requests and responses.
- Learn handler design in the handlers guide.
- Add middleware with pipeline behaviors.