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 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,3 @@
1
+ from .slidinglog import SlidingLogLimiter
2
+
3
+ __all__ = ["SlidingLogLimiter"]
@@ -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