Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions packages/extended-data/src/extended_data/primitives/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
- DATETIME_PATTERN: Regex for matching ISO 8601 datetime strings.
- TIME_PATTERN: Regex for matching time strings.
- INTEGER_PATTERN: Regex for matching integer strings.
- NUMBER_PATTERN: Regex for matching numeric strings.
- TRUTHY_PATTERN: Regex for matching truthy strings.
- FALSY_PATTERN: Regex for matching falsy strings.
"""
Expand Down Expand Up @@ -54,7 +53,6 @@
) # Matches extended datetime formats like YYYY-MM-DDTHH:MM[:SS][.fff][Z|±hh:mm]
TIME_PATTERN: re.Pattern[str] = re.compile(r"^\d{2}:\d{2}(:\d{2}(\.\d{1,6})?)?$") # Matches HH:MM[:SS] and microseconds
INTEGER_PATTERN: re.Pattern[str] = re.compile(r"^-?\d+$")
NUMBER_PATTERN: re.Pattern[str] = re.compile(r"^-?\d+(\.\d+)?$")
TRUTHY_PATTERN: re.Pattern[str] = re.compile(r"^(y|yes|t|true|on|1)$", re.IGNORECASE)
FALSY_PATTERN: re.Pattern[str] = re.compile(r"^(n|no|f|false|off|0)$", re.IGNORECASE)

Expand Down Expand Up @@ -85,6 +83,21 @@ def _is_valid_absolute_path_string(value: str) -> bool:
)


def _is_decimal_number_string(value: str) -> bool:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve the documented NUMBER_PATTERN export

NUMBER_PATTERN was a non-underscored constant explicitly listed in this module’s documentation, so consumers may import it directly from extended_data.primitives.types; replacing it outright with this private helper makes those imports fail after a patch upgrade. Retain and deprecate a compatible public matcher while using the bounded parser internally, or defer removal to a breaking release.

Useful? React with 👍 / 👎.

"""Return whether *value* uses the package's integer or decimal syntax.

A tiny parser keeps untrusted numeric text out of a backtracking regular
expression. It intentionally accepts only an optional leading minus sign,
one non-empty integer component, and an optional non-empty fractional
component, matching the existing conversion contract.
"""
unsigned = value.removeprefix("-")
integer_part, separator, fractional_part = unsigned.partition(".")
if not integer_part.isdecimal():
return False
return not separator or fractional_part.isdecimal()


class ConversionError(ValueError):
"""Custom error class for handling conversion failures.

Expand Down Expand Up @@ -182,7 +195,7 @@ def string_to_float(val: str, raise_on_error: bool = False) -> float | None:
ConversionError: If the value is invalid and raise_on_error is True.
"""
val = str(val)
if NUMBER_PATTERN.match(val):
if _is_decimal_number_string(val):
try:
return float(val)
except ValueError as exc:
Expand Down Expand Up @@ -463,7 +476,7 @@ def reconstruct_special_type(converted_obj: str, fail_silently: bool = False) ->
return Path(converted_obj)
if TRUTHY_PATTERN.match(converted_obj) or FALSY_PATTERN.match(converted_obj):
return string_to_bool(converted_obj)
if NUMBER_PATTERN.match(converted_obj):
if _is_decimal_number_string(converted_obj):
if INTEGER_PATTERN.match(converted_obj):
return string_to_int(converted_obj)
return string_to_float(converted_obj)
Expand Down
5 changes: 5 additions & 0 deletions packages/extended-data/tests/core/test_type_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,11 @@ def test_string_to_int_swallows_nested_conversion_errors_when_not_requested(mock
assert string_to_int("3.14") is None


def test_string_to_float_rejects_adversarial_decimal_text() -> None:
"""Reject long malformed decimals without regex backtracking."""
assert string_to_float(("9" * 10_000) + ".") is None


def test_string_to_int_raises_when_nested_conversion_returns_none(mocker) -> None:
"""Raise an integer conversion error when nested conversion returns no value."""
mocker.patch("extended_data.primitives.types.string_to_float", return_value=None)
Expand Down
4 changes: 2 additions & 2 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading