Skip to content

Commit 966b0da

Browse files
authored
Merge pull request #128 from nanotaboada/feature/fastapi-cache2
feat(routes): add in-memory cache
2 parents d9a1777 + fdeff68 commit 966b0da

File tree

5 files changed

+94
-24
lines changed

5 files changed

+94
-24
lines changed

main.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,17 @@
22
# Main
33
# ------------------------------------------------------------------------------
44

5+
from contextlib import asynccontextmanager
56
from fastapi import FastAPI
7+
from fastapi_cache import FastAPICache
8+
from fastapi_cache.backends.inmemory import InMemoryBackend
69
from routes import player_route
710

8-
app = FastAPI()
11+
12+
@asynccontextmanager
13+
async def lifespan(app: FastAPI):
14+
FastAPICache.init(InMemoryBackend())
15+
yield
16+
17+
app = FastAPI(lifespan=lifespan)
918
app.include_router(player_route.api_router)

requirements.txt

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,75 @@
1+
aiohttp==3.9.5
2+
aiosignal==1.3.1
13
annotated-types==0.7.0
24
anyio==4.4.0
5+
attrs==23.2.0
36
certifi==2024.6.2
7+
charset-normalizer==3.3.2
48
click==8.1.7
9+
colorclass==2.2.2
510
coverage==7.5.4
11+
dnspython==2.6.1
12+
docopt==0.6.2
13+
email_validator==2.1.2
614
fastapi==0.111.0
15+
fastapi-cache2==0.2.1
16+
fastapi-cli==0.0.4
717
flake8==7.1.0
18+
frozenlist==1.4.1
819
greenlet==3.0.3
920
h11==0.14.0
1021
httpcore==1.0.5
1122
httptools==0.6.1
1223
httpx==0.27.0
1324
idna==3.7
25+
ijson==3.2.3
1426
iniconfig==2.0.0
27+
Jinja2==3.1.4
28+
markdown-it-py==3.0.0
29+
MarkupSafe==2.1.5
1530
mccabe==0.7.0
31+
mdurl==0.1.2
32+
multidict==6.0.5
33+
orjson==3.10.5
1634
packaging==24.1
35+
pendulum==3.0.0
1736
pluggy==1.5.0
37+
pur==7.3.1
1838
pycodestyle==2.12.0
1939
pydantic==2.7.4
2040
pydantic_core
41+
pydeps==1.12.19
2142
pyflakes==3.2.0
43+
Pygments==2.18.0
2244
pytest==8.2.2
2345
pytest-cov==5.0.0
2446
pytest-sugar==1.0.0
47+
python-dateutil==2.9.0.post0
2548
python-dotenv==1.0.1
49+
python-multipart==0.0.9
2650
PyYAML==6.0.1
51+
requests==2.31.0
52+
responses==0.21.0
53+
rfc3986==1.5.0
54+
rich==13.7.1
55+
setuptools==69.5.1
56+
shellingham==1.5.4
57+
six==1.16.0
2758
sniffio==1.3.1
2859
SQLAlchemy==2.0.31
2960
starlette
61+
stdlib-list==0.10.0
62+
termcolor==2.4.0
63+
terminaltables==3.1.10
64+
time-machine==2.14.1
65+
tree-sitter==0.20.4
66+
typer==0.12.3
3067
typing_extensions==4.12.2
68+
tzdata==2024.1
69+
ujson==5.10.0
70+
urllib3==2.2.1
3171
uvicorn==0.30.1
3272
uvloop==0.19.0
3373
watchfiles==0.22.0
3474
websockets==12.0
75+
yarl==1.9.4

routes/player_route.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,19 @@
55
from typing import List
66
from fastapi import APIRouter, Body, Depends, HTTPException, status, Path
77
from sqlalchemy.orm import Session
8+
from fastapi_cache import FastAPICache
9+
from fastapi_cache.decorator import cache
810
from data.player_database import OrmSession
911
from models.player_model import PlayerModel
1012
from services import player_service
1113

1214
api_router = APIRouter()
1315

16+
CACHING_TIME_IN_SECONDS = 600
1417

1518
# https://fastapi.tiangolo.com/tutorial/sql-databases/#create-a-dependency
19+
20+
1621
def get_orm_session():
1722
orm_session = OrmSession()
1823
try:
@@ -39,6 +44,8 @@ def post(
3944

4045
player_service.create(orm_session, player_model)
4146

47+
FastAPICache.clear()
48+
4249
# GET --------------------------------------------------------------------------
4350

4451

@@ -48,6 +55,7 @@ def post(
4855
status_code=status.HTTP_200_OK,
4956
summary="Retrieves a collection of Players"
5057
)
58+
@cache(expire=CACHING_TIME_IN_SECONDS)
5159
def get_all(
5260
orm_session: Session = Depends(get_orm_session)
5361
):
@@ -62,6 +70,7 @@ def get_all(
6270
status_code=status.HTTP_200_OK,
6371
summary="Retrieves a Player by its Id"
6472
)
73+
@cache(expire=CACHING_TIME_IN_SECONDS)
6574
def get_by_id(
6675
player_id: int = Path(..., title="The Id of the Player"),
6776
orm_session: Session = Depends(get_orm_session)
@@ -80,6 +89,7 @@ def get_by_id(
8089
status_code=status.HTTP_200_OK,
8190
summary="Retrieves a Player by its Squad Number"
8291
)
92+
@cache(expire=CACHING_TIME_IN_SECONDS)
8393
def get_by_squad_number(
8494
squad_number: int = Path(..., title="The Squad Number of the Player"),
8595
orm_session: Session = Depends(get_orm_session)
@@ -112,6 +122,8 @@ def put(
112122

113123
player_service.update(orm_session, player_model)
114124

125+
FastAPICache.clear()
126+
115127
# DELETE -----------------------------------------------------------------------
116128

117129

@@ -130,3 +142,5 @@ def delete(
130142
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Player not found")
131143

132144
player_service.delete(orm_session, player_id)
145+
146+
FastAPICache.clear()

tests/conftest.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import warnings
2+
import pytest
3+
from fastapi.testclient import TestClient
4+
from main import app
5+
6+
# Suppress the DeprecationWarning from httpx
7+
warnings.filterwarnings("ignore", category=DeprecationWarning)
8+
9+
10+
@pytest.fixture(scope="module")
11+
def client():
12+
with TestClient(app) as test_client:
13+
yield test_client

tests/test_main.py

Lines changed: 16 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,18 @@
44

55
import json
66
from tests.player_stub import existing_player, non_existing_player, unknown_player
7-
from main import app
8-
from fastapi.testclient import TestClient
9-
import warnings
10-
# Suppress the DeprecationWarning from httpx
11-
warnings.filterwarnings("ignore", category=DeprecationWarning)
127

13-
14-
client = TestClient(app)
158
PATH = "/players/"
169

1710
# GET /players/ ----------------------------------------------------------------
1811

1912

20-
def test_given_get_when_request_path_has_no_id_then_response_status_code_should_be_200_ok():
13+
def test_given_get_when_request_path_has_no_id_then_response_status_code_should_be_200_ok(client):
2114
response = client.get(PATH)
2215
assert response.status_code == 200
2316

2417

25-
def test_given_get_when_request_path_has_no_id_then_response_body_should_be_collection_of_players():
18+
def test_given_get_when_request_path_has_no_id_then_response_body_should_be_collection_of_players(client):
2619
response = client.get(PATH)
2720
players = response.json()
2821
player_id = 0
@@ -33,19 +26,19 @@ def test_given_get_when_request_path_has_no_id_then_response_body_should_be_coll
3326
# GET /players/{player_id} -----------------------------------------------------
3427

3528

36-
def test_given_get_when_request_path_is_non_existing_id_then_response_status_code_should_be_404_not_found():
29+
def test_given_get_when_request_path_is_non_existing_id_then_response_status_code_should_be_404_not_found(client):
3730
player_id = 999
3831
response = client.get(PATH + str(player_id))
3932
assert response.status_code == 404
4033

4134

42-
def test_given_get_when_request_path_is_existing_id_then_response_status_code_should_be_200_ok():
35+
def test_given_get_when_request_path_is_existing_id_then_response_status_code_should_be_200_ok(client):
4336
player_id = 1
4437
response = client.get(PATH + str(player_id))
4538
assert response.status_code == 200
4639

4740

48-
def test_given_get_when_request_path_is_existing_id_then_response_body_should_be_matching_player():
41+
def test_given_get_when_request_path_is_existing_id_then_response_body_should_be_matching_player(client):
4942
player_id = 1
5043
response = client.get(PATH + str(player_id))
5144
player = response.json()
@@ -54,19 +47,19 @@ def test_given_get_when_request_path_is_existing_id_then_response_body_should_be
5447
# GET /players/squadnumber/{squad_number} --------------------------------------
5548

5649

57-
def test_given_get_when_request_path_is_non_existing_squad_number_then_response_status_code_should_be_404_not_found():
50+
def test_given_get_when_request_path_is_non_existing_squad_number_then_response_status_code_should_be_404_not_found(client):
5851
squad_number = 999
5952
response = client.get(PATH + "squadnumber" + "/" + str(squad_number))
6053
assert response.status_code == 404
6154

6255

63-
def test_given_get_when_request_path_is_existing_squad_number_then_response_status_code_should_be_200_ok():
56+
def test_given_get_when_request_path_is_existing_squad_number_then_response_status_code_should_be_200_ok(client):
6457
squad_number = 10
6558
response = client.get(PATH + "squadnumber" + "/" + str(squad_number))
6659
assert response.status_code == 200
6760

6861

69-
def test_given_get_when_request_path_is_existing_squad_number_then_response_body_should_be_matching_player():
62+
def test_given_get_when_request_path_is_existing_squad_number_then_response_body_should_be_matching_player(client):
7063
squad_number = 10
7164
response = client.get(PATH + "squadnumber" + "/" + str(squad_number))
7265
player = response.json()
@@ -75,20 +68,20 @@ def test_given_get_when_request_path_is_existing_squad_number_then_response_body
7568
# POST /players/ ---------------------------------------------------------------
7669

7770

78-
def test_given_post_when_request_body_is_empty_then_response_status_code_should_be_422_unprocessable_entity():
71+
def test_given_post_when_request_body_is_empty_then_response_status_code_should_be_422_unprocessable_entity(client):
7972
body = json.dumps({})
8073
response = client.post(PATH, data=body)
8174
assert response.status_code == 422
8275

8376

84-
def test_given_post_when_request_body_is_existing_player_then_response_status_code_should_be_409_conflict():
77+
def test_given_post_when_request_body_is_existing_player_then_response_status_code_should_be_409_conflict(client):
8578
player = existing_player()
8679
body = json.dumps(player.__dict__)
8780
response = client.post(PATH, data=body)
8881
assert response.status_code == 409
8982

9083

91-
def test_given_post_when_request_body_is_non_existing_player_then_response_status_code_should_be_201_created():
84+
def test_given_post_when_request_body_is_non_existing_player_then_response_status_code_should_be_201_created(client):
9285
player = non_existing_player()
9386
body = json.dumps(player.__dict__)
9487
response = client.post(PATH, data=body)
@@ -97,22 +90,22 @@ def test_given_post_when_request_body_is_non_existing_player_then_response_statu
9790
# PUT /players/{player_id} -----------------------------------------------------
9891

9992

100-
def test_given_put_when_request_body_is_empty_then_response_status_code_should_be_422_unprocessable_entity():
93+
def test_given_put_when_request_body_is_empty_then_response_status_code_should_be_422_unprocessable_entity(client):
10194
player_id = 1
10295
body = {}
10396
response = client.put(PATH + str(player_id), data=body)
10497
assert response.status_code == 422
10598

10699

107-
def test_given_put_when_request_body_is_unknown_player_then_response_status_code_should_be_404_not_found():
100+
def test_given_put_when_request_body_is_unknown_player_then_response_status_code_should_be_404_not_found(client):
108101
player_id = 999
109102
player = unknown_player()
110103
body = json.dumps(player.__dict__)
111104
response = client.put(PATH + str(player_id), data=body)
112105
assert response.status_code == 404
113106

114107

115-
def test_given_put_when_request_body_is_existing_player_then_response_status_code_should_be_204_no_content():
108+
def test_given_put_when_request_body_is_existing_player_then_response_status_code_should_be_204_no_content(client):
116109
player_id = 1
117110
player = existing_player()
118111
player.first_name = "Emiliano"
@@ -124,13 +117,13 @@ def test_given_put_when_request_body_is_existing_player_then_response_status_cod
124117
# DELETE /players/{player_id} --------------------------------------------------
125118

126119

127-
def test_given_delete_when_request_path_is_non_existing_id_then_response_status_code_should_be_404_not_found():
120+
def test_given_delete_when_request_path_is_non_existing_id_then_response_status_code_should_be_404_not_found(client):
128121
player_id = 999
129122
response = client.delete(PATH + str(player_id))
130123
assert response.status_code == 404
131124

132125

133-
def test_given_delete_when_request_path_is_existing_id_then_response_status_code_should_be__204_no_content():
126+
def test_given_delete_when_request_path_is_existing_id_then_response_status_code_should_be__204_no_content(client):
134127
player_id = 12
135128
response = client.delete(PATH + str(player_id))
136129
assert response.status_code == 204

0 commit comments

Comments
 (0)