Skip to content

Commit bc22454

Browse files
committed
refactor(routes)!: replace fastapi-cache2 with aioache
1 parent 2cbc5fa commit bc22454

File tree

4 files changed

+54
-22
lines changed

4 files changed

+54
-22
lines changed

main.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,24 @@
33
# ------------------------------------------------------------------------------
44

55
from contextlib import asynccontextmanager
6+
import logging
7+
from typing import AsyncIterator
68
from fastapi import FastAPI
7-
from fastapi_cache import FastAPICache
8-
from fastapi_cache.backends.inmemory import InMemoryBackend
99
from routes import player_route
1010

11+
# https://github.com/encode/uvicorn/issues/562
12+
UVICORN_LOGGER = "uvicorn.error"
13+
logger = logging.getLogger(UVICORN_LOGGER)
1114

1215
@asynccontextmanager
13-
async def lifespan_context_manager(_):
16+
async def lifespan(_: FastAPI) -> AsyncIterator[None]:
17+
""""
18+
Lifespan event handler for FastAPI.
1419
"""
15-
Context manager for the FastAPI app lifespan.
16-
17-
Initializes FastAPICache with an InMemoryBackend for the duration of the app's lifespan.
18-
"""
19-
FastAPICache.init(InMemoryBackend())
20+
logger.info("Lifespan event handler execution complete.")
2021
yield
2122

22-
app = FastAPI(lifespan=lifespan_context_manager,
23+
app = FastAPI(lifespan=lifespan,
2324
title="python-samples-fastapi-restful",
2425
description="🧪 Proof of Concept for a RESTful API made with Python 3 and FastAPI",
2526
version="1.0.0",)

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# https://fastapi.tiangolo.com/#standard-dependencies
22
fastapi[standard]==0.115.12
3-
fastapi-cache2==0.2.2
43
SQLAlchemy==2.0.40
54
aiosqlite==0.21.0
5+
aiocache==0.12.3

routes/player_route.py

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,19 @@
33
# ------------------------------------------------------------------------------
44

55
from typing import List
6-
from fastapi import APIRouter, Body, Depends, HTTPException, status, Path
6+
from fastapi import APIRouter, Body, Depends, HTTPException, status, Path, Response
77
from sqlalchemy.ext.asyncio import AsyncSession
8-
from fastapi_cache import FastAPICache
9-
from fastapi_cache.decorator import cache
8+
from aiocache import SimpleMemoryCache
109

1110
from data.player_database import generate_async_session
1211
from models.player_model import PlayerModel
1312
from services import player_service
1413

1514
api_router = APIRouter()
15+
simple_memory_cache = SimpleMemoryCache()
1616

17-
CACHING_TIME_IN_SECONDS = 600
17+
CACHE_KEY = "players"
18+
CACHE_TTL = 600 # 10 minutes
1819

1920
# POST -------------------------------------------------------------------------
2021

@@ -43,7 +44,7 @@ async def post_async(
4344
if player:
4445
raise HTTPException(status_code=status.HTTP_409_CONFLICT)
4546
await player_service.create_async(async_session, player_model)
46-
await FastAPICache.clear()
47+
await simple_memory_cache.clear(CACHE_KEY)
4748

4849
# GET --------------------------------------------------------------------------
4950

@@ -55,8 +56,8 @@ async def post_async(
5556
summary="Retrieves a collection of Players",
5657
tags=["Players"]
5758
)
58-
@cache(expire=CACHING_TIME_IN_SECONDS)
5959
async def get_all_async(
60+
response: Response,
6061
async_session: AsyncSession = Depends(generate_async_session)
6162
):
6263
"""
@@ -68,7 +69,12 @@ async def get_all_async(
6869
Returns:
6970
List[PlayerModel]: A list of Pydantic models representing all players.
7071
"""
71-
players = await player_service.retrieve_all_async(async_session)
72+
players = await simple_memory_cache.get(CACHE_KEY)
73+
response.headers["X-Cache"] = "HIT"
74+
if not players:
75+
players = await player_service.retrieve_all_async(async_session)
76+
await simple_memory_cache.set(CACHE_KEY, players, ttl=CACHE_TTL)
77+
response.headers["X-Cache"] = "MISS"
7278
return players
7379

7480

@@ -79,7 +85,6 @@ async def get_all_async(
7985
summary="Retrieves a Player by its Id",
8086
tags=["Players"]
8187
)
82-
@cache(expire=CACHING_TIME_IN_SECONDS)
8388
async def get_by_id_async(
8489
player_id: int = Path(..., title="The ID of the Player"),
8590
async_session: AsyncSession = Depends(generate_async_session)
@@ -110,7 +115,6 @@ async def get_by_id_async(
110115
summary="Retrieves a Player by its Squad Number",
111116
tags=["Players"]
112117
)
113-
@cache(expire=CACHING_TIME_IN_SECONDS)
114118
async def get_by_squad_number_async(
115119
squad_number: int = Path(..., title="The Squad Number of the Player"),
116120
async_session: AsyncSession = Depends(generate_async_session)
@@ -162,7 +166,7 @@ async def put_async(
162166
if not player:
163167
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
164168
await player_service.update_async(async_session, player_model)
165-
await FastAPICache.clear()
169+
await simple_memory_cache.clear(CACHE_KEY)
166170

167171
# DELETE -----------------------------------------------------------------------
168172

@@ -191,4 +195,4 @@ async def delete_async(
191195
if not player:
192196
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
193197
await player_service.delete_async(async_session, player_id)
194-
await FastAPICache.clear()
198+
await simple_memory_cache.clear(CACHE_KEY)

tests/test_main.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,33 @@
1010
# GET /players/ ----------------------------------------------------------------
1111

1212

13+
def test_given_get_when_request_is_initial_then_response_header_x_cache_should_be_miss(client):
14+
"""
15+
Given GET /players/
16+
when request is initial
17+
then response Header X-Cache value should be MISS
18+
"""
19+
# Act
20+
response = client.get(PATH)
21+
22+
# Assert
23+
assert "X-Cache" in response.headers
24+
assert response.headers.get("X-Cache") == "MISS"
25+
26+
def test_given_get_when_request_is_subsequent_then_response_header_x_cache_should_be_hit(client):
27+
"""
28+
Given GET /players/
29+
when request is subsequent
30+
then response Header X-Cache should be HIT
31+
"""
32+
# Act
33+
client.get(PATH) # initial
34+
response = client.get(PATH) # subsequent (cached)
35+
36+
# Assert
37+
assert "X-Cache" in response.headers
38+
assert response.headers.get("X-Cache") == "HIT"
39+
1340
def test_given_get_when_request_path_has_no_id_then_response_status_code_should_be_200_ok(client):
1441
"""
1542
Given GET /players/
@@ -26,7 +53,7 @@ def test_given_get_when_request_path_has_no_id_then_response_body_should_be_coll
2653
"""
2754
Given GET /players/
2855
when request path has no ID
29-
then response Status Code should be collection of players
56+
then response Body should be collection of players
3057
"""
3158
# Act
3259
response = client.get(PATH)

0 commit comments

Comments
 (0)