long2ice / fastapi-limiter

A request rate limiter for fastapi
https://github.com/long2ice/fastapi-limiter
Apache License 2.0
510 stars 55 forks source link

Is it possible to override the Rate Limter dependency ? #27

Open Miguelme opened 1 year ago

Miguelme commented 1 year ago

What is the expected way of overriding the dependency for the rate limiter from the tests? Any example would be appreciated

WaldemarEnns commented 1 year ago

Also looking forward to a solution ... how are we supposed to test our endpoints when rate-limiting is active?

In my case, I have the following test-file:

from fastapi.testclient import TestClient
from app.misc import normalize_phone_number
from app.database.models.user import User
from app.misc import get_db
from app.services.otp import store_otp_for_user
from app.main import app

client = TestClient(app)

def test_register_new_user(clear_database):
  clear_database()
  phone_number = "+4915202551672"
  response = client.post(
    "/users/register",
    json={
      "name": "Test User",
      "phone_number": phone_number
    }
  )

  assert response.status_code == 201
  assert response.json()["id"] == 1
  assert response.json()["name"] == "Test User"
  assert response.json()["phone_number"] == normalize_phone_number(phone_number)
  assert response.json()["verified"] == False

  # get the user from the db
  session = next(get_db())
  user = session.query(User).filter(User.id == 1).first()
  assert len(user.otps) == 1

Using pytest for testing, results in the following error:

FAILED tests/test_user_registration.py::test_register_new_user - Exception: You must call FastAPILimiter.init in startup event of fastapi!

As far as I know, the startup event is not being called by the TestClient?

JameStitel commented 1 year ago

I have managed to do so by modifying the RateLimiter class:

class CustomRateLimiter(RateLimiter):
    def __hash__(self) -> int:
        return hash(f"limiter-{hash(self.CUSTOM_ID_VALUE)}")

    def __eq__(self, other: object) -> bool:
        if isinstance(other, CustomRateLimiter):
            return self.CUSTOM_ID_VALUE == other.CUSTOM_ID_VALUE
        return False  

Then use app.dependency_overrides[CustomRateLimiter(xxx)] = lambda: True, where xxx is the exact same setting as in the endpoint for which you want to override the RateLimiter dependency (in the case of lambda: True it basically disables the rate limit).

Source: https://github.com/tiangolo/fastapi/issues/2795#issuecomment-819476279

n0t-4m17h commented 1 month ago

I have managed to do so by modifying the RateLimiter class:

class CustomRateLimiter(RateLimiter):
    def __hash__(self) -> int:
        return hash(f"limiter-{hash(self.CUSTOM_ID_VALUE)}")

    def __eq__(self, other: object) -> bool:
        if isinstance(other, CustomRateLimiter):
            return self.CUSTOM_ID_VALUE == other.CUSTOM_ID_VALUE
        return False  

Then use app.dependency_overrides[CustomRateLimiter(xxx)] = lambda: True, where xxx is the exact same setting as in the endpoint for which you want to override the RateLimiter dependency (in the case of lambda: True it basically disables the rate limit).

Source: fastapi/fastapi#2795 (comment)

FIxes pytest issues, but for my implementation, where I've seperated GET and PUT,etc., rate limit values, my fastapi app applies the GET timer to non-GET endpoints. Some overlap in the hash by redis or something.