From a2ff89cbd343b634fbb3fb085a9f2d10b5b5c46a Mon Sep 17 00:00:00 2001 From: Chandra Tekulapally Date: Sun, 18 Oct 2026 00:52:44 -0700 Subject: [PATCH 1/2] Correct clamp annotations for callable formatters and None --- src/humanize/number.py | 33 +++++++++++++++++++++++++++++---- tests/test_number.py | 9 +++++++++ 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/humanize/number.py b/src/humanize/number.py index fe00ae04..8980c01a 100644 --- a/src/humanize/number.py +++ b/src/humanize/number.py @@ -5,6 +5,7 @@ __lazy_modules__ = {"bisect"} import bisect +from typing import overload from .i18n import _gettext as _ from .i18n import _gettext_noop as N_ @@ -15,6 +16,7 @@ TYPE_CHECKING = False if TYPE_CHECKING: + from collections.abc import Callable from typing import TypeAlias # This type can be better defined by typing.SupportsFloat @@ -457,14 +459,36 @@ def scientific(value: NumberOrString, precision: int = 2) -> str: return part1 + " x 10" + str(int(part2)).translate(_SUPERSCRIPT_TRANS) +@overload +def clamp( + value: None, + format: str | Callable[[float], str] = "{:}", + floor: float | None = None, + ceil: float | None = None, + floor_token: str = "<", + ceil_token: str = ">", +) -> None: ... + + +@overload def clamp( value: float, - format: str = "{:}", + format: str | Callable[[float], str] = "{:}", floor: float | None = None, ceil: float | None = None, floor_token: str = "<", ceil_token: str = ">", -) -> str: +) -> str: ... + + +def clamp( + value: float | None, + format: str | Callable[[float], str] = "{:}", + floor: float | None = None, + ceil: float | None = None, + floor_token: str = "<", + ceil_token: str = ">", +) -> str | None: """Returns number with the specified format, clamped between floor and ceil. If the number is larger than ceil or smaller than floor, then the respective limit @@ -488,7 +512,7 @@ def clamp( ``` Args: - value (int, float): Input number. + value (int, float, None): Input number, or `None` to return `None`. format (str OR callable): Can either be a formatting string, or a callable function that receives value and returns a string. floor (int, float): Smallest value before clamping. @@ -499,7 +523,8 @@ def clamp( to output. Returns: - str: Formatted number. The output is clamped between the indicated floor and + str | None: Formatted number, or `None` when `value` is `None`. + The output is clamped between the indicated floor and ceil. If the number is larger than ceil or smaller than floor, the output will be prepended with a token indicating as such. diff --git a/tests/test_number.py b/tests/test_number.py index 5fb12fa6..867cbedc 100644 --- a/tests/test_number.py +++ b/tests/test_number.py @@ -297,6 +297,15 @@ def test_clamp(test_args: list[typing.Any], expected: str) -> None: assert humanize.clamp(*test_args) == expected +def test_clamp_return_types() -> None: + assert typing.assert_type(humanize.clamp(1.0), str) == "1.0" + assert typing.assert_type(humanize.clamp(None), None) is None + assert ( + typing.assert_type(humanize.clamp(1, format=humanize.intword, floor=1e6), str) + == "<1.0 million" + ) + + @pytest.mark.parametrize( "test_args, expected", [ From 018c274a39a0bd1ec48249d03743bb0fc6cd0654 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Thu, 12 Nov 2026 13:56:35 +0200 Subject: [PATCH 2/2] Remove eager typing import and redundant typing test --- src/humanize/number.py | 23 ----------------------- tests/test_number.py | 9 --------- 2 files changed, 32 deletions(-) diff --git a/src/humanize/number.py b/src/humanize/number.py index 8980c01a..e5a2612d 100644 --- a/src/humanize/number.py +++ b/src/humanize/number.py @@ -5,7 +5,6 @@ __lazy_modules__ = {"bisect"} import bisect -from typing import overload from .i18n import _gettext as _ from .i18n import _gettext_noop as N_ @@ -459,28 +458,6 @@ def scientific(value: NumberOrString, precision: int = 2) -> str: return part1 + " x 10" + str(int(part2)).translate(_SUPERSCRIPT_TRANS) -@overload -def clamp( - value: None, - format: str | Callable[[float], str] = "{:}", - floor: float | None = None, - ceil: float | None = None, - floor_token: str = "<", - ceil_token: str = ">", -) -> None: ... - - -@overload -def clamp( - value: float, - format: str | Callable[[float], str] = "{:}", - floor: float | None = None, - ceil: float | None = None, - floor_token: str = "<", - ceil_token: str = ">", -) -> str: ... - - def clamp( value: float | None, format: str | Callable[[float], str] = "{:}", diff --git a/tests/test_number.py b/tests/test_number.py index 867cbedc..5fb12fa6 100644 --- a/tests/test_number.py +++ b/tests/test_number.py @@ -297,15 +297,6 @@ def test_clamp(test_args: list[typing.Any], expected: str) -> None: assert humanize.clamp(*test_args) == expected -def test_clamp_return_types() -> None: - assert typing.assert_type(humanize.clamp(1.0), str) == "1.0" - assert typing.assert_type(humanize.clamp(None), None) is None - assert ( - typing.assert_type(humanize.clamp(1, format=humanize.intword, floor=1e6), str) - == "<1.0 million" - ) - - @pytest.mark.parametrize( "test_args, expected", [