From 64f13fe7ac23efdf72e0157af27c87adfc9ea577 Mon Sep 17 00:00:00 2001 From: Seth Morton Date: Thu, 28 Oct 2021 21:42:27 -0700 Subject: Add type annotations to tests That was... a lot. There are still errors in the os_sorted tests, but that requires me to rethink some typing decisions in the main code so that will be for a future commit. --- tests/conftest.py | 10 ++- tests/profile_natsorted.py | 9 ++- tests/test_fake_fastnumbers.py | 49 ++++++------ tests/test_final_data_transform_factory.py | 13 +++- tests/test_input_string_transform_factory.py | 27 ++++--- tests/test_main.py | 66 ++++++++--------- tests/test_natsort_key.py | 23 +++--- tests/test_natsort_keygen.py | 58 ++++++++++----- tests/test_natsorted.py | 94 ++++++++++++++++-------- tests/test_natsorted_convenience.py | 39 ++++++---- tests/test_ns_enum.py | 4 +- tests/test_os_sorted.py | 11 +-- tests/test_parse_bytes_function.py | 8 +- tests/test_parse_number_function.py | 14 +++- tests/test_parse_string_function.py | 29 +++++--- tests/test_regex.py | 9 ++- tests/test_string_component_transform_factory.py | 11 ++- tests/test_unicode_numbers.py | 14 ++-- tests/test_utils.py | 46 +++++++----- tox.ini | 5 +- 20 files changed, 328 insertions(+), 211 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 74d7f4f..c63e149 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,6 +3,7 @@ Fixtures for pytest. """ import locale +from typing import Iterator import hypothesis import pytest @@ -16,7 +17,7 @@ hypothesis.settings.register_profile( ) -def load_locale(x): +def load_locale(x: str) -> None: """Convenience to load a locale, trying ISO8859-1 first.""" try: locale.setlocale(locale.LC_ALL, str("{}.ISO8859-1".format(x))) @@ -25,15 +26,16 @@ def load_locale(x): @pytest.fixture() -def with_locale_en_us(): +def with_locale_en_us() -> Iterator[None]: """Convenience to load the en_US locale - reset when complete.""" orig = locale.getlocale() - yield load_locale("en_US") + load_locale("en_US") + yield locale.setlocale(locale.LC_ALL, orig) @pytest.fixture() -def with_locale_de_de(): +def with_locale_de_de() -> Iterator[None]: """ Convenience to load the de_DE locale - reset when complete - skip if missing. """ diff --git a/tests/profile_natsorted.py b/tests/profile_natsorted.py index a2b1a5c..f6580a3 100644 --- a/tests/profile_natsorted.py +++ b/tests/profile_natsorted.py @@ -7,6 +7,7 @@ inputs and different settings. import cProfile import locale import sys +from typing import List, Union try: from natsort import ns, natsort_keygen @@ -14,6 +15,8 @@ except ImportError: sys.path.insert(0, ".") from natsort import ns, natsort_keygen +from natsort.natsort import NatsortKeyType + locale.setlocale(locale.LC_ALL, "en_US.UTF-8") # Samples to parse @@ -32,7 +35,7 @@ path_key = natsort_keygen(alg=ns.PATH) locale_key = natsort_keygen(alg=ns.LOCALE) -def prof_time_to_generate(): +def prof_time_to_generate() -> None: print("*** Generate Plain Key ***") for _ in range(100000): natsort_keygen() @@ -41,7 +44,9 @@ def prof_time_to_generate(): cProfile.run("prof_time_to_generate()", sort="time") -def prof_parsing(a, msg, key=basic_key): +def prof_parsing( + a: Union[str, int, bytes, List[str]], msg: str, key: NatsortKeyType = basic_key +) -> None: print(msg) for _ in range(100000): key(a) diff --git a/tests/test_fake_fastnumbers.py b/tests/test_fake_fastnumbers.py index c75bb11..574f7cf 100644 --- a/tests/test_fake_fastnumbers.py +++ b/tests/test_fake_fastnumbers.py @@ -5,13 +5,14 @@ Test the fake fastnumbers module. import unicodedata from math import isnan +from typing import Union, cast from hypothesis import given from hypothesis.strategies import floats, integers, text from natsort.compat.fake_fastnumbers import fast_float, fast_int -def is_float(x): +def is_float(x: str) -> bool: try: float(x) except ValueError: @@ -25,19 +26,19 @@ def is_float(x): return True -def not_a_float(x): +def not_a_float(x: str) -> bool: return not is_float(x) -def is_int(x): +def is_int(x: Union[str, float]) -> bool: try: - return x.is_integer() + return cast(float, x).is_integer() except AttributeError: try: int(x) except ValueError: try: - unicodedata.digit(x) + unicodedata.digit(cast(str, x)) except (ValueError, TypeError): return False else: @@ -46,7 +47,7 @@ def is_int(x): return True -def not_an_int(x): +def not_an_int(x: Union[str, float]) -> bool: return not is_int(x) @@ -54,56 +55,56 @@ def not_an_int(x): # and a test that uses the hypothesis module. -def test_fast_float_returns_nan_alternate_if_nan_option_is_given(): +def test_fast_float_returns_nan_alternate_if_nan_option_is_given() -> None: assert fast_float("nan", nan=7) == 7 -def test_fast_float_converts_float_string_to_float_example(): +def test_fast_float_converts_float_string_to_float_example() -> None: assert fast_float("45.8") == 45.8 assert fast_float("-45") == -45.0 assert fast_float("45.8e-2", key=len) == 45.8e-2 - assert isnan(fast_float("nan")) - assert isnan(fast_float("+nan")) - assert isnan(fast_float("-NaN")) + assert isnan(cast(float, fast_float("nan"))) + assert isnan(cast(float, fast_float("+nan"))) + assert isnan(cast(float, fast_float("-NaN"))) assert fast_float("۱۲.۱۲") == 12.12 assert fast_float("-۱۲.۱۲") == -12.12 @given(floats(allow_nan=False)) -def test_fast_float_converts_float_string_to_float(x): +def test_fast_float_converts_float_string_to_float(x: float) -> None: assert fast_float(repr(x)) == x -def test_fast_float_leaves_string_as_is_example(): +def test_fast_float_leaves_string_as_is_example() -> None: assert fast_float("invalid") == "invalid" @given(text().filter(not_a_float).filter(bool)) -def test_fast_float_leaves_string_as_is(x): +def test_fast_float_leaves_string_as_is(x: str) -> None: assert fast_float(x) == x -def test_fast_float_with_key_applies_to_string_example(): +def test_fast_float_with_key_applies_to_string_example() -> None: assert fast_float("invalid", key=len) == len("invalid") @given(text().filter(not_a_float).filter(bool)) -def test_fast_float_with_key_applies_to_string(x): +def test_fast_float_with_key_applies_to_string(x: str) -> None: assert fast_float(x, key=len) == len(x) -def test_fast_int_leaves_float_string_as_is_example(): +def test_fast_int_leaves_float_string_as_is_example() -> None: assert fast_int("45.8") == "45.8" assert fast_int("nan") == "nan" assert fast_int("inf") == "inf" @given(floats().filter(not_an_int)) -def test_fast_int_leaves_float_string_as_is(x): +def test_fast_int_leaves_float_string_as_is(x: float) -> None: assert fast_int(repr(x)) == repr(x) -def test_fast_int_converts_int_string_to_int_example(): +def test_fast_int_converts_int_string_to_int_example() -> None: assert fast_int("-45") == -45 assert fast_int("+45") == 45 assert fast_int("۱۲") == 12 @@ -111,23 +112,23 @@ def test_fast_int_converts_int_string_to_int_example(): @given(integers()) -def test_fast_int_converts_int_string_to_int(x): +def test_fast_int_converts_int_string_to_int(x: int) -> None: assert fast_int(repr(x)) == x -def test_fast_int_leaves_string_as_is_example(): +def test_fast_int_leaves_string_as_is_example() -> None: assert fast_int("invalid") == "invalid" @given(text().filter(not_an_int).filter(bool)) -def test_fast_int_leaves_string_as_is(x): +def test_fast_int_leaves_string_as_is(x: str) -> None: assert fast_int(x) == x -def test_fast_int_with_key_applies_to_string_example(): +def test_fast_int_with_key_applies_to_string_example() -> None: assert fast_int("invalid", key=len) == len("invalid") @given(text().filter(not_an_int).filter(bool)) -def test_fast_int_with_key_applies_to_string(x): +def test_fast_int_with_key_applies_to_string(x: str) -> None: assert fast_int(x, key=len) == len(x) diff --git a/tests/test_final_data_transform_factory.py b/tests/test_final_data_transform_factory.py index f6bf636..5437d53 100644 --- a/tests/test_final_data_transform_factory.py +++ b/tests/test_final_data_transform_factory.py @@ -1,17 +1,20 @@ # -*- coding: utf-8 -*- """These test the utils.py functions.""" +from typing import Callable, Union import pytest from hypothesis import example, given from hypothesis.strategies import floats, integers, text -from natsort.ns_enum import NS_DUMB, ns +from natsort.ns_enum import NS_DUMB, NS_t, ns from natsort.utils import final_data_transform_factory @pytest.mark.parametrize("alg", [ns.DEFAULT, ns.UNGROUPLETTERS, ns.LOCALE]) @given(x=text(), y=floats(allow_nan=False, allow_infinity=False) | integers()) @pytest.mark.usefixtures("with_locale_en_us") -def test_final_data_transform_factory_default(x, y, alg): +def test_final_data_transform_factory_default( + x: str, y: Union[int, float], alg: NS_t +) -> None: final_data_transform_func = final_data_transform_factory(alg, "", "::") value = (x, y) original_value = "".join(map(str, value)) @@ -34,7 +37,9 @@ def test_final_data_transform_factory_default(x, y, alg): @given(x=text(), y=floats(allow_nan=False, allow_infinity=False) | integers()) @example(x="İ", y=0) @pytest.mark.usefixtures("with_locale_en_us") -def test_final_data_transform_factory_ungroup_and_locale(x, y, alg, func): +def test_final_data_transform_factory_ungroup_and_locale( + x: str, y: Union[int, float], alg: NS_t, func: Callable[[str], str] +) -> None: final_data_transform_func = final_data_transform_factory(alg, "", "::") value = (x, y) original_value = "".join(map(str, value)) @@ -46,6 +51,6 @@ def test_final_data_transform_factory_ungroup_and_locale(x, y, alg, func): assert result == expected -def test_final_data_transform_factory_ungroup_and_locale_empty_tuple(): +def test_final_data_transform_factory_ungroup_and_locale_empty_tuple() -> None: final_data_transform_func = final_data_transform_factory(ns.UG | ns.L, "", "::") assert final_data_transform_func((), "") == ((), ()) diff --git a/tests/test_input_string_transform_factory.py b/tests/test_input_string_transform_factory.py index 7d54afd..d265492 100644 --- a/tests/test_input_string_transform_factory.py +++ b/tests/test_input_string_transform_factory.py @@ -1,14 +1,15 @@ # -*- coding: utf-8 -*- """These test the utils.py functions.""" +from typing import Callable import pytest from hypothesis import example, given from hypothesis.strategies import integers, text -from natsort.ns_enum import NS_DUMB, ns +from natsort.ns_enum import NS_DUMB, NS_t, ns from natsort.utils import input_string_transform_factory -def thousands_separated_int(n): +def thousands_separated_int(n: str) -> str: """Insert thousands separators in an int.""" new_int = "" for i, y in enumerate(reversed(n), 1): @@ -20,7 +21,7 @@ def thousands_separated_int(n): @given(text()) -def test_input_string_transform_factory_is_no_op_for_no_alg_options(x): +def test_input_string_transform_factory_is_no_op_for_no_alg_options(x: str) -> None: input_string_transform_func = input_string_transform_factory(ns.DEFAULT) assert input_string_transform_func(x) is x @@ -36,7 +37,9 @@ def test_input_string_transform_factory_is_no_op_for_no_alg_options(x): ], ) @given(x=text()) -def test_input_string_transform_factory(x, alg, example_func): +def test_input_string_transform_factory( + x: str, alg: NS_t, example_func: Callable[[str], str] +) -> None: input_string_transform_func = input_string_transform_factory(alg) assert input_string_transform_func(x) == example_func(x) @@ -44,7 +47,7 @@ def test_input_string_transform_factory(x, alg, example_func): @example(12543642642534980) # 12,543,642,642,534,980 => 12543642642534980 @given(x=integers(min_value=1000)) @pytest.mark.usefixtures("with_locale_en_us") -def test_input_string_transform_factory_cleans_thousands(x): +def test_input_string_transform_factory_cleans_thousands(x: int) -> None: int_str = str(x).rstrip("lL") thousands_int_str = thousands_separated_int(int_str) assert thousands_int_str.replace(",", "") != thousands_int_str @@ -69,7 +72,9 @@ def test_input_string_transform_factory_cleans_thousands(x): ], ) @pytest.mark.usefixtures("with_locale_en_us") -def test_input_string_transform_factory_handles_us_locale(x, expected): +def test_input_string_transform_factory_handles_us_locale( + x: str, expected: str +) -> None: input_string_transform_func = input_string_transform_factory(ns.LOCALE) assert input_string_transform_func(x) == expected @@ -83,7 +88,9 @@ def test_input_string_transform_factory_handles_us_locale(x, expected): ], ) @pytest.mark.usefixtures("with_locale_de_de") -def test_input_string_transform_factory_handles_de_locale(x, expected): +def test_input_string_transform_factory_handles_de_locale( + x: str, expected: str +) -> None: input_string_transform_func = input_string_transform_factory(ns.LOCALE) assert input_string_transform_func(x) == expected @@ -97,13 +104,15 @@ def test_input_string_transform_factory_handles_de_locale(x, expected): ], ) @pytest.mark.usefixtures("with_locale_de_de") -def test_input_string_transform_factory_handles_german_locale(alg, expected): +def test_input_string_transform_factory_handles_german_locale( + alg: NS_t, expected: str +) -> None: input_string_transform_func = input_string_transform_factory(alg) assert input_string_transform_func("1543,753") == expected @pytest.mark.usefixtures("with_locale_de_de") -def test_input_string_transform_factory_does_nothing_with_non_num_input(): +def test_input_string_transform_factory_does_nothing_with_non_num_input() -> None: input_string_transform_func = input_string_transform_factory(ns.LOCALE | ns.FLOAT) expected = "154s,t53" assert input_string_transform_func("154s,t53") == expected diff --git a/tests/test_main.py b/tests/test_main.py index da91fdd..d2a3e28 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -5,11 +5,13 @@ Test the natsort command-line tool functions. import re import sys +from typing import Any as Any_t, List, Union import pytest from hypothesis import given -from hypothesis.strategies import data, floats, integers, lists +from hypothesis.strategies import DataObject, data, floats, integers, lists from natsort.__main__ import ( + TypedArgs, check_filters, keep_entry_range, keep_entry_value, @@ -17,9 +19,12 @@ from natsort.__main__ import ( range_check, sort_and_print_entries, ) +from pytest_mock import MockerFixture -def test_main_passes_default_arguments_with_no_command_line_options(mocker): +def test_main_passes_default_arguments_with_no_command_line_options( + mocker: MockerFixture, +) -> None: p = mocker.patch("natsort.__main__.sort_and_print_entries") main("num-2", "num-6", "num-1") args = p.call_args[0][1] @@ -34,7 +39,9 @@ def test_main_passes_default_arguments_with_no_command_line_options(mocker): assert not args.locale -def test_main_passes_arguments_with_all_command_line_options(mocker): +def test_main_passes_arguments_with_all_command_line_options( + mocker: MockerFixture, +) -> None: arguments = ["--paths", "--reverse", "--locale"] arguments.extend(["--filter", "4", "10"]) arguments.extend(["--reverse-filter", "100", "110"]) @@ -57,21 +64,6 @@ def test_main_passes_arguments_with_all_command_line_options(mocker): assert args.locale -class Args: - """A dummy class to simulate the argparse Namespace object""" - - def __init__(self, filt, reverse_filter, exclude, as_path, reverse): - self.filter = filt - self.reverse_filter = reverse_filter - self.exclude = exclude - self.reverse = reverse - self.number_type = "float" - self.signed = True - self.exp = True - self.paths = as_path - self.locale = 0 - - mock_print = "__builtin__.print" if sys.version[0] == "2" else "builtins.print" entries = [ @@ -135,9 +127,11 @@ entries = [ ([None, None, False, True, True], reversed([2, 3, 1, 0, 5, 6, 4])), ], ) -def test_sort_and_print_entries(options, order, mocker): +def test_sort_and_print_entries( + options: List[Any_t], order: List[int], mocker: MockerFixture +) -> None: p = mocker.patch(mock_print) - sort_and_print_entries(entries, Args(*options)) + sort_and_print_entries(entries, TypedArgs(*options)) e = [mocker.call(entries[i]) for i in order] p.assert_has_calls(e) @@ -146,13 +140,15 @@ def test_sort_and_print_entries(options, order, mocker): # and a test that uses the hypothesis module. -def test_range_check_returns_range_as_is_but_with_floats_example(): +def test_range_check_returns_range_as_is_but_with_floats_example() -> None: assert range_check(10, 11) == (10.0, 11.0) assert range_check(6.4, 30) == (6.4, 30.0) @given(x=floats(allow_nan=False, min_value=-1e8, max_value=1e8) | integers(), d=data()) -def test_range_check_returns_range_as_is_if_first_is_less_than_second(x, d): +def test_range_check_returns_range_as_is_if_first_is_less_than_second( + x: Union[int, float], d: DataObject +) -> None: # Pull data such that the first is less than the second. if isinstance(x, float): y = d.draw(floats(min_value=x + 1.0, max_value=1e9, allow_nan=False)) @@ -161,44 +157,48 @@ def test_range_check_returns_range_as_is_if_first_is_less_than_second(x, d): assert range_check(x, y) == (x, y) -def test_range_check_raises_value_error_if_second_is_less_than_first_example(): +def test_range_check_raises_value_error_if_second_is_less_than_first_example() -> None: with pytest.raises(ValueError, match="low >= high"): range_check(7, 2) @given(x=floats(allow_nan=False), d=data()) -def test_range_check_raises_value_error_if_second_is_less_than_first(x, d): +def test_range_check_raises_value_error_if_second_is_less_than_first( + x: float, d: DataObject +) -> None: # Pull data such that the first is greater than or equal to the second. y = d.draw(floats(max_value=x, allow_nan=False)) with pytest.raises(ValueError, match="low >= high"): range_check(x, y) -def test_check_filters_returns_none_if_filter_evaluates_to_false(): +def test_check_filters_returns_none_if_filter_evaluates_to_false() -> None: assert check_filters(()) is None - assert check_filters(False) is None - assert check_filters(None) is None -def test_check_filters_returns_input_as_is_if_filter_is_valid_example(): +def test_check_filters_returns_input_as_is_if_filter_is_valid_example() -> None: assert check_filters([(6, 7)]) == [(6, 7)] assert check_filters([(6, 7), (2, 8)]) == [(6, 7), (2, 8)] @given(x=lists(integers(), min_size=1), d=data()) -def test_check_filters_returns_input_as_is_if_filter_is_valid(x, d): +def test_check_filters_returns_input_as_is_if_filter_is_valid( + x: List[int], d: DataObject +) -> None: # ensure y is element-wise greater than x y = [d.draw(integers(min_value=val + 1)) for val in x] assert check_filters(list(zip(x, y))) == [(i, j) for i, j in zip(x, y)] -def test_check_filters_raises_value_error_if_filter_is_invalid_example(): +def test_check_filters_raises_value_error_if_filter_is_invalid_example() -> None: with pytest.raises(ValueError, match="Error in --filter: low >= high"): check_filters([(7, 2)]) @given(x=lists(integers(), min_size=1), d=data()) -def test_check_filters_raises_value_error_if_filter_is_invalid(x, d): +def test_check_filters_raises_value_error_if_filter_is_invalid( + x: List[int], d: DataObject +) -> None: # ensure y is element-wise less than or equal to x y = [d.draw(integers(max_value=val)) for val in x] with pytest.raises(ValueError, match="Error in --filter: low >= high"): @@ -212,11 +212,11 @@ def test_check_filters_raises_value_error_if_filter_is_invalid(x, d): # 3. No portion is between the bounds => False. [([0], [100], True), ([1, 88], [20, 90], True), ([1], [20], False)], ) -def test_keep_entry_range(lows, highs, truth): +def test_keep_entry_range(lows: List[int], highs: List[int], truth: bool) -> None: assert keep_entry_range("a56b23c89", lows, highs, int, re.compile(r"\d+")) is truth # 1. Values not in entry => True. 2. Values in entry => False. @pytest.mark.parametrize("values, truth", [([100, 45], True), ([23], False)]) -def test_keep_entry_value(values, truth): +def test_keep_entry_value(values: List[int], truth: bool) -> None: assert keep_entry_value("a56b23c89", values, int, re.compile(r"\d+")) is truth diff --git a/tests/test_natsort_key.py b/tests/test_natsort_key.py index 6b56bb1..8034a8c 100644 --- a/tests/test_natsort_key.py +++ b/tests/test_natsort_key.py @@ -1,42 +1,43 @@ # -*- coding: utf-8 -*- """These test the utils.py functions.""" +from typing import Any as Any_t, List, NoReturn, Tuple, Union from hypothesis import given from hypothesis.strategies import binary, floats, integers, lists, text from natsort.utils import natsort_key -def str_func(x): +def str_func(x: Any_t) -> Tuple[str]: if isinstance(x, str): - return x + return (x,) else: raise TypeError("Not a str!") -def fail(_): +def fail(_: Any_t) -> NoReturn: raise AssertionError("This should never be reached!") @given(floats(allow_nan=False) | integers()) -def test_natsort_key_with_numeric_input_takes_number_path(x): - assert natsort_key(x, None, str_func, fail, lambda y: y) is x +def test_natsort_key_with_numeric_input_takes_number_path(x: Union[float, int]) -> None: + assert natsort_key(x, None, str_func, fail, lambda y: ("", y)) is x @given(binary().filter(bool)) -def test_natsort_key_with_bytes_input_takes_bytes_path(x): - assert natsort_key(x, None, str_func, lambda y: y, fail) is x +def test_natsort_key_with_bytes_input_takes_bytes_path(x: bytes) -> None: + assert natsort_key(x, None, str_func, lambda y: (y,), fail) is x @given(text()) -def test_natsort_key_with_text_input_takes_string_path(x): +def test_natsort_key_with_text_input_takes_string_path(x: str) -> None: assert natsort_key(x, None, str_func, fail, fail) is x @given(lists(elements=text(), min_size=1, max_size=10)) -def test_natsort_key_with_nested_input_takes_nested_path(x): +def test_natsort_key_with_nested_input_takes_nested_path(x: List[str]) -> None: assert natsort_key(x, None, str_func, fail, fail) == tuple(x) @given(text()) -def test_natsort_key_with_key_argument_applies_key_before_processing(x): - assert natsort_key(x, len, str_func, fail, lambda y: y) == len(x) +def test_natsort_key_with_key_argument_applies_key_before_processing(x: str) -> None: + assert natsort_key(x, len, str_func, fail, lambda y: ("", y)) == len(x) diff --git a/tests/test_natsort_keygen.py b/tests/test_natsort_keygen.py index 5fb8cf3..0abdefb 100644 --- a/tests/test_natsort_keygen.py +++ b/tests/test_natsort_keygen.py @@ -5,23 +5,27 @@ See the README or the natsort homepage for more details. """ import os +from typing import List, Tuple, Union import pytest from natsort import natsort_key, natsort_keygen, natsorted, ns from natsort.compat.locale import get_strxfrm, null_string_locale +from natsort.ns_enum import NS_t +from natsort.utils import BytesTransform, FinalTransform +from pytest_mock import MockerFixture @pytest.fixture -def arbitrary_input(): +def arbitrary_input() -> List[Union[str, float]]: return ["6A-5.034e+1", "/Folder (1)/Foo", 56.7] @pytest.fixture -def bytes_input(): +def bytes_input() -> bytes: return b"6A-5.034e+1" -def test_natsort_keygen_demonstration(): +def test_natsort_keygen_demonstration() -> None: original_list = ["a50", "a51.", "a50.31", "a50.4", "a5.034e1", "a50.300"] copy_of_list = original_list[:] original_list.sort(key=natsort_keygen(alg=ns.F)) @@ -29,21 +33,23 @@ def test_natsort_keygen_demonstration(): assert original_list == natsorted(copy_of_list, alg=ns.F) -def test_natsort_key_public(): +def test_natsort_key_public() -> None: assert natsort_key("a-5.034e2") == ("a-", 5, ".", 34, "e", 2) -def test_natsort_keygen_with_invalid_alg_input_raises_value_error(): +def test_natsort_keygen_with_invalid_alg_input_raises_value_error() -> None: # Invalid arguments give the correct response with pytest.raises(ValueError, match="'alg' argument"): - natsort_keygen(None, "1") + natsort_keygen(None, "1") # type: ignore @pytest.mark.parametrize( "alg, expected", [(ns.DEFAULT, ("a-", 5, ".", 34, "e", 1)), (ns.FLOAT | ns.SIGNED, ("a", -50.34))], ) -def test_natsort_keygen_returns_natsort_key_that_parses_input(alg, expected): +def test_natsort_keygen_returns_natsort_key_that_parses_input( + alg: NS_t, expected: Tuple[Union[str, int, float], ...] +) -> None: ns_key = natsort_keygen(alg=alg) assert ns_key("a-5.034e1") == expected @@ -78,7 +84,9 @@ def test_natsort_keygen_returns_natsort_key_that_parses_input(alg, expected): ), ], ) -def test_natsort_keygen_handles_arbitrary_input(arbitrary_input, alg, expected): +def test_natsort_keygen_handles_arbitrary_input( + arbitrary_input: List[Union[str, float]], alg: NS_t, expected: FinalTransform +) -> None: ns_key = natsort_keygen(alg=alg) assert ns_key(arbitrary_input) == expected @@ -93,13 +101,15 @@ def test_natsort_keygen_handles_arbitrary_input(arbitrary_input, alg, expected): (ns.PATH | ns.GROUPLETTERS, ((b"6A-5.034e+1",),)), ], ) -def test_natsort_keygen_handles_bytes_input(bytes_input, alg, expected): +def test_natsort_keygen_handles_bytes_input( + bytes_input: bytes, alg: NS_t, expected: BytesTransform +) -> None: ns_key = natsort_keygen(alg=alg) assert ns_key(bytes_input) == expected @pytest.mark.parametrize( - "alg, expected, is_dumb", + "alg, expected_in, is_dumb", [ ( ns.LOCALE, @@ -131,23 +141,29 @@ def test_natsort_keygen_handles_bytes_input(bytes_input, alg, expected): ], ) @pytest.mark.usefixtures("with_locale_en_us") -def test_natsort_keygen_with_locale(mocker, arbitrary_input, alg, expected, is_dumb): +def test_natsort_keygen_with_locale( + mocker: MockerFixture, + arbitrary_input: List[Union[str, float]], + alg: NS_t, + expected: FinalTransform, + is_dumb: bool, +) -> None: # First, apply the correct strxfrm function to the string values. strxfrm = get_strxfrm() - expected = [list(sub) for sub in expected] + expected_tmp = [list(sub) for sub in expected] try: for i in (2, 4, 6): - expected[0][i] = strxfrm(expected[0][i]) + expected_tmp[0][i] = strxfrm(expected_tmp[0][i]) for i in (0, 2): - expected[1][i] = strxfrm(expected[1][i]) - expected = tuple(tuple(sub) for sub in expected) + expected_tmp[1][i] = strxfrm(expected_tmp[1][i]) + expected = tuple(tuple(sub) for sub in expected_tmp) except IndexError: # ns.LOCALE | ns.CAPITALFIRST - expected = [[list(subsub) for subsub in sub] for sub in expected] + expected_tmp = [[list(subsub) for subsub in sub] for sub in expected_tmp] for i in (2, 4, 6): - expected[0][1][i] = strxfrm(expected[0][1][i]) + expected_tmp[0][1][i] = strxfrm(expected_tmp[0][1][i]) for i in (0, 2): - expected[1][1][i] = strxfrm(expected[1][1][i]) - expected = tuple(tuple(tuple(subsub) for subsub in sub) for sub in expected) + expected_tmp[1][1][i] = strxfrm(expected_tmp[1][1][i]) + expected = tuple(tuple(tuple(subsub) for subsub in sub) for sub in expected_tmp) mocker.patch("natsort.compat.locale.dumb_sort", return_value=is_dumb) ns_key = natsort_keygen(alg=alg) @@ -159,7 +175,9 @@ def test_natsort_keygen_with_locale(mocker, arbitrary_input, alg, expected, is_d [(ns.LOCALE, False), (ns.LOCALE, True), (ns.LOCALE | ns.CAPITALFIRST, False)], ) @pytest.mark.usefixtures("with_locale_en_us") -def test_natsort_keygen_with_locale_bytes(mocker, bytes_input, alg, is_dumb): +def test_natsort_keygen_with_locale_bytes( + mocker: MockerFixture, bytes_input: bytes, alg: NS_t, is_dumb: bool +) -> None: expected = (b"6A-5.034e+1",) mocker.patch("natsort.compat.locale.dumb_sort", return_value=is_dumb) ns_key = natsort_keygen(alg=alg) diff --git a/tests/test_natsorted.py b/tests/test_natsorted.py index 4254e6c..e71678f 100644 --- a/tests/test_natsorted.py +++ b/tests/test_natsorted.py @@ -5,6 +5,8 @@ See the README or the natsort homepage for more details. """ from operator import itemgetter +from typing import List, Tuple, Union +from natsort.ns_enum import NS_t import pytest from natsort import as_utf8, natsorted, ns @@ -12,27 +14,29 @@ from pytest import raises @pytest.fixture -def float_list(): +def float_list() -> List[str]: return ["a50", "a51.", "a50.31", "a-50", "a50.4", "a5.034e1", "a50.300"] @pytest.fixture -def fruit_list(): +def fruit_list() -> List[str]: return ["Apple", "corn", "Corn", "Banana", "apple", "banana"] @pytest.fixture -def mixed_list(): +def mixed_list() -> List[Union[str, int, float]]: return ["Ä", "0", "ä", 3, "b", 1.5, "2", "Z"] -def test_natsorted_numbers_in_ascending_order(): +def test_natsorted_numbers_in_ascending_order() -> None: given = ["a2", "a5", "a9", "a1", "a4", "a10", "a6"] expected = ["a1", "a2", "a4", "a5", "a6", "a9", "a10"] assert natsorted(given) == expected -def test_natsorted_can_sort_as_signed_floats_with_exponents(float_list): +def test_natsorted_can_sort_as_signed_floats_with_exponents( + float_list: List[str], +) -> None: expected = ["a-50", "a50", "a50.300", "a50.31", "a5.034e1", "a50.4", "a51."] assert natsorted(float_list, alg=ns.REAL) == expected @@ -42,19 +46,23 @@ def test_natsorted_can_sort_as_signed_floats_with_exponents(float_list): "alg", [ns.NOEXP | ns.FLOAT | ns.UNSIGNED, ns.NOEXP | ns.FLOAT], ) -def test_natsorted_can_sort_as_unsigned_and_ignore_exponents(float_list, alg): +def test_natsorted_can_sort_as_unsigned_and_ignore_exponents( + float_list: List[str], alg: NS_t +) -> None: expected = ["a5.034e1", "a50", "a50.300", "a50.31", "a50.4", "a51.", "a-50"] assert natsorted(float_list, alg=alg) == expected # DEFAULT and INT are all equivalent. @pytest.mark.parametrize("alg", [ns.DEFAULT, ns.INT]) -def test_natsorted_can_sort_as_unsigned_ints_which_is_default(float_list, alg): +def test_natsorted_can_sort_as_unsigned_ints_which_is_default( + float_list: List[str], alg: NS_t +) -> None: expected = ["a5.034e1", "a50", "a50.4", "a50.31", "a50.300", "a51.", "a-50"] assert natsorted(float_list, alg=alg) == expected -def test_natsorted_can_sort_as_signed_ints(float_list): +def test_natsorted_can_sort_as_signed_ints(float_list: List[str]) -> None: expected = ["a-50", "a5.034e1", "a50", "a50.4", "a50.31", "a50.300", "a51."] assert natsorted(float_list, alg=ns.SIGNED) == expected @@ -63,12 +71,14 @@ def test_natsorted_can_sort_as_signed_ints(float_list): "alg, expected", [(ns.UNSIGNED, ["a7", "a+2", "a-5"]), (ns.SIGNED, ["a-5", "a+2", "a7"])], ) -def test_natsorted_can_sort_with_or_without_accounting_for_sign(alg, expected): +def test_natsorted_can_sort_with_or_without_accounting_for_sign( + alg: NS_t, expected: List[str] +) -> None: given = ["a-5", "a7", "a+2"] assert natsorted(given, alg=alg) == expected -def test_natsorted_can_sort_as_version_numbers(): +def test_natsorted_can_sort_as_version_numbers() -> None: given = ["1.9.9a", "1.11", "1.9.9b", "1.11.4", "1.10.1"] expected = ["1.9.9a", "1.9.9b", "1.10.1", "1.11", "1.11.4"] assert natsorted(given) == expected @@ -81,7 +91,11 @@ def test_natsorted_can_sort_as_version_numbers(): (ns.NUMAFTER, ["Ä", "Z", "ä", "b", "0", 1.5, "2", 3]), ], ) -def test_natsorted_handles_mixed_types(mixed_list, alg, expected): +def test_natsorted_handles_mixed_types( + mixed_list: List[Union[str, int, float]], + alg: NS_t, + expected: List[Union[str, int, float]], +) -> None: assert natsorted(mixed_list, alg=alg) == expected @@ -92,14 +106,16 @@ def test_natsorted_handles_mixed_types(mixed_list, alg, expected): (ns.NANLAST, [5, "25", 1e40, float("nan")], slice(None, 3)), ], ) -def test_natsorted_handles_nan(alg, expected, slc): - given = ["25", 5, float("nan"), 1e40] +def test_natsorted_handles_nan( + alg: NS_t, expected: List[Union[str, float, int]], slc: slice +) -> 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] -def test_natsorted_with_mixed_bytes_and_str_input_raises_type_error(): +def test_natsorted_with_mixed_bytes_and_str_input_raises_type_error() -> None: with raises(TypeError, match="bytes"): natsorted(["ä", b"b"]) @@ -107,29 +123,31 @@ def test_natsorted_with_mixed_bytes_and_str_input_raises_type_error(): assert natsorted(["ä", b"b"], key=as_utf8) == ["ä", b"b"] -def test_natsorted_raises_type_error_for_non_iterable_input(): +def test_natsorted_raises_type_error_for_non_iterable_input() -> None: with raises(TypeError, match="'int' object is not iterable"): - natsorted(100) + natsorted(100) # type: ignore -def test_natsorted_recurses_into_nested_lists(): +def test_natsorted_recurses_into_nested_lists() -> None: given = [["a1", "a5"], ["a1", "a40"], ["a10", "a1"], ["a2", "a5"]] expected = [["a1", "a5"], ["a1", "a40"], ["a2", "a5"], ["a10", "a1"]] assert natsorted(given) == expected -def test_natsorted_applies_key_to_each_list_element_before_sorting_list(): +def test_natsorted_applies_key_to_each_list_element_before_sorting_list() -> None: given = [("a", "num3"), ("b", "num5"), ("c", "num2")] expected = [("c", "num2"), ("a", "num3"), ("b", "num5")] assert natsorted(given, key=itemgetter(1)) == expected -def test_natsorted_returns_list_in_reversed_order_with_reverse_option(float_list): +def test_natsorted_returns_list_in_reversed_order_with_reverse_option( + float_list: List[str], +) -> None: expected = natsorted(float_list)[::-1] assert natsorted(float_list, reverse=True) == expected -def test_natsorted_handles_filesystem_paths(): +def test_natsorted_handles_filesystem_paths() -> None: given = [ "/p/Folder (10)/file.tar.gz", "/p/Folder (1)/file (1).tar.gz", @@ -157,10 +175,10 @@ def test_natsorted_handles_filesystem_paths(): assert natsorted(given, alg=ns.FLOAT | ns.PATH) == expected_correct -def test_natsorted_handles_numbers_and_filesystem_paths_simultaneously(): +def test_natsorted_handles_numbers_and_filesystem_paths_simultaneously() -> None: # You can sort paths and numbers, not that you'd want to - given = ["/Folder (9)/file.exe", 43] - expected = [43, "/Folder (9)/file.exe"] + given: List[Union[str, int]] = ["/Folder (9)/file.exe", 43] + expected: List[Union[str, int]] = [43, "/Folder (9)/file.exe"] assert natsorted(given, alg=ns.PATH) == expected @@ -174,7 +192,9 @@ def test_natsorted_handles_numbers_and_filesystem_paths_simultaneously(): (ns.G | ns.LF, ["apple", "Apple", "banana", "Banana", "corn", "Corn"]), ], ) -def test_natsorted_supports_case_handling(alg, expected, fruit_list): +def test_natsorted_supports_case_handling( + alg: NS_t, expected: List[str], fruit_list: List[str] +) -> None: assert natsorted(fruit_list, alg=alg) == expected @@ -186,7 +206,9 @@ def test_natsorted_supports_case_handling(alg, expected, fruit_list): (ns.IGNORECASE, [("a3", "a1"), ("A5", "a6")]), ], ) -def test_natsorted_supports_nested_case_handling(alg, expected): +def test_natsorted_supports_nested_case_handling( + alg: NS_t, expected: List[Tuple[str, str]] +) -> None: given = [("A5", "a6"), ("a3", "a1")] assert natsorted(given, alg=alg) == expected @@ -201,26 +223,28 @@ def test_natsorted_supports_nested_case_handling(alg, expected): ], ) @pytest.mark.usefixtures("with_locale_en_us") -def test_natsorted_can_sort_using_locale(fruit_list, alg, expected): +def test_natsorted_can_sort_using_locale( + fruit_list: List[str], alg: NS_t, expected: List[str] +) -> None: assert natsorted(fruit_list, alg=ns.LOCALE | alg) == expected @pytest.mark.usefixtures("with_locale_en_us") -def test_natsorted_can_sort_locale_specific_numbers_en(): +def test_natsorted_can_sort_locale_specific_numbers_en() -> None: given = ["c", "a5,467.86", "ä", "b", "a5367.86", "a5,6", "a5,50"] expected = ["a5,6", "a5,50", "a5367.86", "a5,467.86", "ä", "b", "c"] assert natsorted(given, alg=ns.LOCALE | ns.F) == expected @pytest.mark.usefixtures("with_locale_de_de") -def test_natsorted_can_sort_locale_specific_numbers_de(): +def test_natsorted_can_sort_locale_specific_numbers_de() -> None: given = ["c", "a5.467,86", "ä", "b", "a5367.86", "a5,6", "a5,50"] expected = ["a5,50", "a5,6", "a5367.86", "a5.467,86", "ä", "b", "c"] assert natsorted(given, alg=ns.LOCALE | ns.F) == expected @pytest.mark.usefixtures("with_locale_de_de") -def test_natsorted_locale_bug_regression_test_109(): +def test_natsorted_locale_bug_regression_test_109() -> None: # https://github.com/SethMMorton/natsort/issues/109 given = ["462166", "461761"] expected = ["461761", "462166"] @@ -242,7 +266,11 @@ def test_natsorted_locale_bug_regression_test_109(): ], ) @pytest.mark.usefixtures("with_locale_en_us") -def test_natsorted_handles_mixed_types_with_locale(mixed_list, alg, expected): +def test_natsorted_handles_mixed_types_with_locale( + mixed_list: List[Union[str, int, float]], + alg: NS_t, + expected: List[Union[str, int, float]], +) -> None: assert natsorted(mixed_list, alg=ns.LOCALE | alg) == expected @@ -253,12 +281,14 @@ def test_natsorted_handles_mixed_types_with_locale(mixed_list, alg, expected): (ns.NUMAFTER, ["Banana", "apple", "corn", "~~~~~~", "73", "5039"]), ], ) -def test_natsorted_sorts_an_odd_collection_of_strings(alg, expected): +def test_natsorted_sorts_an_odd_collection_of_strings( + alg: NS_t, expected: List[str] +) -> None: given = ["apple", "Banana", "73", "5039", "corn", "~~~~~~"] assert natsorted(given, alg=alg) == expected -def test_natsorted_sorts_mixed_ascii_and_non_ascii_numbers(): +def test_natsorted_sorts_mixed_ascii_and_non_ascii_numbers() -> None: given = [ "1st street", "10th street", diff --git a/tests/test_natsorted_convenience.py b/tests/test_natsorted_convenience.py index cdc2c50..599c4ba 100644 --- a/tests/test_natsorted_convenience.py +++ b/tests/test_natsorted_convenience.py @@ -5,6 +5,7 @@ See the README or the natsort homepage for more details. """ from operator import itemgetter +from typing import List import pytest from natsort import ( @@ -23,21 +24,21 @@ from natsort import ( @pytest.fixture -def version_list(): +def version_list() -> List[str]: return ["1.9.9a", "1.11", "1.9.9b", "1.11.4", "1.10.1"] @pytest.fixture -def float_list(): +def float_list() -> List[str]: return ["a50", "a51.", "a50.31", "a-50", "a50.4", "a5.034e1", "a50.300"] @pytest.fixture -def fruit_list(): +def fruit_list() -> List[str]: return ["Apple", "corn", "Corn", "Banana", "apple", "banana"] -def test_decoder_returns_function_that_can_decode_bytes_but_return_non_bytes_as_is(): +def test_decoder_returns_function_that_can_decode_bytes_but_return_non_bytes_as_is() -> None: func = decoder("latin1") str_obj = "bytes" int_obj = 14 @@ -46,24 +47,28 @@ def test_decoder_returns_function_that_can_decode_bytes_but_return_non_bytes_as_ assert func(str_obj) is str_obj # same object returned b/c only bytes has decode -def test_as_ascii_converts_bytes_to_ascii(): +def test_as_ascii_converts_bytes_to_ascii() -> None: assert decoder("ascii")(b"bytes") == as_ascii(b"bytes") -def test_as_utf8_converts_bytes_to_utf8(): +def test_as_utf8_converts_bytes_to_utf8() -> None: assert decoder("utf8")(b"bytes") == as_utf8(b"bytes") -def test_realsorted_is_identical_to_natsorted_with_real_alg(float_list): +def test_realsorted_is_identical_to_natsorted_with_real_alg( + float_list: List[str], +) -> None: assert realsorted(float_list) == natsorted(float_list, alg=ns.REAL) @pytest.mark.usefixtures("with_locale_en_us") -def test_humansorted_is_identical_to_natsorted_with_locale_alg(fruit_list): +def test_humansorted_is_identical_to_natsorted_with_locale_alg( + fruit_list: List[str], +) -> None: assert humansorted(fruit_list) == natsorted(fruit_list, alg=ns.LOCALE) -def test_index_natsorted_returns_integer_list_of_sort_order_for_input_list(): +def test_index_natsorted_returns_integer_list_of_sort_order_for_input_list() -> None: given = ["num3", "num5", "num2"] other = ["foo", "bar", "baz"] index = index_natsorted(given) @@ -72,27 +77,31 @@ def test_index_natsorted_returns_integer_list_of_sort_order_for_input_list(): assert [other[i] for i in index] == ["baz", "foo", "bar"] -def test_index_natsorted_reverse(): +def test_index_natsorted_reverse() -> None: given = ["num3", "num5", "num2"] assert index_natsorted(given, reverse=True) == index_natsorted(given)[::-1] -def test_index_natsorted_applies_key_function_before_sorting(): +def test_index_natsorted_applies_key_function_before_sorting() -> None: given = [("a", "num3"), ("b", "num5"), ("c", "num2")] expected = [2, 0, 1] assert index_natsorted(given, key=itemgetter(1)) == expected -def test_index_realsorted_is_identical_to_index_natsorted_with_real_alg(float_list): +def test_index_realsorted_is_identical_to_index_natsorted_with_real_alg( + float_list: List[str], +) -> None: assert index_realsorted(float_list) == index_natsorted(float_list, alg=ns.REAL) @pytest.mark.usefixtures("with_locale_en_us") -def test_index_humansorted_is_identical_to_index_natsorted_with_locale_alg(fruit_list): +def test_index_humansorted_is_identical_to_index_natsorted_with_locale_alg( + fruit_list: List[str], +) -> None: assert index_humansorted(fruit_list) == index_natsorted(fruit_list, alg=ns.LOCALE) -def test_order_by_index_sorts_list_according_to_order_of_integer_list(): +def test_order_by_index_sorts_list_according_to_order_of_integer_list() -> None: given = ["num3", "num5", "num2"] index = [2, 0, 1] expected = [given[i] for i in index] @@ -100,7 +109,7 @@ def test_order_by_index_sorts_list_according_to_order_of_integer_list(): assert order_by_index(given, index) == expected -def test_order_by_index_returns_generator_with_iter_true(): +def test_order_by_index_returns_generator_with_iter_true() -> None: given = ["num3", "num5", "num2"] index = [2, 0, 1] assert order_by_index(given, index, True) != [given[i] for i in index] diff --git a/tests/test_ns_enum.py b/tests/test_ns_enum.py index 312c454..7a30718 100644 --- a/tests/test_ns_enum.py +++ b/tests/test_ns_enum.py @@ -42,7 +42,7 @@ from natsort import ns ("NL", 0x0400), ("CN", 0x0800), ("NA", 0x1000), - ] + ], ) -def test_ns_enum(given, expected): +def test_ns_enum(given: str, expected: int) -> None: assert ns[given] == expected diff --git a/tests/test_os_sorted.py b/tests/test_os_sorted.py index afb15cf..d0ecc79 100644 --- a/tests/test_os_sorted.py +++ b/tests/test_os_sorted.py @@ -3,6 +3,7 @@ Testing for the OS sorting """ import platform +from typing import cast import natsort import pytest @@ -15,7 +16,7 @@ else: has_icu = True -def test_os_sorted_compound(): +def test_os_sorted_compound() -> None: given = [ "/p/Folder (10)/file.tar.gz", "/p/Folder (1)/file (1).tar.gz", @@ -36,14 +37,14 @@ def test_os_sorted_compound(): assert result == expected -def test_os_sorted_misc_no_fail(): +def test_os_sorted_misc_no_fail() -> None: natsort.os_sorted([9, 4.3, None, float("nan")]) -def test_os_sorted_key(): +def test_os_sorted_key() -> None: given = ["foo0", "foo2", "goo1"] expected = ["foo0", "goo1", "foo2"] - result = natsort.os_sorted(given, key=lambda x: x.replace("g", "f")) + result = natsort.os_sorted(given, key=lambda x: cast(str, x).replace("g", "f")) assert result == expected @@ -199,7 +200,7 @@ else: @pytest.mark.usefixtures("with_locale_en_us") -def test_os_sorted_corpus(): +def test_os_sorted_corpus() -> None: result = natsort.os_sorted(given) print(result) assert result == expected diff --git a/tests/test_parse_bytes_function.py b/tests/test_parse_bytes_function.py index 6637cbd..fd358ba 100644 --- a/tests/test_parse_bytes_function.py +++ b/tests/test_parse_bytes_function.py @@ -4,8 +4,8 @@ import pytest from hypothesis import given from hypothesis.strategies import binary -from natsort.ns_enum import ns -from natsort.utils import parse_bytes_factory +from natsort.ns_enum import NS_t, ns +from natsort.utils import BytesTransformer, parse_bytes_factory @pytest.mark.parametrize( @@ -19,6 +19,8 @@ from natsort.utils import parse_bytes_factory ], ) @given(x=binary()) -def test_parse_bytest_factory_makes_function_that_returns_tuple(x, alg, example_func): +def test_parse_bytest_factory_makes_function_that_returns_tuple( + x: bytes, alg: NS_t, example_func: BytesTransformer +) -> None: parse_bytes_func = parse_bytes_factory(alg) assert parse_bytes_func(x) == example_func(x) diff --git a/tests/test_parse_number_function.py b/tests/test_parse_number_function.py index 29ee5a3..3ce6d97 100644 --- a/tests/test_parse_number_function.py +++ b/tests/test_parse_number_function.py @@ -1,11 +1,13 @@ # -*- coding: utf-8 -*- """These test the utils.py functions.""" +from typing import Optional, Tuple, Union + import pytest from hypothesis import given from hypothesis.strategies import floats, integers -from natsort.ns_enum import ns -from natsort.utils import parse_number_or_none_factory +from natsort.ns_enum import NS_t, ns +from natsort.utils import MaybeNumTransformer, parse_number_or_none_factory @pytest.mark.usefixtures("with_locale_en_us") @@ -19,7 +21,9 @@ from natsort.utils import parse_number_or_none_factory ], ) @given(x=floats(allow_nan=False) | integers()) -def test_parse_number_factory_makes_function_that_returns_tuple(x, alg, example_func): +def test_parse_number_factory_makes_function_that_returns_tuple( + x: Union[float, int], alg: NS_t, example_func: MaybeNumTransformer +) -> None: parse_number_func = parse_number_or_none_factory(alg, "", "xx") assert parse_number_func(x) == example_func(x) @@ -34,6 +38,8 @@ def test_parse_number_factory_makes_function_that_returns_tuple(x, alg, example_ (ns.NANLAST, None, ("", float("+inf"))), # NANLAST makes it +infinity ], ) -def test_parse_number_factory_treats_nan_and_none_special(alg, x, result): +def test_parse_number_factory_treats_nan_and_none_special( + alg: NS_t, x: Optional[Union[float, int]], result: Tuple[str, Union[float, int]] +) -> None: parse_number_func = parse_number_or_none_factory(alg, "", "xx") assert parse_number_func(x) == result diff --git a/tests/test_parse_string_function.py b/tests/test_parse_string_function.py index 46347f1..c199964 100644 --- a/tests/test_parse_string_function.py +++ b/tests/test_parse_string_function.py @@ -2,23 +2,28 @@ """These test the utils.py functions.""" import unicodedata +from typing import Any as Any_t, Callable, Iterable, List, Tuple, Union import pytest from hypothesis import given from hypothesis.strategies import floats, integers, lists, text from natsort.compat.fastnumbers import fast_float -from natsort.ns_enum import NS_DUMB, ns -from natsort.utils import NumericalRegularExpressions as NumRegex +from natsort.ns_enum import NS_DUMB, NS_t, ns +from natsort.utils import ( + FinalTransform, + NumericalRegularExpressions as NumRegex, + StrParser, +) from natsort.utils import parse_string_factory -class CustomTuple(tuple): +class CustomTuple(Tuple[Any_t, ...]): """Used to ensure what is given during testing is what is returned.""" - original = None + original: Any_t = None -def input_transform(x): +def input_transform(x: Any_t) -> Any_t: """Make uppercase.""" try: return x.upper() @@ -26,14 +31,14 @@ def input_transform(x): return x -def final_transform(x, original): +def final_transform(x: Iterable[Any_t], original: str) -> FinalTransform: """Make the input a CustomTuple.""" t = CustomTuple(x) t.original = original return t -def parse_string_func_factory(alg): +def parse_string_func_factory(alg: NS_t) -> StrParser: """A parse_string_factory result with sample arguments.""" sep = "" return parse_string_factory( @@ -47,10 +52,12 @@ def parse_string_func_factory(alg): @given(x=floats() | integers()) -def test_parse_string_factory_raises_type_error_if_given_number(x): +def test_parse_string_factory_raises_type_error_if_given_number( + x: Union[int, float] +) -> None: parse_string_func = parse_string_func_factory(ns.DEFAULT) with pytest.raises(TypeError): - assert parse_string_func(x) + assert parse_string_func(x) # type: ignore # noinspection PyCallingNonCallable @@ -68,7 +75,9 @@ def test_parse_string_factory_raises_type_error_if_given_number(x): ) ) @pytest.mark.usefixtures("with_locale_en_us") -def test_parse_string_factory_invariance(x, alg, orig_func): +def test_parse_string_factory_invariance( + x: List[Union[float, str, int]], alg: NS_t, orig_func: Callable[[str], str] +) -> None: parse_string_func = parse_string_func_factory(alg) # parse_string_factory is the high-level combination of several dedicated # functions involved in splitting and manipulating a string. The details of diff --git a/tests/test_regex.py b/tests/test_regex.py index f647f5f..67d66e0 100644 --- a/tests/test_regex.py +++ b/tests/test_regex.py @@ -1,8 +1,11 @@ # -*- coding: utf-8 -*- """These test the splitting regular expressions.""" +from typing import List, Pattern + import pytest from natsort import ns, numeric_regex_chooser +from natsort.ns_enum import NS_t from natsort.utils import NumericalRegularExpressions as NumRegex @@ -95,7 +98,9 @@ labels = ["{}-{}".format(given, regex_names[regex]) for given, _, regex in regex @pytest.mark.parametrize("x, expected, regex", regex_params, ids=labels) -def test_regex_splits_correctly(x, expected, regex): +def test_regex_splits_correctly( + x: str, expected: List[str], regex: Pattern[str] +) -> None: # noinspection PyUnresolvedReferences assert regex.split(x) == expected @@ -115,5 +120,5 @@ def test_regex_splits_correctly(x, expected, regex): (ns.FLOAT | ns.UNSIGNED | ns.NOEXP, NumRegex.float_nosign_noexp()), ], ) -def test_regex_chooser(given, expected): +def test_regex_chooser(given: NS_t, expected: Pattern[str]) -> None: assert numeric_regex_chooser(given) == expected.pattern[1:-1] # remove parens diff --git a/tests/test_string_component_transform_factory.py b/tests/test_string_component_transform_factory.py index 8b77d38..7af2036 100644 --- a/tests/test_string_component_transform_factory.py +++ b/tests/test_string_component_transform_factory.py @@ -2,13 +2,14 @@ """These test the utils.py functions.""" from functools import partial +from typing import Any as Any_t, Callable, FrozenSet, Union import pytest from hypothesis import example, given from hypothesis.strategies import floats, integers, text from natsort.compat.fastnumbers import fast_float, fast_int from natsort.compat.locale import get_strxfrm -from natsort.ns_enum import NS_DUMB, ns +from natsort.ns_enum import NS_DUMB, NS_t, ns from natsort.utils import groupletters, string_component_transform_factory # There are some unicode values that are known failures with the builtin locale @@ -21,12 +22,12 @@ except ValueError: bad_uni_chars = frozenset() -def no_bad_uni_chars(x, _bad_chars=bad_uni_chars): +def no_bad_uni_chars(x: str, _bad_chars: FrozenSet[str] = bad_uni_chars) -> bool: """Ensure text does not contain bad unicode characters""" return not any(y in _bad_chars for y in x) -def no_null(x): +def no_null(x: str) -> bool: """Ensure text does not contain a null character.""" return "\0" not in x @@ -65,7 +66,9 @@ def no_null(x): | text().filter(bool).filter(no_bad_uni_chars).filter(no_null) ) @pytest.mark.usefixtures("with_locale_en_us") -def test_string_component_transform_factory(x, alg, example_func): +def test_string_component_transform_factory( + x: Union[str, float, int], alg: NS_t, example_func: Callable[[str], Any_t] +) -> None: string_component_transform_func = string_component_transform_factory(alg) try: assert string_component_transform_func(str(x)) == example_func(str(x)) diff --git a/tests/test_unicode_numbers.py b/tests/test_unicode_numbers.py index be0f4ee..eb71125 100644 --- a/tests/test_unicode_numbers.py +++ b/tests/test_unicode_numbers.py @@ -14,27 +14,27 @@ from natsort.unicode_numbers import ( digits_no_decimals, numeric, numeric_chars, - numeric_hex, numeric_no_decimals, ) +from natsort.unicode_numeric_hex import numeric_hex -def test_numeric_chars_contains_only_valid_unicode_numeric_characters(): +def test_numeric_chars_contains_only_valid_unicode_numeric_characters() -> None: for a in numeric_chars: assert unicodedata.numeric(a, None) is not None -def test_digit_chars_contains_only_valid_unicode_digit_characters(): +def test_digit_chars_contains_only_valid_unicode_digit_characters() -> None: for a in digit_chars: assert unicodedata.digit(a, None) is not None -def test_decimal_chars_contains_only_valid_unicode_decimal_characters(): +def test_decimal_chars_contains_only_valid_unicode_decimal_characters() -> None: for a in decimal_chars: assert unicodedata.decimal(a, None) is not None -def test_numeric_chars_contains_all_valid_unicode_numeric_and_digit_characters(): +def test_numeric_chars_contains_all_valid_unicode_numeric_and_digit_characters() -> None: set_numeric_chars = set(numeric_chars) set_digit_chars = set(digit_chars) set_decimal_chars = set(decimal_chars) @@ -46,7 +46,7 @@ def test_numeric_chars_contains_all_valid_unicode_numeric_and_digit_characters() assert set_numeric_chars.issuperset(numeric_no_decimals) -def test_missing_unicode_number_in_collection(): +def test_missing_unicode_number_in_collection() -> None: ok = True set_numeric_hex = set(numeric_hex) for i in range(0x110000): @@ -71,7 +71,7 @@ repository (https://github.com/SethMMorton/natsort) with the resulting change. ) -def test_combined_string_contains_all_characters_in_list(): +def test_combined_string_contains_all_characters_in_list() -> None: assert numeric == "".join(numeric_chars) assert digits == "".join(digit_chars) assert decimals == "".join(decimal_chars) diff --git a/tests/test_utils.py b/tests/test_utils.py index d559803..ff866b6 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -6,15 +6,16 @@ import pathlib import string from itertools import chain from operator import neg as op_neg +from typing import List, Pattern, Union import pytest from hypothesis import given from hypothesis.strategies import integers, lists, sampled_from, text from natsort import utils -from natsort.ns_enum import ns +from natsort.ns_enum import NS_t, ns -def test_do_decoding_decodes_bytes_string_to_unicode(): +def test_do_decoding_decodes_bytes_string_to_unicode() -> None: assert type(utils.do_decoding(b"bytes", "ascii")) is str assert utils.do_decoding(b"bytes", "ascii") == "bytes" assert utils.do_decoding(b"bytes", "ascii") == b"bytes".decode("ascii") @@ -33,7 +34,9 @@ def test_do_decoding_decodes_bytes_string_to_unicode(): (ns.F | ns.S | ns.N, utils.NumericalRegularExpressions.float_sign_noexp()), ], ) -def test_regex_chooser_returns_correct_regular_expression_object(alg, expected): +def test_regex_chooser_returns_correct_regular_expression_object( + alg: NS_t, expected: Pattern[str] +) -> None: assert utils.regex_chooser(alg).pattern == expected.pattern @@ -68,21 +71,21 @@ def test_regex_chooser_returns_correct_regular_expression_object(alg, expected): (ns.REAL, ns.FLOAT | ns.SIGNED), ], ) -def test_ns_enum_values_and_aliases(alg, value_or_alias): +def test_ns_enum_values_and_aliases(alg: NS_t, value_or_alias: NS_t) -> None: assert alg == value_or_alias -def test_chain_functions_is_a_no_op_if_no_functions_are_given(): +def test_chain_functions_is_a_no_op_if_no_functions_are_given() -> None: x = 2345 assert utils.chain_functions([])(x) is x -def test_chain_functions_does_one_function_if_one_function_is_given(): +def test_chain_functions_does_one_function_if_one_function_is_given() -> None: x = "2345" assert utils.chain_functions([len])(x) == 4 -def test_chain_functions_combines_functions_in_given_order(): +def test_chain_functions_combines_functions_in_given_order() -> None: x = 2345 assert utils.chain_functions([str, len, op_neg])(x) == -len(str(x)) @@ -91,33 +94,37 @@ def test_chain_functions_combines_functions_in_given_order(): # and a test that uses the hypothesis module. -def test_groupletters_returns_letters_with_lowercase_transform_of_letter_example(): +def test_groupletters_returns_letters_with_lowercase_transform_of_letter_example() -> None: assert utils.groupletters("HELLO") == "hHeElLlLoO" assert utils.groupletters("hello") == "hheelllloo" @given(text().filter(bool)) -def test_groupletters_returns_letters_with_lowercase_transform_of_letter(x): +def test_groupletters_returns_letters_with_lowercase_transform_of_letter( + x: str, +) -> None: assert utils.groupletters(x) == "".join( chain.from_iterable([y.casefold(), y] for y in x) ) -def test_sep_inserter_does_nothing_if_no_numbers_example(): +def test_sep_inserter_does_nothing_if_no_numbers_example() -> None: assert list(utils.sep_inserter(iter(["a", "b", "c"]), "")) == ["a", "b", "c"] assert list(utils.sep_inserter(iter(["a"]), "")) == ["a"] -def test_sep_inserter_does_nothing_if_only_one_number_example(): +def test_sep_inserter_does_nothing_if_only_one_number_example() -> None: assert list(utils.sep_inserter(iter(["a", 5]), "")) == ["a", 5] -def test_sep_inserter_inserts_separator_string_between_two_numbers_example(): +def test_sep_inserter_inserts_separator_string_between_two_numbers_example() -> None: assert list(utils.sep_inserter(iter([5, 9]), "")) == ["", 5, "", 9] @given(lists(elements=text().filter(bool) | integers(), min_size=3)) -def test_sep_inserter_inserts_separator_between_two_numbers(x): +def test_sep_inserter_inserts_separator_between_two_numbers( + x: List[Union[str, int]] +) -> None: # Rather than just replicating the results in a different algorithm, # validate that the "shape" of the output is as expected. result = list(utils.sep_inserter(iter(x), "")) @@ -127,28 +134,29 @@ def test_sep_inserter_inserts_separator_between_two_numbers(x): assert isinstance(result[i + 1], int) -def test_path_splitter_splits_path_string_by_separator_example(): +def test_path_splitter_splits_path_string_by_separator_example() -> None: given = "/this/is/a/path" expected = (os.sep, "this", "is", "a", "path") assert tuple(utils.path_splitter(given)) == tuple(expected) - given = pathlib.Path(given) - assert tuple(utils.path_splitter(given)) == tuple(expected) + assert tuple(utils.path_splitter(pathlib.Path(given))) == tuple(expected) @given(lists(sampled_from(string.ascii_letters), min_size=2).filter(all)) -def test_path_splitter_splits_path_string_by_separator(x): +def test_path_splitter_splits_path_string_by_separator(x: List[str]) -> None: z = str(pathlib.Path(*x)) assert tuple(utils.path_splitter(z)) == tuple(pathlib.Path(z).parts) -def test_path_splitter_splits_path_string_by_separator_and_removes_extension_example(): +def test_path_splitter_splits_path_string_by_separator_and_removes_extension_example() -> None: given = "/this/is/a/path/file.x1.10.tar.gz" expected = (os.sep, "this", "is", "a", "path", "file.x1.10", ".tar", ".gz") assert tuple(utils.path_splitter(given)) == tuple(expected) @given(lists(sampled_from(string.ascii_letters), min_size=3).filter(all)) -def test_path_splitter_splits_path_string_by_separator_and_removes_extension(x): +def test_path_splitter_splits_path_string_by_separator_and_removes_extension( + x: List[str], +) -> None: z = str(pathlib.Path(*x[:-2])) + "." + x[-1] y = tuple(pathlib.Path(z).parts) assert tuple(utils.path_splitter(z)) == y[:-1] + ( diff --git a/tox.ini b/tox.ini index fb2c58f..7bc66bc 100644 --- a/tox.ini +++ b/tox.ini @@ -56,8 +56,11 @@ skip_install = true [testenv:mypy] deps = mypy + hypothesis + pytest + pytest-mock commands = - mypy --strict natsort + mypy --strict natsort tests skip_install = true # Build documentation. -- cgit v1.2.1