Skip to content

Commit

Permalink
🐛 Preserve months when using the Pendulum Duration type (#283)
Browse files Browse the repository at this point in the history
* Preserve months when using the Pendulum Duration type

* format
  • Loading branch information
gareththackeray authored Jan 3, 2025
1 parent 3cf70f5 commit 01866e6
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 6 deletions.
22 changes: 19 additions & 3 deletions pydantic_extra_types/pendulum_dt.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,9 +199,25 @@ def _validate(cls, value: Any, handler: core_schema.ValidatorFunctionWrapHandler
Returns:
The validated value or raises a PydanticCustomError.
"""
# if we are passed an existing instance, pass it straight through.
if isinstance(value, (_Duration, timedelta)):
return Duration(seconds=value.total_seconds())

if isinstance(value, _Duration):
return Duration(
years=value.years,
months=value.months,
weeks=value.weeks,
days=value.remaining_days,
hours=value.hours,
minutes=value.minutes,
seconds=value.remaining_seconds,
microseconds=value.microseconds,
)

if isinstance(value, timedelta):
return Duration(
days=value.days,
seconds=value.seconds,
microseconds=value.microseconds,
)

try:
parsed = parse(value, exact=True)
Expand Down
48 changes: 45 additions & 3 deletions tests/test_pendulum_dt.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,30 @@ def test_pendulum_date_existing_instance(instance):
[
pendulum.duration(days=42, hours=13, minutes=37),
pendulum.duration(days=-42, hours=13, minutes=37),
pendulum.duration(weeks=97),
pendulum.duration(days=463),
pendulum.duration(milliseconds=90122),
pendulum.duration(microseconds=90122),
pendulum.duration(
years=2,
months=3,
weeks=19,
days=1,
hours=25,
seconds=732,
milliseconds=123,
microseconds=1324,
),
timedelta(days=42, hours=13, minutes=37),
timedelta(days=-42, hours=13, minutes=37),
timedelta(
weeks=19,
days=1,
hours=25,
seconds=732,
milliseconds=123,
microseconds=1324,
),
],
)
def test_duration_timedelta__existing_instance(instance):
Expand Down Expand Up @@ -227,7 +249,11 @@ def test_pendulum_dt_from_str_unix_timestamp_is_utc(dt):

@pytest.mark.parametrize(
'd',
[pendulum.now().date().isoformat(), pendulum.now().to_w3c_string(), pendulum.now().to_iso8601_string()],
[
pendulum.now().date().isoformat(),
pendulum.now().to_w3c_string(),
pendulum.now().to_iso8601_string(),
],
)
def test_pendulum_date_from_serialized(d):
"""Verifies that building an instance from serialized, well-formed strings decode properly."""
Expand Down Expand Up @@ -308,7 +334,10 @@ def test_pendulum_dt_non_strict_malformed(dt):
DtModelNotStrict(dt=dt)


@pytest.mark.parametrize('invalid_value', [None, 'malformed', pendulum.today().to_iso8601_string()[:5], 'P10Y10M10D'])
@pytest.mark.parametrize(
'invalid_value',
[None, 'malformed', pendulum.today().to_iso8601_string()[:5], 'P10Y10M10D'],
)
def test_pendulum_date_malformed(invalid_value):
"""Verifies that the instance fails to validate if malformed date are passed."""
with pytest.raises(ValidationError):
Expand All @@ -317,7 +346,14 @@ def test_pendulum_date_malformed(invalid_value):

@pytest.mark.parametrize(
'delta_t',
[None, 'malformed', pendulum.today().to_iso8601_string()[:5], 42, '12m', '2021-01-01T12:00:00'],
[
None,
'malformed',
pendulum.today().to_iso8601_string()[:5],
42,
'12m',
'2021-01-01T12:00:00',
],
)
def test_pendulum_duration_malformed(delta_t):
"""Verifies that the instance fails to validate if malformed durations are passed."""
Expand All @@ -344,3 +380,9 @@ def test_date_type_adapter(input_type: type, value, is_instance: type):
assert type(validated) is input_type
assert isinstance(validated, input_type)
assert isinstance(validated, is_instance)


def test_pendulum_duration_months_are_preserved():
m = DurationModel(delta_t=pendulum.Duration(months=1))

assert m.delta_t.months == 1

0 comments on commit 01866e6

Please sign in to comment.