diff options
-rw-r--r-- | natsort/utils.py | 12 | ||||
-rw-r--r-- | tests/test_natsorted.py | 29 | ||||
-rw-r--r-- | tests/test_parse_number_function.py | 20 |
3 files changed, 46 insertions, 15 deletions
diff --git a/natsort/utils.py b/natsort/utils.py index b86225e..2a3745f 100644 --- a/natsort/utils.py +++ b/natsort/utils.py @@ -420,7 +420,17 @@ def parse_number_or_none_factory( val: Any, _nan_replace: float = nan_replace, _sep: StrOrBytes = sep ) -> BasicTuple: """Given a number, place it in a tuple with a leading null string.""" - return _sep, (_nan_replace if val != val or val is None else val) + # Add a trailing string numbers equaling _nan_replace. This will make + # the ordering between None NaN, and the NaN replacement value... + # None comes first, then NaN, then the replacement value. + if val is None: + return _sep, _nan_replace, "1" + elif val != val: + return _sep, _nan_replace, "2" + elif val == _nan_replace: + return _sep, _nan_replace, "3" + else: + return _sep, val # Return the function, possibly wrapping in tuple if PATH is selected. if alg & ns.PATH and alg & ns.UNGROUPLETTERS and alg & ns.LOCALEALPHA: diff --git a/tests/test_natsorted.py b/tests/test_natsorted.py index eccb9d2..3d6375c 100644 --- a/tests/test_natsorted.py +++ b/tests/test_natsorted.py @@ -4,6 +4,7 @@ Here are a collection of examples of how this module can be used. See the README or the natsort homepage for more details. """ +import math from operator import itemgetter from pathlib import PurePosixPath from typing import List, Tuple, Union @@ -110,19 +111,29 @@ def test_natsorted_handles_mixed_types( @pytest.mark.parametrize( - "alg, expected, slc", + "alg, expected", [ - (ns.DEFAULT, [float("nan"), 5, "25", 1e40], slice(1, None)), - (ns.NANLAST, [5, "25", 1e40, float("nan")], slice(None, 3)), + (ns.DEFAULT, [None, float("nan"), float("-inf"), 5, "25", 1e40, float("inf")]), + (ns.NANLAST, [float("-inf"), 5, "25", 1e40, None, float("nan"), float("inf")]), ], ) -def test_natsorted_handles_nan( - alg: NSType, expected: List[Union[str, float, int]], slc: slice +def test_natsorted_consistent_ordering_with_nan_and_friends( + alg: NSType, expected: List[Union[str, float, None, int]] ) -> None: - given: List[Union[str, float, int]] = ["25", 5, float("nan"), 1e40] - # The slice is because NaN != NaN - # noinspection PyUnresolvedReferences - assert natsorted(given, alg=alg)[slc] == expected[slc] + sentinel = math.pi + expected = [sentinel if x != x else x for x in expected] + given: List[Union[str, float, None, int]] = [ + float("inf"), + float("-inf"), + "25", + 5, + float("nan"), + 1e40, + None, + ] + result = natsorted(given, alg=alg) + result = [sentinel if x != x else x for x in result] + assert result == expected def test_natsorted_with_mixed_bytes_and_str_input_raises_type_error() -> None: diff --git a/tests/test_parse_number_function.py b/tests/test_parse_number_function.py index 85d6b96..5ac5700 100644 --- a/tests/test_parse_number_function.py +++ b/tests/test_parse_number_function.py @@ -20,7 +20,7 @@ from natsort.utils import NumTransformer, parse_number_or_none_factory (ns.PATH | ns.UNGROUPLETTERS | ns.LOCALE, lambda x: ((("xx",), ("", x)),)), ], ) -@given(x=floats(allow_nan=False) | integers()) +@given(x=floats(allow_nan=False, allow_infinity=False) | integers()) def test_parse_number_factory_makes_function_that_returns_tuple( x: Union[float, int], alg: NSType, example_func: NumTransformer ) -> None: @@ -32,10 +32,20 @@ def test_parse_number_factory_makes_function_that_returns_tuple( "alg, x, result", [ (ns.DEFAULT, 57, ("", 57)), - (ns.DEFAULT, float("nan"), ("", float("-inf"))), # NaN transformed to -infinity - (ns.NANLAST, float("nan"), ("", float("+inf"))), # NANLAST makes it +infinity - (ns.DEFAULT, None, ("", float("-inf"))), # None transformed to -infinity - (ns.NANLAST, None, ("", float("+inf"))), # NANLAST makes it +infinity + ( + ns.DEFAULT, + float("nan"), + ("", float("-inf"), "2"), + ), # NaN transformed to -infinity + ( + ns.NANLAST, + float("nan"), + ("", float("+inf"), "2"), + ), # NANLAST makes it +infinity + (ns.DEFAULT, None, ("", float("-inf"), "1")), # None transformed to -infinity + (ns.NANLAST, None, ("", float("+inf"), "1")), # NANLAST makes it +infinity + (ns.DEFAULT, float("-inf"), ("", float("-inf"), "3")), + (ns.NANLAST, float("+inf"), ("", float("+inf"), "3")), ], ) def test_parse_number_factory_treats_nan_and_none_special( |