From 3d27d5d9b64003b457762466354ac03febf36800 Mon Sep 17 00:00:00 2001 From: Pierre-Louis Guhur Date: Wed, 22 Mar 2023 09:03:00 +0100 Subject: [PATCH] fix: election date format --- app/schemas.py | 52 ++++++++++++++------------------------- app/tests/test_schemas.py | 52 ++++++++++++++++++++++++++++++++++++++- requirements.txt | 1 + 3 files changed, 70 insertions(+), 35 deletions(-) diff --git a/app/schemas.py b/app/schemas.py index 6a7f956..71d534b 100644 --- a/app/schemas.py +++ b/app/schemas.py @@ -1,5 +1,6 @@ import typing as t -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone +import dateutil.parser from pydantic import BaseModel, Field, validator from pydantic.fields import ModelField from .settings import settings @@ -18,37 +19,6 @@ Description = t.Annotated[str, Field(..., max_length=1024)] Color = t.Annotated[str, Field(min_length=3, max_length=10)] -def _causal_dates_validator(*fields: str): - """ - Create a validator to assess the temporal logic for a list of field - """ - - def validator_fn(cls, value: datetime, values: dict[str, datetime], field): - """ - Check that the field date_created happens before the field date_modified. - """ - if field.name not in fields: - return value - - if any(f not in values or values[f] is None for f in fields): - return value - - idx_field = fields.index(field.name) - for i, f in enumerate(fields): - if i < idx_field and values[f] > value: - raise ArgumentsSchemaError(f"{f} is after {field.name}") - if i > idx_field and values[f] < value: - raise ArgumentsSchemaError(f"{f} is before {field.name}") - - return value - - return validator( - *fields, - allow_reuse=True, - always=True, - )(validator_fn) - - class CandidateBase(BaseModel): name: Name description: Description = "" @@ -124,11 +94,25 @@ class ElectionBase(BaseModel): name: Name description: Description = "" ref: Ref = "" - date_start: datetime = Field(default_factory=datetime.now) - date_end: datetime | None = Field(default_factory=_in_a_long_time) + date_start: datetime | int | str = Field(default_factory=datetime.now) + date_end: datetime | int | str | None = Field(default_factory=_in_a_long_time) hide_results: bool = True restricted: bool = False + @validator("date_end", "date_start", pre=True) + def parse_date(cls, value): + if value is None: + return None + if isinstance(value, datetime): + return value + if isinstance(value, int): + return datetime.fromtimestamp(value) + try: + return dateutil.parser.parse(value) + except dateutil.parser.ParserError: + value = value[: value.index("(")] + return dateutil.parser.parse(value) + class Config: orm_mode = True arbitrary_types_allowed = True diff --git a/app/tests/test_schemas.py b/app/tests/test_schemas.py index 9a28f08..e6d3cc7 100644 --- a/app/tests/test_schemas.py +++ b/app/tests/test_schemas.py @@ -1,6 +1,7 @@ from datetime import datetime import pytest from pydantic.error_wrappers import ValidationError +import dateutil.parser from ..schemas import ( GradeCreate, GradeGet, @@ -98,7 +99,10 @@ def test_election(): ) assert election.candidates == [candidate1, candidate0] assert len(election.grades) == 2 - assert election.date_end > now + + if election.date_end is None: + raise ArgumentsSchemaError("date_end is None") + assert election.date_end > election.date_start with pytest.raises(ArgumentsSchemaError): @@ -114,3 +118,49 @@ def test_election(): ElectionCreate( name="test", grades=[grade0, grade1], candidates=[candidate1, candidate2] ) + + +def test_election_date_string(): + """ + Can create an Election with custom date + """ + candidate0 = CandidateCreate(name="bar") + candidate1 = CandidateCreate(name="foo") + grade1 = GradeCreate(name="very good", value=2) + grade0 = GradeCreate(name="fair enough", value=1) + election = ElectionCreate( + name="test", + grades=[grade0, grade1], + candidates=[candidate1, candidate0], + date_end="Tue Mar 28 2023 09:23:54 GMT+0200 (Central European Summer Time)", + date_start="Tue Mar 27 2023 09:23:54 GMT+0200 (Central European Summer Time)", + ) + assert election.candidates == [candidate1, candidate0] + assert len(election.grades) == 2 + + if election.date_end is None: + raise ArgumentsSchemaError("date_end is None") + assert election.date_end > election.date_start + + +def test_election_date_int(): + """ + Can create an Election with custom date + """ + candidate0 = CandidateCreate(name="bar") + candidate1 = CandidateCreate(name="foo") + grade1 = GradeCreate(name="very good", value=2) + grade0 = GradeCreate(name="fair enough", value=1) + election = ElectionCreate( + name="test", + grades=[grade0, grade1], + candidates=[candidate1, candidate0], + date_start=167947177, + date_end=167947178, + ) + assert election.candidates == [candidate1, candidate0] + assert len(election.grades) == 2 + + if election.date_end is None: + raise ArgumentsSchemaError("date_end is None") + assert election.date_end > election.date_start diff --git a/requirements.txt b/requirements.txt index 65644ab..3bfd82e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,3 +5,4 @@ pydantic==1.10.2 psycopg2==2.9.5 git+https://github.com/MieuxVoter/majority-judgment-library-python python-jose==3.3.0 +python-dateutil==2.8.2