ratelimiter-keshav 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ratelimiter_keshav-0.1.0/.coverage +0 -0
- ratelimiter_keshav-0.1.0/.github/workflows/tests.yml +48 -0
- ratelimiter_keshav-0.1.0/CHANGELOG.md +16 -0
- ratelimiter_keshav-0.1.0/PKG-INFO +35 -0
- ratelimiter_keshav-0.1.0/README.md +17 -0
- ratelimiter_keshav-0.1.0/pyproject.toml +38 -0
- ratelimiter_keshav-0.1.0/rate_limiter/DESIGN.md +31 -0
- ratelimiter_keshav-0.1.0/rate_limiter/__init__.py +4 -0
- ratelimiter_keshav-0.1.0/rate_limiter/algorithms/__init__.py +4 -0
- ratelimiter_keshav-0.1.0/rate_limiter/algorithms/base.py +13 -0
- ratelimiter_keshav-0.1.0/rate_limiter/algorithms/fixed_window.py +31 -0
- ratelimiter_keshav-0.1.0/rate_limiter/algorithms/sliding_window_counter.py +90 -0
- ratelimiter_keshav-0.1.0/rate_limiter/algorithms/sliding_window_log.py +67 -0
- ratelimiter_keshav-0.1.0/rate_limiter/algorithms/token_bucket.py +71 -0
- ratelimiter_keshav-0.1.0/rate_limiter/core/exceptions.py +5 -0
- ratelimiter_keshav-0.1.0/rate_limiter/core/limiter.py +39 -0
- ratelimiter_keshav-0.1.0/rate_limiter/core/models.py +9 -0
- ratelimiter_keshav-0.1.0/rate_limiter/examples/__init__.py +0 -0
- ratelimiter_keshav-0.1.0/rate_limiter/examples/fastapi_demo.py +34 -0
- ratelimiter_keshav-0.1.0/rate_limiter/examples/flask_demo.py +38 -0
- ratelimiter_keshav-0.1.0/rate_limiter/middleware/__init__.py +2 -0
- ratelimiter_keshav-0.1.0/rate_limiter/middleware/fastapi_middleware.py +131 -0
- ratelimiter_keshav-0.1.0/rate_limiter/middleware/flask_middleware.py +125 -0
- ratelimiter_keshav-0.1.0/rate_limiter/storage/__init__.py +6 -0
- ratelimiter_keshav-0.1.0/rate_limiter/storage/base.py +30 -0
- ratelimiter_keshav-0.1.0/rate_limiter/storage/in_memory.py +62 -0
- ratelimiter_keshav-0.1.0/rate_limiter/storage/redis_backend.py +55 -0
- ratelimiter_keshav-0.1.0/rate_limiter/tests/test_algorithms.py +134 -0
- ratelimiter_keshav-0.1.0/rate_limiter/tests/test_concurrency.py +99 -0
- ratelimiter_keshav-0.1.0/rate_limiter/tests/test_fastapi_middleware.py +61 -0
- ratelimiter_keshav-0.1.0/rate_limiter/tests/test_flask_middleware.py +60 -0
- ratelimiter_keshav-0.1.0/rate_limiter/tests/test_integration.py +38 -0
- ratelimiter_keshav-0.1.0/rate_limiter/tests/test_limiter.py +18 -0
- ratelimiter_keshav-0.1.0/rate_limiter/tests/test_sliding_window_counter.py +91 -0
- ratelimiter_keshav-0.1.0/rate_limiter/tests/test_sliding_window_log.py +62 -0
- ratelimiter_keshav-0.1.0/rate_limiter/tests/test_storage.py +72 -0
|
Binary file
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
name: Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
- master
|
|
8
|
+
|
|
9
|
+
pull_request:
|
|
10
|
+
branches:
|
|
11
|
+
- main
|
|
12
|
+
- master
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
test:
|
|
16
|
+
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
|
|
19
|
+
strategy:
|
|
20
|
+
matrix:
|
|
21
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
22
|
+
|
|
23
|
+
steps:
|
|
24
|
+
|
|
25
|
+
- name: Checkout Repository
|
|
26
|
+
uses: actions/checkout@v4
|
|
27
|
+
|
|
28
|
+
- name: Setup Python
|
|
29
|
+
uses: actions/setup-python@v5
|
|
30
|
+
with:
|
|
31
|
+
python-version: ${{ matrix.python-version }}
|
|
32
|
+
|
|
33
|
+
- name: Upgrade pip
|
|
34
|
+
run: |
|
|
35
|
+
python -m pip install --upgrade pip
|
|
36
|
+
|
|
37
|
+
- name: Install Dependencies
|
|
38
|
+
run: |
|
|
39
|
+
pip install pytest pytest-cov
|
|
40
|
+
pip install flask fastapi uvicorn redis httpx
|
|
41
|
+
|
|
42
|
+
- name: Run Tests
|
|
43
|
+
run: |
|
|
44
|
+
pytest
|
|
45
|
+
|
|
46
|
+
- name: Run Coverage
|
|
47
|
+
run: |
|
|
48
|
+
pytest --cov=rate_limiter
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## v0.1.0
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Fixed Window Algorithm
|
|
8
|
+
- Sliding Window Log
|
|
9
|
+
- Sliding Window Counter
|
|
10
|
+
- Token Bucket
|
|
11
|
+
- InMemoryStorage
|
|
12
|
+
- RedisStorage
|
|
13
|
+
- Flask Middleware
|
|
14
|
+
- FastAPI Middleware
|
|
15
|
+
- Concurrency Tests
|
|
16
|
+
- Integration Tests
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ratelimiter-keshav
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A pluggable rate limiting library for Python web frameworks
|
|
5
|
+
Author: Keshav Sharma
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Requires-Dist: redis>=4.0
|
|
8
|
+
Provides-Extra: dev
|
|
9
|
+
Requires-Dist: httpx; extra == 'dev'
|
|
10
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
11
|
+
Requires-Dist: pytest-cov; extra == 'dev'
|
|
12
|
+
Provides-Extra: fastapi
|
|
13
|
+
Requires-Dist: fastapi>=0.95; extra == 'fastapi'
|
|
14
|
+
Requires-Dist: starlette; extra == 'fastapi'
|
|
15
|
+
Provides-Extra: flask
|
|
16
|
+
Requires-Dist: flask>=2.0; extra == 'flask'
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# API Rate Limiter
|
|
20
|
+
|
|
21
|
+
A pluggable rate limiting library for Python web frameworks.
|
|
22
|
+
|
|
23
|
+
## Features
|
|
24
|
+
|
|
25
|
+
- Fixed Window
|
|
26
|
+
- Sliding Window Log
|
|
27
|
+
- Sliding Window Counter
|
|
28
|
+
- Token Bucket
|
|
29
|
+
- Flask Middleware
|
|
30
|
+
- FastAPI Middleware
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install ratelimiter-keshav
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# API Rate Limiter
|
|
2
|
+
|
|
3
|
+
A pluggable rate limiting library for Python web frameworks.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Fixed Window
|
|
8
|
+
- Sliding Window Log
|
|
9
|
+
- Sliding Window Counter
|
|
10
|
+
- Token Bucket
|
|
11
|
+
- Flask Middleware
|
|
12
|
+
- FastAPI Middleware
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install ratelimiter-keshav
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ratelimiter-keshav"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "A pluggable rate limiting library for Python web frameworks"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
|
|
12
|
+
dependencies = [
|
|
13
|
+
"redis>=4.0"
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
authors = [
|
|
17
|
+
{name = "Keshav Sharma"}
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[project.optional-dependencies]
|
|
21
|
+
|
|
22
|
+
flask = [
|
|
23
|
+
"flask>=2.0"
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
fastapi = [
|
|
27
|
+
"fastapi>=0.95",
|
|
28
|
+
"starlette"
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
dev = [
|
|
32
|
+
"pytest",
|
|
33
|
+
"pytest-cov",
|
|
34
|
+
"httpx"
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[tool.hatch.build.targets.wheel]
|
|
38
|
+
packages = ["rate_limiter"]
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Design Decisions
|
|
2
|
+
|
|
3
|
+
## Storage
|
|
4
|
+
|
|
5
|
+
Supported:
|
|
6
|
+
- InMemoryStorage
|
|
7
|
+
- RedisStorage
|
|
8
|
+
|
|
9
|
+
Future:
|
|
10
|
+
- PostgreSQL
|
|
11
|
+
- DynamoDB
|
|
12
|
+
|
|
13
|
+
## Thread Safety
|
|
14
|
+
|
|
15
|
+
InMemoryStorage will use threading.Lock.
|
|
16
|
+
|
|
17
|
+
Redis relies on atomic operations.
|
|
18
|
+
|
|
19
|
+
## Clock Source
|
|
20
|
+
|
|
21
|
+
Server time is authoritative.
|
|
22
|
+
|
|
23
|
+
Client timestamps are ignored.
|
|
24
|
+
|
|
25
|
+
## Rate Limits
|
|
26
|
+
|
|
27
|
+
Supports:
|
|
28
|
+
- Global limits
|
|
29
|
+
- Route-specific limits
|
|
30
|
+
|
|
31
|
+
Route-specific limits override global limits.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import time
|
|
2
|
+
|
|
3
|
+
from .base import RateLimitAlgorithm
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class FixedWindow(RateLimitAlgorithm):
|
|
7
|
+
|
|
8
|
+
def __init__(
|
|
9
|
+
self,
|
|
10
|
+
limit: int,
|
|
11
|
+
window_seconds: int
|
|
12
|
+
):
|
|
13
|
+
self.limit = limit
|
|
14
|
+
self.window = window_seconds
|
|
15
|
+
|
|
16
|
+
def is_allowed(
|
|
17
|
+
self,
|
|
18
|
+
key: str,
|
|
19
|
+
store
|
|
20
|
+
) -> tuple[bool, dict]:
|
|
21
|
+
now = int(time.time())
|
|
22
|
+
window_key = f"{key}:{now // self.window}"
|
|
23
|
+
count = store.increment(window_key, ttl=self.window)
|
|
24
|
+
remaining = max(0, self.limit - count)
|
|
25
|
+
return count <= self.limit, {
|
|
26
|
+
"limit": self.limit,
|
|
27
|
+
"remaining": remaining,
|
|
28
|
+
"reset": (now // self.window + 1) * self.window
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import time
|
|
2
|
+
|
|
3
|
+
from .base import RateLimitAlgorithm
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SlidingWindowCounter(RateLimitAlgorithm):
|
|
7
|
+
|
|
8
|
+
def __init__(
|
|
9
|
+
self,
|
|
10
|
+
limit: int,
|
|
11
|
+
window_seconds: int
|
|
12
|
+
):
|
|
13
|
+
self.limit = limit
|
|
14
|
+
self.window = window_seconds
|
|
15
|
+
|
|
16
|
+
def is_allowed(
|
|
17
|
+
self,
|
|
18
|
+
key: str,
|
|
19
|
+
store
|
|
20
|
+
) -> tuple[bool, dict]:
|
|
21
|
+
|
|
22
|
+
now = time.time()
|
|
23
|
+
|
|
24
|
+
current_window = int(
|
|
25
|
+
now // self.window
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
current_key = (
|
|
29
|
+
f"{key}:{current_window}"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
previous_key = (
|
|
33
|
+
f"{key}:{current_window - 1}"
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
current_count = (
|
|
37
|
+
store.get(current_key)
|
|
38
|
+
or 0
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
previous_count = (
|
|
42
|
+
store.get(previous_key)
|
|
43
|
+
or 0
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
elapsed = (
|
|
47
|
+
now % self.window
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
weight = (
|
|
51
|
+
self.window - elapsed
|
|
52
|
+
) / self.window
|
|
53
|
+
|
|
54
|
+
estimated_count = (
|
|
55
|
+
current_count +
|
|
56
|
+
previous_count * weight
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
if estimated_count >= self.limit:
|
|
60
|
+
|
|
61
|
+
return False, {
|
|
62
|
+
"limit": self.limit,
|
|
63
|
+
"remaining": 0,
|
|
64
|
+
"reset":
|
|
65
|
+
(
|
|
66
|
+
current_window + 1
|
|
67
|
+
) * self.window
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
current_count = store.increment(
|
|
71
|
+
current_key,
|
|
72
|
+
ttl=self.window * 2
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
return True, {
|
|
76
|
+
"limit": self.limit,
|
|
77
|
+
"remaining":
|
|
78
|
+
max(
|
|
79
|
+
0,
|
|
80
|
+
int(
|
|
81
|
+
self.limit -
|
|
82
|
+
estimated_count -
|
|
83
|
+
1
|
|
84
|
+
)
|
|
85
|
+
),
|
|
86
|
+
"reset":
|
|
87
|
+
(
|
|
88
|
+
current_window + 1
|
|
89
|
+
) * self.window
|
|
90
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import time
|
|
2
|
+
|
|
3
|
+
from .base import RateLimitAlgorithm
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SlidingWindowLog(RateLimitAlgorithm):
|
|
7
|
+
|
|
8
|
+
def __init__(
|
|
9
|
+
self,
|
|
10
|
+
limit: int,
|
|
11
|
+
window_seconds: int
|
|
12
|
+
):
|
|
13
|
+
self.limit = limit
|
|
14
|
+
self.window = window_seconds
|
|
15
|
+
|
|
16
|
+
def is_allowed(
|
|
17
|
+
self,
|
|
18
|
+
key: str,
|
|
19
|
+
store
|
|
20
|
+
) -> tuple[bool, dict]:
|
|
21
|
+
|
|
22
|
+
now = time.time()
|
|
23
|
+
|
|
24
|
+
timestamps = store.get(key)
|
|
25
|
+
|
|
26
|
+
if timestamps is None:
|
|
27
|
+
timestamps = []
|
|
28
|
+
|
|
29
|
+
timestamps = [
|
|
30
|
+
ts
|
|
31
|
+
for ts in timestamps
|
|
32
|
+
if now - ts < self.window
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
if len(timestamps) >= self.limit:
|
|
36
|
+
|
|
37
|
+
store.set(
|
|
38
|
+
key,
|
|
39
|
+
timestamps,
|
|
40
|
+
ttl=self.window
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
return False, {
|
|
44
|
+
"limit": self.limit,
|
|
45
|
+
"remaining": 0,
|
|
46
|
+
"reset": int(
|
|
47
|
+
timestamps[0] +
|
|
48
|
+
self.window
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
timestamps.append(now)
|
|
53
|
+
|
|
54
|
+
store.set(
|
|
55
|
+
key,
|
|
56
|
+
timestamps,
|
|
57
|
+
ttl=self.window
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
return True, {
|
|
61
|
+
"limit": self.limit,
|
|
62
|
+
"remaining":
|
|
63
|
+
self.limit -
|
|
64
|
+
len(timestamps),
|
|
65
|
+
"reset":
|
|
66
|
+
int(now + self.window)
|
|
67
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import time
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class TokenBucket:
|
|
5
|
+
|
|
6
|
+
def __init__(
|
|
7
|
+
self,
|
|
8
|
+
capacity: int,
|
|
9
|
+
refill_rate: float
|
|
10
|
+
):
|
|
11
|
+
self.capacity = capacity
|
|
12
|
+
self.refill_rate = refill_rate
|
|
13
|
+
|
|
14
|
+
def is_allowed(
|
|
15
|
+
self,
|
|
16
|
+
key: str,
|
|
17
|
+
store
|
|
18
|
+
) -> tuple[bool, dict]:
|
|
19
|
+
|
|
20
|
+
now = time.time()
|
|
21
|
+
|
|
22
|
+
bucket = store.get(key)
|
|
23
|
+
|
|
24
|
+
if bucket is None:
|
|
25
|
+
bucket = {
|
|
26
|
+
"tokens": self.capacity,
|
|
27
|
+
"last": now
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
elapsed = now - bucket["last"]
|
|
31
|
+
|
|
32
|
+
bucket["tokens"] = min(
|
|
33
|
+
self.capacity,
|
|
34
|
+
bucket["tokens"] +
|
|
35
|
+
elapsed * self.refill_rate
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
bucket["last"] = now
|
|
39
|
+
|
|
40
|
+
if bucket["tokens"] >= 1:
|
|
41
|
+
|
|
42
|
+
bucket["tokens"] -= 1
|
|
43
|
+
|
|
44
|
+
store.set(
|
|
45
|
+
key,
|
|
46
|
+
bucket
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
return True, {
|
|
50
|
+
"remaining":
|
|
51
|
+
int(bucket["tokens"])
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
store.set(
|
|
55
|
+
key,
|
|
56
|
+
bucket
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
retry_after = None
|
|
60
|
+
|
|
61
|
+
if self.refill_rate > 0:
|
|
62
|
+
|
|
63
|
+
retry_after = (
|
|
64
|
+
(1 - bucket["tokens"])
|
|
65
|
+
/ self.refill_rate
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
return False, {
|
|
69
|
+
"retry_after":
|
|
70
|
+
retry_after
|
|
71
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from functools import wraps
|
|
2
|
+
class RateLimiter:
|
|
3
|
+
|
|
4
|
+
def __init__(
|
|
5
|
+
self,
|
|
6
|
+
algorithm,
|
|
7
|
+
storage,
|
|
8
|
+
key_func=None
|
|
9
|
+
):
|
|
10
|
+
self.algorithm = algorithm
|
|
11
|
+
self.storage = storage
|
|
12
|
+
self.key_func = key_func
|
|
13
|
+
|
|
14
|
+
def check(self, key):
|
|
15
|
+
return self.algorithm.is_allowed(
|
|
16
|
+
key,
|
|
17
|
+
self.storage
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def limit(self):
|
|
22
|
+
|
|
23
|
+
def decorator(func):
|
|
24
|
+
|
|
25
|
+
@wraps(func)
|
|
26
|
+
def wrapper(*args, **kwargs):
|
|
27
|
+
|
|
28
|
+
allowed, meta = self.check("global")
|
|
29
|
+
|
|
30
|
+
if not allowed:
|
|
31
|
+
raise Exception(
|
|
32
|
+
"Rate limit exceeded"
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
return func(*args, **kwargs)
|
|
36
|
+
|
|
37
|
+
return wrapper
|
|
38
|
+
|
|
39
|
+
return decorator
|
|
File without changes
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from fastapi import FastAPI
|
|
2
|
+
|
|
3
|
+
from rate_limiter.storage import (
|
|
4
|
+
InMemoryStorage
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
from rate_limiter.algorithms import (
|
|
8
|
+
FixedWindow
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
from rate_limiter.middleware import (
|
|
12
|
+
FastAPIRateLimiter
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
app = FastAPI()
|
|
17
|
+
|
|
18
|
+
app.add_middleware(
|
|
19
|
+
FastAPIRateLimiter,
|
|
20
|
+
algorithm=FixedWindow(
|
|
21
|
+
limit=3,
|
|
22
|
+
window_seconds=60
|
|
23
|
+
),
|
|
24
|
+
storage=InMemoryStorage()
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@app.get("/")
|
|
29
|
+
def home():
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
"message":
|
|
33
|
+
"hello fastapi"
|
|
34
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from flask import Flask
|
|
2
|
+
|
|
3
|
+
from rate_limiter.storage import (
|
|
4
|
+
InMemoryStorage
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
from rate_limiter.algorithms import (
|
|
8
|
+
FixedWindow
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
from rate_limiter.middleware import (
|
|
12
|
+
FlaskRateLimiter
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
app = Flask(__name__)
|
|
17
|
+
|
|
18
|
+
limiter = FlaskRateLimiter(
|
|
19
|
+
algorithm=FixedWindow(
|
|
20
|
+
limit=3,
|
|
21
|
+
window_seconds=60
|
|
22
|
+
),
|
|
23
|
+
storage=InMemoryStorage()
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@app.route("/")
|
|
28
|
+
@limiter.limit()
|
|
29
|
+
def home():
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
"message":
|
|
33
|
+
"hello world"
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
if __name__ == "__main__":
|
|
38
|
+
app.run(debug=True)
|