Idempotency middleware for Django REST Framework
Project description
drf-idem
Читать на русском (Read in Russian)
Idempotency middleware for Django REST Framework. Prevents duplicate request processing using Redis.
When a client sends the same request twice (network retry, double-click, bug), drf-idem detects the duplicate and returns an immediate response — without running your business logic a second time.
Installation
# In a uv-managed project
uv add drf-idem
# Or via pip
pip install drf-idem
Quick Start
1. Add to INSTALLED_APPS:
INSTALLED_APPS = [
...
"drf_idem",
]
2. Add middleware first in the list:
MIDDLEWARE = [
"drf_idem.middleware.IdempotencyMiddleware",
...
]
3. Configure a dedicated Redis cache backend:
CACHES = {
"default": { ... },
"drf_idem": {
"BACKEND": "django.core.cache.backends.redis.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/2",
},
}
4. Add settings:
DRF_IDEM = {
"HEADER": "HTTP_X_REQUEST_ID", # reads X-Request-ID header
"TTL": 60, # seconds, max 60
"CACHE_BACKEND": "drf_idem",
"ENDPOINTS": [], # empty = check all endpoints
# formats: "POST /path", "/path" (all METHODS), "* /path" (absolutely all)
"METHODS": ["POST", "PUT", "PATCH", "DELETE"],
}
5. Mount admin stats page in urls.py:
# IMPORTANT: place before path("admin/", admin.site.urls)
urlpatterns = [
path("admin/drf-idem/", include("drf_idem.urls")),
path("admin/", admin.site.urls),
...
]
Open /admin/drf-idem/stats/ to see duplicate request statistics.
How It Works
The client attaches a unique X-Request-ID header to each request:
POST /api/payments/ HTTP/1.1
X-Request-ID: 550e8400-e29b-41d4-a716-446655440000
Content-Type: application/json
{"amount": 100}
First request — processed normally, key stored in Redis with TTL.
Duplicate request (same X-Request-ID + same method + same path, within TTL):
HTTP/1.1 200 OK
Content-Type: application/json
{"amount": 100}
Business logic is not executed. The exact same HTTP status and body from the original response are returned transparently to the client.
Settings Reference
| Key | Default | Description |
|---|---|---|
HEADER |
HTTP_X_REQUEST_ID |
Django META key for the idempotency header (X-Request-ID → HTTP_X_REQUEST_ID) |
TTL |
60 |
Seconds to remember a request. Capped at 60. |
CACHE_BACKEND |
"drf_idem" |
Django CACHES alias to use |
ENDPOINTS |
[] |
Endpoint filter (empty = apply to all). Formats: "METHOD /path", "/path", "* /path" |
METHODS |
["POST", "PUT", "PATCH", "DELETE"] |
HTTP methods to check when ENDPOINTS is empty |
STATS_TTL |
604800 (7 days) |
TTL for statistics data in Redis |
Endpoint Filtering
Use ENDPOINTS to restrict which endpoints are protected:
DRF_IDEM = {
"ENDPOINTS": [
"POST /api/payments/", # only POST to /api/payments/*
"/api/orders/", # any method from METHODS to /api/orders/*
"* /api/critical/", # all methods to /api/critical/*
],
}
Pattern formats:
"METHOD /path/prefix"— specific method + path prefix"/path/prefix"— any method fromMETHODS+ path prefix"* /path/prefix"— all methods + path prefix
When ENDPOINTS is empty, all endpoints matching METHODS are checked.
Security
- Key isolation: Cache keys use SHA-256 of
(method, path, request_id)— no injection via separator characters. - Input validation:
request_idmust be ≤ 128 chars and match[A-Za-z0-9\-_./]+. Invalid values return HTTP 400. - Atomic writes: Uses
cache.add()(Redis SETNX) to eliminate TOCTOU race conditions. - Admin protection: Stats page requires
is_staff=True.
Admin Statistics
The stats page at /admin/drf-idem/stats/ shows:
- Top endpoints by duplicate count
- Timestamp of last duplicate
- Total Redis memory used by
drf_idem:*keys (with warning if > 10 MB)
Development
git clone https://github.com/example/drf-idem
cd drf-idem
uv sync --extra dev
uv run pytest -v
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file drf_idem-0.1.1.tar.gz.
File metadata
- Download URL: drf_idem-0.1.1.tar.gz
- Upload date:
- Size: 12.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2b1866da75bac2c8c078e2924e5f7043c92abecc97c487933cdb95e87d812264
|
|
| MD5 |
7e6c60808d92477864e26d92746e7234
|
|
| BLAKE2b-256 |
d5eca962d050503fcc82330ec4f0ac6b1c24ca45ab6b889df02d0e7ffad42e0e
|
Provenance
The following attestation bundles were made for drf_idem-0.1.1.tar.gz:
Publisher:
publish.yml on safonin/drf-idem
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
drf_idem-0.1.1.tar.gz -
Subject digest:
2b1866da75bac2c8c078e2924e5f7043c92abecc97c487933cdb95e87d812264 - Sigstore transparency entry: 1383354217
- Sigstore integration time:
-
Permalink:
safonin/drf-idem@5502dde7ff68d364bae02b2f012e8970125f3f2c -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/safonin
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@5502dde7ff68d364bae02b2f012e8970125f3f2c -
Trigger Event:
push
-
Statement type:
File details
Details for the file drf_idem-0.1.1-py3-none-any.whl.
File metadata
- Download URL: drf_idem-0.1.1-py3-none-any.whl
- Upload date:
- Size: 10.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6528298e322e9be52f65a003f549954cd6ecbf17f0002de9b7598d3a93bd6987
|
|
| MD5 |
f3e054bfc0c5e8a2f7822af6cf850ade
|
|
| BLAKE2b-256 |
a9977a11b227671f4f27dd8b6eabe8f3374450e9d165d66cff6cff24bbca0702
|
Provenance
The following attestation bundles were made for drf_idem-0.1.1-py3-none-any.whl:
Publisher:
publish.yml on safonin/drf-idem
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
drf_idem-0.1.1-py3-none-any.whl -
Subject digest:
6528298e322e9be52f65a003f549954cd6ecbf17f0002de9b7598d3a93bd6987 - Sigstore transparency entry: 1383354245
- Sigstore integration time:
-
Permalink:
safonin/drf-idem@5502dde7ff68d364bae02b2f012e8970125f3f2c -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/safonin
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@5502dde7ff68d364bae02b2f012e8970125f3f2c -
Trigger Event:
push
-
Statement type: