From 862f48aa929cb898ec184c5e7f284e9b54674d63 Mon Sep 17 00:00:00 2001 From: Flynn Date: Fri, 16 Aug 2024 15:35:39 -0400 Subject: [PATCH 1/3] Clean up range checking in format_duration. Signed-off-by: Flynn --- kubernetes/utils/duration.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/kubernetes/utils/duration.py b/kubernetes/utils/duration.py index 326d11a6c4..cacb7be05c 100644 --- a/kubernetes/utils/duration.py +++ b/kubernetes/utils/duration.py @@ -21,6 +21,9 @@ # really be a big deal. reDuration = re.compile(r'^([0-9]{1,5}(h|m|s|ms)){1,4}$') +# maxDuration_ms is the maximum duration that GEP-2257 can support, in milliseconds. +maxDuration_ms = (((99999 * 3600) + (59 * 60) + 59) * 1_000) + 999 + def parse_duration(duration) -> datetime.timedelta: """ Parse GEP-2257 Duration format to a datetime.timedelta object. @@ -65,6 +68,13 @@ def format_duration(delta: datetime.timedelta) -> str: if delta == datetime.timedelta(0): return "0s" + # Check range early. + if delta < datetime.timedelta(0): + raise ValueError("Cannot express negative durations in GEP-2257: {}".format(delta)) + + if delta > datetime.timedelta(milliseconds=maxDuration_ms): + raise ValueError("Cannot express durations longer than 99999h59m59s999ms in GEP-2257: {}".format(delta)) + # durationpy.to_str() is happy to use floating-point seconds, which # GEP-2257 is _not_ happy with. So start by peeling off any microseconds # from our delta. @@ -90,8 +100,5 @@ def format_duration(delta: datetime.timedelta) -> str: delta_str += f"{delta_ms}ms" - if not reDuration.match(delta_str): - raise ValueError("Invalid duration format: {}".format(durationpy.to_str(delta))) - return delta_str From 40aaae5e53d8cfa497ff7f41fb0ebadeddd5c446 Mon Sep 17 00:00:00 2001 From: Flynn Date: Fri, 16 Aug 2024 15:35:53 -0400 Subject: [PATCH 2/3] Doctests! Signed-off-by: Flynn --- kubernetes/utils/duration.py | 59 +++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/kubernetes/utils/duration.py b/kubernetes/utils/duration.py index cacb7be05c..eb85abc9d0 100644 --- a/kubernetes/utils/duration.py +++ b/kubernetes/utils/duration.py @@ -35,10 +35,39 @@ def parse_duration(duration) -> datetime.timedelta: See https://gateway-api.sigs.k8s.io/geps/gep-2257/ for more details. Input: duration: string - Returns: datetime.timedelta Raises: ValueError on invalid or unknown input + + Examples: + >>> parse_duration("1h") + datetime.timedelta(seconds=3600) + >>> parse_duration("1m") + datetime.timedelta(seconds=60) + >>> parse_duration("1s") + datetime.timedelta(seconds=1) + >>> parse_duration("1ms") + datetime.timedelta(microseconds=1000) + >>> parse_duration("1h1m1s") + datetime.timedelta(seconds=3661) + >>> parse_duration("10s30m1h") + datetime.timedelta(seconds=5410) + + Units are always required. + >>> parse_duration("1") + Traceback (most recent call last): + ... + ValueError: Invalid duration format: 1 + + Floating-point and negative durations are not valid. + >>> parse_duration("1.5m") + Traceback (most recent call last): + ... + ValueError: Invalid duration format: 1.5m + >>> parse_duration("-1m") + Traceback (most recent call last): + ... + ValueError: Invalid duration format: -1m """ if not reDuration.match(duration): @@ -62,6 +91,34 @@ def format_duration(delta: datetime.timedelta) -> str: Raises: ValueError if the timedelta given cannot be expressed as a GEP-2257 Duration. + + Examples: + >>> format_duration(datetime.timedelta(seconds=3600)) + '1h' + >>> format_duration(datetime.timedelta(seconds=60)) + '1m' + >>> format_duration(datetime.timedelta(seconds=1)) + '1s' + >>> format_duration(datetime.timedelta(microseconds=1000)) + '1ms' + >>> format_duration(datetime.timedelta(seconds=5410)) + '1h30m10s' + + The zero duration is always "0s". + >>> format_duration(datetime.timedelta(0)) + '0s' + + Sub-millisecond precision is not allowed. + >>> format_duration(datetime.timedelta(microseconds=100)) + Traceback (most recent call last): + ... + ValueError: Cannot express sub-millisecond precision in GEP-2257: 0:00:00.000100 + + Negative durations are not allowed. + >>> format_duration(datetime.timedelta(seconds=-1)) + Traceback (most recent call last): + ... + ValueError: Cannot express negative durations in GEP-2257: -1 day, 23:59:59 """ # Short-circuit if we have a zero delta. From 8e3d9c4d89c4b65825f6fe4b04e63ebd92a455e7 Mon Sep 17 00:00:00 2001 From: Flynn Date: Fri, 16 Aug 2024 15:46:35 -0400 Subject: [PATCH 3/3] Formatting. Signed-off-by: Flynn --- kubernetes/utils/duration.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/kubernetes/utils/duration.py b/kubernetes/utils/duration.py index eb85abc9d0..3f434d0812 100644 --- a/kubernetes/utils/duration.py +++ b/kubernetes/utils/duration.py @@ -21,9 +21,11 @@ # really be a big deal. reDuration = re.compile(r'^([0-9]{1,5}(h|m|s|ms)){1,4}$') -# maxDuration_ms is the maximum duration that GEP-2257 can support, in milliseconds. +# maxDuration_ms is the maximum duration that GEP-2257 can support, in +# milliseconds. maxDuration_ms = (((99999 * 3600) + (59 * 60) + 59) * 1_000) + 999 + def parse_duration(duration) -> datetime.timedelta: """ Parse GEP-2257 Duration format to a datetime.timedelta object. @@ -75,6 +77,7 @@ def parse_duration(duration) -> datetime.timedelta: return durationpy.from_str(duration) + def format_duration(delta: datetime.timedelta) -> str: """ Format a datetime.timedelta object to GEP-2257 Duration format. @@ -130,7 +133,8 @@ def format_duration(delta: datetime.timedelta) -> str: raise ValueError("Cannot express negative durations in GEP-2257: {}".format(delta)) if delta > datetime.timedelta(milliseconds=maxDuration_ms): - raise ValueError("Cannot express durations longer than 99999h59m59s999ms in GEP-2257: {}".format(delta)) + raise ValueError( + "Cannot express durations longer than 99999h59m59s999ms in GEP-2257: {}".format(delta)) # durationpy.to_str() is happy to use floating-point seconds, which # GEP-2257 is _not_ happy with. So start by peeling off any microseconds @@ -158,4 +162,3 @@ def format_duration(delta: datetime.timedelta) -> str: delta_str += f"{delta_ms}ms" return delta_str -