python 90 lines · 3 tabs

Custom DRF Throttle Class for a Public API With Sliding-Window Rate Limits

Shared by codesnips Jul 2026
3 tabs
CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379/1",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
        },
    }
}

REST_FRAMEWORK = {
    "DEFAULT_THROTTLE_RATES": {
        "public": "60/min",
    },
    "EXCEPTION_HANDLER": "rest_framework.views.exception_handler",
}
3 files · python Explain with highlit

This snippet shows how a public Django REST Framework endpoint is protected from abuse with a custom sliding-window throttle backed by Redis. Django's built-in SimpleRateThrottle uses a fixed-window algorithm that stores a list of timestamps per key in the cache; the problem is that fixed windows allow bursts at window boundaries (a client can fire the full quota at the end of one window and again at the start of the next). The SlidingWindowThrottle in throttling.py fixes that by keeping every request timestamp in a sorted set and counting only the timestamps inside a rolling duration, giving a smoother, harder-to-game limit.

In throttling.py, SlidingWindowThrottle subclasses DRF's BaseThrottle rather than SimpleRateThrottle because the counting logic is bespoke. The get_cache_key method identifies the caller: authenticated users key on pk, anonymous callers key on the client IP resolved via get_ident, so a shared limit is applied per-IP for the public case. allow_request executes a small Redis pipeline: it removes timestamps older than the window with zremrangebyscore, adds the current request with a unique member, reads the cardinality with zcard, and refreshes the key TTL with expire. Doing all four in one pipeline makes the read-modify-write effectively atomic, which matters under concurrent traffic where two requests could otherwise both pass a stale count.

When the count exceeds num_requests, the pre-inserted member is rolled back with zrem so a rejected request does not consume a slot, and allow_request returns False. The wait method computes how long until the oldest in-window request expires, which DRF surfaces as the Retry-After header.

In views.py, PublicQuoteView simply lists the throttle in throttle_classes; DRF calls allow_request before the handler runs and raises Throttled automatically on rejection. settings.py wires up the Redis-backed cache and a default rate string parsed by parse_rate. The main trade-off is memory and Redis round-trips: a sorted set per client is heavier than a single counter, so the sliding window suits endpoints where fairness matters more than raw throughput. Keeping the TTL slightly longer than duration ensures idle keys are reaped without dropping timestamps that are still relevant.


Related snips

Share this code

Here's the card — post it anywhere.

Custom DRF Throttle Class for a Public API With Sliding-Window Rate Limits — share card
Link copied