Flask-Limiter¶
Flask-Limiter adds rate limiting to Flask
applications.
By adding the extension to your flask application, you can configure various
rate limits at different levels (e.g. application wide, per Blueprint
,
routes, resource etc).
Flask-Limiter can be configured to persist the rate limit state to many commonly used storage backends via the limits library.
Let’s get started!
Installation¶
Flask-Limiter can be installed via pip.
$ pip install Flask-Limiter
To include extra dependencies for a specific storage backend you can add the
specific backend name via the extras
notation. For example:
$ pip install Flask-Limiter[redis]
$ pip install Flask-Limiter[memcached]
$ pip install Flask-Limiter[mongodb]
Quick start¶
A very basic setup can be achieved as follows:
from flask import Flask
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
app = Flask(__name__)
limiter = Limiter(
get_remote_address,
app=app,
default_limits=["200 per day", "50 per hour"],
storage_uri="memory://",
)
@app.route("/slow")
@limiter.limit("1 per day")
def slow():
return ":("
@app.route("/medium")
@limiter.limit("1/second", override_defaults=False)
def medium():
return ":|"
@app.route("/fast")
def fast():
return ":)"
@app.route("/ping")
@limiter.exempt
def ping():
return "PONG"
The above Flask app will have the following rate limiting characteristics:
Use an in-memory storage provided by
limits.storage.MemoryStorage
.Note
This is only meant for testing/development and should be replaced with an appropriate storage of your choice before moving to production.
Rate limiting by the
remote_address
of the requestA default rate limit of 200 per day, and 50 per hour applied to all routes.
The
slow
route having an explicit rate limit decorator will bypass the default rate limit and only allow 1 request per day.The
medium
route inherits the default limits and adds on a decorated limit of 1 request per second.The
ping
route will be exempt from any default rate limits.Tip
The built in flask static files routes are also exempt from rate limits.
Every time a request exceeds the rate limit, the view function will not get called and instead a 429 http error will be raised.
The extension adds a limiter
subcommand to the Flask CLI which can be used to inspect
the effective configuration and applied rate limits (See Command Line Interface for more details).
Given the quick start example above:
$ flask limiter config
Flask-Limiter Config
┏━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Notes ┃ Configuration ┃ Value ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ Enabled │ RATELIMIT_ENABLED │ True │
│ Key Function │ RATELIMIT_KEY_FUNC │ flask_limiter.util.get… │
│ Key Prefix │ RATELIMIT_KEY_PREFIX │ '' │
│ Rate Limiting Config │ RATELIMIT_STRATEGY │ FixedWindowRateLimiter │
│ │ ├── RATELIMIT_STORAGE_U… │ └── memory:// │
│ │ │ ├── Instance │ ├── MemoryStorage │
│ │ │ └── Backend │ ├── Counter() │
│ │ ├── RATELIMIT_STORAGE_O… │ ├── {} │
│ │ └── Status │ └── OK │
│ ApplicationLimits │ RATELIMIT_APPLICATION │ [] │
│ Limits │ │ │
│ Default Limits │ RATELIMIT_DEFAULT │ [ │
│ │ │ '200 per 1 day', │
│ │ │ '50 per 1 hour' │
│ │ │ ] │
│ │ RATELIMIT_DEFAULTS_PER_… │ False │
│ │ RATELIMIT_DEFAULTS_EXEM… │ None │
│ │ RATELIMIT_DEFAULTS_DEDU… │ None │
│ │ RATELIMIT_DEFAULTS_COST │ 1 │
│ Header configuration │ RATELIMIT_HEADERS_ENABL… │ False │
│ Fail on first breach │ RATELIMIT_FAIL_ON_FIRST… │ True │
│ On breach callback │ RATELIMIT_ON_BREACH_CAL… │ None │
└─────────────────────────┴──────────────────────────┴─────────────────────────┘
$ flask limiter limits
sample
├── fast: /fast
│ ├── 200 per 1 day
│ └── 50 per 1 hour
├── medium: /medium
│ ├── 200 per 1 day
│ ├── 50 per 1 hour
│ └── 1 per 1 second
├── ping: /ping
│ └── Exempt
└── slow: /slow
└── 1 per 1 day
The Flask-Limiter extension¶
The extension can be initialized with the flask.Flask
application
in the usual ways.
Using the constructor
from flask_limiter import Limiter from flask_limiter.util import get_remote_address .... limiter = Limiter(get_remote_address, app=app)
Deferred app initialization using init_app()
limiter = Limiter(get_remote_address) limiter.init_app(app)
At this point it might be a good idea to look at the configuration options
available in the extension in the Using Flask Config section and the
flask_limiter.Limiter
class documentation.
Configuring a storage backend¶
The extension can be configured to use any storage supported by limits. Here are a few common examples:
Any additional parameters provided in storage_options
will be passed to the constructor of the memcached client
(either PooledClient
or HashClient
).
For more details see MemcachedStorage
.
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
....
limiter = Limiter(
get_remote_address,
app=app,
storage_uri="memcached://localhost:11211",
storage_options={}
)
Any additional parameters provided in storage_options
will be passed to redis.Redis.from_url()
as keyword arguments.
For more details see RedisStorage
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
....
limiter = Limiter(
get_remote_address,
app=app,
storage_uri="redis://localhost:6379",
storage_options={"socket_connect_timeout": 30},
strategy="fixed-window", # or "moving-window"
)
If you wish to reuse a redis.connection.ConnectionPool
instance
you can pass that in storage_option
import redis
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
....
pool = redis.connection.BlockingConnectionPool.from_url("redis://.....")
limiter = Limiter(
get_remote_address,
app=app,
storage_uri="redis://",
storage_options={"connection_pool": pool},
strategy="fixed-window", # or "moving-window"
)
Any additional parameters provided in storage_options
will be passed to RedisCluster
as keyword arguments.
For more details see RedisClusterStorage
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
....
limiter = Limiter(
get_remote_address,
app=app,
storage_uri="redis+cluster://localhost:7000,localhost:7001,localhost:7002",
storage_options={"socket_connect_timeout": 30},
strategy="fixed-window", # or "moving-window"
)
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
....
limiter = Limiter(
get_remote_address,
app=app,
storage_uri="mongodb://localhost:27017",
strategy="fixed-window", # or "moving-window"
)
The storage_uri
and storage_options
parameters
can also be provided by Using Flask Config variables. The different
configuration options for each storage can be found in the storage backend documentation for limits
as that is delegated to the limits library.
Rate Limit Domain¶
Each Limiter
instance must be initialized with a
key_func
that returns the bucket in which each request
is put into when evaluating whether it is within the rate limit or not.
For simple setups a utility function is provided:
get_remote_address()
which uses the
remote_addr
from flask.Request
.
Please refer to Deploying an application behind a proxy for an example.
Decorators to declare rate limits¶
Decorators made available as instance methods of the Limiter
instance to be used with the flask.Flask
application.
Route specific limits¶
- Limiter.limit(limit_value: str | Callable[[], str], *, key_func: Callable[[], str] | None = None, per_method: bool = False, methods: List[str] | None = None, error_message: str | None = None, exempt_when: Callable[[], bool] | None = None, override_defaults: bool = True, deduct_when: Callable[[Response], bool] | None = None, on_breach: Callable[[RequestLimit], Response | None] | None = None, cost: int | Callable[[], int] = 1, scope: str | Callable[[str], str] | None = None) LimitDecorator [source]
Decorator to be used for rate limiting individual routes or blueprints.
- Parameters:
limit_value¶ – rate limit string or a callable that returns a string. Rate limit string notation for more details.
key_func¶ – function/lambda to extract the unique identifier for the rate limit. defaults to remote address of the request.
per_method¶ – whether the limit is sub categorized into the http method of the request.
methods¶ – if specified, only the methods in this list will be rate limited (default:
None
).error_message¶ – string (or callable that returns one) to override the error message used in the response.
exempt_when¶ – function/lambda used to decide if the rate limit should skipped.
override_defaults¶ –
whether the decorated limit overrides the default limits (Default:
True
).Note
When used with a
Blueprint
the meaning of the parameter extends to any parents the blueprint instance is registered under. For more details see Nested Blueprintsdeduct_when¶ – a function that receives the current
flask.Response
object and returns True/False to decide if a deduction should be done from the rate limiton_breach¶ – a function that will be called when this limit is breached. If the function returns an instance of
flask.Response
that will be the response embedded into theRateLimitExceeded
exception raised.cost¶ – The cost of a hit or a function that takes no parameters and returns the cost as an integer (Default:
1
).scope¶ – a string or callable that returns a string for further categorizing the rate limiting scope. This scope is combined with the current endpoint of the request.
- Changes
Added in version 2.9.0: The returned object can also be used as a context manager
for rate limiting a code block inside a view. For example:
@app.route("/") def route(): try: with limiter.limit("10/second"): # something expensive except RateLimitExceeded: pass
There are a few ways of using the limit()
decorator
depending on your preference and use-case.
Single decorator¶
The limit string can be a single limit or a delimiter separated string
@app.route("....")
@limiter.limit("100/day;10/hour;1/minute")
def my_route()
...
Multiple decorators¶
The limit string can be a single limit or a delimiter separated string or a combination of both.
@app.route("....")
@limiter.limit("100/day")
@limiter.limit("10/hour")
@limiter.limit("1/minute")
def my_route():
...
Custom keying function¶
By default rate limits are applied based on the key function that the Limiter
instance
was initialized with. You can implement your own function to retrieve the key to rate limit by
when decorating individual routes. Take a look at Rate Limit Key Functions for some examples..
def my_key_func():
...
@app.route("...")
@limiter.limit("100/day", my_key_func)
def my_route():
...
Note
The key function is called from within a flask request context.
Dynamically loaded limit string(s)¶
There may be situations where the rate limits need to be retrieved from sources external to the code (database, remote api, etc…). This can be achieved by providing a callable to the decorator.
def rate_limit_from_config():
return current_app.config.get("CUSTOM_LIMIT", "10/s")
@app.route("...")
@limiter.limit(rate_limit_from_config)
def my_route():
...
Warning
The provided callable will be called for every request on the decorated route. For expensive retrievals, consider caching the response.
Note
The callable is called from within a flask request context during the before_request phase.
Exemption conditions¶
Each limit can be exempted when given conditions are fulfilled. These
conditions can be specified by supplying a callable as an
exempt_when
argument when defining the limit.
@app.route("/expensive")
@limiter.limit("100/day", exempt_when=lambda: current_user.is_admin)
def expensive_route():
...
Reusable limits¶
For scenarios where a rate limit should be shared by multiple routes (For example when you want to protect routes using the same resource with an umbrella rate limit).
- Limiter.shared_limit(limit_value: str | Callable[[], str], scope: str | Callable[[str], str], *, key_func: Callable[[], str] | None = None, per_method: bool = False, methods: List[str] | None = None, error_message: str | None = None, exempt_when: Callable[[], bool] | None = None, override_defaults: bool = True, deduct_when: Callable[[Response], bool] | None = None, on_breach: Callable[[RequestLimit], Response | None] | None = None, cost: int | Callable[[], int] = 1) LimitDecorator [source]
decorator to be applied to multiple routes sharing the same rate limit.
- Parameters:
limit_value¶ – rate limit string or a callable that returns a string. Rate limit string notation for more details.
scope¶ – a string or callable that returns a string for defining the rate limiting scope.
key_func¶ – function/lambda to extract the unique identifier for the rate limit. defaults to remote address of the request.
per_method¶ – whether the limit is sub categorized into the http method of the request.
methods¶ – if specified, only the methods in this list will be rate limited (default:
None
).error_message¶ – string (or callable that returns one) to override the error message used in the response.
exempt_when¶ (function) – function/lambda used to decide if the rate limit should skipped.
override_defaults¶ –
whether the decorated limit overrides the default limits. (default:
True
)Note
When used with a
Blueprint
the meaning of the parameter extends to any parents the blueprint instance is registered under. For more details see Nested Blueprintsdeduct_when¶ – a function that receives the current
flask.Response
object and returns True/False to decide if a deduction should be done from the rate limiton_breach¶ – a function that will be called when this limit is breached. If the function returns an instance of
flask.Response
that will be the response embedded into theRateLimitExceeded
exception raised.cost¶ – The cost of a hit or a function that takes no parameters and returns the cost as an integer (default:
1
).
Decorators for skipping rate limits¶
- Limiter.exempt(obj: Blueprint, *, flags: ExemptionScope = ExemptionScope.APPLICATION | ExemptionScope.DEFAULT | ExemptionScope.META) Blueprint [source]
- Limiter.exempt(obj: Callable[[...], R], *, flags: ExemptionScope = ExemptionScope.APPLICATION | ExemptionScope.DEFAULT | ExemptionScope.META) Callable[[...], R]
- Limiter.exempt(*, flags: ExemptionScope = ExemptionScope.APPLICATION | ExemptionScope.DEFAULT | ExemptionScope.META) Callable[[Callable[[P], R]], Callable[[P], R]] | Callable[[Blueprint], Blueprint]
Mark a view function or all views in a blueprint as exempt from rate limits.
- Parameters:
obj¶ – view function or blueprint to mark as exempt.
flags¶ – Controls the scope of the exemption. By default application wide limits, defaults configured on the extension and meta limits are opted out of. Additional flags can be used to control the behavior when
obj
is a Blueprint that is nested under another Blueprint or has other Blueprints nested under it (See Nested Blueprints)
The method can be used either as a decorator without any arguments (the default flags will apply and the route will be exempt from default and application limits:
@app.route("...") @limiter.exempt def route(...): ...
Specific exemption flags can be provided at decoration time:
@app.route("...") @limiter.exempt(flags=ExemptionScope.APPLICATION) def route(...): ...
If an entire blueprint (i.e. all routes under it) are to be exempted the method can be called with the blueprint as the first parameter and any additional flags:
bp = Blueprint(...) limiter.exempt(bp) limiter.exempt( bp, flags=ExemptionScope.DEFAULT|ExemptionScope.APPLICATION|ExemptionScope.ANCESTORS )
This decorator marks a function as a filter for requests that are going to be tested for rate limits. If any of the request filters return True
no
rate limiting will be performed for that request. This mechanism can be used to
create custom white lists.
- Limiter.request_filter(fn: Callable[[], bool]) Callable[[], bool] [source]
decorator to mark a function as a filter to be executed to check if the request is exempt from rate limiting.
- Parameters:
fn¶ – The function will be called before evaluating any rate limits to decide whether to perform rate limit or skip it.
@limiter.request_filter
def header_whitelist():
return request.headers.get("X-Internal", "") == "true"
@limiter.request_filter
def ip_whitelist():
return request.remote_addr == "127.0.0.1"
In the above example, any request that contains the header X-Internal: true
or originates from localhost will not be rate limited.
For more complex use cases, refer to the Recipes section.