Skip to content

Commit f45aacd

Browse files
authored
Merge pull request #8 from r3tr0ananas/main
Add SlowAPI
2 parents 1054901 + 27d463b commit f45aacd

File tree

4 files changed

+80
-11
lines changed

4 files changed

+80
-11
lines changed

api/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "1.3.2"
1+
__version__ = "1.3.5"

api/errors.py

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
1+
from fastapi import Request
2+
from slowapi.errors import RateLimitExceeded
3+
from fastapi.responses import JSONResponse
14
from pydantic import BaseModel
25

6+
__all__ = (
7+
"APIException",
8+
"CategoryNotFound",
9+
"BookNotFound",
10+
"RateLimited",
11+
"rate_limit_handler"
12+
)
13+
314
class APIException(Exception):
415
def __init__(self, msg) -> None:
516
self.msg = msg
@@ -34,4 +45,35 @@ class BookNotFound(BaseModel):
3445
}
3546
]
3647
}
37-
}
48+
}
49+
50+
class RateLimited(BaseModel):
51+
error: str
52+
message: str
53+
54+
model_config = {
55+
"json_schema_extra": {
56+
"examples": [
57+
{
58+
"error": "RateLimited",
59+
"message": "Rate Limit exceeded: 3 per 1 second"
60+
}
61+
]
62+
}
63+
}
64+
65+
66+
def rate_limit_handler(request: Request, exc: RateLimitExceeded):
67+
response = JSONResponse(
68+
status_code = 429,
69+
content = {
70+
"error": "RateLimited",
71+
"message": f"Rate limit exceeded: {exc.detail} (Follow the rates: https://github.com/THEGOLDENPRO/aghpb_api/wiki#rate-limiting)"
72+
}
73+
)
74+
75+
response = request.app.state.limiter._inject_headers(
76+
response, request.state.view_rate_limit
77+
)
78+
79+
return response

api/main.py

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
from __future__ import annotations
21
from typing import TYPE_CHECKING, List # DON'T YOU DARE PUT THIS UNDER TYPE_CHECKING!!! I'm warning you!
32

43
if TYPE_CHECKING:
@@ -9,9 +8,13 @@
98
from . import errors, __version__
109
from .anime_girls import AGHPB, CategoryNotFound, Book, BookDict
1110

12-
from fastapi import FastAPI, Query
11+
from fastapi import FastAPI, Query, Request
1312
from fastapi.responses import FileResponse, JSONResponse, RedirectResponse
1413

14+
from slowapi import Limiter
15+
from slowapi.util import get_remote_address
16+
from slowapi.errors import RateLimitExceeded
17+
1518
ROOT_PATH = (lambda x: x if x is not None else "")(os.environ.get("ROOT_PATH")) # Like: /aghpb/v1
1619

1720
TAGS_METADATA = [
@@ -27,13 +30,24 @@
2730
]
2831

2932
DESCRIPTION = """
30-
Behold the **anime girls holding programming books** API. ✴️
33+
<div align="center">
34+
35+
<img src="https://raw.githubusercontent.com/THEGOLDENPRO/aghpb_api/main/assets/logo.png" alt="Logo" width="180">
36+
37+
Behold the **anime girls holding programming books** API. ✴️
38+
39+
This is a ✨ feature rich 🌟 [open source](https://github.com/THEGOLDENPRO/aghpb_api) API I made for the anime girls holding programming books [github repo](https://github.com/cat-milk/Anime-Girls-Holding-Programming-Books) because I was bored.
40+
41+
🐞 Report bugs [over here](https://github.com/THEGOLDENPRO/aghpb_api/issues).
3142
32-
This is a ✨ feature rich 🌟 [open source](https://github.com/THEGOLDENPRO/aghpb_api) API I made for the anime girls holding programming books [github repo](https://github.com/cat-milk/Anime-Girls-Holding-Programming-Books) because I was bored.
43+
</div>
3344
34-
🐞 Report bugs [over here](https://github.com/THEGOLDENPRO/aghpb_api/issues).
45+
<br>
46+
47+
Rate limiting applies to the ``/random`` and ``/get`` endpoints. Check out the rate limits [over here](https://github.com/THEGOLDENPRO/aghpb_api/wiki#rate-limiting).
3548
"""
3649

50+
limiter = Limiter(key_func=get_remote_address, headers_enabled=True)
3751
app = FastAPI(
3852
title = "AGHPB API",
3953
description = DESCRIPTION,
@@ -46,6 +60,9 @@
4660

4761
root_path = ROOT_PATH
4862
)
63+
app.state.limiter = limiter
64+
app.add_exception_handler(RateLimitExceeded, errors.rate_limit_handler)
65+
4966
@app.get(
5067
"/",
5168
name = "Takes you to these docs.",
@@ -74,10 +91,15 @@ async def root():
7491
404: {
7592
"model": errors.CategoryNotFound,
7693
"description": "The category was not Found."
94+
},
95+
429: {
96+
"model": errors.RateLimited,
97+
"description": "Rate limit exceeded!"
7798
}
7899
},
79100
)
80-
async def random(category: str = None) -> FileResponse:
101+
@limiter.limit("3/second")
102+
async def random(request: Request, category: str = None) -> FileResponse:
81103
"""Returns a random book."""
82104
if category is None:
83105
category = aghpb.random_category()
@@ -138,7 +160,6 @@ async def search(
138160
book[1].to_dict() for book in books
139161
]
140162

141-
142163
@app.get(
143164
"/get/id/{search_id}",
144165
name = "Allows you to get a book by search id.",
@@ -155,10 +176,15 @@ async def search(
155176
404: {
156177
"model": errors.BookNotFound,
157178
"description": "The book was not Found."
179+
},
180+
429: {
181+
"model": errors.RateLimited,
182+
"description": "Rate Limit exceeded"
158183
}
159184
},
160185
)
161-
async def get_id(search_id: str) -> FileResponse:
186+
@limiter.limit("3/second")
187+
async def get_id(request: Request, search_id: str) -> FileResponse:
162188
"""Returns the book found."""
163189
for book in aghpb.books:
164190

requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ thefuzz
22
fastapi[all]
33
devgoldyutils
44
typing-extensions
5-
uvicorn[standard]
5+
uvicorn[standard]
6+
slowapi

0 commit comments

Comments
 (0)