Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use Pydantic's native data types and handle timezone-aware datetimes #110

Merged
merged 6 commits into from
Jul 30, 2024
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
40 changes: 8 additions & 32 deletions src/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@
Pydantic: https://docs.pydantic.dev/2.0/
"""

from datetime import date, datetime
from typing import Any, Dict, List, Optional
from datetime import date
from typing import List, Optional

from pydantic import BaseModel, ConfigDict, Field, EmailStr
from pydantic import NonNegativeInt, PastDatetime, PositiveInt
from pydantic import model_validator, field_validator

from models import (
Expand Down Expand Up @@ -382,7 +383,7 @@ class EntregaSchema(BaseModel):
description=Entrega.nome_entrega.comment,
max_length=STR_FIELD_MAX_SIZE,
)
meta_entrega: int = Field(
meta_entrega: NonNegativeInt = Field(
title="Meta estipulada na inclusão no plano",
description=Entrega.meta_entrega.comment,
)
Expand All @@ -405,13 +406,6 @@ class EntregaSchema(BaseModel):
max_length=STR_FIELD_MAX_SIZE,
)

@field_validator("meta_entrega")
@staticmethod
def must_be_positive(meta_entrega: int) -> int:
if meta_entrega < 0:
raise ValueError("Valor meta_entrega deve ser maior ou igual a 0.")
return meta_entrega

@model_validator(mode="after")
def validate_meta_percentual(self) -> "EntregaSchema":
if (
Expand All @@ -434,15 +428,15 @@ class PlanoEntregasSchema(BaseModel):
title="Código do sistema da unidade",
description=PlanoEntregas.origem_unidade.comment,
)
cod_unidade_autorizadora: int = Field(
cod_unidade_autorizadora: PositiveInt = Field(
title="Código da unidade autorizadora",
description=PlanoEntregas.cod_unidade_autorizadora.comment,
)
cod_unidade_instituidora: int = Field(
cod_unidade_instituidora: PositiveInt = Field(
title="Código da unidade instituidora",
description=PlanoEntregas.cod_unidade_instituidora.comment,
)
cod_unidade_executora: int = Field(
cod_unidade_executora: PositiveInt = Field(
title="Código da unidade executora",
description=PlanoEntregas.cod_unidade_executora.comment,
)
Expand Down Expand Up @@ -476,16 +470,6 @@ class PlanoEntregasSchema(BaseModel):
description="Lista de entregas associadas ao Plano de Entregas",
)

@field_validator(
"cod_unidade_autorizadora", "cod_unidade_instituidora", "cod_unidade_executora"
)
@staticmethod
def validate_codigo_unidade(value: int) -> int:
"""Valida o código da unidade."""
if value < 1:
raise ValueError(f"Código da unidade inválido: {value}")
return value

@model_validator(mode="after")
def validate_entregas_uniqueness(self) -> "PlanoEntregasSchema":
"""Valida a unicidade das entregas."""
Expand Down Expand Up @@ -562,7 +546,7 @@ class ParticipanteSchema(BaseModel):
title="Modalidade e regime de execução do trabalho",
description=Participante.modalidade_execucao.comment,
)
data_assinatura_tcr: Optional[datetime] = Field(
data_assinatura_tcr: Optional[PastDatetime] = Field(
title="Data de assinatura do TCR",
description=Participante.data_assinatura_tcr.comment,
)
Expand Down Expand Up @@ -606,14 +590,6 @@ def cpf_part_validate(cpf: str) -> str:
"Valida o CPF do participante."
return cpf_validate(cpf)

@field_validator("data_assinatura_tcr")
@staticmethod
def data_assinatura_tcr_validate(data_assinatura_tcr: datetime) -> datetime:
"Valida a data de assinatura do TCR."
if data_assinatura_tcr > datetime.now():
raise ValueError("A data_assinatura_tcr não pode ser data futura.")
return data_assinatura_tcr


class Token(BaseModel):
access_token: str
Expand Down
57 changes: 43 additions & 14 deletions tests/participantes_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
Testes relacionados aos status de participantes.
"""

from datetime import date, datetime, timedelta

from httpx import Client
from datetime import date, datetime, timedelta, timezone
from typing import Optional

from fastapi import status
from httpx import Client

import pytest

Expand Down Expand Up @@ -266,7 +266,7 @@ def test_put_participante_missing_mandatory_fields(
):
"""Tenta submeter participantes faltando campos obrigatórios"""
matricula_siape = input_part["matricula_siape"]
cod_unidade_lotacao = input_part['cod_unidade_lotacao']
cod_unidade_lotacao = input_part["cod_unidade_lotacao"]
offset, field_list = missing_fields
for field in field_list:
del input_part[field]
Expand Down Expand Up @@ -404,10 +404,7 @@ def test_put_participante_invalid_matricula_siape(
]
received_error = response.json().get("detail")
if isinstance(received_error, str):
assert any(
message in received_error
for message in detail_messages
)
assert any(message in received_error for message in detail_messages)
else:
assert any(
f"Value error, {message}" in error["msg"]
Expand Down Expand Up @@ -532,10 +529,7 @@ def test_put_invalid_data_assinatura_tcr(
):
"""Tenta criar um participante com data futura do TCR."""
# data de amanhã
input_part["data_assinatura_tcr"] = (
(date.today() + timedelta(days=1))
.isoformat()
)
input_part["data_assinatura_tcr"] = (date.today() + timedelta(days=1)).isoformat()
response = client.put(
f"/organizacao/SIAPE/{user1_credentials['cod_unidade_autorizadora']}"
f"/{input_part['cod_unidade_lotacao']}"
Expand All @@ -545,9 +539,44 @@ def test_put_invalid_data_assinatura_tcr(
)

assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
detail_messages = "A data_assinatura_tcr não pode ser data futura."
detail_messages = "Input should be in the past"
assert any(
f"Value error, {message}" in error["msg"]
message in error["msg"]
for message in detail_messages
for error in response.json().get("detail")
)


@pytest.mark.parametrize(
"timezone_utc",
[
-3, # hora de Brasília
-5, # horário de Rio Branco
None, # sem fuso horário (timezone-naïve)
],
)
def test_put_data_assinatura_tcr_timezone(
truncate_participantes, # pylint: disable=unused-argument
input_part: dict,
user1_credentials: dict,
timezone_utc: Optional[int],
header_usr_1: dict,
client: Client,
):
"""Tenta criar um participante com data de assinatura do TCR em
diversos fuso-horários."""
input_part["data_assinatura_tcr"] = (
datetime.now(
**({"tz": timezone(timedelta(hours=timezone_utc))} if timezone_utc else {})
)
- timedelta(days=1)
).isoformat()
response = client.put(
f"/organizacao/SIAPE/{user1_credentials['cod_unidade_autorizadora']}"
f"/{input_part['cod_unidade_lotacao']}"
f"/participante/{input_part['matricula_siape']}",
json=input_part,
headers=header_usr_1,
)

assert response.status_code == status.HTTP_201_CREATED
8 changes: 4 additions & 4 deletions tests/plano_entregas/core_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -587,9 +587,9 @@ def test_create_invalid_cod_unidade(
assert response.status_code == http_status.HTTP_201_CREATED
else:
assert response.status_code == http_status.HTTP_422_UNPROCESSABLE_ENTITY
detail_message = "Código da unidade inválido"
detail_message = "Input should be greater than 0"
assert any(
f"Value error, {detail_message}" in error["msg"]
detail_message in error["msg"]
for error in response.json().get("detail")
)

Expand Down Expand Up @@ -640,10 +640,10 @@ def test_create_entrega_invalid_percent(
elif tipo_meta == "unidade" and (meta_entrega < 0):
assert response.status_code == http_status.HTTP_422_UNPROCESSABLE_ENTITY
detail_message = (
"Valor meta_entrega deve ser maior ou igual a 0."
"Input should be greater than or equal to 0"
)
assert any(
f"Value error, {detail_message}" in error["msg"]
detail_message in error["msg"]
for error in response.json().get("detail")
)
else:
Expand Down