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)
An alternative implement of Imjad API | Imjad API 的开源替代

HibiAPI An alternative implement of Imjad API. Imjad API 的开源替代. 前言 由于Imjad API这是什么?使用人数过多, 致使调用超出限制, 所以本人希望提供一个开源替代来供社区进行自由的部署和使用, 从而减轻一部分该API的使用压力 优势

Mix Technology 450 Dec 29, 2022
✨️🐍 SPARQL endpoint built with RDFLib to serve machine learning models, or any other logic implemented in Python

✨ SPARQL endpoint for RDFLib rdflib-endpoint is a SPARQL endpoint based on a RDFLib Graph to easily serve machine learning models, or any other logic

Vincent Emonet 27 Dec 19, 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
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

Adam Watkins 950 Jan 08, 2023
FastAPI backend for Repost

Repost FastAPI This is the FastAPI implementation of the Repost API. Installation Python 3 must be installed and accessible through the use of a termi

PC 7 Jun 15, 2021
Easily integrate socket.io with your FastAPI app 🚀

fastapi-socketio Easly integrate socket.io with your FastAPI app. Installation Install this plugin using pip: $ pip install fastapi-socketio Usage To

Srdjan Stankovic 210 Dec 23, 2022
Flask-vs-FastAPI - Understanding Flask vs FastAPI Web Framework. A comparison of two different RestAPI frameworks.

Flask-vs-FastAPI Understanding Flask vs FastAPI Web Framework. A comparison of two different RestAPI frameworks. IntroductionIn Flask is a popular mic

Mithlesh Navlakhe 1 Jan 01, 2022
Code for my FastAPI tutorial

FastAPI tutorial Code for my video tutorial FastAPI tutorial What is FastAPI? FastAPI is a high-performant REST API framework for Python. It's built o

José Haro Peralta 9 Nov 15, 2022
A simple example of deploying FastAPI as a Zeit Serverless Function

FastAPI Zeit Now Deploy a FastAPI app as a Zeit Serverless Function. This repo deploys the FastAPI SQL Databases Tutorial to demonstrate how a FastAPI

Paul Weidner 26 Dec 21, 2022
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
🚀 Cookiecutter Template for FastAPI + React Projects. Using PostgreSQL, SQLAlchemy, and Docker

FastAPI + React · A cookiecutter template for bootstrapping a FastAPI and React project using a modern stack. Features FastAPI (Python 3.8) JWT authen

Gabriel Abud 1.4k Jan 02, 2023
:rocket: CLI tool for FastAPI. Generating new FastAPI projects & boilerplates made easy.

Project generator and manager for FastAPI. Source Code: View it on Github Features 🚀 Creates customizable project boilerplate. Creates customizable a

Yagiz Degirmenci 1k Jan 02, 2023
Qwerkey is a social media platform for connecting and learning more about mechanical keyboards built on React and Redux in the frontend and Flask in the backend on top of a PostgreSQL database.

Flask React Project This is the backend for the Flask React project. Getting started Clone this repository (only this branch) git clone https://github

Peter Mai 22 Dec 20, 2022
cookiecutter template for web API with python

Python project template for Web API with cookiecutter What's this This provides the project template including minimum test/lint/typechecking package

Hitoshi Manabe 4 Jan 28, 2021
Full stack, modern web application generator. Using FastAPI, PostgreSQL as database, Docker, automatic HTTPS and more.

Full Stack FastAPI and PostgreSQL - Base Project Generator Generate a backend and frontend stack using Python, including interactive API documentation

Sebastián Ramírez 10.8k Jan 08, 2023
CLI and Streamlit applications to create APIs from Excel data files within seconds, using FastAPI

FastAPI-Wrapper CLI & APIness Streamlit App Arvindra Sehmi, Oxford Economics Ltd. | Website | LinkedIn (Updated: 21 April, 2021) fastapi-wrapper is mo

Arvindra 49 Dec 03, 2022
Generate Class & Decorators for your FastAPI project ✨🚀

Classes and Decorators to use FastAPI with class based routing. In particular this allows you to construct an instance of a class and have methods of that instance be route handlers for FastAPI & Pyt

Yasser Tahiri 34 Oct 27, 2022
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
Single Page App with Flask and Vue.js

Developing a Single Page App with FastAPI and Vue.js Want to learn how to build this? Check out the post. Want to use this project? Build the images a

91 Jan 05, 2023
Simple example of FastAPI + Celery + Triton for benchmarking

You can see the previous work from: https://github.com/Curt-Park/producer-consumer-fastapi-celery https://github.com/Curt-Park/triton-inference-server

Jinwoo Park (Curt) 37 Dec 29, 2022