Using Middlewares
Middlewares are components that acts as a bridge between the client and the data source, responsible for doing specific tasks with the request and response of an invocation. For instance, some of the tasks involved are error handling, rate limiting, deserialization, caching, etc. Additionally, see reference of built-in middlewares with source codes.
Middleware Syntax:
def middleware_1(): #(1)!
#(2)!
def constructor(next: MiddlewareCallable): #(3)!
#(4)!
async def middleware(invocation: Invocation): #(5)!
... #(6)!
response = await next(invocation) #(7)!
... #(8)!
return response #(9)!
return middleware
return constructor
def middleware_2(): ...
def middleware_3(): ...
- Middleware configuration. May accept configuration parameters.
- Middleware setup during configuration (runs only once).
- Middleware constructor. Called during client
__init__
withnext
being the next middleware. - Middleware setup during instantiation (runs once for each client).
- Middleware function. Called for each invocation.
- Tasks to be done with the invocation.
- Calls the next middleware with the invocation, this may happen more than once (e.g. in an error handling middleware).
- Tasks to be done with the invocation and response.
- Returns a value to the previous middleware, this may happen prematurely (e.g. in a cache middleware).
Client Syntax:
sequenceDiagram
autonumber
Client->>middleware_1: invocation
par middleware_1
middleware_1->>middleware_2: invocation
par middleware_2
middleware_2->>middleware_3: invocation
par middleware_3
middleware_3->>Data Source: invocation
Data Source->>middleware_3: response
end
middleware_3->>middleware_2: response
end
middleware_2->>middleware_1: response
end
middleware_1->>Client: response
Custom Middlewares
You may write custom middlewares following the syntax above for your own use-cases.
Clients comes with a default list of middlewares, to change them, provide a new list of middlewares to the middlewares
parameter during instantiation as a replacement. The following example adds a cache middleware with an in-memory cache to the CDragonClient.
from pulsefire.caches import MemoryCache
from pulsefire.clients import CDragonClient
from pulsefire.middlewares import (
cache_middleware,
http_error_middleware,
json_response_middleware,
)
cache = MemoryCache()
async with CDragonClient(
default_params={"patch": "latest", "locale": "default"},
middlewares=[
cache_middleware(cache, [
(lambda inv: inv.invoker.__name__ == "get_lol_v1_champion", 3600), #(1)!
]),
json_response_middleware(),
http_error_middleware(),
]
) as client:
champion = await client.get_lol_v1_champion(id=777) #(2)!
assert champion["name"] == "Yone"
champion = await client.get_lol_v1_champion(id=777) #(3)!
assert champion["name"] == "Yone"
- Cache middleware defining a TTL of 1 hour and applies to all requests. See reference for detailed usage.
- This request misses the cache, proceeds to next middleware, then cache the response.
- This request hits the cache, returns the cached response immediately.
Contributing
Please consider creating a pull request if you consider your custom middlewares to be in scope and useful for most users.