diff --git a/Dockerfile b/Dockerfile index d6e6c52..92e3a97 100644 --- a/Dockerfile +++ b/Dockerfile @@ -45,13 +45,13 @@ COPY requirements.txt . RUN pip install --no-cache-dir --no-index --find-links /app/wheelhouse -r requirements.txt && \ rm -rf /app/wheelhouse -# Copy application source code -COPY main.py ./ -COPY databases/ ./databases/ -COPY models/ ./models/ -COPY routes/ ./routes/ -COPY schemas/ ./schemas/ -COPY services/ ./services/ +# Copy application source code +COPY src/main.py ./src/ +COPY src/databases/ ./src/databases/ +COPY src/models/ ./src/models/ +COPY src/routes/ ./src/routes/ +COPY src/schemas/ ./src/schemas/ +COPY src/services/ ./src/services/ # https://rules.sonarsource.com/docker/RSPEC-6504/ @@ -78,4 +78,4 @@ HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \ CMD ["./healthcheck.sh"] ENTRYPOINT ["./entrypoint.sh"] -CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "9000"] +CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "9000"] diff --git a/databases/__init__.py b/src/__init__.py similarity index 100% rename from databases/__init__.py rename to src/__init__.py diff --git a/models/__init__.py b/src/databases/__init__.py similarity index 100% rename from models/__init__.py rename to src/databases/__init__.py diff --git a/databases/player_database.py b/src/databases/player.py similarity index 100% rename from databases/player_database.py rename to src/databases/player.py diff --git a/main.py b/src/main.py similarity index 87% rename from main.py rename to src/main.py index a1167f0..a0d64bb 100644 --- a/main.py +++ b/src/main.py @@ -12,7 +12,7 @@ import logging from typing import AsyncIterator from fastapi import FastAPI -from routes import player_route, health_route +from src.routes import player, health # https://github.com/encode/uvicorn/issues/562 UVICORN_LOGGER = "uvicorn.error" @@ -35,5 +35,5 @@ async def lifespan(_: FastAPI) -> AsyncIterator[None]: version="1.0.0", ) -app.include_router(player_route.api_router) -app.include_router(health_route.api_router) +app.include_router(player.router) +app.include_router(health.router) diff --git a/routes/__init__.py b/src/models/__init__.py similarity index 100% rename from routes/__init__.py rename to src/models/__init__.py diff --git a/models/player_model.py b/src/models/player.py similarity index 100% rename from models/player_model.py rename to src/models/player.py diff --git a/schemas/__init__.py b/src/routes/__init__.py similarity index 100% rename from schemas/__init__.py rename to src/routes/__init__.py diff --git a/routes/health_route.py b/src/routes/health.py similarity index 84% rename from routes/health_route.py rename to src/routes/health.py index 0db6333..6f06fbc 100644 --- a/routes/health_route.py +++ b/src/routes/health.py @@ -7,10 +7,10 @@ from fastapi import APIRouter -api_router = APIRouter() +router = APIRouter() -@api_router.get("/health", tags=["Health"]) +@router.get("/health", tags=["Health"]) async def health_check(): """ Simple health check endpoint. diff --git a/routes/player_route.py b/src/routes/player.py similarity index 82% rename from routes/player_route.py rename to src/routes/player.py index ddeecc8..92632ee 100644 --- a/routes/player_route.py +++ b/src/routes/player.py @@ -22,20 +22,22 @@ from sqlalchemy.ext.asyncio import AsyncSession from aiocache import SimpleMemoryCache -from databases.player_database import generate_async_session -from models.player_model import PlayerModel -from services import player_service +from src.databases.player import generate_async_session +from src.models.player import PlayerModel +from src.services import player -api_router = APIRouter() +router = APIRouter() simple_memory_cache = SimpleMemoryCache() CACHE_KEY = "players" CACHE_TTL = 600 # 10 minutes +PLAYER_TITLE = "The ID of the Player" + # POST ------------------------------------------------------------------------- -@api_router.post( +@router.post( "/players/", status_code=status.HTTP_201_CREATED, summary="Creates a new Player", @@ -56,17 +58,17 @@ async def post_async( Raises: HTTPException: HTTP 409 Conflict error if the Player already exists. """ - player = await player_service.retrieve_by_id_async(async_session, player_model.id) - if player: + player_res = await player.retrieve_by_id_async(async_session, player_model.id) + if player_res: raise HTTPException(status_code=status.HTTP_409_CONFLICT) - await player_service.create_async(async_session, player_model) + await player.create_async(async_session, player_model) await simple_memory_cache.clear(CACHE_KEY) # GET -------------------------------------------------------------------------- -@api_router.get( +@router.get( "/players/", response_model=List[PlayerModel], status_code=status.HTTP_200_OK, @@ -88,13 +90,13 @@ async def get_all_async( 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) + players = await player.retrieve_all_async(async_session) await simple_memory_cache.set(CACHE_KEY, players, ttl=CACHE_TTL) response.headers["X-Cache"] = "MISS" return players -@api_router.get( +@router.get( "/players/{player_id}", response_model=PlayerModel, status_code=status.HTTP_200_OK, @@ -102,7 +104,7 @@ async def get_all_async( tags=["Players"], ) async def get_by_id_async( - player_id: int = Path(..., title="The ID of the Player"), + player_id: int = Path(..., title=PLAYER_TITLE), async_session: AsyncSession = Depends(generate_async_session), ): """ @@ -119,13 +121,13 @@ async def get_by_id_async( HTTPException: Not found error if the Player with the specified ID does not exist. """ - player = await player_service.retrieve_by_id_async(async_session, player_id) - if not player: + player_res = await player.retrieve_by_id_async(async_session, player_id) + if not player_res: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) - return player + return player_res -@api_router.get( +@router.get( "/players/squadnumber/{squad_number}", response_model=PlayerModel, status_code=status.HTTP_200_OK, @@ -150,25 +152,25 @@ async def get_by_squad_number_async( HTTPException: HTTP 404 Not Found error if the Player with the specified Squad Number does not exist. """ - player = await player_service.retrieve_by_squad_number_async( + player_res = await player.retrieve_by_squad_number_async( async_session, squad_number ) - if not player: + if not player_res: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) - return player + return player_res # PUT -------------------------------------------------------------------------- -@api_router.put( +@router.put( "/players/{player_id}", status_code=status.HTTP_204_NO_CONTENT, summary="Updates an existing Player", tags=["Players"], ) async def put_async( - player_id: int = Path(..., title="The ID of the Player"), + player_id: int = Path(..., title=PLAYER_TITLE), player_model: PlayerModel = Body(...), async_session: AsyncSession = Depends(generate_async_session), ): @@ -185,24 +187,24 @@ async def put_async( HTTPException: HTTP 404 Not Found error if the Player with the specified ID does not exist. """ - player = await player_service.retrieve_by_id_async(async_session, player_id) - if not player: + player_res = await player.retrieve_by_id_async(async_session, player_id) + if not player_res: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) - await player_service.update_async(async_session, player_model) + await player.update_async(async_session, player_model) await simple_memory_cache.clear(CACHE_KEY) # DELETE ----------------------------------------------------------------------- -@api_router.delete( +@router.delete( "/players/{player_id}", status_code=status.HTTP_204_NO_CONTENT, summary="Deletes an existing Player", tags=["Players"], ) async def delete_async( - player_id: int = Path(..., title="The ID of the Player"), + player_id: int = Path(..., title=PLAYER_TITLE), async_session: AsyncSession = Depends(generate_async_session), ): """ @@ -216,8 +218,8 @@ async def delete_async( HTTPException: HTTP 404 Not Found error if the Player with the specified ID does not exist. """ - player = await player_service.retrieve_by_id_async(async_session, player_id) - if not player: + player_res = await player.retrieve_by_id_async(async_session, player_id) + if not player_res: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) - await player_service.delete_async(async_session, player_id) + await player.delete_async(async_session, player_id) await simple_memory_cache.clear(CACHE_KEY) diff --git a/services/__init__.py b/src/schemas/__init__.py similarity index 100% rename from services/__init__.py rename to src/schemas/__init__.py diff --git a/schemas/player_schema.py b/src/schemas/player.py similarity index 97% rename from schemas/player_schema.py rename to src/schemas/player.py index a3524a3..12e7295 100644 --- a/schemas/player_schema.py +++ b/src/schemas/player.py @@ -7,7 +7,7 @@ """ from sqlalchemy import Column, String, Integer, Boolean -from databases.player_database import Base +from src.databases.player import Base class Player(Base): diff --git a/src/services/__init__.py b/src/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/services/player_service.py b/src/services/player.py similarity index 98% rename from services/player_service.py rename to src/services/player.py index 5c9e894..f93b09a 100644 --- a/services/player_service.py +++ b/src/services/player.py @@ -15,8 +15,8 @@ from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.exc import SQLAlchemyError -from models.player_model import PlayerModel -from schemas.player_schema import Player +from src.models.player import PlayerModel +from src.schemas.player import Player # Create ----------------------------------------------------------------------- diff --git a/storage/players-sqlite3.db b/storage/players-sqlite3.db index 07bb620..03c49b9 100644 Binary files a/storage/players-sqlite3.db and b/storage/players-sqlite3.db differ diff --git a/tests/conftest.py b/tests/conftest.py index 655690b..574ca5d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,7 +1,7 @@ import warnings import pytest from fastapi.testclient import TestClient -from main import app +from src.main import app # Suppress the DeprecationWarning from httpx warnings.filterwarnings("ignore", category=DeprecationWarning)