Flask-Limiter

raw JSON →
4.1.1 verified Tue May 12 auth: no python install: verified

Flask-Limiter is an active Python extension that adds rate limiting capabilities to Flask applications, preventing abuse and ensuring stability. It allows configuration of limits at various levels (application-wide, per Blueprint, per route) and supports multiple storage backends like Redis, Memcached, MongoDB, and Valkey. The current version is 4.1.1, with a regular release cadence.

pip install Flask-Limiter
error TypeError: Limiter.__init__() got multiple values for argument 'key_func'
cause This error occurs when the 'app' instance is passed as a positional argument after 'key_func' during Limiter initialization, causing Python to interpret 'app' as a second value for 'key_func' because 'key_func' is the only positional argument.
fix
Pass the 'app' instance using the keyword argument app=app during Limiter initialization. For example: limiter = Limiter(key_func=get_remote_address, app=app, default_limits=['200 per day']).
error ModuleNotFoundError: No module named 'flask_limiter.wrappers'
cause The 'flask_limiter.wrappers' module was removed as a breaking change in Flask-Limiter version 3.13, causing applications that directly import or rely on this module (especially older versions of dependent libraries like Flask-AppBuilder or Apache Superset) to fail.
fix
Pin your 'flask-limiter' dependency to a version prior to 3.13 (e.g., flask-limiter==3.12.1) or update any dependent libraries to versions compatible with newer 'flask-limiter' releases.
error flask-limiter redis not working
cause This issue, or similar 'time out' errors, typically arises when the specified storage backend (like Redis, Memcached, or MongoDB) is not running, is misconfigured (e.g., incorrect `storage_uri`), or the necessary Python client library for that backend has not been installed as an extra dependency.
fix
Ensure the chosen storage backend service is running and accessible, verify the storage_uri is correctly formatted (e.g., redis://localhost:6379), and install the required extra dependencies for your backend (e.g., pip install Flask-Limiter[redis]).
error AttributeError: module 'configparser' has no attribute 'SafeConfigParser'
cause This error occurs because 'SafeConfigParser' was deprecated and removed in Python 3.10 and later, and an older version of 'flask-limiter' or its underlying 'limits' library attempts to use it.
fix
Upgrade 'flask-limiter' and its 'limits' dependency to versions compatible with Python 3.10+ (typically flask-limiter>=2.0.0 and limits>=2.0.0).
breaking Version 4.0.0 introduced significant breaking changes in module structure and limit definition. All internal submodules are now prefixed with an underscore, and direct imports from them (e.g., `from flask_limiter.limits import Limit`) are deprecated. Instead, import classes like `Limit`, `RouteLimit`, `ApplicationLimit`, and `MetaLimit` directly from the root `flask_limiter` namespace.
fix Update imports to use `from flask_limiter import ClassName` (e.g., `from flask_limiter import Limit`) and adjust how limits are configured, leveraging the new limit description classes.
breaking Version 3.0.0 changed the `Limiter` constructor arguments. `key_func` is now a mandatory positional argument, and all other arguments must be passed as keyword arguments. The `RATELIMIT_STORAGE_URL` configuration variable was removed, and legacy Flask < 2 compatibility was dropped.
fix Ensure `key_func` is the first argument in `Limiter()` and all subsequent arguments are explicitly named (e.g., `Limiter(get_remote_address, app=app, storage_uri='...')`). Replace `RATELIMIT_STORAGE_URL` with `storage_uri` or `RATELIMIT_STORAGE_URI` in Flask config.
gotcha Using in-memory storage (`memory://`) in production with multiple worker processes will lead to inaccurate and unreliable rate limiting. Each worker will maintain its own independent limit state, making global rate limits ineffective.
fix Always configure a persistent storage backend (e.g., Redis, Memcached, MongoDB, Valkey) for production deployments by setting `storage_uri` or the `RATELIMIT_STORAGE_URI` Flask config variable.
deprecated The 3.13 release was yanked from PyPI due to compatibility issues with Flask-AppBuilder and Airflow. Users who installed this specific version might encounter unexpected behavior or errors.
fix Avoid using version 3.13. If you are on 3.13, downgrade to a stable 3.x release (e.g., 3.12 or earlier) or upgrade to version 4.0.0 or later.
breaking Flask-Limiter frequently adjusts its supported Python versions. For example, Python 3.9 support was dropped in v3.12, and Python 3.8 support was dropped in v3.9.0. Currently, Python >=3.10 is required.
fix Always check the `requires_python` metadata (or `py_modules` in the PyPI classifiers) for the specific Flask-Limiter version you intend to use and ensure your Python environment meets the requirements.
gotcha When deploying behind a proxy (e.g., Nginx, Gunicorn), `get_remote_address` might return the proxy's IP address instead of the client's. This can lead to all requests being limited by the proxy's IP, effectively acting as a single global limit for all users.
fix Properly configure your proxy to forward the client's IP in a header (e.g., `X-Forwarded-For`) and configure Flask-Limiter to use this header, potentially with a custom `key_func` or by configuring `RATELIMIT_HEADERS_ENABLED` and `RATELIMIT_HEADER_ID`.
pip install Flask-Limiter[redis]
pip install Flask-Limiter[memcached]
pip install Flask-Limiter[mongodb]
pip install Flask-Limiter[valkey]
pip install Flask-Limiter[cli]
python os / libc variant status wheel install import disk
3.10 alpine (musl) Flask-Limiter wheel - 0.60s 25.1M
3.10 alpine (musl) Flask-Limiter - - 0.58s 24.9M
3.10 alpine (musl) cli wheel - 0.65s 37.3M
3.10 alpine (musl) cli - - 0.58s 37.1M
3.10 alpine (musl) memcached wheel - 0.62s 25.7M
3.10 alpine (musl) memcached - - 0.58s 25.5M
3.10 alpine (musl) mongodb sdist - 0.56s 33.1M
3.10 alpine (musl) mongodb - - 0.57s 32.9M
3.10 alpine (musl) redis wheel - 0.64s 28.9M
3.10 alpine (musl) redis - - 0.61s 28.7M
3.10 alpine (musl) valkey wheel - 0.62s 27.4M
3.10 alpine (musl) valkey - - 0.58s 27.3M
3.10 slim (glibc) Flask-Limiter wheel 2.9s 0.46s 26M
3.10 slim (glibc) Flask-Limiter - - 0.45s 25M
3.10 slim (glibc) cli wheel 3.9s 0.47s 38M
3.10 slim (glibc) cli - - 0.44s 38M
3.10 slim (glibc) memcached wheel 3.0s 0.44s 26M
3.10 slim (glibc) memcached - - 0.44s 26M
3.10 slim (glibc) mongodb wheel 4.1s 0.44s 35M
3.10 slim (glibc) mongodb - - 0.45s 35M
3.10 slim (glibc) redis wheel 3.2s 0.45s 29M
3.10 slim (glibc) redis - - 0.45s 29M
3.10 slim (glibc) valkey wheel 3.1s 0.44s 28M
3.10 slim (glibc) valkey - - 0.46s 28M
3.11 alpine (musl) Flask-Limiter wheel - 0.77s 28.1M
3.11 alpine (musl) Flask-Limiter - - 0.87s 27.9M
3.11 alpine (musl) cli wheel - 0.74s 41.5M
3.11 alpine (musl) cli - - 0.85s 41.3M
3.11 alpine (musl) memcached wheel - 0.76s 28.9M
3.11 alpine (musl) memcached - - 0.87s 28.6M
3.11 alpine (musl) mongodb sdist - 0.74s 37.7M
3.11 alpine (musl) mongodb - - 0.87s 37.3M
3.11 alpine (musl) redis wheel - 0.75s 32.7M
3.11 alpine (musl) redis - - 0.87s 32.4M
3.11 alpine (musl) valkey wheel - 0.74s 30.9M
3.11 alpine (musl) valkey - - 0.84s 30.7M
3.11 slim (glibc) Flask-Limiter wheel 2.9s 0.68s 29M
3.11 slim (glibc) Flask-Limiter - - 0.67s 28M
3.11 slim (glibc) cli wheel 4.0s 0.70s 42M
3.11 slim (glibc) cli - - 0.66s 42M
3.11 slim (glibc) memcached wheel 3.0s 0.69s 29M
3.11 slim (glibc) memcached - - 0.65s 29M
3.11 slim (glibc) mongodb wheel 3.8s 0.68s 41M
3.11 slim (glibc) mongodb - - 0.65s 40M
3.11 slim (glibc) redis wheel 3.2s 0.68s 33M
3.11 slim (glibc) redis - - 0.64s 33M
3.11 slim (glibc) valkey wheel 3.1s 0.74s 31M
3.11 slim (glibc) valkey - - 0.66s 31M
3.12 alpine (musl) Flask-Limiter wheel - 0.92s 19.8M
3.12 alpine (musl) Flask-Limiter - - 0.99s 19.5M
3.12 alpine (musl) cli wheel - 0.94s 33.0M
3.12 alpine (musl) cli - - 0.97s 32.7M
3.12 alpine (musl) memcached wheel - 0.89s 20.4M
3.12 alpine (musl) memcached - - 1.01s 20.2M
3.12 alpine (musl) mongodb sdist - 1.02s 29.1M
3.12 alpine (musl) mongodb - - 0.98s 28.8M
3.12 alpine (musl) redis wheel - 0.93s 24.1M
3.12 alpine (musl) redis - - 1.00s 23.9M
3.12 alpine (musl) valkey wheel - 1.00s 22.4M
3.12 alpine (musl) valkey - - 0.99s 22.2M
3.12 slim (glibc) Flask-Limiter wheel 2.6s 0.92s 20M
3.12 slim (glibc) Flask-Limiter - - 0.93s 20M
3.12 slim (glibc) cli wheel 3.5s 0.94s 34M
3.12 slim (glibc) cli - - 0.91s 33M
3.12 slim (glibc) memcached wheel 2.6s 0.91s 21M
3.12 slim (glibc) memcached - - 0.96s 21M
3.12 slim (glibc) mongodb wheel 3.3s 0.93s 33M
3.12 slim (glibc) mongodb - - 0.90s 32M
3.12 slim (glibc) redis wheel 2.9s 0.90s 25M
3.12 slim (glibc) redis - - 0.96s 24M
3.12 slim (glibc) valkey wheel 2.7s 0.92s 23M
3.12 slim (glibc) valkey - - 1.02s 23M
3.13 alpine (musl) Flask-Limiter wheel - 0.91s 19.5M
3.13 alpine (musl) Flask-Limiter - - 0.95s 19.2M
3.13 alpine (musl) cli wheel - 0.92s 32.7M
3.13 alpine (musl) cli - - 0.97s 32.4M
3.13 alpine (musl) memcached wheel - 0.90s 20.2M
3.13 alpine (musl) memcached - - 0.99s 19.8M
3.13 alpine (musl) mongodb sdist - 0.96s 28.8M
3.13 alpine (musl) mongodb - - 0.95s 28.4M
3.13 alpine (musl) redis wheel - 0.93s 23.8M
3.13 alpine (musl) redis - - 1.01s 23.5M
3.13 alpine (musl) valkey wheel - 0.90s 22.1M
3.13 alpine (musl) valkey - - 0.94s 21.8M
3.13 slim (glibc) Flask-Limiter wheel 2.7s 0.87s 20M
3.13 slim (glibc) Flask-Limiter - - 0.97s 20M
3.13 slim (glibc) cli wheel 3.5s 0.87s 33M
3.13 slim (glibc) cli - - 0.95s 33M
3.13 slim (glibc) memcached wheel 2.7s 0.87s 21M
3.13 slim (glibc) memcached - - 0.96s 20M
3.13 slim (glibc) mongodb wheel 3.5s 0.86s 33M
3.13 slim (glibc) mongodb - - 0.93s 33M
3.13 slim (glibc) redis wheel 2.9s 0.85s 24M
3.13 slim (glibc) redis - - 0.93s 24M
3.13 slim (glibc) valkey wheel 2.7s 0.88s 23M
3.13 slim (glibc) valkey - - 0.93s 22M
3.9 alpine (musl) Flask-Limiter wheel - 0.51s 35.9M
3.9 alpine (musl) Flask-Limiter - - 0.54s 35.9M
3.9 alpine (musl) cli wheel - 0.52s 35.9M
3.9 alpine (musl) cli - - 0.54s 35.9M
3.9 alpine (musl) memcached wheel - 0.54s 36.5M
3.9 alpine (musl) memcached - - 0.55s 36.5M
3.9 alpine (musl) mongodb sdist - 0.50s 43.8M
3.9 alpine (musl) mongodb - - 0.53s 43.7M
3.9 alpine (musl) redis wheel - 0.52s 38.8M
3.9 alpine (musl) redis - - 0.57s 38.7M
3.9 alpine (musl) valkey wheel - 0.53s 35.9M
3.9 alpine (musl) valkey - - 0.54s 35.9M
3.9 slim (glibc) Flask-Limiter wheel 4.8s 0.50s 36M
3.9 slim (glibc) Flask-Limiter - - 0.45s 36M
3.9 slim (glibc) cli wheel 4.8s 0.53s 36M
3.9 slim (glibc) cli - - 0.46s 36M
3.9 slim (glibc) memcached wheel 4.8s 0.47s 37M
3.9 slim (glibc) memcached - - 0.46s 37M
3.9 slim (glibc) mongodb wheel 6.0s 0.49s 45M
3.9 slim (glibc) mongodb - - 0.47s 45M
3.9 slim (glibc) redis wheel 5.2s 0.48s 39M
3.9 slim (glibc) redis - - 0.47s 39M
3.9 slim (glibc) valkey wheel 4.9s 0.49s 36M
3.9 slim (glibc) valkey - - 0.47s 36M

Initializes a Flask application with global and per-route rate limits. It uses `get_remote_address` as the default key function and specifies a default storage URI. Routes demonstrate application-wide limits, specific route limits, combined limits, and exemptions. An error handler for HTTP 429 is included for custom responses.

import os
from flask import Flask
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address

app = Flask(__name__)

# Configure storage_uri, using in-memory for example, or from an environment variable
# In-memory storage is for development/testing only and should not be used in production with multiple workers.
# For production, use backends like Redis: 'redis://localhost:6379'
storage_uri = os.environ.get('FLASK_RATELIMIT_STORAGE_URI', 'memory://')

limiter = Limiter(
    key_func=get_remote_address,
    app=app,
    default_limits=["200 per day", "50 per hour"],
    storage_uri=storage_uri,
    strategy="fixed-window" # Or 'moving-window', 'sliding-window-counter'
)

@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"

# Example error handler for rate limit exceeded (HTTP 429)
@app.errorhandler(429)
def ratelimit_handler(e):
    return f"Rate limit exceeded: {e.description}", 429

if __name__ == '__main__':
    app.run(debug=True)