Skip to content

Commit 3e673f3

Browse files
committed
Fixed linting issues.
1 parent 33ab701 commit 3e673f3

File tree

12 files changed

+90
-29
lines changed

12 files changed

+90
-29
lines changed

backend/app/core/rate_limiter/key_strategy/header_key_strategy.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from starlette.requests import Request
2+
23
from app.core.rate_limiter.key_strategy.key_strategy import KeyStrategy
34

45

@@ -8,4 +9,4 @@ def __init__(self, header_name: str = "X-Client-ID"):
89

910
def get_key(self, request: Request, route_path: str) -> str:
1011
value = request.headers.get(self.header_name, "unknown")
11-
return f"header:{self.header_name}:{value}:{route_path}"
12+
return f"header:{self.header_name}:{value}:{route_path}"
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
from app.core.rate_limiter.key_strategy.key_strategy import KeyStrategy
21
from starlette.requests import Request
32

3+
from app.core.rate_limiter.key_strategy.key_strategy import KeyStrategy
4+
5+
46
class IPKeyStrategy(KeyStrategy):
57
"""Generate rate limit key based on client IP address."""
68

79
def get_key(self, request: Request, route_path: str) -> str:
810
client_ip = request.client.host if request.client else "unknown"
9-
return f"ip:{client_ip}:{route_path}"
11+
return f"ip:{client_ip}:{route_path}"

backend/app/core/rate_limiter/key_strategy/key_strategy.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from abc import ABC, abstractmethod
2+
23
from starlette.requests import Request
34

5+
46
class KeyStrategy(ABC):
57
"""Base interface for rate-limit key generation."""
68

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from enum import Enum
22

3+
34
class KeyStrategyName(str, Enum):
45
IP = "ip"
56
HEADER = "header"

backend/app/core/rate_limiter/rate_limiter.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import logging
2-
from fastapi import Request, HTTPException
2+
3+
from fastapi import HTTPException, Request
4+
35
from app.core.rate_limiter.key_strategy import key_strategy_registry
46
from app.core.rate_limiter.key_strategy.key_strategy_enum import KeyStrategyName
57

@@ -11,15 +13,16 @@ def __init__(self, limit: int, window_seconds: int, key_policy: KeyStrategyName
1113
self.window_seconds = window_seconds
1214
self.key_policy = key_policy
1315

14-
async def __call__(self, request: Request):
16+
async def __call__(self, request: Request) -> None:
1517
rate_limiter = getattr(request.app.state, "rate_limiter", None)
1618

1719
if rate_limiter is None:
18-
return
20+
return None
1921

2022
# Create Key
2123
key_strategy = key_strategy_registry.get_key_strategy(self.key_policy)
22-
key = key_strategy.get_key(request, request.scope.get('path'))
24+
path: str = request.scope.get("path") or ""
25+
key = key_strategy.get_key(request, path)
2326

2427
allowed = True
2528
retry_after = None
@@ -40,4 +43,4 @@ async def __call__(self, request: Request):
4043
status_code=429,
4144
detail=f"Too Many Requests. Retry after {retry_after}s",
4245
headers={"Retry-After": str(retry_after)}
43-
)
46+
)

backend/app/core/rate_limiter/rate_limiting_algorithm/base.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
from __future__ import annotations
2+
23
from abc import ABC, abstractmethod
3-
from typing import Tuple, Optional
4+
45

56
class BaseRateLimiter(ABC):
67
"""Interface for pluggable rate limiter strategies."""
78

89
@abstractmethod
9-
async def allow_request(self, key: str, limit: int, window_seconds: int, member_id: Optional[str] = None) -> Tuple[bool, Optional[int]]:
10+
async def allow_request(self, key: str, limit: int, window_seconds: int, member_id: str | None = None) -> tuple[bool, int | None]:
1011
"""
1112
Return (allowed: bool, retry_after_seconds: Optional[int]).
1213
If allowed True -> retry_after_seconds is None.
Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1-
from typing import Optional
2-
from app.core.rate_limiter.rate_limiting_algorithm.base import BaseRateLimiter
3-
from app.core.rate_limiter.rate_limiting_algorithm.sliding_window import SlidingWindowRateLimiter
1+
42
import redis.asyncio as redis
53

6-
def get_rate_limiter(strategy: Optional[str], redis_url: Optional[str], fail_open: Optional[str]) -> Optional[BaseRateLimiter]:
4+
from app.core.rate_limiter.rate_limiting_algorithm.base import BaseRateLimiter
5+
from app.core.rate_limiter.rate_limiting_algorithm.sliding_window import (
6+
SlidingWindowRateLimiter,
7+
)
8+
9+
10+
def get_rate_limiter(strategy: str | None, redis_url: str | None, fail_open: bool | None) -> BaseRateLimiter | None:
711
"""
812
Factory: returns an instance of BaseRateLimiter or None (if disabled).
913
"""
@@ -13,10 +17,10 @@ def get_rate_limiter(strategy: Optional[str], redis_url: Optional[str], fail_ope
1317
if not redis_url:
1418
return None
1519

16-
rc = redis.from_url(redis_url, encoding="utf-8", decode_responses=True)
20+
rc: redis.Redis = redis.from_url(redis_url, encoding="utf-8", decode_responses=True) # type: ignore[no-untyped-call]
1721
st = strategy.lower()
1822
if st == "sliding_window" or st == "sliding-window":
1923
return SlidingWindowRateLimiter(rc, fail_open or False)
2024

2125
# extendable for other strategies
22-
raise ValueError(f"Unknown rate limiter strategy: {strategy}")
26+
raise ValueError(f"Unknown rate limiter strategy: {strategy}")

backend/app/core/rate_limiter/rate_limiting_algorithm/sliding_window.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
1-
import time
21
import logging
2+
import time
33
from pathlib import Path
4-
from typing import Optional, Tuple
54

65
import redis.asyncio as redis
76

87
from app.core.rate_limiter.rate_limiting_algorithm.base import BaseRateLimiter
98

109
logger = logging.getLogger(__name__)
1110

12-
SCRIPT_PATH = Path(__file__).parent /".."/".."/".."/"alembic"/ "rate_limiting_algorithms" / "sliding_window.lua"
11+
SCRIPT_PATH = (
12+
Path(__file__).resolve()
13+
.parents[3]
14+
/ "alembic"
15+
/ "rate_limiting_algorithms"
16+
/ "sliding_window.lua"
17+
)
1318

1419

1520
class SlidingWindowRateLimiter(BaseRateLimiter):
@@ -18,7 +23,7 @@ def __init__(self, redis_client: redis.Redis, fail_open: bool):
1823
self.lua_script = None
1924
self.fail_open = fail_open
2025

21-
async def load_script(self):
26+
async def load_script(self) -> str | None:
2227
if self.lua_script is None:
2328
script_text = SCRIPT_PATH.read_text()
2429
# LOAD script into redis → returns SHA
@@ -30,16 +35,18 @@ async def allow_request(
3035
key: str,
3136
limit: int,
3237
window_seconds: int,
33-
member_id: Optional[str] = None
34-
) -> Tuple[bool, Optional[int]]:
38+
member_id: str | None = None
39+
) -> tuple[bool, int | None]:
3540

3641
now_ms = int(time.time() * 1000)
3742
window_ms = window_seconds * 1000
3843
member = member_id or f"{now_ms}"
3944

4045
try:
4146
sha = await self.load_script()
42-
res = await self.redis.evalsha(
47+
if sha is None:
48+
raise Exception
49+
res = await self.redis.evalsha(# type: ignore[misc]
4350
sha,
4451
1,
4552
key,
@@ -62,5 +69,5 @@ async def allow_request(
6269

6370
return False, retry_after_s
6471

65-
def get_fail_open(self):
72+
def get_fail_open(self) -> bool:
6673
return self.fail_open

backend/app/main.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
from app.api.main import api_router
77
from app.core.config import settings
8-
98
from app.core.rate_limiter.rate_limiting_algorithm.registry import get_rate_limiter
109

1110

backend/tests/rate_limiter/test_dependency_rate_limit.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
from unittest.mock import AsyncMock
2+
13
import pytest
2-
from fastapi import FastAPI, Depends
4+
from fastapi import Depends, FastAPI
35
from fastapi.testclient import TestClient
4-
from unittest.mock import AsyncMock
56

67
from app.core.rate_limiter.rate_limiter import RateLimiter
78

0 commit comments

Comments
 (0)