Flask-Limiter#

https://img.shields.io/github/last-commit/alisaifee/flask-limiter?logo=github&style=for-the-badge&labelColor=#282828 https://img.shields.io/github/actions/workflow/status/alisaifee/flask-limiter/main.yml?logo=github&style=for-the-badge&labelColor=#282828 https://img.shields.io/codecov/c/github/alisaifee/flask-limiter?logo=codecov&style=for-the-badge&labelColor=#282828 https://img.shields.io/pypi/pyversions/flask-limiter?style=for-the-badge&logo=pypi

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 request

  • A 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 Blueprints

  • deduct_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 limit

  • on_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 the RateLimitExceeded 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
  • New 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 Blueprints

  • deduct_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 limit

  • on_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 the RateLimitExceeded exception raised.

  • cost – The cost of a hit or a function that takes no parameters and returns the cost as an integer (default: 1).

Named shared limit#

mysql_limit = limiter.shared_limit("100/hour", scope="mysql")

@app.route("..")
@mysql_limit
def r1():
    ...

@app.route("..")
@mysql_limit
def r2():
    ...

Dynamic shared limit#

When a callable is passed as scope, the return value of the function will be used as the scope. Note that the callable takes one argument: a string representing the request endpoint.

def host_scope(endpoint_name):
    return request.host
host_limit = limiter.shared_limit("100/hour", scope=host_scope)

@app.route("..")
@host_limit
def r1():
    ...

@app.route("..")
@host_limit
def r2():
    ...

Decorators for skipping rate limits#

Limiter.exempt(obj: Blueprint, *, flags: ExemptionScope = ExemptionScope.APPLICATION | ExemptionScope.DEFAULT) Blueprint[source]
Limiter.exempt(obj: Callable[[...], R], *, flags: ExemptionScope = ExemptionScope.APPLICATION | ExemptionScope.DEFAULT) Callable[[...], R]
Limiter.exempt(*, flags: ExemptionScope = ExemptionScope.APPLICATION | ExemptionScope.DEFAULT) 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 and defaults configured on the extension 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.