From deb0315f1da53bf396730abcc2913b32f3903d1f Mon Sep 17 00:00:00 2001 From: ddelange <14880945+ddelange@users.noreply.github.com> Date: Wed, 20 Nov 2024 19:34:59 +0200 Subject: [PATCH] Fix timedelta parsing breaking change (#369) * Fix timedelta parsing breaking change * Account for empty string with whitespace * Add integer as string default value test case --- src/environs/__init__.py | 23 ++++++++++++----------- tests/test_environs.py | 13 ++++++++++++- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/environs/__init__.py b/src/environs/__init__.py index 2fb8e40..a8e6edc 100644 --- a/src/environs/__init__.py +++ b/src/environs/__init__.py @@ -378,17 +378,18 @@ class TimeDeltaField(ma.fields.TimeDelta): def _deserialize(self, value, *args, **kwargs) -> timedelta: if isinstance(value, timedelta): return value - match = _TIMEDELTA_PATTERN.match(value) - if match is not None and match.group(0): # disallow "", allow "0s" - return timedelta( - weeks=int(match.group(1) or 0), - days=int(match.group(2) or 0), - hours=int(match.group(3) or 0), - minutes=int(match.group(4) or 0), - seconds=int(match.group(5) or 0), - milliseconds=int(match.group(6) or 0), - microseconds=int(match.group(7) or 0), - ) + if isinstance(value, str): + match = _TIMEDELTA_PATTERN.match(value) + if match is not None and any(groups := match.groups(default=0)): + return timedelta( + weeks=int(groups[0]), + days=int(groups[1]), + hours=int(groups[2]), + minutes=int(groups[3]), + seconds=int(groups[4]), + milliseconds=int(groups[5]), + microseconds=int(groups[6]), + ) return super()._deserialize(value, *args, **kwargs) diff --git a/tests/test_environs.py b/tests/test_environs.py index 102e0b8..f8ac109 100644 --- a/tests/test_environs.py +++ b/tests/test_environs.py @@ -229,6 +229,13 @@ def test_date_cast(self, set_env, env): assert env.date("DATE") == date def test_timedelta_cast(self, set_env, env): + # default values + assert env.timedelta("TIMEDELTA", "42") == dt.timedelta(seconds=42) + assert env.timedelta("TIMEDELTA", 42) == dt.timedelta(seconds=42) + assert env.timedelta("TIMEDELTA", 42.9) == dt.timedelta(seconds=42) # bug? + assert env.timedelta("TIMEDELTA", dt.timedelta(seconds=42)) == dt.timedelta( + seconds=42, + ) # seconds as integer set_env({"TIMEDELTA": "0"}) assert env.timedelta("TIMEDELTA") == dt.timedelta() @@ -244,7 +251,7 @@ def test_timedelta_cast(self, set_env, env): set_env({"TIMEDELTA": "-42s"}) assert env.timedelta("TIMEDELTA") == dt.timedelta(seconds=-42) # whitespaces, units subselection (but descending ordering) - set_env({"TIMEDELTA": " 42 d -42s "}) + set_env({"TIMEDELTA": " 42 d \t -42s "}) assert env.timedelta("TIMEDELTA") == dt.timedelta(days=42, seconds=-42) # unicode µs (in addition to us below) set_env({"TIMEDELTA": "42µs"}) @@ -262,6 +269,10 @@ def test_timedelta_cast(self, set_env, env): ) # empty string not allowed set_env({"TIMEDELTA": ""}) + with pytest.raises(environs.EnvError): + env.timedelta("TIMEDELTA") + # empty string with whitespace not allowed + set_env({"TIMEDELTA": " "}) with pytest.raises(environs.EnvError): env.timedelta("TIMEDELTA") # float not allowed