From 1da31556bf70c407a107aae00812e14b137ed79b Mon Sep 17 00:00:00 2001 From: Pepa Date: Fri, 31 May 2024 18:24:39 +0200 Subject: [PATCH] Add native `datetime` to `pendulum_dt` (#176) Co-authored-by: pepe-rtmlab --- pydantic_extra_types/pendulum_dt.py | 12 +++++ tests/test_pendulum_dt.py | 74 ++++++++++++++++++++++++----- 2 files changed, 74 insertions(+), 12 deletions(-) diff --git a/pydantic_extra_types/pendulum_dt.py b/pydantic_extra_types/pendulum_dt.py index f3a304fe..d5e2b96e 100644 --- a/pydantic_extra_types/pendulum_dt.py +++ b/pydantic_extra_types/pendulum_dt.py @@ -3,6 +3,8 @@ CoreSchema implementation. This allows Pydantic to validate the DateTime object. """ +import pendulum + try: from pendulum import Date as _Date from pendulum import DateTime as _DateTime @@ -12,6 +14,7 @@ raise RuntimeError( 'The `pendulum_dt` module requires "pendulum" to be installed. You can install it with "pip install pendulum".' ) +from datetime import date, datetime, timedelta from typing import Any, List, Type from pydantic import GetCoreSchemaHandler @@ -68,6 +71,9 @@ def _validate(cls, value: Any, handler: core_schema.ValidatorFunctionWrapHandler if isinstance(value, _DateTime): return handler(value) + if isinstance(value, datetime): + return handler(DateTime.instance(value)) + # otherwise, parse it. try: data = parse(value) @@ -126,6 +132,9 @@ def _validate(cls, value: Any, handler: core_schema.ValidatorFunctionWrapHandler if isinstance(value, _Date): return handler(value) + if isinstance(value, date): + return handler(pendulum.instance(value)) + # otherwise, parse it. try: data = parse(value) @@ -184,6 +193,9 @@ def _validate(cls, value: Any, handler: core_schema.ValidatorFunctionWrapHandler if isinstance(value, _Duration): return handler(value) + if isinstance(value, timedelta): + return handler(_Duration(seconds=value.total_seconds())) + # otherwise, parse it. try: data = parse(value) diff --git a/tests/test_pendulum_dt.py b/tests/test_pendulum_dt.py index 18ec5efc..2cd05451 100644 --- a/tests/test_pendulum_dt.py +++ b/tests/test_pendulum_dt.py @@ -1,9 +1,14 @@ +from datetime import date, datetime, timedelta +from datetime import timezone as tz + import pendulum import pytest from pydantic import BaseModel, ValidationError from pydantic_extra_types.pendulum_dt import Date, DateTime, Duration +UTC = tz.utc + class DtModel(BaseModel): dt: DateTime @@ -17,32 +22,77 @@ class DurationModel(BaseModel): delta_t: Duration -def test_pendulum_dt_existing_instance(): +@pytest.mark.parametrize( + 'instance', + [ + pendulum.now(), + datetime.now(), + datetime.now(UTC), + ], +) +def test_existing_instance(instance): """ Verifies that constructing a model with an existing pendulum dt doesn't throw. """ - now = pendulum.now() - model = DtModel(dt=now) - assert model.dt == now + model = DtModel(dt=instance) + if isinstance(instance, datetime): + assert model.dt == pendulum.instance(instance) + if instance.tzinfo is None and isinstance(instance, datetime): + instance = model.dt.replace(tzinfo=UTC) # pendulum defaults to UTC + dt = model.dt + else: + assert model.dt == instance + dt = model.dt + + assert dt.day == instance.day + assert dt.month == instance.month + assert dt.year == instance.year + assert dt.hour == instance.hour + assert dt.minute == instance.minute + assert dt.second == instance.second + assert dt.microsecond == instance.microsecond + if dt.tzinfo != instance.tzinfo: + assert dt.tzinfo.utcoffset(dt) == instance.tzinfo.utcoffset(instance) -def test_pendulum_date_existing_instance(): +@pytest.mark.parametrize( + 'instance', + [ + pendulum.today(), + date.today(), + ], +) +def test_pendulum_date_existing_instance(instance): """ Verifies that constructing a model with an existing pendulum date doesn't throw. """ - today = pendulum.today().date() - model = DateModel(d=today) - assert model.d == today + model = DateModel(d=instance) + if isinstance(instance, datetime): + assert model.d == pendulum.instance(instance).date() + else: + assert model.d == instance + d = model.d + assert d.day == instance.day + assert d.month == instance.month + assert d.year == instance.year -def test_pendulum_duration_existing_instance(): +@pytest.mark.parametrize( + 'instance', + [ + pendulum.duration(days=42, hours=13, minutes=37), + pendulum.duration(days=-42, hours=13, minutes=37), + timedelta(days=42, hours=13, minutes=37), + timedelta(days=-42, hours=13, minutes=37), + ], +) +def test_duration_timedelta__existing_instance(instance): """ Verifies that constructing a model with an existing pendulum duration doesn't throw. """ - delta_t = pendulum.duration(days=42, hours=13, minutes=37) - model = DurationModel(delta_t=delta_t) + model = DurationModel(delta_t=instance) - assert model.delta_t.total_seconds() == delta_t.total_seconds() + assert model.delta_t.total_seconds() == instance.total_seconds() @pytest.mark.parametrize(