Skip to main content

No project description provided

Project description

idempot

Idempotency for APIs via decorators. Framework-agnostic core with adapters for FastAPI and Flask.

Install

pip install idempot                  # core + backends (pyyaml, redis)
pip install "idempot[fastapi]"       # core + fastapi
pip install "idempot[flask]"         # core + flask
pip install "idempot[dev]"           # everything + test/lint deps

Zero-config Quickstart

No config needed. Import, decorate, done.

FastAPI

# app.py
from fastapi import FastAPI, Request
from idempot.contrib.fastapi import idempotent

app = FastAPI()

@app.post("/charge")
@idempotent
async def charge(request: Request):
    body = await request.json()
    return {"charged": body["amount"]}
uvicorn app:app --reload

Flask

# app.py
import flask
from idempot.contrib.flask import idempotent

app = flask.Flask(__name__)

@app.post("/charge")
@idempotent
def charge():
    data = flask.request.get_json()
    return flask.jsonify({"charged": data["amount"]})
flask --app app run

Defaults: memory backend, 24h TTL, Idempotency-Key header, 5s lock timeout.

Client Usage

Send Idempotency-Key header with every request:

curl -X POST http://localhost:8000/charge \
  -H "Idempotency-Key: abc-123" \
  -H "Content-Type: application/json" \
  -d '{"amount": 99}'
# → {"charged": 99}

# Same key → replayed response (handler not called)
curl -X POST http://localhost:8000/charge \
  -H "Idempotency-Key: abc-123" \
  -H "Content-Type: application/json" \
  -d '{"amount": 50}'        # different body, ignored
# → {"charged": 99}           # same reply

Configuration

Option A — configure() at startup

In your app entry point, before registering routes:

from idempot import configure

# Redis in production:
configure(
    backend="redis",
    redis_url="redis://localhost:6379",
    redis_key_prefix="myapp:",
    default_ttl=86400,
    header_name="Idempotency-Key",
    lock_timeout=5,
    cache_on_status=[200, 201],
)

# Or override just one thing:
configure(backend="memory", default_ttl=3600)

Option B — YAML file

Create idempot-config.yaml in your project root:

backend: redis
redis:
  url: redis://localhost:6379
  key_prefix: "myapp:"
default_ttl: 86400
header_name: Idempotency-Key
lock_timeout: 5
cache_on_status: [200, 201]
from idempot import configure
configure(config_path="idempot-config.yaml")

A working example file: idempot-config.example.yaml

The config file is not auto-generated. If no config file is found and configure() is never called, memory backend + defaults are used.

Config reference

Key Default Description
backend memory memory or redis
redis.url redis://localhost:6379 Redis connection URL
redis.key_prefix idempot: Prefix for Redis keys
default_ttl 86400 (24h) Seconds before cached response expires
header_name Idempotency-Key HTTP header to read the key from
lock_timeout 5 Max seconds to wait for concurrent same-key request
cache_on_status [200] Which HTTP status codes get cached
max_body_size 10485760 (10 MB) Largest response body to cache

Per-endpoint Overrides

@app.post("/payments")
@idempotent(
    ttl=3600,                      # cache 1 hour (instead of default 24h)
    required=True,                 # reject requests without key → 400
    cache_on_status=[200, 201],    # cache on 200 or 201
    lock_timeout=10,               # wait up to 10s for concurrent
)
async def process_payment(request: Request):
    ...

Override params: ttl, required, lock_timeout, cache_on_status, max_body_size.

Import Cheatsheet

Context Import
Config & setup from idempot import configure, get_config
FastAPI decorator from idempot.contrib.fastapi import idempotent
Flask decorator from idempot.contrib.flask import idempotent
Base decorator (no framework) from idempot import idempotent
Exceptions from idempot import IdempotencyKeyMissingError, KeyConflictError
Backends (custom) from idempot.backends.memory import MemoryBackend

How It Works

  1. Decorator reads Idempotency-Key from request headers
  2. Key missing → 400 if required=True, otherwise handler runs normally (not cached)
  3. Key in cache → replays stored response verbatim (same status, headers, body)
  4. Key new → acquires distributed lock (Redis SETNX) → runs handler → caches response → releases lock
  5. Concurrent same-key → second request polls for cached response, returns it. If first request exceeds lock_timeout → 409 Conflict

Backends

  • Memory (default, dev): dict-backed, no persistence
  • Redis (prod): SETNX locks, SETEX caching with TTL, key prefix isolation

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

idempot-1.0.0.tar.gz (13.9 kB view details)

Uploaded Source

Built Distribution

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

idempot-1.0.0-py3-none-any.whl (12.2 kB view details)

Uploaded Python 3

File details

Details for the file idempot-1.0.0.tar.gz.

File metadata

  • Download URL: idempot-1.0.0.tar.gz
  • Upload date:
  • Size: 13.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.11

File hashes

Hashes for idempot-1.0.0.tar.gz
Algorithm Hash digest
SHA256 fcaaf25a1e6292eaea7d38c29f586eed8d395bcd7d9dcadafd40695aa7a40d56
MD5 c87ff644c4d3c2d6d461eda2c28ace80
BLAKE2b-256 853d901b4236164c03b487c5f5583e5e4713f28b2b738af37382f00df7962725

See more details on using hashes here.

File details

Details for the file idempot-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: idempot-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 12.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.11

File hashes

Hashes for idempot-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 5f1c42c0a9e022b6fd9a1def5e9f788b9446c6048a320ffad0d9fb88fadd0b99
MD5 d3a5d4527e22ee23216a2ecad9020760
BLAKE2b-256 1c9d611c034c5f6cb4140aaf944a79e5d23c0fdde4550ad9204ebb70f39a8162

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