From 3a755eead14ee0bbd058344b78c652d919152857 Mon Sep 17 00:00:00 2001 From: Martin Hauser Date: Thu, 2 Oct 2025 19:40:36 +0200 Subject: [PATCH] fix(api): Update NumericRange handling to use half-open intervals Changes NumericRange boundaries to use half-open intervals [lower, upper). Updates logic in data utilities, and corresponding tests to reflect this standard. Ensures consistency across range handling and validation outputs. Fixes #20471 --- netbox/netbox/api/fields.py | 2 +- netbox/utilities/data.py | 15 +++++++++++---- netbox/utilities/tests/test_data.py | 12 ++++++------ 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/netbox/netbox/api/fields.py b/netbox/netbox/api/fields.py index db5ec184db9..7dfd7d7eb6e 100644 --- a/netbox/netbox/api/fields.py +++ b/netbox/netbox/api/fields.py @@ -169,7 +169,7 @@ def to_internal_value(self, data): if type(data[0]) is not int or type(data[1]) is not int: raise ValidationError(_("Range boundaries must be defined as integers.")) - return NumericRange(data[0], data[1], bounds='[]') + return NumericRange(data[0], data[1] + 1, bounds='[)') def to_representation(self, instance): return instance.lower, instance.upper - 1 diff --git a/netbox/utilities/data.py b/netbox/utilities/data.py index 7b50d26b84d..05d119ff0b0 100644 --- a/netbox/utilities/data.py +++ b/netbox/utilities/data.py @@ -152,9 +152,16 @@ def ranges_to_string(ranges): def string_to_ranges(value): """ - Given a string in the format "1-100, 200-300" return an list of NumericRanges. Intended for use with ArrayField. - For example: - "1-99,200-299" => [NumericRange(1, 100), NumericRange(200, 300)] + Converts a string representation of numeric ranges into a list of NumericRange objects. + + This function parses a string containing numeric values and ranges separated by commas (e.g., + "1-5,8,10-12") and converts it into a list of NumericRange objects. + In the case of a single integer, it is treated as a range where the start and end + are equal. The returned ranges are represented as half-open intervals [lower, upper). + Intended for use with ArrayField. + + Example: + "1-5,8,10-12" => [NumericRange(1, 6), NumericRange(8, 9), NumericRange(10, 13)] """ if not value: return None @@ -172,5 +179,5 @@ def string_to_ranges(value): upper = dash_range[1] else: return None - values.append(NumericRange(int(lower), int(upper), bounds='[]')) + values.append(NumericRange(int(lower), int(upper) + 1, bounds='[)')) return values diff --git a/netbox/utilities/tests/test_data.py b/netbox/utilities/tests/test_data.py index 7b313baf787..5d211c7bd8c 100644 --- a/netbox/utilities/tests/test_data.py +++ b/netbox/utilities/tests/test_data.py @@ -61,18 +61,18 @@ def test_string_to_ranges(self): self.assertEqual( string_to_ranges('10-19, 30-39, 100-199'), [ - NumericRange(10, 19, bounds='[]'), # 10-19 - NumericRange(30, 39, bounds='[]'), # 30-39 - NumericRange(100, 199, bounds='[]'), # 100-199 + NumericRange(10, 20, bounds='[)'), # 10-20 + NumericRange(30, 40, bounds='[)'), # 30-40 + NumericRange(100, 200, bounds='[)'), # 100-200 ] ) self.assertEqual( string_to_ranges('1-2, 5, 10-12'), [ - NumericRange(1, 2, bounds='[]'), # 1-2 - NumericRange(5, 5, bounds='[]'), # 5-5 - NumericRange(10, 12, bounds='[]'), # 10-12 + NumericRange(1, 3, bounds='[)'), # 1-3 + NumericRange(5, 6, bounds='[)'), # 5-6 + NumericRange(10, 13, bounds='[)'), # 10-13 ] )