Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@
"request": "launch",
"module": "uvicorn",
"args": ["main:app", "--reload", "--port", "9000"],
"jinja": true
"jinja": true,
"serverReadyAction": {
"action": "openExternally",
"pattern": "Uvicorn running on .*:(\\d+)",
"uriFormat": "http://localhost:%s/docs"
}
}
]
}
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ The following is a simplified dependency diagram of modules and main libraries:
## Install

```console
pip install --requirement requirements.txt
pip install -r requirements.txt
pip install -r requirements-lint.txt
pip install -r requirements-test.txt
```

## Start
Expand Down
19 changes: 10 additions & 9 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,24 @@
# ------------------------------------------------------------------------------

from contextlib import asynccontextmanager
import logging
from typing import AsyncIterator
from fastapi import FastAPI

Check failure on line 8 in main.py

View check run for this annotation

Codeac.io / Codeac Code Quality

import-error

Unable to import 'fastapi'
from fastapi_cache import FastAPICache
from fastapi_cache.backends.inmemory import InMemoryBackend
from routes import player_route

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

@asynccontextmanager
async def lifespan_context_manager(_):
async def lifespan(_: FastAPI) -> AsyncIterator[None]:
""""
Lifespan event handler for FastAPI.
"""
Context manager for the FastAPI app lifespan.

Initializes FastAPICache with an InMemoryBackend for the duration of the app's lifespan.
"""
FastAPICache.init(InMemoryBackend())
logger.info("Lifespan event handler execution complete.")
yield

app = FastAPI(lifespan=lifespan_context_manager,
app = FastAPI(lifespan=lifespan,
title="python-samples-fastapi-restful",
description="🧪 Proof of Concept for a RESTful API made with Python 3 and FastAPI",
version="1.0.0",)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
}
},
"url": {
"raw": "http://localhost:9000/players",
"raw": "http://localhost:9000/players/",
"protocol": "http",
"host": ["localhost"],
"port": "9000",
Expand Down Expand Up @@ -50,7 +50,7 @@
}
},
"url": {
"raw": "http://localhost:9000/players",
"raw": "http://localhost:9000/players/",
"protocol": "http",
"host": ["localhost"],
"port": "9000",
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# https://fastapi.tiangolo.com/#standard-dependencies
fastapi[standard]==0.115.12
fastapi-cache2==0.2.2
SQLAlchemy==2.0.40
aiosqlite==0.21.0
aiocache==0.12.3
26 changes: 15 additions & 11 deletions routes/player_route.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,19 @@
# ------------------------------------------------------------------------------

from typing import List
from fastapi import APIRouter, Body, Depends, HTTPException, status, Path
from fastapi import APIRouter, Body, Depends, HTTPException, status, Path, Response

Check failure on line 6 in routes/player_route.py

View check run for this annotation

Codeac.io / Codeac Code Quality

import-error

Unable to import 'fastapi'
from sqlalchemy.ext.asyncio import AsyncSession

Check failure on line 7 in routes/player_route.py

View check run for this annotation

Codeac.io / Codeac Code Quality

import-error

Unable to import 'sqlalchemy.ext.asyncio'
from fastapi_cache import FastAPICache
from fastapi_cache.decorator import cache
from aiocache import SimpleMemoryCache

Check failure on line 8 in routes/player_route.py

View check run for this annotation

Codeac.io / Codeac Code Quality

import-error

Unable to import 'aiocache'

from data.player_database import generate_async_session
from models.player_model import PlayerModel
from services import player_service

api_router = APIRouter()
simple_memory_cache = SimpleMemoryCache()

CACHING_TIME_IN_SECONDS = 600
CACHE_KEY = "players"
CACHE_TTL = 600 # 10 minutes

# POST -------------------------------------------------------------------------

Expand Down Expand Up @@ -43,7 +44,7 @@
if player:
raise HTTPException(status_code=status.HTTP_409_CONFLICT)
await player_service.create_async(async_session, player_model)
await FastAPICache.clear()
await simple_memory_cache.clear(CACHE_KEY)

# GET --------------------------------------------------------------------------

Expand All @@ -55,8 +56,8 @@
summary="Retrieves a collection of Players",
tags=["Players"]
)
@cache(expire=CACHING_TIME_IN_SECONDS)
async def get_all_async(
response: Response,
async_session: AsyncSession = Depends(generate_async_session)
):
"""
Expand All @@ -68,7 +69,12 @@
Returns:
List[PlayerModel]: A list of Pydantic models representing all players.
"""
players = await player_service.retrieve_all_async(async_session)
players = await simple_memory_cache.get(CACHE_KEY)
response.headers["X-Cache"] = "HIT"
if not players:
players = await player_service.retrieve_all_async(async_session)
await simple_memory_cache.set(CACHE_KEY, players, ttl=CACHE_TTL)
response.headers["X-Cache"] = "MISS"
return players


Expand All @@ -79,7 +85,6 @@
summary="Retrieves a Player by its Id",
tags=["Players"]
)
@cache(expire=CACHING_TIME_IN_SECONDS)
async def get_by_id_async(
player_id: int = Path(..., title="The ID of the Player"),
async_session: AsyncSession = Depends(generate_async_session)
Expand Down Expand Up @@ -110,7 +115,6 @@
summary="Retrieves a Player by its Squad Number",
tags=["Players"]
)
@cache(expire=CACHING_TIME_IN_SECONDS)
async def get_by_squad_number_async(
squad_number: int = Path(..., title="The Squad Number of the Player"),
async_session: AsyncSession = Depends(generate_async_session)
Expand Down Expand Up @@ -162,7 +166,7 @@
if not player:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
await player_service.update_async(async_session, player_model)
await FastAPICache.clear()
await simple_memory_cache.clear(CACHE_KEY)

# DELETE -----------------------------------------------------------------------

Expand Down Expand Up @@ -191,4 +195,4 @@
if not player:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
await player_service.delete_async(async_session, player_id)
await FastAPICache.clear()
await simple_memory_cache.clear(CACHE_KEY)
2 changes: 1 addition & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import warnings
import pytest

Check failure on line 2 in tests/conftest.py

View check run for this annotation

Codeac.io / Codeac Code Quality

import-error

Unable to import 'pytest'
from fastapi.testclient import TestClient

Check failure on line 3 in tests/conftest.py

View check run for this annotation

Codeac.io / Codeac Code Quality

import-error

Unable to import 'fastapi.testclient'
from main import app

# Suppress the DeprecationWarning from httpx
warnings.filterwarnings("ignore", category=DeprecationWarning)


@pytest.fixture(scope="module")
@pytest.fixture(scope="function")
def client():

Check warning on line 11 in tests/conftest.py

View check run for this annotation

Codeac.io / Codeac Code Quality

missing-function-docstring

Missing function or method docstring
with TestClient(app) as test_client:
yield test_client
29 changes: 28 additions & 1 deletion tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,33 @@
# GET /players/ ----------------------------------------------------------------


def test_given_get_when_request_is_initial_then_response_header_x_cache_should_be_miss(client):
"""
Given GET /players/
when request is initial
then response Header X-Cache value should be MISS
"""
# Act
response = client.get(PATH)

# Assert
assert "X-Cache" in response.headers

Check warning on line 23 in tests/test_main.py

View check run for this annotation

Codeac.io / Codeac Code Quality

B101

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
assert response.headers.get("X-Cache") == "MISS"

Check warning on line 24 in tests/test_main.py

View check run for this annotation

Codeac.io / Codeac Code Quality

B101

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.

def test_given_get_when_request_is_subsequent_then_response_header_x_cache_should_be_hit(client):
"""
Given GET /players/
when request is subsequent
then response Header X-Cache should be HIT
"""
# Act
client.get(PATH) # initial
response = client.get(PATH) # subsequent (cached)

# Assert
assert "X-Cache" in response.headers

Check warning on line 37 in tests/test_main.py

View check run for this annotation

Codeac.io / Codeac Code Quality

B101

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
assert response.headers.get("X-Cache") == "HIT"

Check warning on line 38 in tests/test_main.py

View check run for this annotation

Codeac.io / Codeac Code Quality

B101

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.

def test_given_get_when_request_path_has_no_id_then_response_status_code_should_be_200_ok(client):
"""
Given GET /players/
Expand All @@ -19,14 +46,14 @@
# Act
response = client.get(PATH)
# Assert
assert response.status_code == 200

Check warning on line 49 in tests/test_main.py

View check run for this annotation

Codeac.io / Codeac Code Quality

B101

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.


def test_given_get_when_request_path_has_no_id_then_response_body_should_be_collection_of_players(client):
"""
Given GET /players/
when request path has no ID
then response Status Code should be collection of players
then response Body should be collection of players
"""
# Act
response = client.get(PATH)
Expand All @@ -35,7 +62,7 @@
player_id = 0
for player in players:
player_id += 1
assert player["id"] == player_id

Check warning on line 65 in tests/test_main.py

View check run for this annotation

Codeac.io / Codeac Code Quality

B101

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.

# GET /players/{player_id} -----------------------------------------------------

Expand All @@ -51,7 +78,7 @@
# Act
response = client.get(PATH + str(player_id))
# Assert
assert response.status_code == 404

Check warning on line 81 in tests/test_main.py

View check run for this annotation

Codeac.io / Codeac Code Quality

B101

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.


def test_given_get_when_request_path_is_existing_id_then_response_status_code_should_be_200_ok(client):
Expand All @@ -65,7 +92,7 @@
# Act
response = client.get(PATH + str(player_id))
# Assert
assert response.status_code == 200

Check warning on line 95 in tests/test_main.py

View check run for this annotation

Codeac.io / Codeac Code Quality

B101

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.


def test_given_get_when_request_path_is_existing_id_then_response_body_should_be_matching_player(client):
Expand All @@ -80,7 +107,7 @@
response = client.get(PATH + str(player_id))
# Assert
player = response.json()
assert player["id"] == player_id

Check warning on line 110 in tests/test_main.py

View check run for this annotation

Codeac.io / Codeac Code Quality

B101

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.

# GET /players/squadnumber/{squad_number} --------------------------------------

Expand All @@ -96,7 +123,7 @@
# Act
response = client.get(PATH + "squadnumber" + "/" + str(squad_number))
# Assert
assert response.status_code == 404

Check warning on line 126 in tests/test_main.py

View check run for this annotation

Codeac.io / Codeac Code Quality

B101

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.


def test_given_get_when_request_path_is_existing_squad_number_then_response_status_code_should_be_200_ok(client):
Expand All @@ -110,7 +137,7 @@
# Act
response = client.get(PATH + "squadnumber" + "/" + str(squad_number))
# Assert
assert response.status_code == 200

Check warning on line 140 in tests/test_main.py

View check run for this annotation

Codeac.io / Codeac Code Quality

B101

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.


def test_given_get_when_request_path_is_existing_squad_number_then_response_body_should_be_matching_player(client):
Expand All @@ -125,7 +152,7 @@
response = client.get(PATH + "squadnumber" + "/" + str(squad_number))
# Assert
player = response.json()
assert player["squadNumber"] == squad_number

Check warning on line 155 in tests/test_main.py

View check run for this annotation

Codeac.io / Codeac Code Quality

B101

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.

# POST /players/ ---------------------------------------------------------------

Expand All @@ -141,7 +168,7 @@
# Act
response = client.post(PATH, data=body)
# Assert
assert response.status_code == 422

Check warning on line 171 in tests/test_main.py

View check run for this annotation

Codeac.io / Codeac Code Quality

B101

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.


def test_given_post_when_request_body_is_existing_player_then_response_status_code_should_be_409_conflict(client):
Expand All @@ -156,7 +183,7 @@
# Act
response = client.post(PATH, data=body)
# Assert
assert response.status_code == 409

Check warning on line 186 in tests/test_main.py

View check run for this annotation

Codeac.io / Codeac Code Quality

B101

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.


def test_given_post_when_request_body_is_nonexistent_player_then_response_status_code_should_be_201_created(client):
Expand All @@ -171,7 +198,7 @@
# Act
response = client.post(PATH, data=body)
# Assert
assert response.status_code == 201

Check warning on line 201 in tests/test_main.py

View check run for this annotation

Codeac.io / Codeac Code Quality

B101

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.

# PUT /players/{player_id} -----------------------------------------------------

Expand All @@ -188,7 +215,7 @@
# Act
response = client.put(PATH + str(player_id), data=body)
# Assert
assert response.status_code == 422

Check warning on line 218 in tests/test_main.py

View check run for this annotation

Codeac.io / Codeac Code Quality

B101

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.


def test_given_put_when_request_path_is_unknown_id_then_response_status_code_should_be_404_not_found(client):
Expand All @@ -204,7 +231,7 @@
# Act
response = client.put(PATH + str(player_id), data=body)
# Assert
assert response.status_code == 404

Check warning on line 234 in tests/test_main.py

View check run for this annotation

Codeac.io / Codeac Code Quality

B101

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.


def test_given_put_when_request_path_is_existing_id_then_response_status_code_should_be_204_no_content(client):
Expand All @@ -222,7 +249,7 @@
# Act
response = client.put(PATH + str(player_id), data=body)
# Assert
assert response.status_code == 204

Check warning on line 252 in tests/test_main.py

View check run for this annotation

Codeac.io / Codeac Code Quality

B101

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.

# DELETE /players/{player_id} --------------------------------------------------

Expand All @@ -238,7 +265,7 @@
# Act
response = client.delete(PATH + str(player_id))
# Assert
assert response.status_code == 404

Check warning on line 268 in tests/test_main.py

View check run for this annotation

Codeac.io / Codeac Code Quality

B101

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.


def test_given_delete_when_request_path_is_existing_id_then_response_status_code_should_be__204_no_content(client):
Expand All @@ -252,4 +279,4 @@
# Act
response = client.delete(PATH + str(player_id))
# Assert
assert response.status_code == 204

Check warning on line 282 in tests/test_main.py

View check run for this annotation

Codeac.io / Codeac Code Quality

B101

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
Loading