long2ice / fastapi-limiter

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

Unable to run pytests #51

Open goussous0 opened 7 months ago

goussous0 commented 7 months ago

According to the github repo README.md I have to use

@asynccontextmanager
async def lifespan(_: FastAPI):
    redis_connection = redis.from_url("redis://localhost:6379", encoding="utf8")
    await FastAPILimiter.init(redis_connection)
    yield
    await FastAPILimiter.close()

or startup event as mentioned on pypi but both approaches seems to result in the same error when trying to run pytests

The following is a basic recreation of the error config/__init__.py

from fastapi import FastAPI, Depends
import redis.asyncio as redis
from contextlib import asynccontextmanager
from fastapi_limiter import FastAPILimiter
from fastapi_limiter.depends import RateLimiter

@asynccontextmanager
async def lifespan(_: FastAPI):
    redis_connection = redis.from_url("redis://localhost:6379", encoding="utf8")
    await FastAPILimiter.init(redis_connection)
    yield
    await FastAPILimiter.close()

class App(FastAPI):
    def __init__(self):
        super().__init__(lifespan=lifespan)

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

tests/__init__.py

from config import App
from fastapi.testclient import TestClient

app = App()
client  = TestClient(app)

def test_index():
    resp = client.get("/")
    assert resp.status_code() == 200

the error output

============================= test session starts ==============================
platform linux -- Python 3.11.2, pytest-7.4.4, pluggy-1.4.0
rootdir: /home/test/Desktop/limiter/src
plugins: anyio-4.2.0
collected 1 item

tests/test_example.py F                                                  [100%]

=================================== FAILURES ===================================
__________________________________ test_index __________________________________

    def test_index():
>       resp = client.get("/")

tests/test_example.py:6: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../lib/python3.11/site-packages/starlette/testclient.py:502: in get
    return super().get(
../lib/python3.11/site-packages/httpx/_client.py:1055: in get
    return self.request(
../lib/python3.11/site-packages/starlette/testclient.py:468: in request
    return super().request(
../lib/python3.11/site-packages/httpx/_client.py:828: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
../lib/python3.11/site-packages/httpx/_client.py:915: in send
    response = self._send_handling_auth(
../lib/python3.11/site-packages/httpx/_client.py:943: in _send_handling_auth
    response = self._send_handling_redirects(
../lib/python3.11/site-packages/httpx/_client.py:980: in _send_handling_redirects
    response = self._send_single_request(request)
../lib/python3.11/site-packages/httpx/_client.py:1016: in _send_single_request
    response = transport.handle_request(request)
../lib/python3.11/site-packages/starlette/testclient.py:344: in handle_request
    raise exc
../lib/python3.11/site-packages/starlette/testclient.py:341: in handle_request
    portal.call(self.app, scope, receive, send)
../lib/python3.11/site-packages/anyio/from_thread.py:288: in call
    return cast(T_Retval, self.start_task_soon(func, *args).result())
/usr/lib/python3.11/concurrent/futures/_base.py:449: in result
    return self.__get_result()
/usr/lib/python3.11/concurrent/futures/_base.py:401: in __get_result
    raise self._exception
../lib/python3.11/site-packages/anyio/from_thread.py:217: in _call_func
    retval = await retval_or_awaitable
../lib/python3.11/site-packages/fastapi/applications.py:1054: in __call__
    await super().__call__(scope, receive, send)
../lib/python3.11/site-packages/starlette/applications.py:123: in __call__
    await self.middleware_stack(scope, receive, send)
../lib/python3.11/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
../lib/python3.11/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
../lib/python3.11/site-packages/starlette/middleware/exceptions.py:62: in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../lib/python3.11/site-packages/starlette/_exception_handler.py:64: in wrapped_app
    raise exc
../lib/python3.11/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    await app(scope, receive, sender)
../lib/python3.11/site-packages/starlette/routing.py:762: in __call__
    await self.middleware_stack(scope, receive, send)
../lib/python3.11/site-packages/starlette/routing.py:782: in app
    await route.handle(scope, receive, send)
../lib/python3.11/site-packages/starlette/routing.py:297: in handle
    await self.app(scope, receive, send)
../lib/python3.11/site-packages/starlette/routing.py:77: in app
    await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../lib/python3.11/site-packages/starlette/_exception_handler.py:64: in wrapped_app
    raise exc
../lib/python3.11/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    await app(scope, receive, sender)
../lib/python3.11/site-packages/starlette/routing.py:72: in app
    response = await func(request)
../lib/python3.11/site-packages/fastapi/routing.py:285: in app
    raise e
../lib/python3.11/site-packages/fastapi/routing.py:275: in app
    solved_result = await solve_dependencies(
../lib/python3.11/site-packages/fastapi/dependencies/utils.py:598: in solve_dependencies
    solved = await call(**sub_values)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <fastapi_limiter.depends.RateLimiter object at 0x7f8ae5020810>
request = <starlette.requests.Request object at 0x7f8ae44f0e90>
response = <starlette.responses.Response object at 0x7f8ae44f0e50>

    async def __call__(self, request: Request, response: Response):
        if not FastAPILimiter.redis:
>           raise Exception("You must call FastAPILimiter.init in startup event of fastapi!")
E           Exception: You must call FastAPILimiter.init in startup event of fastapi!

../lib/python3.11/site-packages/fastapi_limiter/depends.py:37: Exception
=========================== short test summary info ============================
FAILED tests/test_example.py::test_index - Exception: You must call FastAPILi...
============================== 1 failed in 0.85s ===============================
a1d4r commented 4 months ago

I used this dirty hack. Yet it is quite simple and gets the work done.

dependencies = []
if "pytest" not in sys.modules:
    dependencies.append(
        Depends(
            RateLimiter(...)
        )
    )
api_router = APIRouter(prefix="/api/v1", dependencies=dependencies)
# add your routes to `api_router`
app.include_router(api_router)