Flask-Limiter Documentation

repository·master·Indexed 22 days ago

https://github.com/alisaifee/flask-limiter

An extension for Flask applications providing flexible rate limiting capabilities. It supports global, per-route, and per-blueprint limits using storage backends such as Redis, Memcached, MongoDB, and Valkey. Key features include granular limit definition via Limit dataclasses, custom keying functions, shared limits, and a CLI for inspecting configuration and resetting limits.

Tokens
8.3K
Snippets
28
Records
44
Agent score
79%

What's inside Flask-Limiter

  1. Understand the Rate Limit Domain and key_func

    master

    Every Limiter instance requires a key_func. This function is responsible for returning a unique key (a 'bucket') for each request. All requests that return the same key from key_func will share the same rate limit pool.

    For most web applications, you want to rate limit by the client's IP address. The utility function flask_limiter.util.get_remote_address is provided for this purpose; it uses flask.Request.remote_addr to generate the key.

  2. Use the Fixed Window strategy

    master

    The fixed-window strategy is the most memory-efficient option because it uses a single counter per resource and rate limit.

    How it works: When the first request arrives, a window starts for a fixed duration. All requests within that window increment the counter. Once the window expires, the counter resets.

    When to use: Use this when memory efficiency is a priority and you can tolerate the 'burst' behavior at the edges of window boundaries.

    Configuration value: fixed-window

  3. Use the Sliding Window strategy

    master

    The sliding-window-counter strategy approximates the precision of a moving window while being more memory-efficient.

    How it works: It maintains two counters: a Current bucket (requests in the ongoing period) and a Previous bucket (requests in the immediately preceding period). It calculates a weighted sum of these counters based on how much time has elapsed in the current bucket.

    When to use: Use this as a middle ground between the memory efficiency of fixed-window and the precision of moving-window.

    Configuration value: sliding-window-counter

  4. Rate limit string notation

    master

    Rate limits are specified using a specific string format: [count] [per|/] [n (optional)] [second|minute|hour|day|month|year][s]. You can combine multiple limits using a delimiter (like a semicolon or comma).

    Warning: If strings provided to the @limiter.limit decorator are malformed, the route falls back to default limits and logs an ERROR. However, malformed default limit strings will raise an exception during application startup.

    # Examples of valid notation:
    10 per hour
    10 per 2 hours
    10/hour
    5/2 seconds;10/hour;100/day;2000 per year
    100/day, 500/7 days
  5. Control limit inheritance on nested Blueprints

    master

    You can fine-tune how limits are inherited or overridden in a hierarchy of Blueprints using two parameters:

    1. override_defaults in limiter.limit(): If set to False, the blueprint's limit will be added to the existing limits (e.g., application defaults and parent blueprint limits) rather than replacing them.
    2. flags in limiter.exempt(): Use ExemptionScope.ANCESTORS to ensure a blueprint is exempt from limits inherited from its parent Blueprints.

    Example Behavior:

    • limiter.limit("2/minute")(parent): Overrides application defaults for the parent.
    • limiter.limit("1/second", override_defaults=False)(child): Child respects both parent and application defaults.
    • limiter.exempt(health, flags=ExemptionScope.ANCESTORS): The health blueprint is exempt from all limits, including those inherited from its ancestors.
        limiter = Limiter(
            ...,
            default_limits = ["100/hour"],
            application_limits = ["100/minute"]
        )
    
        # ... blueprint hierarchy setup ...
    
        limiter.limit("2/minute")(parent)
        limiter.limit("1/second", override_defaults=False)(child)
        limiter.limit("10/minute")(grandchild)
    
        limiter.exempt(
            health,
            flags=ExemptionScope.DEFAULT|ExemptionScope.APPLICATION|ExemptionScope.ANCESTORS
        )
  6. Understand Rate-limiting Headers

    master

    When rate limiting is enabled, Flask-Limiter adds information about the current rate limit status to the response headers. If multiple rate limits are active for a route, the header will reflect the one with the lowest time granularity (provided the request hasn't breached any limits).

    Standard headers include:

    • X-RateLimit-Limit: Total requests allowed in the active window.
    • X-RateLimit-Remaining: Requests remaining in the active window.
    • X-RateLimit-Reset: UTC seconds since epoch when the window resets.
    • Retry-After: Seconds to retry or an HTTP date when the limit resets. The format depends on the RATELIMIT_HEADER_RETRY_AFTER_VALUE configuration (defaults to delta-seconds).
  7. Customize rate limit keys with `key_func`

    master

    You can customize rate limits to be based on any characteristic of the incoming request (e.g., user ID, IP address, or country) by providing a key_func argument. This argument accepts a callable that must return a string or an object with a string representation.

    key_func can be provided in:

    • The Limiter constructor (to set a global default key function).
    • The @limiter.limit decorator (to set a specific key function for a single route).
    # Example: Rate limiting by current user (Flask-Login)
    @route("/test")
    @login_required
    @limiter.limit("1 per day", key_func = lambda : current_user.username)
    def test_route():
        return "42"
    
    # Example: Rate limiting by country
    def get_request_country():
        return gi.record_by_name(request.remote_addr)['region_name']
    
    app = Flask(__name__)
    limiter = Limiter(get_request_country, app=app, default_limits=["10/hour"])
  8. Define granular rate limits with Limit objects

    master

    While the Limiter constructor accepts simple strings, you can use specific dataclasses to define rate limits with more granularity, particularly for application-wide, default, or meta limits.

    Available limit dataclasses:

    • Limit: The base dataclass for defining rate limits.
    • ApplicationLimit: Used for defining limits that apply to the entire application.
    • MetaLimit: Used for defining meta limits.
    • RouteLimit: Used specifically for decorating individual routes or blueprints.
  9. Use the Moving Window strategy

    master

    The moving-window strategy provides high precision by tracking individual request timestamps.

    How it works: It maintains a log of timestamps. A new request is allowed if the nth oldest entry (where n is the limit) is either missing or older than the window duration. Expired entries are truncated from the log.

    When to use: Use this when you need the most accurate rate limiting and can afford the higher memory usage required to store request logs.

    Configuration value: moving-window

  10. Customize rate limit exceeded responses

    master

    By default, exceeding a limit raises a RateLimitExceeded exception, resulting in a 429 Too Many Requests response. You can customize this behavior in two ways:

    1. Global Error Handler

    Register a standard Flask error handler for the 429 status code. This is the simplest way to return JSON instead of HTML for all routes.

    2. Using on_breach callback

    Provide an on_breach callback to the Limiter constructor or the @limiter.limit decorator. The callback receives a RequestLimit object and should return a flask.Response instance.

    Priority Rules:

    • If a specific route has an on_breach callback defined via @limiter.limit, it takes priority over the global on_breach callback defined in the Limiter constructor.
    • If you have both an on_breach callback AND a Flask @app.errorhandler(429), the error handler will be called. To ensure the on_breach response is used, your error handler should check error.get_response() first.

    Note: Since version 2.8.0, errors in the on_breach callback are re-raised unless swallow_errors=True is set in the Limiter configuration.

    # Global error handler approach
    @app.errorhandler(429)
    def ratelimit_handler(e):
        return make_response(
                jsonify(error=f"ratelimit exceeded {e.description}")
                , 429
        )
    
    # Using on_breach callback (Global)
    from flask_limiter import Limiter, RequestLimit
    
    def default_error_responder(request_limit: RequestLimit):
        return make_response(
            render_template("my_ratelimit_template.tmpl", request_limit=request_limit),
            429
        )
    
    app = Limiter(
        key_func=...,
        default_limits=["100/minute"],
        on_breach=default_error_responder
    )
    
    # Using on_breach callback (Route-specific)
    @app.route("/")
    @limiter.limit("10/minute", on_breach=index_ratelimit_error_responder)
    def index():
        ...
  11. Install Flask-Limiter

    master

    Install the core package using pip:

    $ pip install Flask-Limiter

    To include dependencies for specific storage backends, use the extras notation:

    • Redis: pip install Flask-Limiter[redis]
    • Memcached: pip install Flask-Limiter[memcached]
    • MongoDB: pip install Flask-Limiter[mongodb]
    • Valkey: pip install Flask-Limiter[valkey]

    To use the Flask CLI commands for inspecting configuration and limits, install the cli extra:

    $ pip install Flask-Limiter[cli]
    $ pip install Flask-Limiter