Middleware
Composable processing for requests.
Chain
Banshee processes requests using a nested chain of middleware. Each link in the chain does a single step in the processing. You arrange these links to work how you want them to. We provide a set of standard middleware, and it’s easy to create your own.
Context
Middleware communicate via context classes. A context can be any class but a we
recommend a frozen dataclass
for their immutability. Middleware
can add or remove contexts as messages move along the chain.
import dataclasses
@dataclasses.dataclass(frozen=True)
class Count:
number: int
Messages
The Message
class associates a request with related context objects
from processing it. It provides methods to add, remove, and get contexts by class.
message = banshee.message_for(requset)
message = message.including(Count(number=10))
assert message[Count].number == 10
Registering
We execute middleware in the order you register them. You add them via the
Builder
. The builder always adds a Dispatch
middleware as the inner most middleware.
bus = (
banshee.Builder()
.with_locator(registry)
.with_middleware(banshee.IdentityMiddleware())
.with_middleware(banshee.CausationMiddleware())
.with_middleware(banshee.HandleAfterMiddleware())
.build()
)
Custom middleware
You can add custom middleware to the chain. The Middleware
protocol
defines them. You can use either a callable class or a simple function.
T = typing.TypeVar("T")
class CountMiddleware:
def __init__(self) -> None:
self.number = 0
async def __call__(
message: banshee.Message[T],
handle: banshee.HandleMessage
) -> banshee.Message[T]:
self.number += 1
message = message.including(Count(number=self.number))
return await handle(message)
async def filter_middleware(
message: banshee.Message[T],
handle: banshee.HandleMessage
) -> banshee.Message[T]:
context = message.get(Count)
if context and context.number % 2 == 1:
return message
return await handle(message)
We defined two middleware above. A class based CountMiddleware
named that adds the
count to each message. And another function based filter_middleware
that will refuse
to process every other message.
Reference
- class banshee.HandleMessage(*args, **kwargs)
Bases:
Protocol
Handle message protocol.
Handle the message with the next middleware in the chain.
- class banshee.Message(request, *, contexts=<factory>)
Bases:
Generic
[T
]Message.
Associates extra context with a request.
- Parameters:
- excluding(*key)
Exclude context.
Create a clone of the object with instances of the context types removed.
- get(key: type[CT]) CT | None
- get(key: type[CT], default: CT) CT
Get context.
- Parameters:
key – context type
default – value to return if no value found
- Returns:
requested context or default
- class banshee.Middleware(*args, **kwargs)
Bases:
Protocol
Middleware protocol.
A link in the chain of middleware.
- abstract async __call__(message, handle)
Handle message.
Perform any processing on the message and forward it to the next handler in the chain.
- Parameters:
message (Message[T]) – message to process
handle (HandleMessage) – next middleware invoker
- Returns:
processed message
- Return type:
Message[T]