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.

abstract async __call__(message)

Handle.

Parameters:

message (Message[T]) – message to process

Returns:

processed message

Return type:

Message[T]

class banshee.Message(request, *, contexts=<factory>)

Bases: Generic[T]

Message.

Associates extra context with a request.

Parameters:
  • request (T) – request to be dispatched

  • contexts (tuple[Any, ...]) – additional context about the request

all(key)

All contexts.

Parameters:

key (type[~CT]) – context type

Returns:

sequence of context objects

Return type:

Iterable[CT]

excluding(*key)

Exclude context.

Create a clone of the object with instances of the context types removed.

Parameters:

key (type) – context type to remove

Returns:

clone of message with updated context

Return type:

Message[T]

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

has(key)

Has context.

Parameters:

key (type) – context type

Returns:

whether context exists

Return type:

bool

including(*values)

Include context.

Create a clone of the object with the added context object.

Parameters:

values (object) – context object to add

Returns:

clone of message with updated context

Return type:

Message[T]

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:
Returns:

processed message

Return type:

Message[T]