From 30af7bfec479d7d59b8060befc319c4e8780c23e Mon Sep 17 00:00:00 2001 From: Edson Carlos Date: Sun, 28 Apr 2024 18:17:28 +0200 Subject: [PATCH] desafio-dio-workout --- alembic/versions/47572d824072_atletas.py | 28 +++++++++++++ docker-compose.yml | 2 +- requirements.txt | 38 +++++++++--------- workout_api/atleta/controller.py | 50 +++++++++++++++++++----- workout_api/atleta/schemas.py | 10 +++-- workout_api/routers.py | 4 +- 6 files changed, 99 insertions(+), 33 deletions(-) create mode 100644 alembic/versions/47572d824072_atletas.py diff --git a/alembic/versions/47572d824072_atletas.py b/alembic/versions/47572d824072_atletas.py new file mode 100644 index 00000000..12682ef9 --- /dev/null +++ b/alembic/versions/47572d824072_atletas.py @@ -0,0 +1,28 @@ +"""atletas + +Revision ID: 47572d824072 +Revises: c006e8463eb4 +Create Date: 2024-04-28 17:06:17.227645 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '47572d824072' +down_revision = 'c006e8463eb4' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + pass + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + pass + # ### end Alembic commands ### diff --git a/docker-compose.yml b/docker-compose.yml index 824ba7be..92a46350 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,7 @@ version: "3" services: db: - image: postgres:11-alpine + image: postgres:latest environment: POSTGRES_PASSWORD: workout POSTGRES_USER: workout diff --git a/requirements.txt b/requirements.txt index 033c4abc..791d0d69 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,18 +1,20 @@ -alembic==1.11.1 -annotated-types==0.5.0 -anyio==3.7.1 -asyncpg==0.28.0 -click==8.1.6 -fastapi==0.100.1 -greenlet==2.0.2 -h11==0.14.0 -idna==3.4 -Mako==1.2.4 -MarkupSafe==2.1.3 -pydantic==2.1.1 -pydantic_core==2.4.0 -sniffio==1.3.0 -SQLAlchemy==2.0.19 -starlette==0.27.0 -typing_extensions==4.7.1 -uvicorn==0.23.1 +alembic +annotated-types +anyio +asyncpg +click +fastapi +greenlet +h11 +idna +Mako +MarkupSafe +pydantic +pydantic_core +sniffio +SQLAlchemy +starlette +typing_extensions +uvicorn +fastapi-pagination +pydantic-settings \ No newline at end of file diff --git a/workout_api/atleta/controller.py b/workout_api/atleta/controller.py index b3a28d4c..5b0f9b3b 100644 --- a/workout_api/atleta/controller.py +++ b/workout_api/atleta/controller.py @@ -1,6 +1,8 @@ from datetime import datetime +from typing import Optional from uuid import uuid4 from fastapi import APIRouter, Body, HTTPException, status +from fastapi.params import Query from pydantic import UUID4 from workout_api.atleta.schemas import AtletaIn, AtletaOut, AtletaUpdate @@ -10,6 +12,10 @@ from workout_api.contrib.dependencies import DatabaseDependency from sqlalchemy.future import select +from sqlalchemy.exc import IntegrityError + +from fastapi_pagination import LimitOffsetPage, Page, add_pagination +from fastapi_pagination import Params, paginate router = APIRouter() @@ -54,11 +60,17 @@ async def post( db_session.add(atleta_model) await db_session.commit() - except Exception: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail='Ocorreu um erro ao inserir os dados no banco' - ) + except IntegrityError as e: + if 'cpf' in str(e): + raise HTTPException( + status_code=status.HTTP_303_SEE_OTHER, + detail=f'Já existe um atleta cadastrado com o CPF: {atleta_in.cpf}' + ) + else: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail='Ocorreu um erro ao inserir os dados no banco' + ) return atleta_out @@ -67,12 +79,32 @@ async def post( '/', summary='Consultar todos os Atletas', status_code=status.HTTP_200_OK, - response_model=list[AtletaOut], + response_model=LimitOffsetPage[AtletaOut], ) -async def query(db_session: DatabaseDependency) -> list[AtletaOut]: - atletas: list[AtletaOut] = (await db_session.execute(select(AtletaModel))).scalars().all() +async def query( + db_session: DatabaseDependency, + nome: Optional[str] = Query(None, description="Nome do atleta"), + cpf: Optional[str] = Query(None, description="CPF do atleta") +) -> LimitOffsetPage[AtletaOut]: + query = select(AtletaModel) + if nome: + query = query.filter(AtletaModel.nome == nome) + if cpf: + query = query.filter(AtletaModel.cpf == cpf) + + atletas = await db_session.execute(query) + - return [AtletaOut.model_validate(atleta) for atleta in atletas] + atletas_with_details = [ + { + "nome": atleta.nome, + "centro_treinamento": atleta.centro_treinamento.nome, + "categoria": atleta.categoria.nome + } + for atleta in atletas.scalars().all() + ] + + return paginate(atletas_with_details) @router.get( diff --git a/workout_api/atleta/schemas.py b/workout_api/atleta/schemas.py index f3e33d36..40d5c920 100644 --- a/workout_api/atleta/schemas.py +++ b/workout_api/atleta/schemas.py @@ -3,7 +3,7 @@ from workout_api.categorias.schemas import CategoriaIn from workout_api.centro_treinamento.schemas import CentroTreinamentoAtleta -from workout_api.contrib.schemas import BaseSchema, OutMixin +from workout_api.contrib.schemas import BaseSchema class Atleta(BaseSchema): @@ -21,9 +21,11 @@ class AtletaIn(Atleta): pass -class AtletaOut(Atleta, OutMixin): - pass +class AtletaOut(BaseSchema): + nome: str + centro_treinamento: str + categoria: str class AtletaUpdate(BaseSchema): nome: Annotated[Optional[str], Field(None, description='Nome do atleta', example='Joao', max_length=50)] - idade: Annotated[Optional[int], Field(None, description='Idade do atleta', example=25)] \ No newline at end of file + idade: Annotated[Optional[int], Field(None, description='Idade do atleta', example=25)] diff --git a/workout_api/routers.py b/workout_api/routers.py index e1a8f253..41b9201d 100644 --- a/workout_api/routers.py +++ b/workout_api/routers.py @@ -2,8 +2,10 @@ from workout_api.atleta.controller import router as atleta from workout_api.categorias.controller import router as categorias from workout_api.centro_treinamento.controller import router as centro_treinamento +from fastapi_pagination import add_pagination api_router = APIRouter() api_router.include_router(atleta, prefix='/atletas', tags=['atletas']) api_router.include_router(categorias, prefix='/categorias', tags=['categorias']) -api_router.include_router(centro_treinamento, prefix='/centros_treinamento', tags=['centros_treinamento']) \ No newline at end of file +api_router.include_router(centro_treinamento, prefix='/centros_treinamento', tags=['centros_treinamento']) +add_pagination(api_router) \ No newline at end of file