Skip to main content

tests pypi python-versions codecov

Idempotency Header ASGI Middleware

A middleware for making endpoints idempotent.

The purpose of the middleware is to guarantee that execution of mutating endpoints happens exactly once, regardless of the number of requests. We achieve this by caching responses, and returning already-saved responses to the user on repeated requests. Responses are only cached when an idempotency-key HTTP header is present, so clients must opt-into this behaviour. Additionally, only configured HTTP methods (by default, POST and PATCH) that return JSON payloads are cached and replayed.

This is largely modelled after stripe' implementation.

The middleware is compatible with both Starlette and FastAPI apps.

Installation

pip install asgi-idempotency-header

Setup

Add the middleware to your app like this:

from fastapi import FastAPI

from idempotency_header_middleware import IdempotencyHeaderMiddleware
from idempotency_header_middleware.backends import RedisBackend


backend = RedisBackend(redis=redis)

app = FastAPI()
app.add_middleware(IdempotencyHeaderMiddleware(backend=backend))

or like this:

from fastapi import FastAPI
from fastapi.middleware import Middleware

from idempotency_header_middleware import IdempotencyHeaderMiddleware
from idempotency_header_middleware.backends import RedisBackend


backend = RedisBackend(redis=redis)

app = FastAPI(
    middleware=[
        Middleware(
            IdempotencyHeaderMiddleware,
            backend=backend,
        )
    ]
)

If you're using Starlette, just substitute FastAPI for Starlette and it should work the same.

Configuration

The middleware takes a few arguments. A full example looks like this:

from aioredis import from_url

from idempotency_header_middleware import IdempotencyHeaderMiddleware
from idempotency_header_middleware.backends import RedisBackend


redis = from_url(redis_url)
backend = RedisBackend(redis=redis)

IdempotencyHeaderMiddleware(
    backend,
    idempotency_header_key='Idempotency-Key',
    replay_header_key='Idempotent-Replayed',
    enforce_uuid4_formatting=False,
    expiry=60 * 60 * 24,
    applicable_methods=['POST', 'PATCH']
)

The following section describes each argument:

Backend

from idempotency_header_middleware.backends import RedisBackend, MemoryBackend

backend: Union[RedisBackend, MemoryBackend]

The backend is the only required argument, as it defines how and where to store a response.

The package comes with an aioredis backend implementation, and a memory-backend for testing.

Contributions for more backends are welcomed, and configuring a custom backend is pretty simple - just take a look at the existing ones.

Idempotency header key

idempotency_header_key: str = 'Idempotency-Key'

The idempotency header key is the header value to check for. When present, the middleware will be used if the HTTP method is in the applicable methods.

The default value is "Idempotency-Key", but it can be defined as any string.

Replay header key

replay_header_key: str = 'Idempotent-Replayed'

The replay header is added to replayed responses. It provides a way for the client to tell whether the action was performed for the first time or not.

Enforce UUID formatting

enforce_uuid4_formatting: bool = False

Convenience option for stricter header value validation.

Clients can technically set any value they want in their header, but the shorter the key value is, the higher the risk of value-collisions is from other users. If two users accidentally send in the same header value for what's meant to be two separate requests, the middleware will interpret them as the same.

By enabling this option, you can force users to use UUIDs as header values, and pretty much eliminate this risk.

When validation fails, a 422 response is returned from the middleware, informing the user that the header value is malformed.

Expiry

expiry: int = 60 * 60 * 24

How long to cache responses for, measured in seconds. Set to 24 hours by default.

Applicable Methods

applicable_methods=['POST', 'PATCH']

What HTTP methods to consider for idempotency. If the request method is one of the methods in this list, and the idempotency header is sent, the middleware will be used. By default, only POST and PATCH methods are cached and replayed.

Quick summary of behaviours

Briefly summarized, this is how the middleware functions:

  • The first request is processed, and consequent requests are replayed, until the response expires. expiry can be set to None to skip expiry, but most likely you will want to expire responses after a while.
  • If two requests comes in at the same time - i.e., if a second request hits the middlware before the first request has finished, the middleware will return a 409, informing the user that a request is being processed, and that we cannot handle the second request.
  • The middleware only handles HTTP requests.
  • By default, the middleware only handles requests with POST and PATCH methods. Other HTTP methods skip this middleware.
  • Only valid JSON responses with content-type == application/json are cached.

Release files for asgi-idempotency-header 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for asgi-idempotency-header 0.2.0
File Size Uploaded
asgi-idempotency-header-0.2.0.tar.gz 10.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for asgi-idempotency-header 0.2.0
File Interpreter ABI Platform
asgi_idempotency_header-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 19.9 kB

Release files / asgi-idempotency-header-0.2.0.tar.gz

Download URL asgi-idempotency-header-0.2.0.tar.gz
Size 10.1 kB
Tags Source
SHA-256 checksum
How to use checksums
604b33ebb9517e45cf5872397b25dceb159ba1d4ce0ec6a3d73fd49a0785311e
BLAKE2b-256 checksum
How to use checksums
e6742c6d0c72c2c49bb90da66e0240e83a0250cd6791fc826c178980ae80e7fd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via poetry/1.2.0 CPython/3.9.13 Linux/5.15.0-1017-azure

Release files / asgi_idempotency_header-0.2.0-py3-none-any.whl

Download URL asgi_idempotency_header-0.2.0-py3-none-any.whl
Size 9.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
fa88898ceae2f365c699f43230d6ae5554d3641495fb9aa91e18141ae858a71b
BLAKE2b-256 checksum
How to use checksums
84e1b2d78307c4c298f3d71d0cb221cca1238346270679126c3f818c7eaac502
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via poetry/1.2.0 CPython/3.9.13 Linux/5.15.0-1017-azure

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page