tortle 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.
- tortle-0.1.0/PKG-INFO +10 -0
- tortle-0.1.0/README.md +0 -0
- tortle-0.1.0/pyproject.toml +18 -0
- tortle-0.1.0/pyproject.toml.orig +15 -0
- tortle-0.1.0/src/tortle/__init__.py +3 -0
- tortle-0.1.0/src/tortle/slidinglog.py +58 -0
tortle-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: tortle
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Add your description here
|
|
5
|
+
Author: Abdul Karim
|
|
6
|
+
Author-email: Abdul Karim <karim.develops@gmail.com>
|
|
7
|
+
Requires-Dist: fastapi[standard]>=0.141.1
|
|
8
|
+
Requires-Python: >=3.14
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
|
tortle-0.1.0/README.md
ADDED
|
File without changes
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "tortle"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Add your description here"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.14"
|
|
7
|
+
dependencies = ["fastapi[standard]>=0.141.1"]
|
|
8
|
+
|
|
9
|
+
[[project.authors]]
|
|
10
|
+
name = "Abdul Karim"
|
|
11
|
+
email = "karim.develops@gmail.com"
|
|
12
|
+
|
|
13
|
+
[project.scripts]
|
|
14
|
+
tortle = "tortle:main"
|
|
15
|
+
|
|
16
|
+
[build-system]
|
|
17
|
+
requires = ["uv_build>=0.12.5,<0.13.0"]
|
|
18
|
+
build-backend = "uv_build"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "tortle"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Add your description here"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [{ name = "Abdul Karim", email = "karim.develops@gmail.com" }]
|
|
7
|
+
requires-python = ">=3.14"
|
|
8
|
+
dependencies = ["fastapi[standard]>=0.141.1"]
|
|
9
|
+
|
|
10
|
+
[project.scripts]
|
|
11
|
+
tortle = "tortle:main"
|
|
12
|
+
|
|
13
|
+
[build-system]
|
|
14
|
+
requires = ["uv_build>=0.12.5,<0.13.0"]
|
|
15
|
+
build-backend = "uv_build"
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import time
|
|
2
|
+
|
|
3
|
+
from fastapi import Request, status
|
|
4
|
+
from fastapi.responses import JSONResponse
|
|
5
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Counter:
|
|
9
|
+
def __init__(self) -> None:
|
|
10
|
+
self.value = 0
|
|
11
|
+
self.start_time = time.time()
|
|
12
|
+
|
|
13
|
+
def increment(self):
|
|
14
|
+
self.value += 1
|
|
15
|
+
|
|
16
|
+
def reset(self):
|
|
17
|
+
self.value = 0
|
|
18
|
+
self.start_time = time.time()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class SlidingLogLimiter(BaseHTTPMiddleware):
|
|
22
|
+
def __init__(self, app, limit, window):
|
|
23
|
+
super().__init__(app)
|
|
24
|
+
self.limit = limit
|
|
25
|
+
self.window = window
|
|
26
|
+
self.counters: dict[str, Counter] = {}
|
|
27
|
+
|
|
28
|
+
async def dispatch(self, request: Request, call_next):
|
|
29
|
+
|
|
30
|
+
if not request.client:
|
|
31
|
+
return JSONResponse(
|
|
32
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
33
|
+
content={"detail": "host doesn't exist"},
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
ip = request.client.host
|
|
37
|
+
|
|
38
|
+
if ip not in self.counters:
|
|
39
|
+
counter = Counter()
|
|
40
|
+
self.counters[ip] = counter
|
|
41
|
+
|
|
42
|
+
self.counters[ip].increment()
|
|
43
|
+
|
|
44
|
+
current_time = time.time()
|
|
45
|
+
elapsed_seconds = current_time - self.counters[ip].start_time
|
|
46
|
+
|
|
47
|
+
if elapsed_seconds > self.window:
|
|
48
|
+
self.counters[ip].reset()
|
|
49
|
+
|
|
50
|
+
if self.counters[ip].value > self.limit:
|
|
51
|
+
return JSONResponse(
|
|
52
|
+
status_code=status.HTTP_429_TOO_MANY_REQUESTS,
|
|
53
|
+
content={"detail": "Too many reqs"},
|
|
54
|
+
headers={"Retry-After": f"{60 - elapsed_seconds}"},
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
response = await call_next(request)
|
|
58
|
+
return response
|