A request rate limiter for fastapi

Overview

fastapi-limiter

pypi license workflows workflows

Introduction

FastAPI-Limiter is a rate limiting tool for fastapi routes.

Requirements

Install

Just install from pypi

> pip install fastapi-limiter

Quick Start

FastAPI-Limiter is simple to use, which just provide a dependency RateLimiter, the following example allow 2 times request per 5 seconds in route /.

import aioredis
import uvicorn
from fastapi import Depends, FastAPI

from fastapi_limiter import FastAPILimiter
from fastapi_limiter.depends import RateLimiter

app = FastAPI()


@app.on_event("startup")
async def startup():
    redis = await aioredis.create_redis_pool("redis://localhost")
    FastAPILimiter.init(redis)


@app.get("/", dependencies=[Depends(RateLimiter(times=2, seconds=5))])
async def index():
    return {"msg": "Hello World"}


if __name__ == "__main__":
    uvicorn.run("main:app", debug=True, reload=True)

Usage

There are some config in FastAPILimiter.init.

redis

The redis instance of aioredis.

prefix

Prefix of redis key.

identifier

Identifier of route limit, default is ip, you can override it such as userid and so on.

async def default_identifier(request: Request):
    forwarded = request.headers.get("X-Forwarded-For")
    if forwarded:
        return forwarded.split(",")[0]
    return request.client.host

callback

Callback when access is forbidden, default is raise HTTPException with 429 status code.

async def default_callback(request: Request, expire: int):
    """
    default callback when too many requests
    :param request:
    :param expire: The remaining seconds
    :return:
    """
    raise HTTPException(
        HTTP_429_TOO_MANY_REQUESTS, "Too Many Requests", headers={"Retry-After": str(expire)}
    )

License

This project is licensed under the Apache-2.0 License.

Comments
  • Rare failure mode

    Rare failure mode

    Looking at the core in fastapi_limiter/depends.py#L35-L42:

            p = redis.pipeline()
            p.incrby(key, 1)
            p.pttl(key)
            num, pexpire = await p.execute()
            if num == 1:
                await redis.pexpire(key, self.milliseconds)
            if num > self.times:
                return await callback(request, pexpire)
    

    In the extremely rare case that the process fails between incrementing the value with p.execute() and setting the ttl with redis.pexpire the key won't actually have a time to live set. The next request will increment the count, but as num will already be greater than 1 the expiry won't get set... so after self.times requests all following requests will count as exceeding the rate limit.

    opened by hardbyte 15
  • Multiple rate limiters does not work properly?

    Multiple rate limiters does not work properly?

    Using only 1 RateLimiter works fine, however multiple RateLimiters there are issues?

    Only the first RateLimiter is kind of working but the second one is not triggered?

    @router.get("/time", dependencies=[Depends(RateLimiter(times=3, seconds=5)), Depends(RateLimiter(times=5, hours=1))])
    

    The first RateLimiter only limits 2 times instead of 3 times and the second RateLimiter is not properly being checked until later on? Changing the amount of times will still have similar effects where you can only do less than the amount of times allowed.

    If you change it so that the RateLimiter has a greater time limit first, like this

    @router.get("/time", dependencies=[Depends(RateLimiter(times=10, hours=1)), Depends(RateLimiter(times=3, seconds=5))])
    

    This will only work for the first RateLimiter but not the second one.

    Edit: Whenever I try to change the path url for a specific route and restart the API, the route is still affected by the RateLimiter from before?

    I have to flush the data from the database to fix the issue.

    Edit 2: I guess that issue for why less calls happen is that it's calling the second rate limiter and incrementing if it passes?

    Edit 3: One possible idea for multiple RateLimiters could be something like this

    dependencies=[Depends(RaterLimiters(RateLimiter(...), RateLimiter(...)))]
    

    This class could grab each identifier, see if there are duplicates, if there are duplicates then sort then based on time then increment each unique identifier?

    opened by BookerLoL 6
  • [feature]Two RateLimiter's co-exist

    [feature]Two RateLimiter's co-exist

    Is it feasible to support two RateLimiter's at the same time? For instance,

    [Depends(RateLimiter(times=2, seconds=5)), Depends(RateLimiter(times=100, hours =24))]

    to limit max 2 hits every 5 seconds AND max 100 hits per day from the same ip. very much appreciated.

    enhancement 
    opened by zhiboz 4
  • Allow Rate Limiting within WebSockets

    Allow Rate Limiting within WebSockets

    This PR adds the capability to do rate limiting within websocket requests.

    For example (see examples/main.py):

    @app.websocket("/ws")
    async def websocket_endpoint(websocket: WebSocket):
         await websocket.accept()
         ratelimit = WebSocketRateLimiter(times=1, seconds=5)
         while True:
             try:
                 data = await websocket.receive_text()
                 await ratelimit(websocket, context_key=data) # NB: context_key is optional
                 await websocket.send_text(f"Hello, world")
             except WebSocketRateLimitException:
                 await websocket.send_text(f"Hello again")
    

    Context

    I was attempting to rate limit graphql requests. As graphql requests can come over a request or over a websocket, the conventional method of dependencies doesn't work. But the majority of the things we need to rate limit are already there - we have FastAPI with a redis client. All we have to do is construct the redis cache key a little differently.

    In order to support multiple rate strategies for a single route, currently in depends.py we iterate through the dependencies of the current route and store the index of the dependency which is then used in the rate limiting key. (See https://github.com/peterbraden/fastapi-limiter/commit/dd385d624c8d1000d9f3d2b335186cb6f638f1ab)

    In https://github.com/peterbraden/fastapi-limiter/blob/4e6c6fa6d8339bad459d801814c61735b658ae25/fastapi_limiter/depends.py#L32 the code assumes that the route has a dependencies list.

    In fact dependencies is optional in APIRoute and doesn't appear at all in APIWebSocketRoute.

    In a websocket none of this makes sense, we likely want to ratelimit more often than the lifetime of the websocket connection, and additionally we don't have connection scoped dependencies.

    Instead of trying to shoehorn this into the existing method, I've simply added a class WebSocketRateLimiter that derives from RateLimiter, and can be called directly within a websocket connection.

    opened by peterbraden 3
  • User-based Rate Limiting?

    User-based Rate Limiting?

    How do I enable different rates for each user? I know I can have a rate-limit for each user eg: 5 requests per 10 seconds. However, if I want each user to have their own rate limit eg:

    • user-1: 5 requests per 10 seconds.
    • user-2: 10 requests per 10 seconds.

    How do I do achieve different rate limits per user/ip?

    opened by Manas73 3
  • Support milliseconds

    Support milliseconds

    Thanks for the great contribution to FastApi!

    I was thinking it'd be a good idea to support milliseconds so that:

    1. fractions of seconds can be used, and
    2. it's the unit expiring information is stored https://redis.io/commands/expire#expires-and-persistence

    My first take is that milliseconds should be used as the default for all rate limiting but to avoid breaking changes I'd be happy to add a flag for seconds vs. milliseconds. Please let me know what you think!

    opened by rowrowrowrow 2
  • User customized rate limit ?

    User customized rate limit ?

    I want to have a subscription based api service. there are 3 types of subscription: Free: 10 api calls per 24hour Basic: 30 api calls per 24hour Advanced: 80 api calls per 24hour.

    Along with the request body, suppose the detaiils about the subscription is also available. Can such scenario be handled by your library ? I am using fastapi, and I would prefer using a library than writing my own rate limiter using reddis.

    opened by harshraj22 1
  • request: support aioredis>2.0

    request: support aioredis>2.0

    aioredis is undergoing massive codebase changes to become more stable, performant, and usable. Thus, the syntax has changed slightly regarding the create_pool call and evalsha methods. It'd be great for this library to support the upcoming release.

    opened by thearchitector 1
  • Rate Limit Bypass

    Rate Limit Bypass

    Just by sending X-Forwarded-For header with any random number or string with each request ex: X-Forwarded-For: 23189987 allows anyone to bypass the rate limiter no problem.

    opened by ErikASD 1
  • Access response in callback

    Access response in callback

    In some cases I'd like direct access to the response as well as the request in the callback.

    e.g. https://fastapi.tiangolo.com/advanced/response-change-status-code/

    opened by rowrowrowrow 1
  • Limits for different HTTP methods get merged together

    Limits for different HTTP methods get merged together

    Imagine I have some kind of a form and I want to configure 2 different limits: one for opening the form and the other for submitting it.

    @app.get("/", dependencies=[Depends(RateLimiter(times=10, seconds=5))])
    async def form_get():
        return {"msg": "Hello World"}
    
    
    @app.post("/", dependencies=[Depends(RateLimiter(times=1, seconds=5))])
    async def form_post():
        return {"msg": "Hello World"}
    

    Expected behaviour One could request the form 10 times in 5 seconds, and only after that would they get 429. However, even after that, they should be able to submit the form as they haven't made any POST requests yet.

    Actual behaviour After sending 1 GET request user would get 429 when trying to submit the form.

    See the commit in a forked repo with new test cases: https://github.com/vvkh/fastapi-limiter/commit/70338f968cc61649189a05cfe5fe2d7a43244dae

    opened by vvkh 0
  • NOSCRIPT No matching script. Please use EVAL error

    NOSCRIPT No matching script. Please use EVAL error

    Looking at FastAPI rate limiter v0.1.4 , we got an error after restarting Redis:

       File "/usr/local/lib/python3.8/site-packages/starlette/routing.py", line 52, in app
         response = await func(request)
       File "/usr/local/lib/python3.8/site-packages/fastapi/routing.py", line 204, in app
         solved_result = await solve_dependencies(
       File "/usr/local/lib/python3.8/site-packages/fastapi/dependencies/utils.py", line 548, in solve_dependencies
         solved = await call(**sub_values)
       File "/usr/local/lib/python3.8/site-packages/fastapi_limiter/depends.py", line 42, in __call__
         pexpire = await redis.evalsha(
     aioredis.errors.ReplyError: NOSCRIPT No matching script. Please use EVAL.
    

    fastapi-limiter==0.1.4 aioredis==1.3.1 fastapi==0.65.2

    We've never seen this error before, but think it is should be similar to:

    https://github.com/OptimalBits/bull/issues/1445

    opened by gtoonstra 2
  • evalsha() got an unexpected keyword argument 'keys'

    evalsha() got an unexpected keyword argument 'keys'

    HI: Fist of all, I want to appreciate that you build such awesome tool. When i use this library encounter some problem like title, which code write on

     File "/usr/local/lib/python3.8/site-packages/fastapi_limiter/depends.py", line 42, in __call__
        pexpire = await redis.evalsha(
    TypeError: evalsha() got an unexpected keyword argument 'keys'
    

    Environments:

    • python 3.8
    • aioredis == 2.0.0
    • fastapi-limiter==0.1.4
    • fastapi==0.68.1
    opened by PaiHsuehChung 9
  • Bug with latest version

    Bug with latest version

    Creating a new project with latest versions of fastapi and fastapi-limiter runs into an issue:

    TypeError: evalsha() got an unexpected keyword argument 'keys'
    

    I used the following test code:

    import aioredis
    from fastapi import Depends, FastAPI
    
    from fastapi_limiter import FastAPILimiter
    from fastapi_limiter.depends import RateLimiter
    
    app = FastAPI()
    
    @app.on_event("startup")
    async def startup():
        redis = await aioredis.from_url("redis://localhost", encoding="utf-8", decode_responses=True)
        await FastAPILimiter.init(redis)
    
    
    @app.get("/", dependencies=[Depends(RateLimiter(times=2, seconds=5))])
    async def index():
        return {"message": "Hello World"}
    
    
    opened by hardbyte 4
  • Feature: Added

    Feature: Added "enabled" as a feature to FastAPILimiter

    enabled: Default value is True which changes no features, but can be set to False. When False, no limiting checks will be done.

    Use Cases: For debugging other features of API, excluding the Rate Limiter For enabling/disabling Rate Limiter using an environment variable, for deployment purposes

    opened by trevorWieland 2
Releases(v0.1.5)
Restful Api developed with Flask using Prometheus and Grafana for monitoring and containerization with Docker :rocket:

Hephaestus 🚀 In Greek mythology, Hephaestus was either the son of Zeus and Hera or he was Hera's parthenogenous child. ... As a smithing god, Hephaes

Yasser Tahiri 16 Oct 07, 2022
🐞 A debug toolbar for FastAPI based on the original django-debug-toolbar. 🐞

Debug Toolbar 🐞 A debug toolbar for FastAPI based on the original django-debug-toolbar. 🐞 Swagger UI & GraphQL are supported. Documentation: https:/

Dani 74 Dec 30, 2022
A dynamic FastAPI router that automatically creates CRUD routes for your models

⚡ Create CRUD routes with lighting speed ⚡ A dynamic FastAPI router that automatically creates CRUD routes for your models Documentation: https://fast

Adam Watkins 943 Jan 01, 2023
Socket.IO integration for Flask applications.

Flask-SocketIO Socket.IO integration for Flask applications. Installation You can install this package as usual with pip: pip install flask-socketio

Miguel Grinberg 4.9k Jan 03, 2023
Beyonic API Python official client library simplified examples using Flask, Django and Fast API.

Beyonic API Python Examples. The beyonic APIs Doc Reference: https://apidocs.beyonic.com/ To start using the Beyonic API Python API, you need to start

Harun Mbaabu Mwenda 46 Sep 01, 2022
FastAPI simple cache

FastAPI Cache Implements simple lightweight cache system as dependencies in FastAPI. Installation pip install fastapi-cache Usage example from fastapi

Ivan Sushkov 188 Dec 29, 2022
Prometheus integration for Starlette.

Starlette Prometheus Introduction Prometheus integration for Starlette. Requirements Python 3.6+ Starlette 0.9+ Installation $ pip install starlette-p

José Antonio Perdiguero 229 Dec 21, 2022
FastAPI interesting concepts.

fastapi_related_stuffs FastAPI interesting concepts. FastAPI version :- 0.70 Python3 version :- 3.9.x Steps Test Django Like settings export FASTAPI_S

Mohd Mujtaba 3 Feb 06, 2022
FastAPI native extension, easy and simple JWT auth

fastapi-jwt FastAPI native extension, easy and simple JWT auth

Konstantin Chernyshev 19 Dec 12, 2022
FastAPI Socket.io with first-class documentation using AsyncAPI

fastapi-sio Socket.io FastAPI integration library with first-class documentation using AsyncAPI The usage of the library is very familiar to the exper

Marián Hlaváč 9 Jan 02, 2023
Feature rich robust FastAPI template.

Flexible and Lightweight general-purpose template for FastAPI. Usage ⚠️ Git, Python and Poetry must be installed and accessible ⚠️ Poetry version must

Pavel Kirilin 588 Jan 04, 2023
Cube-CRUD is a simple example of a REST API CRUD in a context of rubik's cube review service.

Cube-CRUD is a simple example of a REST API CRUD in a context of rubik's cube review service. It uses Sqlalchemy ORM to manage the connection and database operations.

Sebastian Andrade 1 Dec 11, 2021
A minimal FastAPI implementation for Django !

Caution!!! This project is in early developing stage. So use it at you own risk. Bug reports / Fix PRs are welcomed. Installation pip install django-m

toki 23 Dec 24, 2022
Adds simple SQLAlchemy support to FastAPI

FastAPI-SQLAlchemy FastAPI-SQLAlchemy provides a simple integration between FastAPI and SQLAlchemy in your application. It gives access to useful help

Michael Freeborn 465 Jan 07, 2023
A rate limiter for Starlette and FastAPI

SlowApi A rate limiting library for Starlette and FastAPI adapted from flask-limiter. Note: this is alpha quality code still, the API may change, and

Laurent Savaete 562 Jan 01, 2023
Dead simple CSRF security middleware for Starlette ⭐ and Fast API ⚡

csrf-starlette-fastapi Dead simple CSRF security middleware for Starlette ⭐ and Fast API ⚡ Will work with either a input type="hidden" field or ajax

Nathaniel Sabanski 9 Nov 20, 2022
Prometheus exporter for metrics from the MyAudi API

Prometheus Audi Exporter This Prometheus exporter exports metrics that it fetches from the MyAudi API. Usage Checkout submodules Install dependencies

Dieter Maes 7 Dec 19, 2022
FastAPI + PeeWee = <3

FastAPIwee FastAPI + PeeWee = 3 Using Python = 3.6 🐍 Installation pip install FastAPIwee 🎉 Documentation Documentation can be found here: https://

16 Aug 30, 2022
Repository for the Demo of using DVC with PyCaret & MLOps (DVC Office Hours - 20th Jan, 2022)

Using DVC with PyCaret & FastAPI (Demo) This repo contains all the resources for my demo explaining how to use DVC along with other interesting tools

Tezan Sahu 6 Jul 22, 2022
Cache-house - Caching tool for python, working with Redis single instance and Redis cluster mode

Caching tool for python, working with Redis single instance and Redis cluster mo

Tural 14 Jan 06, 2022