Package: throttled
ThrottledAPIThrottledAPI is a rate limiter for FastAPI.
Checkour featuresto see the use-cases already tested. The code base is 99% covered by unit and integration tests. It is also full type checked with type hints, assuring code with great quality.Why another rate limiter for FastAPI?Why another rate limiter for FastAPI, if we already haveslowapiandfastapi-limiter? This limiter glues what is good from both projects and
adds a bit more. Here is a list of reasons:Thethrottled-apirate limiter takes full advantage from the composable dependency injection system in FastAPI.
That means you can also create limiters per resource.Want to limit requests per IP or per user? Got it!Want to limit requests based on another weird parameter you are receiving? Just extend ourFastAPILimiterand you
are good to go!You can use different storage backends (different implementations forBaseStorage) for each limiter.Want to limit each API instance to 2000 requests per second? You don´t need more than ain-memorycounter.
Just useMemoryStoragefor the task.Want to limit calls to all your API instances by user or IP? A shared cache is what you need.
OurRedisStorageimplementation is an adapter for the famousredispackage. Other implementations + asyncio support are coming...InstallJust use your favorite python package manager. Here are two examples:With pip:pip install throttledWith poetry:poetry add throttledThe package is in an early development stage. This means the API can change a lot along the way, before hitting 1.0.0,
given community feedback. I recommend you pin the latest version that works for you when using the library for now.UseUse existing limitersWe already implementedTotalLimiterandIPLimiterfor you:TotalLimiter: limits all calls to your API, so you can assure it won't suffocate with too many requests.IPLimiter: as the name suggests, limits requests by IP.Disclaimer: generalize getting IP from theRequestobject is not easy, because there istoo many variations in the community. TheIPLimiterworks when hitting the API running onuvicornfrom another docker container. However, reverse proxies can complicate things. Notwithstanding, theThrottledAPIarchtecture allows creating limiters as you like, so there is nothing stopping you from creating an IP limiter that fits your infrastructure. You can also submit it here as a PR, and I will be glad to merge it in the codebase, given instructions for when to use it.Implement custom limitersYou can implement new limiters easily extending fromFastAPILimiterorMiddlewareLimiter# Your IDE will help you find the importsclassUserLimiter(FastAPILimiter):"""Client specific limiter"""def__call__(self,request:Request,user:UserID=Depends(get_current_user)):# The request parameter is mandatoryself.limit(key=f"username={user.username}")Attach to the APIThere are two options when using the limiters in your APIAll limiters as dependenciesThis is the simplest usage, requiring less codedefcreate_limiters()->Sequence[FastAPILimiter]:memory=MemoryStorage(cache={})api_limiter=TotalLimiter(limit=Rate(2000,1),storage=memory)redis=RedisStorage(client=Redis.from_url("redis://localhost:0"))ip_limiter=IPLimiter(limit=Rate(10,1),storage=redis)user_limiter=UserLimiter(limit=Rate(2,5),storage=redis)returnapi_limiter,ip_limiter,user_limiterdefcreate_app(limiters:Sequence[FastAPILimiter]=tuple())->FastAPI:"""Creates a FastAPI app with attached limiters and routes"""api=FastAPI(title="Snacks bar",dependencies=limiters)api.include_router(products_router,prefix="/products")api.include_router(users_router,prefix="/users")returnapiapp=create_app(limiters=create_limiters())Some limiters as middlewaresAlthough FastAPI dependency injection is really powerful, some limiters doesn't require any special resource in
other to do their job. In that case you cut some latency if using the limiter as a Middleware.defcreate_app(limiters:Sequence[FastAPILimiter]=tuple())->FastAPI:"""Creates a FastAPI app with attached limiters and routes"""dependencies,middlewares=split_dependencies_and_middlewares(*limiters)api=FastAPI(title="Snacks bar",dependencies=dependencies)api.include_router(products_router,prefix="/products")api.include_router(users_router,prefix="/users")formidinmiddlewares:api.add_middleware(BaseHTTPMiddleware,dispatch=mid)returnapiapp=create_app(limiters=create_limiters())# create_limiter: same function aboveMiddleware vs DependencyWhen implementing a custom limiter, how to choose between extendingFastAPILimiterorMiddlewareLimiter?stateDiagram-v2
    state FirstCondition <<choice>>
    state SecondCondition <<choice>>
    
    FirstQuestion: What type of limiter should I choose?
    FirstQuestion --> FirstCondition
    
    FirstCondition: Limiting depends on resources other\nthan Request object from Starlette?
    FirstCondition --> FastAPILimiter: yes
    FirstCondition --> MiddlewareLimiter : no
    FastAPILimiter --> SecondQuestion
    MiddlewareLimiter --> SecondQuestion
    
    SecondQuestion: What storage should I pick?
    SecondQuestion --> SecondCondition
    SecondCondition: The parameters you are limiting spams a parameter space.\n Is that space too large?
    SecondCondition --> RedisStorage : yes
    SecondCondition --> ThirdCondition : no
    
    ThirdCondition: You want to share the limiter\nbetween different API instances (pods)?
    ThirdCondition --> RedisStorage : yes
    ThirdCondition --> MemoryStorage : no
    
    RedisStorage --> End
    MemoryStorage --> End
    End: Attach the limiter to your APIContributingIssues, suggestions, PRs are welcome!