Skip to main content

Idempotency-Key middleware for FastAPI, with pluggable Redis / in-memory storage backends.

Project description

mobius-idempotence-library

Idempotency-Key middleware for FastAPI, with pluggable storage backends.

Protects POST/PATCH-style endpoints (payments, order creation, anything non-idempotent by nature) against duplicate side effects when a client retries a request -- due to a timeout, a dropped connection, or an at-least-once delivery mechanism upstream.

Features

  • Idempotency-Key enforcement following the shape of the common Idempotency-Key header convention used by Stripe and similar APIs.
  • Request fingerprinting: a key reused with a different request body is rejected (422), not silently replayed.
  • In-flight collision detection: a second concurrent request with the same key while the first is still processing gets 409, not a race.
  • Pluggable backends: ships with RedisBackend (distributed, atomic via single-round-trip Lua scripts) and MemoryBackend (in-process, for tests or single-instance deployments). Implement StorageBackend for anything else (Postgres, DynamoDB, ...).
  • Fail-open or fail-closed, your choice, when the backend is unreachable.
  • Fully async, doesn't block the event loop.

Install

pip install mobius-idempotence-library          # in-memory backend only
pip install mobius-idempotence-library[redis]   # + Redis backend

Quick start

from fastapi import FastAPI
from pydantic import BaseModel

from mobius_idempotence import IdempotencyConfig, IdempotencyManager, idempotent
from mobius_idempotence.backends.redis import RedisBackend

app = FastAPI()

backend = RedisBackend("redis://localhost:6379/0")
manager = IdempotencyManager(backend, IdempotencyConfig(fail_open=False))


class PaymentRequest(BaseModel):
    amount: float
    currency: str


@app.post("/payments", status_code=201)
@idempotent(manager, status_code=201)
async def create_payment(payload: PaymentRequest):
    return await charge_the_card(payload)  # your business logic, nothing else


@app.on_event("shutdown")
async def shutdown():
    await manager.aclose()

That's the entire integration. @idempotent(manager) handles:

  • validating the Idempotency-Key header
  • fingerprinting the request body, so a key reused with a different body is rejected (422)
  • detecting an in-flight duplicate request (409)
  • replaying the cached response verbatim on retry, with no re-run of your handler
  • releasing the lock if your handler raises, so the client can safely retry with the same key

No Depends(...), no manual cache lookup, no manual save/release calls in the handler.

Callers must send the same Idempotency-Key header (and the same request body) on retry:

curl -X POST http://localhost:8000/payments \
  -H "Idempotency-Key: 3f29c1a2-..." \
  -H "Content-Type: application/json" \
  -d '{"amount": 10.5, "currency": "USD"}'

What gets cached: the handler's return value (dict, list, Pydantic model, or dataclass), encoded the same way FastAPI itself would encode it. If you need to return a raw Response / StreamingResponse, or want custom caching logic, use the lower-level IdempotencyGuard dependency instead -- see examples/payments_app_manual.py.

Writing your own backend

Subclass mobius_idempotence.StorageBackend and implement five async methods: check_or_lock, save_response, release, ping, aclose. See src/mobius_idempotence/backends/memory.py for the simplest possible reference implementation.

Configuration

IdempotencyConfig is a plain dataclass -- wire it up to whatever config system your app already uses (env vars, Pydantic Settings, a YAML file):

Field Default Meaning
lock_ttl 60 Seconds an in-progress lock lives before being considered abandoned
response_ttl 86400 Seconds a finished response stays cached and replayable
key_prefix "idemp:" Storage key namespace
fail_open True If the backend is unreachable: process anyway (True) or 503 (False)
max_key_len 255 Reject longer Idempotency-Key headers

Testing

pip install -e ".[dev]"
pytest

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mobius_idempotence_library-0.1.1.tar.gz (16.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mobius_idempotence_library-0.1.1-py3-none-any.whl (19.0 kB view details)

Uploaded Python 3

File details

Details for the file mobius_idempotence_library-0.1.1.tar.gz.

File metadata

File hashes

Hashes for mobius_idempotence_library-0.1.1.tar.gz
Algorithm Hash digest
SHA256 66950aa8ac998d6b183f72be9edf2f813bfca1ca9db93cfe0d474a6e83397b78
MD5 cd21a8374804b0715fb84d94eb6edc61
BLAKE2b-256 95ae8cf7034d8641f4be1a81a43a8293d2bb8f8b5e45ebe73592e094eeca66a2

See more details on using hashes here.

File details

Details for the file mobius_idempotence_library-0.1.1-py3-none-any.whl.

File metadata

File hashes

Hashes for mobius_idempotence_library-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 8d4f93f41f0e66e5192d17a5ec65005b7465207352a7c829a349d62b5ad14b06
MD5 f76e285e96e3f5d1bab9c158401827ca
BLAKE2b-256 6dd35a4726cba841b57467fc892d96c302ce5b4e967df72234e039e99be3ea7d

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page