Skip to content

Commit 0d64f1f

Browse files
committed
feat: add skip_limiter decorator and update Makefile commands for consistency (#60)
1 parent 9c1e8a5 commit 0d64f1f

File tree

6 files changed

+50
-5
lines changed

6 files changed

+50
-5
lines changed

Makefile

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,14 @@ deps:
2020
@uv sync --all-groups
2121

2222
style: deps
23-
ruff format $(checkfiles)
23+
@uv run ruff format $(checkfiles)
2424

2525
check: deps
26-
ruff check $(checkfiles)
27-
ty check $(checkfiles)
26+
@uv run ruff check $(checkfiles)
27+
@uv run ty check $(checkfiles)
2828

2929
test: deps
30-
$(py_warn) pytest
30+
@$(py_warn) uv run pytest
3131

3232
build: deps
3333
@uv build

examples/main.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from fastapi import Depends, FastAPI, HTTPException, WebSocket
66
from starlette.websockets import WebSocketDisconnect
77

8-
from fastapi_limiter import FastAPILimiter
8+
from fastapi_limiter import FastAPILimiter, skip_limiter
99
from fastapi_limiter.depends import RateLimiter, WebSocketRateLimiter
1010

1111

@@ -41,6 +41,12 @@ async def multiple():
4141
return {"msg": "Hello World"}
4242

4343

44+
@app.get("/skip", dependencies=[Depends(RateLimiter(times=1, seconds=5))])
45+
@skip_limiter
46+
async def skip_route():
47+
return {"msg": "This route skips rate limiting"}
48+
49+
4450
@app.websocket("/ws")
4551
async def websocket_endpoint(websocket: WebSocket):
4652
await websocket.accept()

fastapi_limiter/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
from starlette.status import HTTP_429_TOO_MANY_REQUESTS
88
from starlette.websockets import WebSocket
99

10+
from fastapi_limiter.decorators import skip_limiter
11+
12+
__all__ = ["FastAPILimiter", "skip_limiter"]
13+
1014

1115
async def default_identifier(request: Union[Request, WebSocket]):
1216
forwarded = request.headers.get("X-Forwarded-For")

fastapi_limiter/decorators.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from functools import wraps
2+
from typing import Callable
3+
4+
5+
def skip_limiter(func: Callable) -> Callable:
6+
"""
7+
Decorator to skip rate limiting for a specific route.
8+
9+
Usage:
10+
@app.get("/health")
11+
@skip_limiter
12+
async def health():
13+
return {"status": "ok"}
14+
"""
15+
16+
@wraps(func)
17+
async def wrapper(*args, **kwargs):
18+
return await func(*args, **kwargs)
19+
20+
wrapper._skip_limiter = True
21+
return wrapper

fastapi_limiter/depends.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ async def __call__(self, request: Request, response: Response):
4747
for i, route in enumerate(request.app.routes):
4848
if route.path == request.scope["path"] and hasattr(route, "methods") and request.method in route.methods:
4949
route_index = i
50+
# Check if the route endpoint has _skip_limiter attribute
51+
if hasattr(route, "endpoint") and getattr(route.endpoint, "_skip_limiter", False):
52+
return
5053
for j, dependency in enumerate(route.dependencies):
5154
if self is dependency.dependency:
5255
dep_index = j

tests/test_skip.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from starlette.testclient import TestClient
2+
3+
from examples.main import app
4+
5+
6+
def test_skip_limiter():
7+
with TestClient(app) as client:
8+
# Even with RateLimiter(times=1), skip_limiter allows unlimited requests
9+
for _ in range(5):
10+
response = client.get("/skip")
11+
assert response.status_code == 200

0 commit comments

Comments
 (0)