API Reference¶
Generated in part from source docstrings via mkdocstrings.
Throttles¶
rate_limit¶
Build a :class:RateLimitThrottle subclass for throttle_classes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rate
|
RateSpec
|
See :attr: |
'60/m'
|
algorithm
|
str
|
One of |
'token_bucket'
|
key
|
str | KeyFunc
|
See :attr: |
'ip'
|
cost
|
int | Callable[[Any], int]
|
See :attr: |
1
|
burst
|
int | None
|
See :attr: |
None
|
scope
|
str | None
|
See :attr: |
None
|
tier_resolver
|
TierResolver
|
See :attr: |
default_tier_resolver
|
client
|
Any
|
See :attr: |
None
|
Returns:
| Type | Description |
|---|---|
type[RateLimitThrottle]
|
A new :class: |
type[RateLimitThrottle]
|
|
Raises:
| Type | Description |
|---|---|
InvalidAlgorithmError
|
If
|
Example
.. code-block:: python
class ArticleViewSet(viewsets.ModelViewSet):
throttle_classes = [
rate_limit(rate="100/m", algorithm="token_bucket", burst=20, key="user"),
]
Source code in src/drf_ratelimit_plus/throttles.py
155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 | |
RateLimitThrottle¶
Bases: BaseThrottle
A configurable rate-limiting throttle.
Don't instantiate or subclass this directly for one-off configuration
— use :func:rate_limit instead, which builds a properly configured
subclass. Subclass this directly only if you want a reusable named
throttle class (the same pattern DRF's own UserRateThrottle uses).
Attributes:
| Name | Type | Description |
|---|---|---|
algorithm |
str
|
One of |
rate |
RateSpec
|
A rate string ( |
key |
str | KeyFunc
|
A built-in key function name ( |
cost |
int | Callable[[Any], int]
|
How many units a single request consumes — an int, or a callable taking the request and returning one, for per-request weighted costs. |
burst |
int | None
|
Token-bucket capacity (ignored by the other algorithms).
Defaults to the rate's count when |
scope |
str | None
|
An explicit scope string grouping requests into the same limit regardless of which view handles them. Defaults to the handling view's dotted class path (i.e. each view gets an independent limit by default). |
tier_resolver |
TierResolver
|
Determines which tier name applies when |
client |
Any
|
An explicit Redis client to use instead of the one
resolved from settings (see
:func: |
Note
If you subclass this directly (rather than using
:func:rate_limit) and want rate, key, or cost to be
a plain function rather than a string/int, wrap it in
staticmethod(...) explicitly — otherwise Python's normal
attribute lookup binds it as an instance method (implicitly
passing self as the first argument), which isn't what these
callables expect. :func:rate_limit handles this wrapping for
you automatically.
Source code in src/drf_ratelimit_plus/throttles.py
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 | |
allow_request(request, view)
¶
See :meth:rest_framework.throttling.BaseThrottle.allow_request.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
Any
|
The incoming request. |
required |
view
|
Any
|
The view handling it. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
Whether the request is within its configured limit. The full |
bool
|
class: |
bool
|
|
bool
|
class: |
Source code in src/drf_ratelimit_plus/throttles.py
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 | |
wait()
¶
See :meth:rest_framework.throttling.BaseThrottle.wait.
Returns:
| Type | Description |
|---|---|
float | None
|
Seconds the client should wait before retrying, or |
float | None
|
if the last check succeeded (or hasn't run yet). |
Source code in src/drf_ratelimit_plus/throttles.py
128 129 130 131 132 133 134 135 136 137 | |
Decorator¶
ratelimit¶
Apply rate limiting to a single function-based view.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rate
|
RateSpec
|
See :attr: |
'60/m'
|
algorithm
|
str
|
See :attr: |
'token_bucket'
|
key
|
str | KeyFunc
|
See :attr: |
'ip'
|
cost
|
int | Callable[[Any], int]
|
See :attr: |
1
|
burst
|
int | None
|
See :attr: |
None
|
scope
|
str | None
|
See :attr: |
None
|
tier_resolver
|
TierResolver
|
See :attr: |
default_tier_resolver
|
client
|
Any
|
See :attr: |
None
|
Returns:
| Type | Description |
|---|---|
Callable[[_F], _F]
|
A decorator that enforces the configured limit, raising |
Callable[[_F], _F]
|
class: |
Callable[[_F], _F]
|
to |
Callable[[_F], _F]
|
adding |
Example
.. code-block:: python
from rest_framework.decorators import api_view
from drf_ratelimit_plus import ratelimit
@api_view(["POST"])
@ratelimit(rate="10/m", key="user")
def expensive_action(request):
...
Source code in src/drf_ratelimit_plus/decorators.py
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 | |
Headers¶
RateLimitHeadersMixin¶
Adds RateLimit-* headers to every response, based on the
:class:~drf_ratelimit_plus.throttles.RateLimitThrottle instances
checked for the current request.
Mix into any APIView/GenericAPIView/viewset that uses
:func:~drf_ratelimit_plus.throttles.rate_limit in its
throttle_classes:
.. code-block:: python
class ArticleViewSet(RateLimitHeadersMixin, viewsets.ModelViewSet):
throttle_classes = [rate_limit(rate="100/m")]
Must come before the DRF base class in the MRO, same as any other DRF mixin.
Source code in src/drf_ratelimit_plus/mixins.py
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | |
finalize_response(request, response, *args, **kwargs)
¶
Add RateLimit-* headers before returning the response.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
Any
|
The current request. |
required |
response
|
Any
|
The response produced by the view (or by DRF's exception handling, for a throttled request). |
required |
*args
|
Any
|
Passed through to the next class in the MRO. |
()
|
**kwargs
|
Any
|
Passed through to the next class in the MRO. |
{}
|
Returns:
| Type | Description |
|---|---|
Any
|
The response, with |
Any
|
and |
Any
|
class: |
Any
|
produced a result for this request. If multiple such |
Any
|
throttles were checked, the one with the lowest remaining |
Any
|
count is reported (the one closest to being exceeded is the |
Any
|
most informative to the client). |
Source code in src/drf_ratelimit_plus/mixins.py
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | |
get_throttles()
¶
Return (and cache) this request's throttle instances.
DRF's own get_throttles() constructs a fresh instance from
throttle_classes on every call — calling it more than once
per request would mean check_throttles() and
finalize_response() each see different throttle instances,
so the last_result this mixin reads for headers would never
match what actually decided the request's outcome. Caching on
self fixes this: a view instance handles exactly one request
in DRF, so caching per-instance is exactly "per request".
Returns:
| Type | Description |
|---|---|
list[Any]
|
The same list of throttle instances for every call during |
list[Any]
|
this request. |
Source code in src/drf_ratelimit_plus/mixins.py
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | |
Keys¶
by_ip¶
Key by the client's IP address.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
Any
|
The incoming request. |
required |
view
|
Any
|
Unused; accepted for calling-convention consistency. |
None
|
Returns:
| Type | Description |
|---|---|
str
|
|
str
|
in a proxied setup), otherwise |
Source code in src/drf_ratelimit_plus/keys.py
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | |
by_user¶
Key by the authenticated user, falling back to IP for anonymous requests.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
Any
|
The incoming request. |
required |
view
|
Any
|
Unused; accepted for calling-convention consistency. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
str
|
|
|
value |
str
|
func: |
Source code in src/drf_ratelimit_plus/keys.py
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | |
by_api_key¶
Key by an API key header, falling back to IP if absent.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
Any
|
The incoming request. |
required |
view
|
Any
|
Unused; accepted for calling-convention consistency. |
None
|
Returns:
| Type | Description |
|---|---|
str
|
|
str
|
|
str
|
produce if the header is missing. |
Source code in src/drf_ratelimit_plus/keys.py
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | |
by_tenant¶
Key by a tenant identifier attached to the request, falling back to IP.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
Any
|
The incoming request. |
required |
view
|
Any
|
Unused; accepted for calling-convention consistency. |
None
|
Returns:
| Type | Description |
|---|---|
str
|
|
str
|
|
str
|
produce if that attribute is absent or |
Source code in src/drf_ratelimit_plus/keys.py
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 | |
by_view¶
Key by the view/endpoint handling the request.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
Any
|
The incoming request. |
required |
view
|
Any
|
The view instance handling it, if available. |
None
|
Returns:
| Type | Description |
|---|---|
str
|
A dotted path identifying the view's class, or the request path |
str
|
if no view instance is available (e.g. a function-based view |
str
|
calling this directly). |
Source code in src/drf_ratelimit_plus/keys.py
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 | |
combine¶
Combine multiple key functions into one, joined by |.
Useful for compound scoping, e.g. per-tenant-per-user:
combine("tenant", "user").
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*key_funcs
|
str | KeyFunc
|
Key specifications (names or callables), applied in
order and joined with |
()
|
Returns:
| Type | Description |
|---|---|
KeyFunc
|
A single key function producing the joined result. |
Example
from unittest.mock import Mock request = Mock(user=Mock(is_authenticated=True, pk=42), META={}, headers={}) request.tenant_id = "acme" combine("tenant", "user")(request) 'tenant:acme|user:42'
Source code in src/drf_ratelimit_plus/keys.py
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 | |
resolve_key_func¶
Resolve a key specification to a callable.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str | KeyFunc
|
Either a callable already matching the |
required |
Returns:
| Type | Description |
|---|---|
KeyFunc
|
The resolved key function. |
Raises:
| Type | Description |
|---|---|
InvalidKeyError
|
If |
Source code in src/drf_ratelimit_plus/keys.py
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 | |
Rates¶
Rate¶
A parsed rate limit: count requests per period seconds.
Attributes:
| Name | Type | Description |
|---|---|---|
count |
int
|
The maximum number of requests (or weighted cost units)
allowed per |
period |
float
|
The window length in seconds. |
Source code in src/drf_ratelimit_plus/rates.py
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | |
per_second
property
¶
The equivalent steady-state rate, in requests per second.
Used by the token bucket algorithm as its refill rate.
parse_rate¶
Parse a "<count>/<period>" string into a :class:Rate.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rate
|
str | Rate
|
Either an already-parsed :class: |
required |
Returns:
| Type | Description |
|---|---|
Rate
|
The parsed :class: |
Raises:
| Type | Description |
|---|---|
InvalidRateError
|
If |
Example
parse_rate("100/m") Rate(count=100, period=60.0) parse_rate("10/s").per_second 10.0
Source code in src/drf_ratelimit_plus/rates.py
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 | |
Tiers¶
default_tier_resolver¶
The built-in tier resolver: reads request.user.plan.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
Any
|
The incoming request. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The value of |
str
|
authenticated user with a |
str
|
|
Example
Attach a plan attribute (a string) to your user model, or
override this entirely by passing your own tier_resolver
callable — e.g. one that reads a related Subscription model.
Source code in src/drf_ratelimit_plus/tiers.py
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | |
resolve_tier_rate¶
Resolve which :class:~drf_ratelimit_plus.rates.Rate applies to a request.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
Any
|
The incoming request. |
required |
tiers
|
Mapping[str, str | Rate]
|
A mapping of tier name to rate string (or already-parsed
:class: |
required |
resolver
|
TierResolver
|
Determines which tier name applies to |
default_tier_resolver
|
Returns:
| Type | Description |
|---|---|
Rate
|
The resolved :class: |
Raises:
| Type | Description |
|---|---|
UnknownTierError
|
If the resolved
tier name has no entry in |
Source code in src/drf_ratelimit_plus/tiers.py
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | |
Algorithms¶
fixed_window_check¶
Check a request against a fixed-window rate limit.
Counts requests in discrete, non-overlapping windows of rate.period
seconds. Simple and cheap, but allows up to 2 * rate.count requests
across a window boundary (e.g. a burst just before a window ends,
followed immediately by another just after it starts). Prefer
:func:sliding_window_check if that boundary burst is a concern.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
client
|
Any
|
A Redis client (standalone or Cluster). |
required |
key
|
str
|
The fully-qualified Redis key for this rate limit identity. |
required |
rate
|
Rate
|
The configured limit. |
required |
cost
|
int
|
How many units this request consumes (for weighted costs). |
1
|
now
|
float | None
|
Unused; accepted for calling-convention symmetry with the other algorithms. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
The |
LimitResult
|
class: |
Source code in src/drf_ratelimit_plus/algorithms.py
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | |
sliding_window_check¶
Check a request against an approximate sliding-window rate limit.
Estimates the request count over the trailing rate.period seconds
as a weighted blend of the current fixed window's count and the
previous window's count, weighted by how far into the current window
"now" is. This is the same approximation Cloudflare's public rate
limiter documentation describes: memory-efficient (two counters per
identity, not one entry per request) while avoiding fixed-window's
boundary-burst problem.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
client
|
Any
|
A Redis client (standalone or Cluster). |
required |
key
|
str
|
The fully-qualified Redis key for this rate limit identity.
Both the current and previous window's actual keys are
derived from this with a |
required |
rate
|
Rate
|
The configured limit. |
required |
cost
|
int
|
How many units this request consumes. |
1
|
now
|
float | None
|
The current time, as seconds since the epoch. Defaults to
:func: |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
The |
LimitResult
|
class: |
LimitResult
|
and |
|
LimitResult
|
count, consistent with this algorithm's approximate nature. |
Source code in src/drf_ratelimit_plus/algorithms.py
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 | |
token_bucket_check¶
Check a request against a token-bucket rate limit.
The bucket refills continuously at rate.count / rate.period tokens
per second, up to a capacity of burst (defaulting to rate.count
if not given — i.e. no extra burst allowance beyond the steady-state
rate). This is the algorithm to reach for when you want to allow a
client to burst above their steady-state rate occasionally, as long as
they've been under it recently (each unused token accumulates, up to
the bucket's capacity).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
client
|
Any
|
A Redis client (standalone or Cluster). |
required |
key
|
str
|
The fully-qualified Redis key for this rate limit identity —
a single Redis Hash storing |
required |
rate
|
Rate
|
The configured steady-state limit. |
required |
cost
|
int
|
How many tokens this request consumes. |
1
|
burst
|
int | None
|
The bucket's capacity, i.e. the maximum number of tokens
that can accumulate for later bursting. Defaults to
|
None
|
now
|
float | None
|
The current time, as seconds since the epoch. Defaults to
:func: |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
The |
LimitResult
|
class: |
LimitResult
|
reported as the bucket's capacity ( |
|
LimitResult
|
maximum a single burst can consume. |
Source code in src/drf_ratelimit_plus/algorithms.py
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 | |
Results¶
LimitResult¶
The outcome of checking one request against a rate limit.
Attributes:
| Name | Type | Description |
|---|---|---|
allowed |
bool
|
Whether the request is within its limit. |
limit |
int
|
The configured maximum (requests, or weighted cost units, per window/bucket capacity). |
remaining |
int
|
How much of the limit is left after this check (never negative). |
reset_seconds |
int
|
Seconds until the limit fully resets (the window
closes, or the bucket refills to capacity). Always present,
regardless of |
retry_after |
int | None
|
Seconds the caller should wait before retrying.
|
Source code in src/drf_ratelimit_plus/results.py
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | |
Client¶
get_redis_client¶
Return the configured Redis client, constructing it on first use.
Resolution order:
- If the
REDIS_CLIENTsetting is set, import it — it may be a dotted path to a client instance, or to a no-argument callable returning one. - Otherwise, build a new :class:
redis.Redisfrom theREDIS_URLsetting.
The result is cached for the lifetime of the process (invalidated
automatically if RATELIMIT_PLUS changes, e.g. via
@override_settings in tests).
Returns:
| Type | Description |
|---|---|
Any
|
A Redis client (standalone or Cluster) implementing at least |
Any
|
|
Source code in src/drf_ratelimit_plus/client.py
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | |
set_redis_client¶
Override the cached client directly (primarily for tests).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
client
|
Any | None
|
The client to use for subsequent calls to
:func: |
required |
Source code in src/drf_ratelimit_plus/client.py
53 54 55 56 57 58 59 60 61 62 | |
Exceptions¶
RateLimitPlusError¶
Bases: Exception
Base class for all configuration errors raised by this package.
Source code in src/drf_ratelimit_plus/exceptions.py
13 14 | |
InvalidRateError¶
Bases: RateLimitPlusError, ValueError
Raised when a rate string cannot be parsed.
See :func:drf_ratelimit_plus.rates.parse_rate.
Source code in src/drf_ratelimit_plus/exceptions.py
17 18 19 20 21 | |
InvalidKeyError¶
Bases: RateLimitPlusError, ValueError
Raised when a key argument names an unknown built-in key function.
See :func:drf_ratelimit_plus.keys.resolve_key_func.
Source code in src/drf_ratelimit_plus/exceptions.py
24 25 26 27 28 | |
InvalidAlgorithmError¶
Bases: RateLimitPlusError, ValueError
Raised when an algorithm argument names an unknown algorithm.
See :func:drf_ratelimit_plus.throttles.rate_limit.
Source code in src/drf_ratelimit_plus/exceptions.py
31 32 33 34 35 | |
UnknownTierError¶
Bases: RateLimitPlusError, ValueError
Raised when a resolved plan-tier name has no matching entry.
See :mod:drf_ratelimit_plus.tiers.
Source code in src/drf_ratelimit_plus/exceptions.py
38 39 40 41 42 | |
Settings¶
See Settings for the full list of recognized
RATELIMIT_PLUS keys.