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
Metadata
Release files for drf-idem 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| drf_idem-0.1.1.tar.gz | 12.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| drf_idem-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 23.3 kB
Release files / drf_idem-0.1.1.tar.gz
| Download URL | drf_idem-0.1.1.tar.gz |
|---|---|
| Size | 12.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
2b1866da75bac2c8c078e2924e5f7043c92abecc97c487933cdb95e87d812264
|
|
BLAKE2b-256 checksum How to use checksums |
d5eca962d050503fcc82330ec4f0ac6b1c24ca45ab6b889df02d0e7ffad42e0e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Apr 25, 2026.
Transparency logRelease files / drf_idem-0.1.1-py3-none-any.whl
| Download URL | drf_idem-0.1.1-py3-none-any.whl |
|---|---|
| Size | 10.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
6528298e322e9be52f65a003f549954cd6ecbf17f0002de9b7598d3a93bd6987
|
|
BLAKE2b-256 checksum How to use checksums |
a9977a11b227671f4f27dd8b6eabe8f3374450e9d165d66cff6cff24bbca0702
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Apr 25, 2026.
Transparency log