diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0f99ad5cb..babe98878 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -26,9 +26,9 @@ repos: rev: v1.18.1 hooks: - id: mypy - exclude: '^(docs|tasks|tests)|setup\.py' + exclude: '^(docs|tasks)' args: [] - additional_dependencies: [pyparsing, nox, orjson] + additional_dependencies: [pyparsing, nox, orjson, 'pytest<9', tomli, tomli_w] - repo: https://github.com/codespell-project/codespell rev: "v2.4.1" diff --git a/pyproject.toml b/pyproject.toml index ceafb77e6..387dc2408 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -80,9 +80,10 @@ strict = true enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] warn_unused_ignores = true python_version = "3.8" +files = ["src", "tests", "noxfile.py"] [[tool.mypy.overrides]] -module = ["_manylinux"] +module = ["_manylinux", "pretend"] ignore_missing_imports = true [tool.ruff] diff --git a/src/packaging/markers.py b/src/packaging/markers.py index 3d1e992f7..6654b5211 100644 --- a/src/packaging/markers.py +++ b/src/packaging/markers.py @@ -8,7 +8,7 @@ import os import platform import sys -from typing import AbstractSet, Any, Callable, Literal, TypedDict, Union, cast +from typing import AbstractSet, Any, Callable, Literal, Mapping, TypedDict, Union, cast from ._parser import MarkerAtom, MarkerList, Op, Value, Variable from ._parser import parse_marker as _parse_marker @@ -314,7 +314,7 @@ def __eq__(self, other: object) -> bool: def evaluate( self, - environment: dict[str, str] | None = None, + environment: Mapping[str, str | AbstractSet[str]] | None = None, context: EvaluateContext = "metadata", ) -> bool: """Evaluate a marker. diff --git a/tests/test_elffile.py b/tests/test_elffile.py index 809f033ac..678f1adb9 100644 --- a/tests/test_elffile.py +++ b/tests/test_elffile.py @@ -22,7 +22,9 @@ ("s390x-s390x", EIClass.C64, EIData.Msb, EMachine.S390), ], ) -def test_elffile_glibc(name, capacity, encoding, machine): +def test_elffile_glibc( + name: str, capacity: EIClass, encoding: EIData, machine: EMachine +) -> None: path = DIR_MANYLINUX.joinpath(f"hello-world-{name}") with path.open("rb") as f: ef = ELFFile(f) @@ -46,7 +48,9 @@ def test_elffile_glibc(name, capacity, encoding, machine): ("x86_64", EIClass.C64, EIData.Lsb, EMachine.X8664, "x86_64"), ], ) -def test_elffile_musl(name, capacity, encoding, machine, interpreter): +def test_elffile_musl( + name: str, capacity: EIClass, encoding: EIData, machine: EMachine, interpreter: str +) -> None: path = DIR_MUSLLINUX.joinpath(f"musl-{name}") with path.open("rb") as f: ef = ELFFile(f) @@ -68,25 +72,25 @@ def test_elffile_musl(name, capacity, encoding, machine, interpreter): ], ids=["no-magic", "wrong-magic", "unknown-format"], ) -def test_elffile_bad_ident(data): +def test_elffile_bad_ident(data: bytes) -> None: with pytest.raises(ELFInvalid): ELFFile(io.BytesIO(data)) -def test_elffile_no_section(): +def test_elffile_no_section() -> None: """Enough for magic, but not the section definitions.""" data = BIN_MUSL_X86_64[:25] with pytest.raises(ELFInvalid): ELFFile(io.BytesIO(data)) -def test_elffile_invalid_section(): +def test_elffile_invalid_section() -> None: """Enough for section definitions, but not the actual sections.""" data = BIN_MUSL_X86_64[:58] assert ELFFile(io.BytesIO(data)).interpreter is None -def test_elffle_no_interpreter_section(): +def test_elffle_no_interpreter_section() -> None: ef = ELFFile(io.BytesIO(BIN_MUSL_X86_64)) # Change all sections to *not* PT_INTERP. diff --git a/tests/test_licenses.py b/tests/test_licenses.py index 204f06688..39279f966 100644 --- a/tests/test_licenses.py +++ b/tests/test_licenses.py @@ -1,11 +1,11 @@ from packaging.licenses._spdx import EXCEPTIONS, LICENSES -def test_licenses(): +def test_licenses() -> None: for license_id in LICENSES: assert license_id == license_id.lower() -def test_exceptions(): +def test_exceptions() -> None: for exception_id in EXCEPTIONS: assert exception_id == exception_id.lower() diff --git a/tests/test_manylinux.py b/tests/test_manylinux.py index 637dddbd1..9768fd06d 100644 --- a/tests/test_manylinux.py +++ b/tests/test_manylinux.py @@ -1,13 +1,19 @@ +from __future__ import annotations + try: import ctypes except ImportError: - ctypes = None + ctypes = None # type: ignore[assignment] import os import pathlib import platform import re import sys import types +import typing + +if typing.TYPE_CHECKING: + from collections.abc import Generator import pretend import pytest @@ -18,6 +24,7 @@ _glibc_version_string, _glibc_version_string_confstr, _glibc_version_string_ctypes, + _GLibCVersion, _is_compatible, _parse_elf, _parse_glibc_version, @@ -25,13 +32,13 @@ @pytest.fixture(autouse=True) -def clear_lru_cache(): +def clear_lru_cache() -> Generator[None, None, None]: yield _get_glibc_version.cache_clear() @pytest.fixture -def manylinux_module(monkeypatch): +def manylinux_module(monkeypatch: pytest.MonkeyPatch) -> types.ModuleType: monkeypatch.setattr(_manylinux, "_get_glibc_version", lambda *args: (2, 20)) module_name = "_manylinux" module = types.ModuleType(module_name) @@ -43,10 +50,17 @@ def manylinux_module(monkeypatch): @pytest.mark.parametrize( ("attribute", "glibc"), [("1", (2, 5)), ("2010", (2, 12)), ("2014", (2, 17))] ) -def test_module_declaration(monkeypatch, manylinux_module, attribute, glibc, tf): +def test_module_declaration( + monkeypatch: pytest.MonkeyPatch, + manylinux_module: types.ModuleType, + attribute: str, + glibc: tuple[int, int], + tf: bool, +) -> None: manylinux = f"manylinux{attribute}_compatible" monkeypatch.setattr(manylinux_module, manylinux, tf, raising=False) - res = _is_compatible("x86_64", glibc) + glibc_version = _GLibCVersion(glibc[0], glibc[1]) + res = _is_compatible("x86_64", glibc_version) assert tf is res @@ -54,30 +68,37 @@ def test_module_declaration(monkeypatch, manylinux_module, attribute, glibc, tf) ("attribute", "glibc"), [("1", (2, 5)), ("2010", (2, 12)), ("2014", (2, 17))] ) def test_module_declaration_missing_attribute( - monkeypatch, manylinux_module, attribute, glibc -): + monkeypatch: pytest.MonkeyPatch, + manylinux_module: types.ModuleType, + attribute: str, + glibc: tuple[int, int], +) -> None: manylinux = f"manylinux{attribute}_compatible" monkeypatch.delattr(manylinux_module, manylinux, raising=False) - assert _is_compatible("x86_64", glibc) + glibc_version = _GLibCVersion(glibc[0], glibc[1]) + assert _is_compatible("x86_64", glibc_version) @pytest.mark.parametrize( ("version", "compatible"), [((2, 0), True), ((2, 5), True), ((2, 10), False)] ) -def test_is_manylinux_compatible_glibc_support(version, compatible, monkeypatch): +def test_is_manylinux_compatible_glibc_support( + version: tuple[int, int], compatible: bool, monkeypatch: pytest.MonkeyPatch +) -> None: monkeypatch.setitem(sys.modules, "_manylinux", None) monkeypatch.setattr(_manylinux, "_get_glibc_version", lambda: (2, 5)) - assert bool(_is_compatible("any", version)) == compatible + glibc_version = _GLibCVersion(version[0], version[1]) + assert bool(_is_compatible("any", glibc_version)) == compatible @pytest.mark.parametrize("version_str", ["glibc-2.4.5", "2"]) -def test_check_glibc_version_warning(version_str): +def test_check_glibc_version_warning(version_str: str) -> None: msg = f"Expected glibc version with 2 components major.minor, got: {version_str}" with pytest.warns(RuntimeWarning, match=re.escape(msg)): _parse_glibc_version(version_str) -@pytest.mark.skipif(not ctypes, reason="requires ctypes") +@pytest.mark.skipif(not ctypes, reason="requires ctypes") # type: ignore[truthy-bool] @pytest.mark.parametrize( ("version_str", "expected"), [ @@ -86,16 +107,18 @@ def test_check_glibc_version_warning(version_str): ("2.4", "2.4"), ], ) -def test_glibc_version_string(version_str, expected, monkeypatch): +def test_glibc_version_string( + version_str: str | bytes, expected: str, monkeypatch: pytest.MonkeyPatch +) -> None: class LibcVersion: - def __init__(self, version_str): + def __init__(self, version_str: str | bytes) -> None: self.version_str = version_str - def __call__(self): - return version_str + def __call__(self) -> str | bytes: + return self.version_str class ProcessNamespace: - def __init__(self, libc_version): + def __init__(self, libc_version: LibcVersion) -> None: self.gnu_get_libc_version = libc_version process_namespace = ProcessNamespace(LibcVersion(version_str)) @@ -108,12 +131,12 @@ def __init__(self, libc_version): assert _glibc_version_string() is None -def test_glibc_version_string_confstr(monkeypatch): +def test_glibc_version_string_confstr(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr(os, "confstr", lambda _: "glibc 2.20", raising=False) assert _glibc_version_string_confstr() == "2.20" -def test_glibc_version_string_fail(monkeypatch): +def test_glibc_version_string_fail(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr(os, "confstr", lambda _: None, raising=False) monkeypatch.setitem(sys.modules, "ctypes", None) assert _glibc_version_string() is None @@ -124,24 +147,28 @@ def test_glibc_version_string_fail(monkeypatch): "failure", [pretend.raiser(ValueError), pretend.raiser(OSError), lambda _: "XXX"], ) -def test_glibc_version_string_confstr_fail(monkeypatch, failure): +def test_glibc_version_string_confstr_fail( + monkeypatch: pytest.MonkeyPatch, failure: typing.Callable[[int], str | None] +) -> None: monkeypatch.setattr(os, "confstr", failure, raising=False) assert _glibc_version_string_confstr() is None -def test_glibc_version_string_confstr_missing(monkeypatch): +def test_glibc_version_string_confstr_missing(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.delattr(os, "confstr", raising=False) assert _glibc_version_string_confstr() is None -def test_glibc_version_string_ctypes_missing(monkeypatch): +def test_glibc_version_string_ctypes_missing(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setitem(sys.modules, "ctypes", None) assert _glibc_version_string_ctypes() is None @pytest.mark.xfail(ctypes is None, reason="ctypes not available") -def test_glibc_version_string_ctypes_raise_oserror(monkeypatch): - def patched_cdll(_name): +def test_glibc_version_string_ctypes_raise_oserror( + monkeypatch: pytest.MonkeyPatch, +) -> None: + def patched_cdll(_name: str) -> None: raise OSError("Dynamic loading not supported") monkeypatch.setattr(ctypes, "CDLL", patched_cdll) @@ -149,30 +176,32 @@ def patched_cdll(_name): @pytest.mark.skipif(platform.system() != "Linux", reason="requires Linux") -def test_is_manylinux_compatible_old(): +def test_is_manylinux_compatible_old() -> None: # Assuming no one is running this test with a version of glibc released in # 1997. - assert _is_compatible("any", (2, 0)) + assert _is_compatible("any", _GLibCVersion(2, 0)) -def test_is_manylinux_compatible(monkeypatch): +def test_is_manylinux_compatible(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr(_manylinux, "_glibc_version_string", lambda: "2.4") - assert _is_compatible("any", (2, 4)) + assert _is_compatible("any", _GLibCVersion(2, 4)) -def test_glibc_version_string_none(monkeypatch): +def test_glibc_version_string_none(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr(_manylinux, "_glibc_version_string", lambda: None) - assert not _is_compatible("any", (2, 4)) + assert not _is_compatible("any", _GLibCVersion(2, 4)) @pytest.mark.parametrize( "content", [None, "invalid-magic", "invalid-class", "invalid-data", "too-short"] ) -def test_parse_elf_bad_executable(content): +def test_parse_elf_bad_executable(content: str | None) -> None: + path_str: str | None if content: path = pathlib.Path(__file__).parent / "manylinux" / f"hello-world-{content}" - path = os.fsdecode(path) + path_str = os.fsdecode(path) else: - path = None - with _parse_elf(path) as ef: + path_str = None + # None is not supported in the type annotation, but it was tested before. + with _parse_elf(path_str) as ef: # type: ignore[arg-type] assert ef is None diff --git a/tests/test_markers.py b/tests/test_markers.py index ad24174f9..b4815c525 100644 --- a/tests/test_markers.py +++ b/tests/test_markers.py @@ -2,6 +2,8 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. +from __future__ import annotations + import collections import itertools import os @@ -60,34 +62,34 @@ class TestNode: @pytest.mark.parametrize("value", ["one", "two", None, 3, 5, []]) - def test_accepts_value(self, value): - assert Node(value).value == value + def test_accepts_value(self, value: str | None | int | list[str]) -> None: + assert Node(value).value == value # type: ignore[arg-type] @pytest.mark.parametrize("value", ["one", "two"]) - def test_str(self, value): + def test_str(self, value: str) -> None: assert str(Node(value)) == str(value) @pytest.mark.parametrize("value", ["one", "two"]) - def test_repr(self, value): + def test_repr(self, value: str) -> None: assert repr(Node(value)) == f"" - def test_base_class(self): + def test_base_class(self) -> None: with pytest.raises(NotImplementedError): Node("cover all the code").serialize() class TestOperatorEvaluation: - def test_prefers_pep440(self): + def test_prefers_pep440(self) -> None: assert Marker('"2.7.9" < "foo"').evaluate(dict(foo="2.7.10")) - def test_falls_back_to_python(self): + def test_falls_back_to_python(self) -> None: assert Marker('"b" > "a"').evaluate(dict(a="a")) - def test_fails_when_undefined(self): + def test_fails_when_undefined(self) -> None: with pytest.raises(UndefinedComparison): Marker("'2.7.0' ~= os_name").evaluate() - def test_allows_prerelease(self): + def test_allows_prerelease(self) -> None: assert Marker('python_full_version > "3.6.2"').evaluate( {"python_full_version": "3.11.0a5"} ) @@ -99,7 +101,7 @@ def test_allows_prerelease(self): class TestDefaultEnvironment: - def test_matches_expected(self): + def test_matches_expected(self) -> None: environment = default_environment() iver = ( @@ -127,7 +129,7 @@ def test_matches_expected(self): "sys_platform": sys.platform, } - def test_multidigit_minor_version(self, monkeypatch): + def test_multidigit_minor_version(self, monkeypatch: pytest.MonkeyPatch) -> None: version_info = (3, 10, 0, "final", 0) monkeypatch.setattr(sys, "version_info", version_info, raising=False) @@ -139,13 +141,13 @@ def test_multidigit_minor_version(self, monkeypatch): environment = default_environment() assert environment["python_version"] == "3.10" - def tests_when_releaselevel_final(self): + def tests_when_releaselevel_final(self) -> None: v = FakeVersionInfo(3, 4, 2, "final", 0) - assert format_full_version(v) == "3.4.2" + assert format_full_version(v) == "3.4.2" # type: ignore[arg-type] - def tests_when_releaselevel_not_final(self): + def tests_when_releaselevel_not_final(self) -> None: v = FakeVersionInfo(3, 4, 2, "beta", 4) - assert format_full_version(v) == "3.4.2b4" + assert format_full_version(v) == "3.4.2b4" # type: ignore[arg-type] class TestMarker: @@ -160,7 +162,7 @@ class TestMarker: for i in itertools.product(VARIABLES, OPERATORS, VALUES) ], ) - def test_parses_valid(self, marker_string): + def test_parses_valid(self, marker_string: str) -> None: Marker(marker_string) @pytest.mark.parametrize( @@ -174,7 +176,7 @@ def test_parses_valid(self, marker_string): '(python_version == "2.7") with random text', ], ) - def test_parses_invalid(self, marker_string): + def test_parses_invalid(self, marker_string: str) -> None: with pytest.raises(InvalidMarker): Marker(marker_string) @@ -213,7 +215,7 @@ def test_parses_invalid(self, marker_string): ), ], ) - def test_str_repr_eq_hash(self, marker_string, expected): + def test_str_repr_eq_hash(self, marker_string: str, expected: str) -> None: m = Marker(marker_string) assert str(m) == expected assert repr(m) == f"" @@ -248,21 +250,23 @@ def test_str_repr_eq_hash(self, marker_string, expected): ), ], ) - def test_different_markers_different_hashes(self, example1, example2): + def test_different_markers_different_hashes( + self, example1: str, example2: str + ) -> None: marker1, marker2 = Marker(example1), Marker(example2) # Markers created from strings that are not equivalent should differ. assert marker1 != marker2 # Different Marker objects should have different hashes. assert hash(marker1) != hash(marker2) - def test_compare_markers_to_other_objects(self): + def test_compare_markers_to_other_objects(self) -> None: # Markers should not be comparable to other kinds of objects. assert Marker("os_name == 'nt'") != "os_name == 'nt'" - def test_environment_assumes_empty_extra(self): + def test_environment_assumes_empty_extra(self) -> None: assert Marker('extra == "im_valid"').evaluate() is False - def test_environment_with_extra_none(self): + def test_environment_with_extra_none(self) -> None: # GIVEN marker_str = 'extra == "im_valid"' @@ -316,8 +320,10 @@ def test_environment_with_extra_none(self): ), ], ) - def test_evaluates(self, marker_string, environment, expected): - args = [] if environment is None else [environment] + def test_evaluates( + self, marker_string: str, environment: dict[str, str] | None, expected: bool + ) -> None: + args = () if environment is None else (environment,) assert Marker(marker_string).evaluate(*args) == expected @pytest.mark.parametrize( @@ -331,7 +337,7 @@ def test_evaluates(self, marker_string, environment, expected): for i in itertools.product(PEP_345_VARIABLES, OPERATORS, VALUES) ], ) - def test_parses_pep345_valid(self, marker_string): + def test_parses_pep345_valid(self, marker_string: str) -> None: Marker(marker_string) @pytest.mark.parametrize( @@ -353,8 +359,10 @@ def test_parses_pep345_valid(self, marker_string): ), ], ) - def test_evaluate_pep345_markers(self, marker_string, environment, expected): - args = [] if environment is None else [environment] + def test_evaluate_pep345_markers( + self, marker_string: str, environment: dict[str, str] | None, expected: bool + ) -> None: + args = () if environment is None else (environment,) assert Marker(marker_string).evaluate(*args) == expected @pytest.mark.parametrize( @@ -368,15 +376,15 @@ def test_evaluate_pep345_markers(self, marker_string, environment, expected): for i in itertools.product(SETUPTOOLS_VARIABLES, OPERATORS, VALUES) ], ) - def test_parses_setuptools_legacy_valid(self, marker_string): + def test_parses_setuptools_legacy_valid(self, marker_string: str) -> None: Marker(marker_string) - def test_evaluate_setuptools_legacy_markers(self): + def test_evaluate_setuptools_legacy_markers(self) -> None: marker_string = "python_implementation=='Jython'" - args = [{"platform_python_implementation": "CPython"}] + args = ({"platform_python_implementation": "CPython"},) assert Marker(marker_string).evaluate(*args) is False - def test_extra_str_normalization(self): + def test_extra_str_normalization(self) -> None: raw_name = "S_P__A_M" normalized_name = "s-p-a-m" lhs = f"{raw_name!r} == extra" @@ -385,13 +393,13 @@ def test_extra_str_normalization(self): assert str(Marker(lhs)) == f'"{normalized_name}" == extra' assert str(Marker(rhs)) == f'extra == "{normalized_name}"' - def test_python_full_version_untagged_user_provided(self): + def test_python_full_version_untagged_user_provided(self) -> None: """A user-provided python_full_version ending with a + is also repaired.""" assert Marker("python_full_version < '3.12'").evaluate( {"python_full_version": "3.11.1+"} ) - def test_python_full_version_untagged(self): + def test_python_full_version_untagged(self) -> None: with mock.patch("platform.python_version", return_value="3.11.1+"): assert Marker("python_full_version < '3.12'").evaluate() @@ -412,12 +420,14 @@ def test_python_full_version_untagged(self): pytest.param('"Foo" in {0}', True, id="case-sensitive"), ], ) - def test_extras_and_dependency_groups(self, variable, expression, result): + def test_extras_and_dependency_groups( + self, variable: str, expression: str, result: bool + ) -> None: environment = {variable: {"foo", "bar"}} assert Marker(expression.format(variable)).evaluate(environment) == result @pytest.mark.parametrize("variable", ["extras", "dependency_groups"]) - def test_extras_and_dependency_groups_disallowed(self, variable): + def test_extras_and_dependency_groups_disallowed(self, variable: str) -> None: marker = Marker(f'"foo" in {variable}') assert not marker.evaluate(context="lock_file") diff --git a/tests/test_metadata.py b/tests/test_metadata.py index 36143ce07..db7e46eaa 100644 --- a/tests/test_metadata.py +++ b/tests/test_metadata.py @@ -8,12 +8,12 @@ import pytest from packaging import metadata, requirements, specifiers, utils, version -from packaging.metadata import ExceptionGroup +from packaging.metadata import ExceptionGroup, RawMetadata class TestRawMetadata: @pytest.mark.parametrize("raw_field", sorted(metadata._STRING_FIELDS)) - def test_non_repeating_fields_only_once(self, raw_field): + def test_non_repeating_fields_only_once(self, raw_field: str) -> None: data = "VaLuE" header_field = metadata._RAW_TO_EMAIL_MAPPING[raw_field] single_header = f"{header_field}: {data}" @@ -21,10 +21,10 @@ def test_non_repeating_fields_only_once(self, raw_field): assert not unparsed assert len(raw) == 1 assert raw_field in raw - assert raw[raw_field] == data + assert raw[raw_field] == data # type: ignore[literal-required] @pytest.mark.parametrize("raw_field", sorted(metadata._STRING_FIELDS)) - def test_non_repeating_fields_repeated(self, raw_field): + def test_non_repeating_fields_repeated(self, raw_field: str) -> None: header_field = metadata._RAW_TO_EMAIL_MAPPING[raw_field] data = "VaLuE" single_header = f"{header_field}: {data}" @@ -36,7 +36,7 @@ def test_non_repeating_fields_repeated(self, raw_field): assert unparsed[header_field] == [data] * 2 @pytest.mark.parametrize("raw_field", sorted(metadata._LIST_FIELDS)) - def test_repeating_fields_only_once(self, raw_field): + def test_repeating_fields_only_once(self, raw_field: str) -> None: data = "VaLuE" header_field = metadata._RAW_TO_EMAIL_MAPPING[raw_field] single_header = f"{header_field}: {data}" @@ -44,10 +44,10 @@ def test_repeating_fields_only_once(self, raw_field): assert not unparsed assert len(raw) == 1 assert raw_field in raw - assert raw[raw_field] == [data] + assert raw[raw_field] == [data] # type: ignore[literal-required] @pytest.mark.parametrize("raw_field", sorted(metadata._LIST_FIELDS)) - def test_repeating_fields_repeated(self, raw_field): + def test_repeating_fields_repeated(self, raw_field: str) -> None: header_field = metadata._RAW_TO_EMAIL_MAPPING[raw_field] data = "VaLuE" single_header = f"{header_field}: {data}" @@ -56,7 +56,7 @@ def test_repeating_fields_repeated(self, raw_field): assert not unparsed assert len(raw) == 1 assert raw_field in raw - assert raw[raw_field] == [data] * 2 + assert raw[raw_field] == [data] * 2 # type: ignore[literal-required] @pytest.mark.parametrize( ("given", "expected"), @@ -71,7 +71,7 @@ def test_repeating_fields_repeated(self, raw_field): ("A B", ["A B"]), ], ) - def test_keywords(self, given, expected): + def test_keywords(self, given: str, expected: list[str]) -> None: header = f"Keywords: {given}" raw, unparsed = metadata.parse_email(header) assert not unparsed @@ -91,7 +91,7 @@ def test_keywords(self, given, expected): ("A,B,C", {"A": "B,C"}), ], ) - def test_project_urls_parsing(self, given, expected): + def test_project_urls_parsing(self, given: str, expected: dict[str, str]) -> None: header = f"project-url: {given}" raw, unparsed = metadata.parse_email(header) assert not unparsed @@ -99,7 +99,7 @@ def test_project_urls_parsing(self, given, expected): assert "project_urls" in raw assert raw["project_urls"] == expected - def test_duplicate_project_urls(self): + def test_duplicate_project_urls(self) -> None: header = "project-url: A, B\nproject-url: A, C" raw, unparsed = metadata.parse_email(header) assert not raw @@ -107,7 +107,7 @@ def test_duplicate_project_urls(self): assert "project-url" in unparsed assert unparsed["project-url"] == ["A, B", "A, C"] - def test_str_input(self): + def test_str_input(self) -> None: name = "Tarek Ziadé" header = f"author: {name}" raw, unparsed = metadata.parse_email(header) @@ -116,7 +116,7 @@ def test_str_input(self): assert "author" in raw assert raw["author"] == name - def test_bytes_input(self): + def test_bytes_input(self) -> None: name = "Tarek Ziadé" header = f"author: {name}".encode() raw, unparsed = metadata.parse_email(header) @@ -125,7 +125,7 @@ def test_bytes_input(self): assert "author" in raw assert raw["author"] == name - def test_header_mojibake(self): + def test_header_mojibake(self) -> None: value = "\xc0msterdam" header_name = "value" header_bytes = f"{header_name}: {value}".encode("latin1") @@ -139,21 +139,22 @@ def test_header_mojibake(self): assert unparsed[header_name] == [value] @pytest.mark.parametrize("given", ["hello", "description: hello", b"hello"]) - def test_description(self, given): + def test_description(self, given: str | bytes) -> None: raw, unparsed = metadata.parse_email(given) assert not unparsed assert len(raw) == 1 assert "description" in raw assert raw["description"] == "hello" - def test_description_non_utf8(self): + def test_description_non_utf8(self) -> None: header = "\xc0msterdam" header_bytes = header.encode("latin1") raw, unparsed = metadata.parse_email(header_bytes) assert not raw assert len(unparsed) == 1 assert "description" in unparsed - assert unparsed["description"] == [header_bytes] + # TODO: type annotations are not happy about this, investigate. + assert unparsed["description"] == [header_bytes] # type: ignore[comparison-overlap] @pytest.mark.parametrize( ("given", "expected"), @@ -163,14 +164,16 @@ def test_description_non_utf8(self): ("description: 1\ndescription: 2\n\n3", ["1", "2", "3"]), ], ) - def test_description_multiple(self, given, expected): + def test_description_multiple( + self, given: str | bytes, expected: list[str] + ) -> None: raw, unparsed = metadata.parse_email(given) assert not raw assert len(unparsed) == 1 assert "description" in unparsed assert unparsed["description"] == expected - def test_lowercase_keys(self): + def test_lowercase_keys(self) -> None: header = "AUTHOR: Tarek Ziadé\nWhatever: Else" raw, unparsed = metadata.parse_email(header) assert len(raw) == 1 @@ -178,7 +181,7 @@ def test_lowercase_keys(self): assert len(unparsed) == 1 assert "whatever" in unparsed - def test_complete(self): + def test_complete(self) -> None: """Test all fields (except `Obsoletes-Dist`). `Obsoletes-Dist` was sacrificed to provide a value for `Dynamic`. @@ -254,19 +257,19 @@ def test_complete(self): class TestExceptionGroup: - def test_attributes(self): + def test_attributes(self) -> None: individual_exception = Exception("not important") exc = metadata.ExceptionGroup("message", [individual_exception]) assert exc.message == "message" assert list(exc.exceptions) == [individual_exception] - def test_repr(self): + def test_repr(self) -> None: individual_exception = RuntimeError("not important") exc = metadata.ExceptionGroup("message", [individual_exception]) assert individual_exception.__class__.__name__ in repr(exc) -_RAW_EXAMPLE = { +_RAW_EXAMPLE: RawMetadata = { "metadata_version": "2.5", "name": "packaging", "version": "2023.0.0", @@ -274,7 +277,14 @@ def test_repr(self): class TestMetadata: - def _invalid_with_cause(self, meta, attr, cause=None, *, field=None): + def _invalid_with_cause( + self, + meta: metadata.Metadata, + attr: str, + cause: type[BaseException] | None = None, + *, + field: str | None = None, + ) -> None: if field is None: field = attr with pytest.raises(metadata.InvalidMetadata) as exc_info: @@ -286,7 +296,7 @@ def _invalid_with_cause(self, meta, attr, cause=None, *, field=None): else: assert isinstance(exc.__cause__, cause) - def test_from_email(self): + def test_from_email(self) -> None: metadata_version = "2.5" meta = metadata.Metadata.from_email( f"Metadata-Version: {metadata_version}", validate=False @@ -295,72 +305,73 @@ def test_from_email(self): assert meta.metadata_version == metadata_version assert meta.import_names is None - def test_from_email_empty_import_name(self): + def test_from_email_empty_import_name(self) -> None: meta = metadata.Metadata.from_email( "Metadata-Version: 2.5\nImport-Name:\n", validate=False ) assert meta.import_names == [] - def test_from_email_unparsed(self): + def test_from_email_unparsed(self) -> None: with pytest.raises(ExceptionGroup) as exc_info: metadata.Metadata.from_email("Hello: PyPA") assert len(exc_info.value.exceptions) == 1 assert isinstance(exc_info.value.exceptions[0], metadata.InvalidMetadata) - def test_from_email_validate(self): + def test_from_email_validate(self) -> None: with pytest.raises(ExceptionGroup): # Lacking all required fields. metadata.Metadata.from_email("Name: packaging", validate=True) - def test_from_email_unparsed_valid_field_name(self): + def test_from_email_unparsed_valid_field_name(self) -> None: with pytest.raises(ExceptionGroup): metadata.Metadata.from_email( "Project-URL: A, B\nProject-URL: A, C", validate=True ) - def test_required_fields(self): + def test_required_fields(self) -> None: meta = metadata.Metadata.from_raw(_RAW_EXAMPLE) assert meta.metadata_version == _RAW_EXAMPLE["metadata_version"] @pytest.mark.parametrize("field", list(_RAW_EXAMPLE.keys())) - def test_required_fields_missing(self, field): + def test_required_fields_missing(self, field: str) -> None: required_fields = _RAW_EXAMPLE.copy() - del required_fields[field] + del required_fields[field] # type: ignore[misc] with pytest.raises(ExceptionGroup): metadata.Metadata.from_raw(required_fields) - def test_raw_validate_unrecognized_field(self): - raw = { + def test_raw_validate_unrecognized_field(self) -> None: + raw: RawMetadata = { "metadata_version": "2.3", "name": "packaging", "version": "2023.0.0", } - # Safety check. - assert metadata.Metadata.from_raw(raw, validate=True) + # Safety check (always true) + assert metadata.Metadata.from_raw(raw, validate=True) # type: ignore[truthy-bool] - raw["dynamc"] = ["Obsoletes-Dist"] # Misspelled; missing an "i". + # Misspelled; missing an "i": + raw["dynamc"] = ["Obsoletes-Dist"] # type: ignore[typeddict-unknown-key] with pytest.raises(ExceptionGroup): metadata.Metadata.from_raw(raw, validate=True) - def test_raw_data_not_mutated(self): + def test_raw_data_not_mutated(self) -> None: raw = _RAW_EXAMPLE.copy() meta = metadata.Metadata.from_raw(raw, validate=True) assert meta.version == version.Version(_RAW_EXAMPLE["version"]) assert raw == _RAW_EXAMPLE - def test_caching(self): + def test_caching(self) -> None: meta = metadata.Metadata.from_raw(_RAW_EXAMPLE, validate=True) assert meta.version is meta.version - def test_from_raw_validate(self): + def test_from_raw_validate(self) -> None: required_fields = _RAW_EXAMPLE.copy() required_fields["version"] = "-----" @@ -370,19 +381,21 @@ def test_from_raw_validate(self): metadata.Metadata.from_raw(required_fields) @pytest.mark.parametrize("meta_version", ["2.2", "2.3"]) - def test_metadata_version_field_introduction(self, meta_version): - raw = { + def test_metadata_version_field_introduction(self, meta_version: str) -> None: + raw: RawMetadata = { "metadata_version": meta_version, "name": "packaging", "version": "2023.0.0", "dynamic": ["Obsoletes-Dist"], # Introduced in 2.2. } - assert metadata.Metadata.from_raw(raw, validate=True) + assert metadata.Metadata.from_raw(raw, validate=True) # type: ignore[truthy-bool] @pytest.mark.parametrize("meta_version", ["1.0", "1.1", "1.2", "2.1"]) - def test_metadata_version_field_introduction_mismatch(self, meta_version): - raw = { + def test_metadata_version_field_introduction_mismatch( + self, meta_version: str + ) -> None: + raw: RawMetadata = { "metadata_version": meta_version, "name": "packaging", "version": "2023.0.0", @@ -405,9 +418,9 @@ def test_metadata_version_field_introduction_mismatch(self, meta_version): "license", ], ) - def test_single_value_unvalidated_attribute(self, attribute): + def test_single_value_unvalidated_attribute(self, attribute: str) -> None: value = "Not important" - meta = metadata.Metadata.from_raw({attribute: value}, validate=False) + meta = metadata.Metadata.from_raw({attribute: value}, validate=False) # type: ignore[misc] assert getattr(meta, attribute) == value @@ -424,47 +437,47 @@ def test_single_value_unvalidated_attribute(self, attribute): "obsoletes", ], ) - def test_multi_value_unvalidated_attribute(self, attribute): + def test_multi_value_unvalidated_attribute(self, attribute: str) -> None: values = ["Not important", "Still not important"] - meta = metadata.Metadata.from_raw({attribute: values}, validate=False) + meta = metadata.Metadata.from_raw({attribute: values}, validate=False) # type: ignore[misc] assert getattr(meta, attribute) == values @pytest.mark.parametrize("version", ["1.0", "1.1", "1.2", "2.1", "2.2", "2.3"]) - def test_valid_metadata_version(self, version): + def test_valid_metadata_version(self, version: str) -> None: meta = metadata.Metadata.from_raw({"metadata_version": version}, validate=False) assert meta.metadata_version == version @pytest.mark.parametrize("version", ["1.3", "2.0"]) - def test_invalid_metadata_version(self, version): + def test_invalid_metadata_version(self, version: str) -> None: meta = metadata.Metadata.from_raw({"metadata_version": version}, validate=False) with pytest.raises(metadata.InvalidMetadata): meta.metadata_version # noqa: B018 - def test_valid_version(self): + def test_valid_version(self) -> None: version_str = "1.2.3" meta = metadata.Metadata.from_raw({"version": version_str}, validate=False) assert meta.version == version.parse(version_str) - def test_missing_version(self): + def test_missing_version(self) -> None: meta = metadata.Metadata.from_raw({}, validate=False) with pytest.raises(metadata.InvalidMetadata) as exc_info: meta.version # noqa: B018 assert exc_info.value.field == "version" - def test_invalid_version(self): + def test_invalid_version(self) -> None: meta = metadata.Metadata.from_raw({"version": "a.b.c"}, validate=False) self._invalid_with_cause(meta, "version", version.InvalidVersion) - def test_valid_summary(self): + def test_valid_summary(self) -> None: summary = "Hello" meta = metadata.Metadata.from_raw({"summary": summary}, validate=False) assert meta.summary == summary - def test_invalid_summary(self): + def test_invalid_summary(self) -> None: meta = metadata.Metadata.from_raw( {"summary": "Hello\n Again"}, validate=False ) @@ -473,12 +486,12 @@ def test_invalid_summary(self): meta.summary # noqa: B018 assert exc_info.value.field == "summary" - def test_valid_name(self): + def test_valid_name(self) -> None: name = "Hello_World" meta = metadata.Metadata.from_raw({"name": name}, validate=False) assert meta.name == name - def test_invalid_name(self): + def test_invalid_name(self) -> None: meta = metadata.Metadata.from_raw({"name": "-not-legal"}, validate=False) self._invalid_with_cause(meta, "name", utils.InvalidName) @@ -497,7 +510,7 @@ def test_invalid_name(self): "text/markdown; variant=CommonMark", ], ) - def test_valid_description_content_type(self, content_type): + def test_valid_description_content_type(self, content_type: str) -> None: meta = metadata.Metadata.from_raw( {"description_content_type": content_type}, validate=False ) @@ -514,7 +527,7 @@ def test_valid_description_content_type(self, content_type): "text/markdown; variant=commonmark", ], ) - def test_invalid_description_content_type(self, content_type): + def test_invalid_description_content_type(self, content_type: str) -> None: meta = metadata.Metadata.from_raw( {"description_content_type": content_type}, validate=False ) @@ -522,13 +535,13 @@ def test_invalid_description_content_type(self, content_type): with pytest.raises(metadata.InvalidMetadata): meta.description_content_type # noqa: B018 - def test_keywords(self): + def test_keywords(self) -> None: keywords = ["hello", "world"] meta = metadata.Metadata.from_raw({"keywords": keywords}, validate=False) assert meta.keywords == keywords - def test_valid_project_urls(self): + def test_valid_project_urls(self) -> None: urls = { "Documentation": "https://example.com/BeagleVote", "Bug Tracker": "http://bitbucket.org/tarek/distribute/issues/", @@ -538,7 +551,7 @@ def test_valid_project_urls(self): assert meta.project_urls == urls @pytest.mark.parametrize("specifier", [">=3", ">2.6,!=3.0.*,!=3.1.*", "~=2.6"]) - def test_valid_requires_python(self, specifier): + def test_valid_requires_python(self, specifier: str) -> None: expected = specifiers.SpecifierSet(specifier) meta = metadata.Metadata.from_raw( {"requires_python": specifier}, validate=False @@ -546,7 +559,7 @@ def test_valid_requires_python(self, specifier): assert meta.requires_python == expected - def test_invalid_requires_python(self): + def test_invalid_requires_python(self) -> None: meta = metadata.Metadata.from_raw( {"requires_python": "NotReal"}, validate=False ) @@ -557,7 +570,7 @@ def test_invalid_requires_python(self): field="requires-python", ) - def test_requires_external(self): + def test_requires_external(self) -> None: externals = [ "C", "libpng (>=1.5)", @@ -570,20 +583,20 @@ def test_requires_external(self): assert meta.requires_external == externals - def test_valid_provides_extra(self): + def test_valid_provides_extra(self) -> None: extras = ["dev", "test"] meta = metadata.Metadata.from_raw({"provides_extra": extras}, validate=False) assert meta.provides_extra == extras - def test_invalid_provides_extra(self): + def test_invalid_provides_extra(self) -> None: extras = ["pdf", "-Not-Valid", "ok"] meta = metadata.Metadata.from_raw({"provides_extra": extras}, validate=False) self._invalid_with_cause( meta, "provides_extra", utils.InvalidName, field="provides-extra" ) - def test_valid_requires_dist(self): + def test_valid_requires_dist(self) -> None: requires = [ "pkginfo", "PasteDeploy", @@ -595,7 +608,7 @@ def test_valid_requires_dist(self): assert meta.requires_dist == expected_requires - def test_invalid_requires_dist(self): + def test_invalid_requires_dist(self) -> None: requires = ["pkginfo", "-not-real", "zope.interface (>3.5.0)"] meta = metadata.Metadata.from_raw({"requires_dist": requires}, validate=False) self._invalid_with_cause( @@ -605,13 +618,13 @@ def test_invalid_requires_dist(self): field="requires-dist", ) - def test_valid_dynamic(self): + def test_valid_dynamic(self) -> None: dynamic = ["Keywords", "Home-Page", "Author"] meta = metadata.Metadata.from_raw({"dynamic": dynamic}, validate=False) assert meta.dynamic == [d.lower() for d in dynamic] - def test_invalid_dynamic_value(self): + def test_invalid_dynamic_value(self) -> None: dynamic = ["Keywords", "NotReal", "Author"] meta = metadata.Metadata.from_raw({"dynamic": dynamic}, validate=False) @@ -619,7 +632,7 @@ def test_invalid_dynamic_value(self): meta.dynamic # noqa: B018 @pytest.mark.parametrize("field_name", ["name", "version", "metadata-version"]) - def test_disallowed_dynamic(self, field_name): + def test_disallowed_dynamic(self, field_name: str) -> None: meta = metadata.Metadata.from_raw({"dynamic": [field_name]}, validate=False) message = f"{field_name!r} is not allowed" @@ -634,7 +647,7 @@ def test_disallowed_dynamic(self, field_name): "field_name", sorted(metadata._RAW_TO_EMAIL_MAPPING.keys() - metadata._REQUIRED_ATTRS), ) - def test_optional_defaults_to_none(self, field_name): + def test_optional_defaults_to_none(self, field_name: str) -> None: meta = metadata.Metadata.from_raw({}, validate=False) assert getattr(meta, field_name) is None @@ -705,7 +718,9 @@ def test_optional_defaults_to_none(self, field_name): ("((MIT AND (MIT)))", "((MIT AND (MIT)))"), ], ) - def test_valid_license_expression(self, license_expression, expected): + def test_valid_license_expression( + self, license_expression: str, expected: str + ) -> None: meta = metadata.Metadata.from_raw( {"license_expression": license_expression}, validate=False ) @@ -745,7 +760,7 @@ def test_valid_license_expression(self, license_expression, expected): "mit with ( ) with ( ) or mit", ], ) - def test_invalid_license_expression(self, license_expression): + def test_invalid_license_expression(self, license_expression: str) -> None: meta = metadata.Metadata.from_raw( {"license_expression": license_expression}, validate=False ) @@ -761,7 +776,7 @@ def test_invalid_license_expression(self, license_expression): ["LICENSE"], ], ) - def test_valid_license_files(self, license_files): + def test_valid_license_files(self, license_files: list[str]) -> None: meta = metadata.Metadata.from_raw( {"license_files": license_files}, validate=False ) @@ -784,7 +799,7 @@ def test_valid_license_files(self, license_files): ["licenses\\LICENSE"], ], ) - def test_invalid_license_files(self, license_files): + def test_invalid_license_files(self, license_files: list[str]) -> None: meta = metadata.Metadata.from_raw( {"license_files": license_files}, validate=False ) @@ -793,45 +808,44 @@ def test_invalid_license_files(self, license_files): meta.license_files # noqa: B018 @pytest.mark.parametrize("key", ["import_namespaces", "import_names"]) - def test_valid_import_names(self, key): + def test_valid_import_names(self, key: str) -> None: import_names = [ "packaging", "packaging.metadata", "_utils ; private", "_stuff;private", ] - meta = metadata.Metadata.from_raw({key: import_names}, validate=False) - + meta = metadata.Metadata.from_raw({key: import_names}, validate=False) # type: ignore[misc] assert getattr(meta, key) == import_names @pytest.mark.parametrize("key", ["import_namespaces", "import_names"]) @pytest.mark.parametrize( "name", ["not-valid", "still.not-valid", "stuff;", "stuff; extra"] ) - def test_invalid_import_names_identifier(self, key, name): - meta = metadata.Metadata.from_raw({key: [name]}, validate=False) + def test_invalid_import_names_identifier(self, key: str, name: str) -> None: + meta = metadata.Metadata.from_raw({key: [name]}, validate=False) # type: ignore[misc] with pytest.raises(metadata.InvalidMetadata): getattr(meta, key) @pytest.mark.parametrize("key", ["import_namespaces", "import_names"]) - def test_invalid_import_names_keyword(self, key): + def test_invalid_import_names_keyword(self, key: str) -> None: import_names = ["class"] - meta = metadata.Metadata.from_raw({key: import_names}, validate=False) + meta = metadata.Metadata.from_raw({key: import_names}, validate=False) # type: ignore[misc] with pytest.raises(metadata.InvalidMetadata): getattr(meta, key) class TestMetadataWriting: - def test_write_metadata(self): + def test_write_metadata(self) -> None: meta = metadata.Metadata.from_raw(_RAW_EXAMPLE) written = meta.as_rfc822().as_string() assert ( written == "metadata-version: 2.5\nname: packaging\nversion: 2023.0.0\n\n" ) - def test_write_metadata_with_description(self): + def test_write_metadata_with_description(self) -> None: # Intentionally out of order to make sure it is written in order meta = metadata.Metadata.from_raw( { @@ -846,14 +860,14 @@ def test_write_metadata_with_description(self): written == "metadata-version: 2.3\nname: Hello\n" "version: 1.2.3\n\nHello\n\nWorld👋" ) - written = meta.as_rfc822().as_bytes() + written_bytes = meta.as_rfc822().as_bytes() assert ( - written + written_bytes == "metadata-version: 2.3\nname: Hello\n" "version: 1.2.3\n\nHello\n\nWorld👋".encode() ) - def test_multiline_license(self): + def test_multiline_license(self) -> None: meta = metadata.Metadata.from_raw( { "version": "1.2.3", @@ -867,14 +881,14 @@ def test_multiline_license(self): written == "metadata-version: 2.3\nname: packaging\nversion: 1.2.3" "\nlicense: Hello\n World🐍\n\n" ) - written = meta.as_rfc822().as_bytes() + written_bytes = meta.as_rfc822().as_bytes() assert ( - written + written_bytes == "metadata-version: 2.3\nname: packaging\nversion: 1.2.3" "\nlicense: Hello\n World🐍\n\n".encode() ) - def test_large(self): + def test_large(self) -> None: meta = metadata.Metadata.from_raw( { "author": "Example!", @@ -952,7 +966,7 @@ def test_large(self): assert core_metadata.get_payload() == "some readme 👋\n" - def test_modern_license(self): + def test_modern_license(self) -> None: meta = metadata.Metadata.from_raw( { "metadata_version": "2.4", @@ -975,7 +989,7 @@ def test_modern_license(self): assert core_metadata.get_payload() is None - def test__import_names(self): + def test__import_names(self) -> None: meta = metadata.Metadata.from_raw( { "metadata_version": "2.5", @@ -998,7 +1012,7 @@ def test__import_names(self): assert core_metadata.get_payload() is None - def test_empty_import_names(self): + def test_empty_import_names(self) -> None: meta = metadata.Metadata.from_raw( { "metadata_version": "2.5", diff --git a/tests/test_musllinux.py b/tests/test_musllinux.py index a62deb822..52964aec7 100644 --- a/tests/test_musllinux.py +++ b/tests/test_musllinux.py @@ -1,6 +1,9 @@ +from __future__ import annotations + import collections import pathlib import subprocess +import typing import pretend import pytest @@ -8,6 +11,9 @@ from packaging import _musllinux from packaging._musllinux import _get_musl_version, _MuslVersion, _parse_musl_version +if typing.TYPE_CHECKING: + from collections.abc import Generator + MUSL_AMD64 = "musl libc (x86_64)\nVersion 1.2.2\n" MUSL_I386 = "musl libc (i386)\nVersion 1.2.1\n" MUSL_AARCH64 = "musl libc (aarch64)\nVersion 1.1.24\n" @@ -27,7 +33,7 @@ @pytest.fixture(autouse=True) -def clear_lru_cache(): +def clear_lru_cache() -> Generator[None, None, None]: yield _get_musl_version.cache_clear() @@ -43,7 +49,7 @@ def clear_lru_cache(): ], ids=["amd64-1.2.2", "i386-1.2.1", "aarch64-1.1.24", "invalid", "unknown"], ) -def test_parse_musl_version(output, version): +def test_parse_musl_version(output: str, version: _MuslVersion | None) -> None: assert _parse_musl_version(output) == version @@ -58,12 +64,18 @@ def test_parse_musl_version(output, version): ], ids=["does-not-exist", "glibc", "x86_64", "i386", "aarch64"], ) -def test_get_musl_version(monkeypatch, executable, output, version, ld_musl): - def mock_run(*args, **kwargs): +def test_get_musl_version( + monkeypatch: pytest.MonkeyPatch, + executable: pathlib.Path, + output: str, + version: _MuslVersion | None, + ld_musl: str | None, +) -> None: + def mock_run(*args: object, **kwargs: object) -> tuple[object, ...]: return collections.namedtuple("Proc", "stderr")(output) run_recorder = pretend.call_recorder(mock_run) - monkeypatch.setattr(_musllinux.subprocess, "run", run_recorder) + monkeypatch.setattr(_musllinux.subprocess, "run", run_recorder) # type: ignore[attr-defined] assert _get_musl_version(str(executable)) == version diff --git a/tests/test_specifiers.py b/tests/test_specifiers.py index ff2a04366..3ab2523d5 100644 --- a/tests/test_specifiers.py +++ b/tests/test_specifiers.py @@ -2,9 +2,12 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. +from __future__ import annotations + import itertools import operator import re +import typing import pytest @@ -13,6 +16,9 @@ from .test_version import VERSIONS +if typing.TYPE_CHECKING: + from collections.abc import Callable + LEGACY_SPECIFIERS = [ "==2.1.0.3", "!=2.2.0.5", @@ -37,7 +43,7 @@ class TestSpecifier: @pytest.mark.parametrize("specifier", SPECIFIERS) - def test_specifiers_valid(self, specifier): + def test_specifiers_valid(self, specifier: str) -> None: Specifier(specifier) @pytest.mark.parametrize( @@ -84,7 +90,7 @@ def test_specifiers_valid(self, specifier): "!=1.0.dev1.*", ], ) - def test_specifiers_invalid(self, specifier): + def test_specifiers_invalid(self, specifier: str) -> None: with pytest.raises(InvalidSpecifier): Specifier(specifier) @@ -196,7 +202,7 @@ def test_specifiers_invalid(self, specifier): " \r \f \v v1.0\t\n", ], ) - def test_specifiers_normalized(self, version): + def test_specifiers_normalized(self, version: str) -> None: if "+" not in version: ops = ["~=", "==", "!=", "<=", ">=", "<", ">"] else: @@ -220,14 +226,14 @@ def test_specifiers_normalized(self, version): ("< 2", "<2"), ], ) - def test_specifiers_str_and_repr(self, specifier, expected): + def test_specifiers_str_and_repr(self, specifier: str, expected: str) -> None: spec = Specifier(specifier) assert str(spec) == expected assert repr(spec) == f"" @pytest.mark.parametrize("specifier", SPECIFIERS) - def test_specifiers_hash(self, specifier): + def test_specifiers_hash(self, specifier: str) -> None: assert hash(Specifier(specifier)) == hash(Specifier(specifier)) @pytest.mark.parametrize( @@ -243,13 +249,18 @@ def test_specifiers_hash(self, specifier): ] ), ) - def test_comparison_true(self, left, right, op): + def test_comparison_true( + self, + left: str, + right: str, + op: typing.Callable[[Specifier | str, Specifier | str], bool], + ) -> None: assert op(Specifier(left), Specifier(right)) assert op(left, Specifier(right)) assert op(Specifier(left), right) @pytest.mark.parametrize(("left", "right"), [("==2.8.0", "==2.8")]) - def test_comparison_canonicalizes(self, left, right): + def test_comparison_canonicalizes(self, left: str, right: str) -> None: assert Specifier(left) == Specifier(right) assert left == Specifier(right) assert Specifier(left) == right @@ -267,19 +278,24 @@ def test_comparison_canonicalizes(self, left, right): ] ), ) - def test_comparison_false(self, left, right, op): + def test_comparison_false( + self, + left: str, + right: str, + op: typing.Callable[[Specifier | str, Specifier | str], bool], + ) -> None: assert not op(Specifier(left), Specifier(right)) assert not op(left, Specifier(right)) assert not op(Specifier(left), right) - def test_comparison_non_specifier(self): + def test_comparison_non_specifier(self) -> None: assert Specifier("==1.0") != 12 assert not Specifier("==1.0") == 12 assert Specifier("==1.0") != "12" assert not Specifier("==1.0") == "12" @pytest.mark.parametrize( - ("version", "spec", "expected"), + ("version", "spec_str", "expected"), [ (v, s, True) for v, s in [ @@ -475,8 +491,8 @@ def test_comparison_non_specifier(self): ] ], ) - def test_specifiers(self, version, spec, expected): - spec = Specifier(spec, prereleases=True) + def test_specifiers(self, version: str, spec_str: str, expected: bool) -> None: + spec = Specifier(spec_str, prereleases=True) if expected: # Test that the plain string form works @@ -496,14 +512,14 @@ def test_specifiers(self, version, spec, expected): assert not spec.contains(Version(version)) @pytest.mark.parametrize( - ("spec", "version"), + ("spec_str", "version"), [ ("==1.0", "not a valid version"), ("===invalid", "invalid"), ], ) - def test_invalid_spec(self, spec, version): - spec = Specifier(spec, prereleases=True) + def test_invalid_spec(self, spec_str: str, version: str) -> None: + spec = Specifier(spec_str, prereleases=True) assert not spec.contains(version) @pytest.mark.parametrize( @@ -544,13 +560,13 @@ def test_invalid_spec(self, spec, version): ) def test_specifier_prereleases_set( self, - specifier, - initial_prereleases, - set_prereleases, - version, - initial_contains, - final_contains, - ): + specifier: str, + initial_prereleases: bool | None, + set_prereleases: bool | None, + version: str, + initial_contains: bool, + final_contains: bool, + ) -> None: """Test setting prereleases property.""" spec = Specifier(specifier, prereleases=initial_prereleases) @@ -563,7 +579,7 @@ def test_specifier_prereleases_set( assert spec.contains(version) == final_contains @pytest.mark.parametrize( - ("version", "spec", "expected"), + ("version", "spec_str", "expected"), [ ("1.0.0", "===1.0", False), ("1.0.dev0", "===1.0", False), @@ -572,8 +588,10 @@ def test_specifier_prereleases_set( ("1.0.dev0", "===1.0.dev0", True), ], ) - def test_specifiers_identity(self, version, spec, expected): - spec = Specifier(spec) + def test_specifiers_identity( + self, version: str, spec_str: str, expected: bool + ) -> None: + spec = Specifier(spec_str) if expected: # Identity comparisons only support the plain string form @@ -601,7 +619,9 @@ def test_specifiers_identity(self, version, spec, expected): ("~=1.0.dev1", True), ], ) - def test_specifier_prereleases_detection(self, specifier, expected): + def test_specifier_prereleases_detection( + self, specifier: str, expected: bool + ) -> None: assert Specifier(specifier).prereleases == expected @pytest.mark.parametrize( @@ -625,8 +645,13 @@ def test_specifier_prereleases_detection(self, specifier, expected): ], ) def test_specifiers_prereleases( - self, specifier, version, spec_pre, contains_pre, expected - ): + self, + specifier: str, + version: str, + spec_pre: bool | None, + contains_pre: bool | None, + expected: bool, + ) -> None: spec = Specifier(specifier, prereleases=spec_pre) assert spec.contains(version, prereleases=contains_pre) == expected @@ -660,8 +685,13 @@ def test_specifiers_prereleases( ], ) def test_specifier_filter( - self, specifier, specifier_prereleases, prereleases, input, expected - ): + self, + specifier: str, + specifier_prereleases: bool | None, + prereleases: bool | None, + input: list[str], + expected: list[str], + ) -> None: if specifier_prereleases is None: spec = Specifier(specifier) else: @@ -687,7 +717,7 @@ def test_specifier_filter( ("===lolwat", "==="), ], ) - def test_specifier_operator_property(self, spec, op): + def test_specifier_operator_property(self, spec: str, op: str) -> None: assert Specifier(spec).operator == op @pytest.mark.parametrize( @@ -706,19 +736,19 @@ def test_specifier_operator_property(self, spec, op): ("===lolwat", "lolwat"), ], ) - def test_specifier_version_property(self, spec, version): + def test_specifier_version_property(self, spec: str, version: str) -> None: assert Specifier(spec).version == version @pytest.mark.parametrize( - ("spec", "expected_length"), + ("spec_str", "expected_length"), [("", 0), ("==2.0", 1), (">=2.0", 1), (">=2.0,<3", 2), (">=2.0,<3,==2.4", 3)], ) - def test_length(self, spec, expected_length): - spec = SpecifierSet(spec) + def test_length(self, spec_str: str, expected_length: int) -> None: + spec = SpecifierSet(spec_str) assert len(spec) == expected_length @pytest.mark.parametrize( - ("spec", "expected_items"), + ("spec_str", "expected_items"), [ ("", []), ("==2.0", ["==2.0"]), @@ -727,21 +757,21 @@ def test_length(self, spec, expected_length): (">=2.0,<3,==2.4", [">=2.0", "<3", "==2.4"]), ], ) - def test_iteration(self, spec, expected_items): - spec = SpecifierSet(spec) + def test_iteration(self, spec_str: str, expected_items: list[str]) -> None: + spec = SpecifierSet(spec_str) items = {str(item) for item in spec} assert items == set(expected_items) - def test_specifier_equal_for_compatible_operator(self): + def test_specifier_equal_for_compatible_operator(self) -> None: assert Specifier("~=1.18.0") != Specifier("~=1.18") - def test_specifier_hash_for_compatible_operator(self): + def test_specifier_hash_for_compatible_operator(self) -> None: assert hash(Specifier("~=1.18.0")) != hash(Specifier("~=1.18")) class TestSpecifierSet: @pytest.mark.parametrize("version", VERSIONS) - def test_empty_specifier(self, version): + def test_empty_specifier(self, version: str) -> None: spec = SpecifierSet(prereleases=True) assert version in spec @@ -749,7 +779,7 @@ def test_empty_specifier(self, version): assert parse(version) in spec assert spec.contains(parse(version)) - def test_create_from_specifiers(self): + def test_create_from_specifiers(self) -> None: spec_strs = [">=1.0", "!=1.1", "!=1.2", "<2.0"] specs = [Specifier(s) for s in spec_strs] spec = SpecifierSet(iter(specs)) @@ -802,13 +832,13 @@ def test_create_from_specifiers(self): ) def test_specifier_prereleases_explicit( self, - initial_prereleases, - set_prereleases, - version, - initial_contains, - final_contains, - spec_str, - ): + initial_prereleases: bool | None, + set_prereleases: bool | None, + version: str, + initial_contains: bool, + final_contains: bool, + spec_str: str, + ) -> None: """Test setting prereleases property with different initial states.""" spec = SpecifierSet(spec_str, prereleases=initial_prereleases) @@ -820,7 +850,7 @@ def test_specifier_prereleases_explicit( assert (version in spec) == final_contains assert spec.contains(version) == final_contains - def test_specifier_contains_prereleases(self): + def test_specifier_contains_prereleases(self) -> None: spec = SpecifierSet() assert spec.prereleases is None assert spec.contains("1.0.dev1") @@ -897,13 +927,13 @@ def test_specifier_contains_prereleases(self): ) def test_specifier_contains_installed_prereleases( self, - specifier, - version, - spec_prereleases, - contains_prereleases, - installed, - expected, - ): + specifier: str, + version: str, + spec_prereleases: bool | None, + contains_prereleases: bool | None, + installed: bool | None, + expected: bool, + ) -> None: """Test the behavior of SpecifierSet.contains with installed and prereleases.""" spec = SpecifierSet(specifier, prereleases=spec_prereleases) @@ -976,8 +1006,13 @@ def test_specifier_contains_installed_prereleases( ], ) def test_specifier_filter( - self, specifier, specifier_prereleases, prereleases, input, expected - ): + self, + specifier: str, + specifier_prereleases: bool | None, + prereleases: bool | None, + input: list[str], + expected: list[str], + ) -> None: if specifier_prereleases is None: spec = SpecifierSet(specifier) else: @@ -1208,7 +1243,13 @@ def test_specifier_filter( ), ], ) - def test_filter_exclusionary_bridges(self, specifier, prereleases, input, expected): + def test_filter_exclusionary_bridges( + self, + specifier: str, + prereleases: bool | None, + input: list[str], + expected: list[str], + ) -> None: """ Test that filter correctly handles exclusionary bridges. @@ -1331,8 +1372,8 @@ def test_filter_exclusionary_bridges(self, specifier, prereleases, input, expect ], ) def test_contains_exclusionary_bridges( - self, specifier, prereleases, version, expected - ): + self, specifier: str, prereleases: bool | None, version: str, expected: bool + ) -> None: """ Test that contains correctly handles exclusionary bridges. @@ -1351,7 +1392,9 @@ def test_contains_exclusionary_bridges( (">=1.0", "not a valid version"), ], ) - def test_contains_rejects_invalid_specifier(self, specifier, input): + def test_contains_rejects_invalid_specifier( + self, specifier: str, input: str + ) -> None: spec = SpecifierSet(specifier, prereleases=True) assert not spec.contains(input) @@ -1373,20 +1416,20 @@ def test_contains_rejects_invalid_specifier(self, specifier, input): ("!=2.0 ,>1.0", "!=2.0,>1.0"), ], ) - def test_specifiers_str_and_repr(self, specifier, expected): + def test_specifiers_str_and_repr(self, specifier: str, expected: str) -> None: spec = SpecifierSet(specifier) assert str(spec) == expected assert repr(spec) == f"" @pytest.mark.parametrize("specifier", SPECIFIERS + LEGACY_SPECIFIERS) - def test_specifiers_hash(self, specifier): + def test_specifiers_hash(self, specifier: str) -> None: assert hash(SpecifierSet(specifier)) == hash(SpecifierSet(specifier)) @pytest.mark.parametrize( ("left", "right", "expected"), [(">2.0", "<5.0", ">2.0,<5.0")] ) - def test_specifiers_combine(self, left, right, expected): + def test_specifiers_combine(self, left: str, right: str, expected: str) -> None: result = SpecifierSet(left) & SpecifierSet(right) assert result == SpecifierSet(expected) @@ -1441,9 +1484,9 @@ def test_specifiers_combine(self, left, right, expected): right, prereleases=True ) - def test_specifiers_combine_not_implemented(self): + def test_specifiers_combine_not_implemented(self) -> None: with pytest.raises(TypeError): - SpecifierSet() & 12 + SpecifierSet() & 12 # type: ignore[operator] @pytest.mark.parametrize( ("left", "right", "op"), @@ -1458,7 +1501,9 @@ def test_specifiers_combine_not_implemented(self): ] ), ) - def test_comparison_true(self, left, right, op): + def test_comparison_true( + self, left: str, right: str, op: Callable[[object, object], bool] + ) -> None: assert op(SpecifierSet(left), SpecifierSet(right)) assert op(SpecifierSet(left), Specifier(right)) assert op(Specifier(left), SpecifierSet(right)) @@ -1478,7 +1523,9 @@ def test_comparison_true(self, left, right, op): ] ), ) - def test_comparison_false(self, left, right, op): + def test_comparison_false( + self, left: str, right: str, op: Callable[[object, object], bool] + ) -> None: assert not op(SpecifierSet(left), SpecifierSet(right)) assert not op(SpecifierSet(left), Specifier(right)) assert not op(Specifier(left), SpecifierSet(right)) @@ -1486,12 +1533,12 @@ def test_comparison_false(self, left, right, op): assert not op(SpecifierSet(left), right) @pytest.mark.parametrize(("left", "right"), [("==2.8.0", "==2.8")]) - def test_comparison_canonicalizes(self, left, right): + def test_comparison_canonicalizes(self, left: str, right: str) -> None: assert SpecifierSet(left) == SpecifierSet(right) assert left == SpecifierSet(right) assert SpecifierSet(left) == right - def test_comparison_non_specifier(self): + def test_comparison_non_specifier(self) -> None: assert SpecifierSet("==1.0") != 12 assert not SpecifierSet("==1.0") == 12 @@ -1506,10 +1553,12 @@ def test_comparison_non_specifier(self): ("1.0.0+local", ">1.0.0", False), ], ) - def test_comparison_ignores_local(self, version, specifier, expected): + def test_comparison_ignores_local( + self, version: str, specifier: str, expected: bool + ) -> None: assert (Version(version) in SpecifierSet(specifier)) == expected - def test_contains_with_compatible_operator(self): + def test_contains_with_compatible_operator(self) -> None: combination = SpecifierSet("~=1.18.0") & SpecifierSet("~=1.18") assert "1.19.5" not in combination assert "1.18.0" in combination @@ -1530,8 +1579,8 @@ def test_contains_with_compatible_operator(self): ], ) def test_arbitrary_equality_is_intersection_preserving( - self, spec1, spec2, input_versions - ): + self, spec1: str, spec2: str, input_versions: list[str] + ) -> None: """ In general we expect for two specifiers s1 and s2, that the two statements are equivalent: diff --git a/tests/test_structures.py b/tests/test_structures.py index cc651cf60..d85c96215 100644 --- a/tests/test_structures.py +++ b/tests/test_structures.py @@ -2,29 +2,31 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. +from __future__ import annotations + import pytest from packaging._structures import Infinity, NegativeInfinity -def test_infinity_repr(): +def test_infinity_repr() -> None: assert repr(Infinity) == "Infinity" -def test_negative_infinity_repr(): +def test_negative_infinity_repr() -> None: assert repr(NegativeInfinity) == "-Infinity" -def test_infinity_hash(): +def test_infinity_hash() -> None: assert hash(Infinity) == hash(Infinity) -def test_negative_infinity_hash(): +def test_negative_infinity_hash() -> None: assert hash(NegativeInfinity) == hash(NegativeInfinity) @pytest.mark.parametrize("left", [1, "a", ("b", 4)]) -def test_infinity_comparison(left): +def test_infinity_comparison(left: int | str | tuple[str, int]) -> None: assert left < Infinity assert left <= Infinity assert not left == Infinity @@ -34,7 +36,7 @@ def test_infinity_comparison(left): @pytest.mark.parametrize("left", [1, "a", ("b", 4)]) -def test_negative_infinity_lesser(left): +def test_negative_infinity_lesser(left: int | str | tuple[str, int]) -> None: assert not left < NegativeInfinity assert not left <= NegativeInfinity assert not left == NegativeInfinity @@ -43,17 +45,17 @@ def test_negative_infinity_lesser(left): assert left >= NegativeInfinity -def test_infinity_equal(): +def test_infinity_equal() -> None: assert Infinity == Infinity -def test_negative_infinity_equal(): +def test_negative_infinity_equal() -> None: assert NegativeInfinity == NegativeInfinity -def test_negate_infinity(): +def test_negate_infinity() -> None: assert isinstance(-Infinity, NegativeInfinity.__class__) -def test_negate_negative_infinity(): +def test_negate_negative_infinity() -> None: assert isinstance(-NegativeInfinity, Infinity.__class__) diff --git a/tests/test_tags.py b/tests/test_tags.py index 9212d0245..735fb8cde 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -2,6 +2,7 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. +from __future__ import annotations import collections.abc import subprocess @@ -9,7 +10,7 @@ try: import ctypes except ImportError: - ctypes = None + ctypes = None # type: ignore[assignment] import importlib import os import pathlib @@ -19,6 +20,7 @@ import sys import sysconfig import types +import typing import pretend import pytest @@ -27,15 +29,18 @@ from packaging._manylinux import _GLibCVersion from packaging._musllinux import _MuslVersion +if typing.TYPE_CHECKING: + from collections.abc import Callable + @pytest.fixture -def example_tag(): +def example_tag() -> tags.Tag: return tags.Tag("py3", "none", "any") @pytest.fixture -def manylinux_module(monkeypatch): - monkeypatch.setattr(tags._manylinux, "_get_glibc_version", lambda *args: (2, 20)) +def manylinux_module(monkeypatch: pytest.MonkeyPatch) -> types.ModuleType: + monkeypatch.setattr(tags._manylinux, "_get_glibc_version", lambda *args: (2, 20)) # type: ignore[attr-defined] module_name = "_manylinux" module = types.ModuleType(module_name) monkeypatch.setitem(sys.modules, module_name, module) @@ -43,8 +48,8 @@ def manylinux_module(monkeypatch): @pytest.fixture -def mock_interpreter_name(monkeypatch): - def mock(name): +def mock_interpreter_name(monkeypatch: pytest.MonkeyPatch) -> Callable[[str], bool]: + def mock(name: str) -> bool: name = name.lower() if sys.implementation.name != name: monkeypatch.setattr(sys.implementation, "name", name) @@ -55,7 +60,7 @@ def mock(name): @pytest.fixture -def mock_ios(monkeypatch): +def mock_ios(monkeypatch: pytest.MonkeyPatch) -> None: # Monkeypatch the platform to be iOS monkeypatch.setattr(sys, "platform", "ios") @@ -69,17 +74,17 @@ def mock_ios(monkeypatch): ) # Mock the return value of platform.ios_ver. - def mock_ios_ver(*args): + def mock_ios_ver(*args: object) -> tuple[str, str, str, bool]: return ("iOS", "13.2", "iPhone15,2", False) if sys.version_info < (3, 13): - platform.ios_ver = mock_ios_ver + platform.ios_ver = mock_ios_ver # type: ignore[attr-defined] else: monkeypatch.setattr(platform, "ios_ver", mock_ios_ver) @pytest.fixture -def mock_android(monkeypatch): +def mock_android(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr(sys, "platform", "android") monkeypatch.setattr(platform, "system", lambda: "Android") monkeypatch.setattr(sysconfig, "get_platform", lambda: "android-21-arm64_v8a") @@ -96,51 +101,51 @@ def mock_android(monkeypatch): class TestTag: - def test_lowercasing(self): + def test_lowercasing(self) -> None: tag = tags.Tag("PY3", "None", "ANY") assert tag.interpreter == "py3" assert tag.abi == "none" assert tag.platform == "any" - def test_equality(self): + def test_equality(self) -> None: args = "py3", "none", "any" assert tags.Tag(*args) == tags.Tag(*args) - def test_equality_fails_with_non_tag(self): + def test_equality_fails_with_non_tag(self) -> None: assert not tags.Tag("py3", "none", "any") == "non-tag" - def test_hashing(self, example_tag): + def test_hashing(self, example_tag: tags.Tag) -> None: tags = {example_tag} # Should not raise TypeError. assert example_tag in tags - def test_hash_equality(self, example_tag): + def test_hash_equality(self, example_tag: tags.Tag) -> None: equal_tag = tags.Tag("py3", "none", "any") assert example_tag == equal_tag # Sanity check. assert example_tag.__hash__() == equal_tag.__hash__() - def test_str(self, example_tag): + def test_str(self, example_tag: tags.Tag) -> None: assert str(example_tag) == "py3-none-any" - def test_repr(self, example_tag): + def test_repr(self, example_tag: tags.Tag) -> None: assert repr(example_tag) == f"" - def test_attribute_access(self, example_tag): + def test_attribute_access(self, example_tag: tags.Tag) -> None: assert example_tag.interpreter == "py3" assert example_tag.abi == "none" assert example_tag.platform == "any" class TestParseTag: - def test_simple(self, example_tag): + def test_simple(self, example_tag: tags.Tag) -> None: parsed_tags = tags.parse_tag(str(example_tag)) assert parsed_tags == {example_tag} - def test_multi_interpreter(self, example_tag): + def test_multi_interpreter(self, example_tag: tags.Tag) -> None: expected = {example_tag, tags.Tag("py2", "none", "any")} given = tags.parse_tag("py2.py3-none-any") assert given == expected - def test_multi_platform(self): + def test_multi_platform(self) -> None: expected = { tags.Tag("cp37", "cp37m", platform) for platform in ( @@ -159,28 +164,30 @@ def test_multi_platform(self): class TestInterpreterName: - def test_sys_implementation_name(self, monkeypatch): + def test_sys_implementation_name(self, monkeypatch: pytest.MonkeyPatch) -> None: class MockImplementation: pass mock_implementation = MockImplementation() - mock_implementation.name = "sillywalk" + mock_implementation.name = "sillywalk" # type: ignore[attr-defined] monkeypatch.setattr(sys, "implementation", mock_implementation, raising=False) assert tags.interpreter_name() == "sillywalk" - def test_interpreter_short_names(self, mock_interpreter_name): + def test_interpreter_short_names( + self, mock_interpreter_name: Callable[[str], bool] + ) -> None: mock_interpreter_name("cpython") assert tags.interpreter_name() == "cp" class TestInterpreterVersion: - def test_warn(self, monkeypatch): + def test_warn(self, monkeypatch: pytest.MonkeyPatch) -> None: class MockConfigVar: - def __init__(self, return_): - self.warn = None + def __init__(self, return_: str) -> None: + self.warn: bool | None = None self._return = return_ - def __call__(self, _name, warn): + def __call__(self, _name: str, warn: bool = False) -> str: self.warn = warn return self._return @@ -189,7 +196,7 @@ def __call__(self, _name, warn): tags.interpreter_version(warn=True) assert mock_config_var.warn - def test_python_version_nodot(self, monkeypatch): + def test_python_version_nodot(self, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr(tags, "_get_config_var", lambda _var, warn: "NN") # noqa: ARG005 assert tags.interpreter_version() == "NN" @@ -203,7 +210,12 @@ def test_python_version_nodot(self, monkeypatch): ((1, 2, 13), "12"), ], ) - def test_sys_version_info(self, version_info, version_str, monkeypatch): + def test_sys_version_info( + self, + version_info: tuple[int, int, int], + version_str: str, + monkeypatch: pytest.MonkeyPatch, + ) -> None: monkeypatch.setattr(tags, "_get_config_var", lambda *args, **kwargs: None) monkeypatch.setattr(sys, "version_info", version_info) assert tags.interpreter_version() == version_str @@ -221,7 +233,7 @@ class TestMacOSPlatforms: ("ppc64", True, "ppc"), ], ) - def test_architectures(self, arch, is_32bit, expected): + def test_architectures(self, arch: str, is_32bit: bool, expected: str) -> None: assert tags._mac_arch(arch, is_32bit=is_32bit) == expected @pytest.mark.parametrize( @@ -263,10 +275,12 @@ def test_architectures(self, arch, is_32bit, expected): ((12, 0), "arm64", ["arm64", "universal2"]), ], ) - def test_binary_formats(self, version, arch, expected): + def test_binary_formats( + self, version: tuple[int, int], arch: str, expected: list[str] + ) -> None: assert tags._mac_binary_formats(version, arch) == expected - def test_version_detection(self, monkeypatch): + def test_version_detection(self, monkeypatch: pytest.MonkeyPatch) -> None: if platform.system() != "Darwin": monkeypatch.setattr( platform, "mac_ver", lambda: ("10.14", ("", "", ""), "x86_64") @@ -286,7 +300,7 @@ def test_version_detection(self, monkeypatch): expected = f"macosx_{major}_{minor}_" assert platforms[0].startswith(expected) - def test_version_detection_10_15(self, monkeypatch): + def test_version_detection_10_15(self, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr( platform, "mac_ver", lambda: ("10.15", ("", "", ""), "x86_64") ) @@ -295,7 +309,9 @@ def test_version_detection_10_15(self, monkeypatch): platforms = list(tags.mac_platforms(arch="x86_64")) assert platforms[0].startswith(expected) - def test_version_detection_compatibility(self, monkeypatch): + def test_version_detection_compatibility( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: if platform.system() != "Darwin": monkeypatch.setattr( subprocess, @@ -313,7 +329,7 @@ def test_version_detection_compatibility(self, monkeypatch): assert not platforms[0].startswith(unexpected) @pytest.mark.parametrize("arch", ["x86_64", "i386"]) - def test_arch_detection(self, arch, monkeypatch): + def test_arch_detection(self, arch: str, monkeypatch: pytest.MonkeyPatch) -> None: if platform.system() != "Darwin" or platform.mac_ver()[2] != arch: monkeypatch.setattr( platform, "mac_ver", lambda: ("10.14", ("", "", ""), arch) @@ -321,7 +337,7 @@ def test_arch_detection(self, arch, monkeypatch): monkeypatch.setattr(tags, "_mac_arch", lambda *args: arch) assert next(tags.mac_platforms((10, 14))).endswith(arch) - def test_mac_platforms(self): + def test_mac_platforms(self) -> None: platforms = list(tags.mac_platforms((10, 5), "x86_64")) assert platforms == [ "macosx_10_5_x86_64", @@ -343,7 +359,7 @@ def test_mac_platforms(self): assert not list(tags.mac_platforms((10, 0), "x86_64")) @pytest.mark.parametrize(("major", "minor"), [(11, 0), (11, 3), (12, 0), (12, 3)]) - def test_macos_11(self, major, minor): + def test_macos_11(self, major: int, minor: int) -> None: platforms = list(tags.mac_platforms((major, minor), "x86_64")) assert "macosx_11_0_arm64" not in platforms assert "macosx_11_0_x86_64" in platforms @@ -379,7 +395,7 @@ def test_macos_11(self, major, minor): class TestIOSPlatforms: @pytest.mark.usefixtures("mock_ios") - def test_version_detection(self): + def test_version_detection(self) -> None: platforms = list(tags.ios_platforms(multiarch="arm64-iphoneos")) assert platforms == [ "ios_13_2_arm64_iphoneos", @@ -398,12 +414,12 @@ def test_version_detection(self): ] @pytest.mark.usefixtures("mock_ios") - def test_multiarch_detection(self): + def test_multiarch_detection(self) -> None: platforms = list(tags.ios_platforms(version=(12, 0))) assert platforms == ["ios_12_0_gothic_iphoneos"] @pytest.mark.usefixtures("mock_ios") - def test_ios_platforms(self): + def test_ios_platforms(self) -> None: # Pre-iOS 12.0 releases won't match anything platforms = list(tags.ios_platforms((7, 0), "arm64-iphoneos")) assert platforms == [] @@ -459,7 +475,7 @@ def test_ios_platforms(self): class TestAndroidPlatforms: - def test_non_android(self): + def test_non_android(self) -> None: non_android_error = pytest.raises(TypeError) with non_android_error: list(tags.android_platforms()) @@ -477,7 +493,7 @@ def test_non_android(self): ] @pytest.mark.usefixtures("mock_android") - def test_detection(self): + def test_detection(self) -> None: assert list(tags.android_platforms()) == [ "android_21_arm64_v8a", "android_20_arm64_v8a", @@ -487,7 +503,7 @@ def test_detection(self): "android_16_arm64_v8a", ] - def test_api_level(self): + def test_api_level(self) -> None: # API levels below the minimum should return nothing. assert list(tags.android_platforms(api_level=14, abi="x86")) == [] assert list(tags.android_platforms(api_level=15, abi="x86")) == [] @@ -505,7 +521,7 @@ def test_api_level(self): "android_16_x86", ] - def test_abi(self): + def test_abi(self) -> None: # Real ABI, normalized. assert list(tags.android_platforms(api_level=16, abi="armeabi_v7a")) == [ "android_16_armeabi_v7a", @@ -523,17 +539,17 @@ def test_abi(self): class TestManylinuxPlatform: - def teardown_method(self): + def teardown_method(self) -> None: # Clear the version cache - tags._manylinux._get_glibc_version.cache_clear() + tags._manylinux._get_glibc_version.cache_clear() # type: ignore[attr-defined] - def test_get_config_var_does_not_log(self, monkeypatch): + def test_get_config_var_does_not_log(self, monkeypatch: pytest.MonkeyPatch) -> None: debug = pretend.call_recorder(lambda *a: None) monkeypatch.setattr(tags.logger, "debug", debug) tags._get_config_var("missing") assert debug.calls == [] - def test_get_config_var_does_log(self, monkeypatch): + def test_get_config_var_does_log(self, monkeypatch: pytest.MonkeyPatch) -> None: debug = pretend.call_recorder(lambda *a: None) monkeypatch.setattr(tags.logger, "debug", debug) tags._get_config_var("missing", warn=True) @@ -554,26 +570,32 @@ def test_get_config_var_does_log(self, monkeypatch): ], ) def test_linux_platforms_32_64bit_on_64bit_os( - self, arch, is_32bit, expected, monkeypatch - ): + self, + arch: str, + is_32bit: bool, + expected: list[str], + monkeypatch: pytest.MonkeyPatch, + ) -> None: monkeypatch.setattr(sysconfig, "get_platform", lambda: arch) monkeypatch.setattr(os, "confstr", lambda _: "glibc 2.20", raising=False) - monkeypatch.setattr(tags._manylinux, "_is_compatible", lambda *args: False) + monkeypatch.setattr(tags._manylinux, "_is_compatible", lambda *args: False) # type: ignore[attr-defined] linux_platform = list(tags._linux_platforms(is_32bit=is_32bit))[ -len(expected) : ] assert linux_platform == expected - def test_linux_platforms_manylinux_unsupported(self, monkeypatch): + def test_linux_platforms_manylinux_unsupported( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: monkeypatch.setattr(sysconfig, "get_platform", lambda: "linux_x86_64") monkeypatch.setattr(os, "confstr", lambda _: "glibc 2.20", raising=False) - monkeypatch.setattr(tags._manylinux, "_is_compatible", lambda *args: False) + monkeypatch.setattr(tags._manylinux, "_is_compatible", lambda *args: False) # type: ignore[attr-defined] linux_platform = list(tags._linux_platforms(is_32bit=False)) assert linux_platform == ["linux_x86_64"] - def test_linux_platforms_manylinux1(self, monkeypatch): + def test_linux_platforms_manylinux1(self, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr( - tags._manylinux, + tags._manylinux, # type: ignore[attr-defined] "_is_compatible", lambda _, glibc_version: glibc_version == _GLibCVersion(2, 5), ) @@ -587,7 +609,9 @@ def test_linux_platforms_manylinux1(self, monkeypatch): "linux_x86_64", ] - def test_linux_platforms_manylinux2010(self, monkeypatch): + def test_linux_platforms_manylinux2010( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: monkeypatch.setattr(sysconfig, "get_platform", lambda: "linux_x86_64") monkeypatch.setattr(platform, "machine", lambda: "x86_64") monkeypatch.setattr(os, "confstr", lambda _: "glibc 2.12", raising=False) @@ -607,7 +631,9 @@ def test_linux_platforms_manylinux2010(self, monkeypatch): ] assert platforms == expected - def test_linux_platforms_manylinux2014(self, monkeypatch): + def test_linux_platforms_manylinux2014( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: monkeypatch.setattr(sysconfig, "get_platform", lambda: "linux_x86_64") monkeypatch.setattr(platform, "machine", lambda: "x86_64") monkeypatch.setattr(os, "confstr", lambda _: "glibc 2.17", raising=False) @@ -639,11 +665,11 @@ def test_linux_platforms_manylinux2014(self, monkeypatch): [("armv7l", "armv7l"), ("armv8l", "armv8l"), ("aarch64", "armv8l")], ) def test_linux_platforms_manylinux2014_armhf_abi( - self, native_arch, cross_arch, monkeypatch - ): - monkeypatch.setattr(tags._manylinux, "_glibc_version_string", lambda: "2.30") + self, native_arch: str, cross_arch: str, monkeypatch: pytest.MonkeyPatch + ) -> None: + monkeypatch.setattr(tags._manylinux, "_glibc_version_string", lambda: "2.30") # type: ignore[attr-defined] monkeypatch.setattr( - tags._manylinux, + tags._manylinux, # type: ignore[attr-defined] "_is_compatible", lambda _, glibc_version: glibc_version == _GLibCVersion(2, 17), ) @@ -665,8 +691,10 @@ def test_linux_platforms_manylinux2014_armhf_abi( expected.extend(f"linux_{arch}" for arch in archs) assert platforms == expected - def test_linux_platforms_manylinux2014_i386_abi(self, monkeypatch): - monkeypatch.setattr(tags._manylinux, "_glibc_version_string", lambda: "2.17") + def test_linux_platforms_manylinux2014_i386_abi( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: + monkeypatch.setattr(tags._manylinux, "_glibc_version_string", lambda: "2.17") # type: ignore[attr-defined] monkeypatch.setattr(sysconfig, "get_platform", lambda: "linux_x86_64") monkeypatch.setattr( sys, @@ -699,10 +727,12 @@ def test_linux_platforms_manylinux2014_i386_abi(self, monkeypatch): ] assert platforms == expected - def test_linux_platforms_manylinux_glibc3(self, monkeypatch): + def test_linux_platforms_manylinux_glibc3( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: # test for a future glic 3.x version - monkeypatch.setattr(tags._manylinux, "_glibc_version_string", lambda: "3.2") - monkeypatch.setattr(tags._manylinux, "_is_compatible", lambda *args: True) + monkeypatch.setattr(tags._manylinux, "_glibc_version_string", lambda: "3.2") # type: ignore[attr-defined] + monkeypatch.setattr(tags._manylinux, "_is_compatible", lambda *args: True) # type: ignore[attr-defined] monkeypatch.setattr(sysconfig, "get_platform", lambda: "linux_aarch64") monkeypatch.setattr( sys, @@ -732,24 +762,29 @@ def test_linux_platforms_manylinux_glibc3(self, monkeypatch): ) @pytest.mark.parametrize("cross32", [True, False], ids=["cross", "native"]) def test_linux_platforms_musllinux( - self, monkeypatch, native_arch, cross32_arch, musl_version, cross32 - ): + self, + monkeypatch: pytest.MonkeyPatch, + native_arch: str, + cross32_arch: str, + musl_version: _MuslVersion, + cross32: bool, + ) -> None: fake_executable = str( pathlib.Path(__file__) .parent.joinpath("musllinux", f"musl-{native_arch}") .resolve() ) - monkeypatch.setattr(tags._musllinux.sys, "executable", fake_executable) + monkeypatch.setattr(tags._musllinux.sys, "executable", fake_executable) # type: ignore[attr-defined] monkeypatch.setattr(sysconfig, "get_platform", lambda: f"linux_{native_arch}") - monkeypatch.setattr(tags._manylinux, "platform_tags", lambda *_: ()) + monkeypatch.setattr(tags._manylinux, "platform_tags", lambda *_: ()) # type: ignore[attr-defined] recorder = pretend.call_recorder(lambda _: musl_version) - monkeypatch.setattr(tags._musllinux, "_get_musl_version", recorder) + monkeypatch.setattr(tags._musllinux, "_get_musl_version", recorder) # type: ignore[attr-defined] platforms = list(tags._linux_platforms(is_32bit=cross32)) target_arch = cross32_arch if cross32 else native_arch archs = {"armv8l": ["armv8l", "armv7l"]}.get(target_arch, [target_arch]) - expected = [] + expected: list[str] = [] for arch in archs: expected.extend( f"musllinux_{musl_version[0]}_{minor}_{arch}" @@ -760,9 +795,11 @@ def test_linux_platforms_musllinux( assert recorder.calls == [pretend.call(fake_executable)] - def test_linux_platforms_manylinux2014_armv6l(self, monkeypatch): + def test_linux_platforms_manylinux2014_armv6l( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: monkeypatch.setattr( - tags._manylinux, + tags._manylinux, # type: ignore[attr-defined] "_is_compatible", lambda _, glibc_version: glibc_version == _GLibCVersion(2, 17), ) @@ -777,9 +814,9 @@ def test_linux_platforms_manylinux2014_armv6l(self, monkeypatch): [("x86_64", "x32", "i686"), ("armv7l", "armel", "armv7l")], ) def test_linux_platforms_not_manylinux_abi( - self, monkeypatch, machine, abi, alt_machine - ): - monkeypatch.setattr(tags._manylinux, "_is_compatible", lambda *args: False) + self, monkeypatch: pytest.MonkeyPatch, machine: str, abi: str, alt_machine: str + ) -> None: + monkeypatch.setattr(tags._manylinux, "_is_compatible", lambda *args: False) # type: ignore[attr-defined] monkeypatch.setattr(sysconfig, "get_platform", lambda: f"linux_{machine}") monkeypatch.setattr( sys, @@ -794,7 +831,7 @@ def test_linux_platforms_not_manylinux_abi( expected = [f"linux_{alt_machine}"] assert platforms == expected - def test_linux_not_linux(self, monkeypatch): + def test_linux_not_linux(self, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr(sysconfig, "get_platform", lambda: "not_linux_x86_64") monkeypatch.setattr(platform, "machine", lambda: "x86_64") monkeypatch.setattr(os, "confstr", lambda _: "glibc 2.17", raising=False) @@ -812,14 +849,16 @@ def test_linux_not_linux(self, monkeypatch): ("Generic", "_generic_platforms"), ], ) -def test_platform_tags(platform_name, dispatch_func, monkeypatch): +def test_platform_tags( + platform_name: str, dispatch_func: str, monkeypatch: pytest.MonkeyPatch +) -> None: expected = ["sillywalk"] monkeypatch.setattr(platform, "system", lambda: platform_name) monkeypatch.setattr(tags, dispatch_func, lambda: expected) - assert tags.platform_tags() == expected + assert list(tags.platform_tags()) == expected -def test_platform_tags_space(monkeypatch): +def test_platform_tags_space(monkeypatch: pytest.MonkeyPatch) -> None: """Ensure spaces in platform tags are normalized to underscores.""" monkeypatch.setattr(platform, "system", lambda: "Isilon OneFS") monkeypatch.setattr(sysconfig, "get_platform", lambda: "isilon onefs") @@ -831,7 +870,13 @@ class TestCPythonABI: ("py_debug", "gettotalrefcount", "result"), [(1, False, True), (0, False, False), (None, True, True)], ) - def test_debug(self, py_debug, gettotalrefcount, result, monkeypatch): + def test_debug( + self, + py_debug: int | None, + gettotalrefcount: bool, + result: bool, + monkeypatch: pytest.MonkeyPatch, + ) -> None: config = {"Py_DEBUG": py_debug, "WITH_PYMALLOC": 0, "Py_UNICODE_SIZE": 2} monkeypatch.setattr(sysconfig, "get_config_var", config.__getitem__) if gettotalrefcount: @@ -839,7 +884,7 @@ def test_debug(self, py_debug, gettotalrefcount, result, monkeypatch): expected = ["cp37d" if result else "cp37"] assert tags._cpython_abis((3, 7)) == expected - def test_debug_file_extension(self, monkeypatch): + def test_debug_file_extension(self, monkeypatch: pytest.MonkeyPatch) -> None: config = {"Py_DEBUG": None} monkeypatch.setattr(sysconfig, "get_config_var", config.__getitem__) monkeypatch.delattr(sys, "gettotalrefcount", raising=False) @@ -849,7 +894,9 @@ def test_debug_file_extension(self, monkeypatch): @pytest.mark.parametrize( ("debug", "expected"), [(True, ["cp38d", "cp38"]), (False, ["cp38"])] ) - def test__debug_cp38(self, debug, expected, monkeypatch): + def test__debug_cp38( + self, debug: int | None, expected: list[str], monkeypatch: pytest.MonkeyPatch + ) -> None: config = {"Py_DEBUG": debug} monkeypatch.setattr(sysconfig, "get_config_var", config.__getitem__) assert tags._cpython_abis((3, 8)) == expected @@ -863,7 +910,13 @@ def test__debug_cp38(self, debug, expected, monkeypatch): (1, (3, 8), False), ], ) - def test_pymalloc(self, pymalloc, version, result, monkeypatch): + def test_pymalloc( + self, + pymalloc: int | None, + version: tuple[int, int], + result: bool, + monkeypatch: pytest.MonkeyPatch, + ) -> None: config = {"Py_DEBUG": 0, "WITH_PYMALLOC": pymalloc, "Py_UNICODE_SIZE": 2} monkeypatch.setattr(sysconfig, "get_config_var", config.__getitem__) base_abi = f"cp{version[0]}{version[1]}" @@ -880,7 +933,14 @@ def test_pymalloc(self, pymalloc, version, result, monkeypatch): (4, 0x10FFFF, (3, 3), False), ], ) - def test_wide_unicode(self, unicode_size, maxunicode, version, result, monkeypatch): + def test_wide_unicode( + self, + unicode_size: int | None, + maxunicode: int, + version: tuple[int, int], + result: bool, + monkeypatch: pytest.MonkeyPatch, + ) -> None: config = {"Py_DEBUG": 0, "WITH_PYMALLOC": 0, "Py_UNICODE_SIZE": unicode_size} monkeypatch.setattr(sysconfig, "get_config_var", config.__getitem__) monkeypatch.setattr(sys, "maxunicode", maxunicode) @@ -890,13 +950,13 @@ def test_wide_unicode(self, unicode_size, maxunicode, version, result, monkeypat class TestCPythonTags: - def test_iterator_returned(self): + def test_iterator_returned(self) -> None: result_iterator = tags.cpython_tags( (3, 8), ["cp38d", "cp38"], ["plat1", "plat2"] ) assert isinstance(result_iterator, collections.abc.Iterator) - def test_all_args(self): + def test_all_args(self) -> None: result_iterator = tags.cpython_tags( (3, 11), ["cp311d", "cp311"], ["plat1", "plat2"] ) @@ -976,58 +1036,64 @@ def test_all_args(self): tags.Tag("cp313", "none", "plat2"), ] - def test_python_version_defaults(self): + def test_python_version_defaults(self) -> None: tag = next(tags.cpython_tags(abis=["abi3"], platforms=["any"])) interpreter = "cp" + tags._version_nodot(sys.version_info[:2]) assert interpreter == tag.interpreter - def test_abi_defaults(self, monkeypatch): + def test_abi_defaults(self, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr(tags, "_cpython_abis", lambda _1, _2: ["cp38"]) result = list(tags.cpython_tags((3, 8), platforms=["any"])) assert tags.Tag("cp38", "cp38", "any") in result assert tags.Tag("cp38", "abi3", "any") in result assert tags.Tag("cp38", "none", "any") in result - def test_abi_defaults_needs_underscore(self, monkeypatch): + def test_abi_defaults_needs_underscore( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: monkeypatch.setattr(tags, "_cpython_abis", lambda _1, _2: ["cp311"]) result = list(tags.cpython_tags((3, 11), platforms=["any"])) assert tags.Tag("cp311", "cp311", "any") in result assert tags.Tag("cp311", "abi3", "any") in result assert tags.Tag("cp311", "none", "any") in result - def test_platforms_defaults(self, monkeypatch): + def test_platforms_defaults(self, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr(tags, "platform_tags", lambda: ["plat1"]) result = list(tags.cpython_tags((3, 8), abis=["whatever"])) assert tags.Tag("cp38", "whatever", "plat1") in result - def test_platforms_defaults_needs_underscore(self, monkeypatch): + def test_platforms_defaults_needs_underscore( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: monkeypatch.setattr(tags, "platform_tags", lambda: ["plat1"]) result = list(tags.cpython_tags((3, 11), abis=["whatever"])) assert tags.Tag("cp311", "whatever", "plat1") in result - def test_platform_name_space_normalization(self, monkeypatch): + def test_platform_name_space_normalization( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: """Ensure that spaces are translated to underscores in platform names.""" monkeypatch.setattr(sysconfig, "get_platform", lambda: "isilon onefs") for tag in tags.cpython_tags(): assert " " not in tag.platform - def test_major_only_python_version(self): + def test_major_only_python_version(self) -> None: result = list(tags.cpython_tags((3,), ["abi"], ["plat"])) assert result == [ tags.Tag("cp3", "abi", "plat"), tags.Tag("cp3", "none", "plat"), ] - def test_major_only_python_version_with_default_abis(self): + def test_major_only_python_version_with_default_abis(self) -> None: result = list(tags.cpython_tags((3,), platforms=["plat"])) assert result == [tags.Tag("cp3", "none", "plat")] @pytest.mark.parametrize("abis", [[], ["abi3"], ["none"]]) - def test_skip_redundant_abis(self, abis): + def test_skip_redundant_abis(self, abis: list[str]) -> None: results = list(tags.cpython_tags((3, 0), abis=abis, platforms=["any"])) assert results == [tags.Tag("cp30", "none", "any")] - def test_abi3_python33(self): + def test_abi3_python33(self) -> None: results = list(tags.cpython_tags((3, 3), abis=["cp33"], platforms=["plat"])) assert results == [ tags.Tag("cp33", "cp33", "plat"), @@ -1036,7 +1102,7 @@ def test_abi3_python33(self): tags.Tag("cp32", "abi3", "plat"), ] - def test_no_excess_abi3_python32(self): + def test_no_excess_abi3_python32(self) -> None: results = list(tags.cpython_tags((3, 2), abis=["cp32"], platforms=["plat"])) assert results == [ tags.Tag("cp32", "cp32", "plat"), @@ -1044,14 +1110,14 @@ def test_no_excess_abi3_python32(self): tags.Tag("cp32", "none", "plat"), ] - def test_no_abi3_python31(self): + def test_no_abi3_python31(self) -> None: results = list(tags.cpython_tags((3, 1), abis=["cp31"], platforms=["plat"])) assert results == [ tags.Tag("cp31", "cp31", "plat"), tags.Tag("cp31", "none", "plat"), ] - def test_no_abi3_python27(self): + def test_no_abi3_python27(self) -> None: results = list(tags.cpython_tags((2, 7), abis=["cp27"], platforms=["plat"])) assert results == [ tags.Tag("cp27", "cp27", "plat"), @@ -1060,14 +1126,14 @@ def test_no_abi3_python27(self): class TestGenericTags: - def test__generic_abi_macos(self, monkeypatch): + def test__generic_abi_macos(self, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr( sysconfig, "get_config_var", lambda _: ".cpython-37m-darwin.so" ) monkeypatch.setattr(tags, "interpreter_name", lambda: "cp") assert tags._generic_abi() == ["cp37m"] - def test__generic_abi_linux_cpython(self, monkeypatch): + def test__generic_abi_linux_cpython(self, monkeypatch: pytest.MonkeyPatch) -> None: config = { "Py_DEBUG": False, "WITH_PYMALLOC": True, @@ -1079,17 +1145,17 @@ def test__generic_abi_linux_cpython(self, monkeypatch): assert tags._cpython_abis((3, 7)) == ["cp37m"] assert tags._generic_abi() == ["cp37m"] - def test__generic_abi_jp(self, monkeypatch): + def test__generic_abi_jp(self, monkeypatch: pytest.MonkeyPatch) -> None: config = {"EXT_SUFFIX": ".return_exactly_this.so"} monkeypatch.setattr(sysconfig, "get_config_var", config.__getitem__) assert tags._generic_abi() == ["return_exactly_this"] - def test__generic_abi_graal(self, monkeypatch): + def test__generic_abi_graal(self, monkeypatch: pytest.MonkeyPatch) -> None: config = {"EXT_SUFFIX": ".graalpy-38-native-x86_64-darwin.so"} monkeypatch.setattr(sysconfig, "get_config_var", config.__getitem__) assert tags._generic_abi() == ["graalpy_38_native"] - def test__generic_abi_disable_gil(self, monkeypatch): + def test__generic_abi_disable_gil(self, monkeypatch: pytest.MonkeyPatch) -> None: config = { "Py_DEBUG": False, "EXT_SUFFIX": ".cpython-313t-x86_64-linux-gnu.so", @@ -1100,20 +1166,22 @@ def test__generic_abi_disable_gil(self, monkeypatch): assert tags._generic_abi() == ["cp313t"] assert tags._generic_abi() == tags._cpython_abis((3, 13)) - def test__generic_abi_none(self, monkeypatch): + def test__generic_abi_none(self, monkeypatch: pytest.MonkeyPatch) -> None: config = {"EXT_SUFFIX": "..so"} monkeypatch.setattr(sysconfig, "get_config_var", config.__getitem__) assert tags._generic_abi() == [] @pytest.mark.parametrize("ext_suffix", ["invalid", None]) - def test__generic_abi_error(self, ext_suffix, monkeypatch): + def test__generic_abi_error( + self, ext_suffix: str | None, monkeypatch: pytest.MonkeyPatch + ) -> None: config = {"EXT_SUFFIX": ext_suffix} monkeypatch.setattr(sysconfig, "get_config_var", config.__getitem__) with pytest.raises(SystemError) as e: tags._generic_abi() assert "EXT_SUFFIX" in str(e.value) - def test__generic_abi_linux_pypy(self, monkeypatch): + def test__generic_abi_linux_pypy(self, monkeypatch: pytest.MonkeyPatch) -> None: # issue gh-606 config = { "Py_DEBUG": False, @@ -1123,7 +1191,7 @@ def test__generic_abi_linux_pypy(self, monkeypatch): monkeypatch.setattr(tags, "interpreter_name", lambda: "pp") assert tags._generic_abi() == ["pypy39_pp73"] - def test__generic_abi_old_windows(self, monkeypatch): + def test__generic_abi_old_windows(self, monkeypatch: pytest.MonkeyPatch) -> None: config = { "EXT_SUFFIX": ".pyd", "Py_DEBUG": 0, @@ -1133,7 +1201,7 @@ def test__generic_abi_old_windows(self, monkeypatch): monkeypatch.setattr(sysconfig, "get_config_var", config.__getitem__) assert tags._generic_abi() == tags._cpython_abis(sys.version_info[:2]) - def test__generic_abi_windows(self, monkeypatch): + def test__generic_abi_windows(self, monkeypatch: pytest.MonkeyPatch) -> None: config = { "EXT_SUFFIX": ".cp310-win_amd64.pyd", } @@ -1141,26 +1209,26 @@ def test__generic_abi_windows(self, monkeypatch): assert tags._generic_abi() == ["cp310"] @pytest.mark.skipif(sys.implementation.name != "cpython", reason="CPython-only") - def test__generic_abi_agree(self): + def test__generic_abi_agree(self) -> None: """Test that the two methods of finding the abi tag agree""" assert tags._generic_abi() == tags._cpython_abis(sys.version_info[:2]) - def test_generic_platforms(self): + def test_generic_platforms(self) -> None: platform = sysconfig.get_platform().replace("-", "_") platform = platform.replace(".", "_") assert list(tags._generic_platforms()) == [platform] - def test_generic_platforms_space(self, monkeypatch): + def test_generic_platforms_space(self, monkeypatch: pytest.MonkeyPatch) -> None: """Ensure platform tags normalize spaces to underscores.""" platform_ = "isilon onefs" monkeypatch.setattr(sysconfig, "get_platform", lambda: platform_) assert list(tags._generic_platforms()) == [platform_.replace(" ", "_")] - def test_iterator_returned(self): + def test_iterator_returned(self) -> None: result_iterator = tags.generic_tags("sillywalk33", ["abi"], ["plat1", "plat2"]) assert isinstance(result_iterator, collections.abc.Iterator) - def test_all_args(self): + def test_all_args(self) -> None: result_iterator = tags.generic_tags("sillywalk33", ["abi"], ["plat1", "plat2"]) result = list(result_iterator) assert result == [ @@ -1171,20 +1239,20 @@ def test_all_args(self): ] @pytest.mark.parametrize("abi", [[], ["none"]]) - def test_abi_unspecified(self, abi): + def test_abi_unspecified(self, abi: list[str]) -> None: no_abi = list(tags.generic_tags("sillywalk34", abi, ["plat1", "plat2"])) assert no_abi == [ tags.Tag("sillywalk34", "none", "plat1"), tags.Tag("sillywalk34", "none", "plat2"), ] - def test_interpreter_default(self, monkeypatch): + def test_interpreter_default(self, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr(tags, "interpreter_name", lambda: "sillywalk") monkeypatch.setattr(tags, "interpreter_version", lambda warn: "NN") # noqa: ARG005 result = list(tags.generic_tags(abis=["none"], platforms=["any"])) assert result == [tags.Tag("sillywalkNN", "none", "any")] - def test_abis_default(self, monkeypatch): + def test_abis_default(self, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr(tags, "_generic_abi", lambda: ["abi"]) result = list(tags.generic_tags(interpreter="sillywalk", platforms=["any"])) assert result == [ @@ -1192,14 +1260,14 @@ def test_abis_default(self, monkeypatch): tags.Tag("sillywalk", "none", "any"), ] - def test_platforms_default(self, monkeypatch): + def test_platforms_default(self, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr(tags, "platform_tags", lambda: ["plat"]) result = list(tags.generic_tags(interpreter="sillywalk", abis=["none"])) assert result == [tags.Tag("sillywalk", "none", "plat")] class TestCompatibleTags: - def test_all_args(self): + def test_all_args(self) -> None: result = list(tags.compatible_tags((3, 3), "cp33", ["plat1", "plat2"])) assert result == [ tags.Tag("py33", "none", "plat1"), @@ -1220,7 +1288,7 @@ def test_all_args(self): tags.Tag("py30", "none", "any"), ] - def test_all_args_needs_underscore(self): + def test_all_args_needs_underscore(self) -> None: result = list(tags.compatible_tags((3, 11), "cp311", ["plat1", "plat2"])) assert result == [ tags.Tag("py311", "none", "plat1"), @@ -1265,7 +1333,7 @@ def test_all_args_needs_underscore(self): tags.Tag("py30", "none", "any"), ] - def test_major_only_python_version(self): + def test_major_only_python_version(self) -> None: result = list(tags.compatible_tags((3,), "cp33", ["plat"])) assert result == [ tags.Tag("py3", "none", "plat"), @@ -1273,7 +1341,7 @@ def test_major_only_python_version(self): tags.Tag("py3", "none", "any"), ] - def test_default_python_version(self, monkeypatch): + def test_default_python_version(self, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr(sys, "version_info", (3, 1)) result = list(tags.compatible_tags(interpreter="cp31", platforms=["plat"])) assert result == [ @@ -1286,7 +1354,9 @@ def test_default_python_version(self, monkeypatch): tags.Tag("py30", "none", "any"), ] - def test_default_python_version_needs_underscore(self, monkeypatch): + def test_default_python_version_needs_underscore( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: monkeypatch.setattr(sys, "version_info", (3, 11)) result = list(tags.compatible_tags(interpreter="cp311", platforms=["plat"])) assert result == [ @@ -1319,7 +1389,7 @@ def test_default_python_version_needs_underscore(self, monkeypatch): tags.Tag("py30", "none", "any"), ] - def test_default_interpreter(self): + def test_default_interpreter(self) -> None: result = list(tags.compatible_tags((3, 1), platforms=["plat"])) assert result == [ tags.Tag("py31", "none", "plat"), @@ -1330,7 +1400,7 @@ def test_default_interpreter(self): tags.Tag("py30", "none", "any"), ] - def test_default_platforms(self, monkeypatch): + def test_default_platforms(self, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr(tags, "platform_tags", lambda: iter(["plat", "plat2"])) result = list(tags.compatible_tags((3, 1), "cp31")) assert result == [ @@ -1348,22 +1418,28 @@ def test_default_platforms(self, monkeypatch): class TestSysTags: - def teardown_method(self): + def teardown_method(self) -> None: # Clear the version cache - tags._glibc_version = [] + tags._glibc_version = [] # type: ignore[attr-defined] @pytest.mark.parametrize( ("name", "expected"), [("CPython", "cp"), ("PyPy", "pp"), ("Jython", "jy"), ("IronPython", "ip")], ) - def test_interpreter_name(self, name, expected, mock_interpreter_name): + def test_interpreter_name( + self, name: str, expected: str, mock_interpreter_name: Callable[[str], bool] + ) -> None: mock_interpreter_name(name) assert tags.interpreter_name() == expected - def test_iterator(self): + def test_iterator(self) -> None: assert isinstance(tags.sys_tags(), collections.abc.Iterator) - def test_mac_cpython(self, mock_interpreter_name, monkeypatch): + def test_mac_cpython( + self, + mock_interpreter_name: Callable[[str], bool], + monkeypatch: pytest.MonkeyPatch, + ) -> None: if mock_interpreter_name("CPython"): monkeypatch.setattr(tags, "_cpython_abis", lambda *a: ["cp33m"]) if platform.system() != "Darwin": @@ -1380,7 +1456,11 @@ def test_mac_cpython(self, mock_interpreter_name, monkeypatch): "py" + tags._version_nodot((sys.version_info[0], 0)), "none", "any" ) - def test_windows_cpython(self, mock_interpreter_name, monkeypatch): + def test_windows_cpython( + self, + mock_interpreter_name: Callable[[str], bool], + monkeypatch: pytest.MonkeyPatch, + ) -> None: if mock_interpreter_name("CPython"): monkeypatch.setattr(tags, "_cpython_abis", lambda *a: ["cp33m"]) if platform.system() != "Windows": @@ -1398,7 +1478,11 @@ def test_windows_cpython(self, mock_interpreter_name, monkeypatch): ) assert result[-1] == expected - def test_linux_cpython(self, mock_interpreter_name, monkeypatch): + def test_linux_cpython( + self, + mock_interpreter_name: Callable[[str], bool], + monkeypatch: pytest.MonkeyPatch, + ) -> None: if mock_interpreter_name("CPython"): monkeypatch.setattr(tags, "_cpython_abis", lambda *a: ["cp33m"]) if platform.system() != "Linux": @@ -1415,7 +1499,7 @@ def test_linux_cpython(self, mock_interpreter_name, monkeypatch): ) assert result[-1] == expected - def test_generic(self, monkeypatch): + def test_generic(self, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr(platform, "system", lambda: "Generic") monkeypatch.setattr(tags, "interpreter_name", lambda: "generic") @@ -1426,16 +1510,20 @@ def test_generic(self, monkeypatch): assert result[-1] == expected @pytest.mark.usefixtures("manylinux_module") - def test_linux_platforms_manylinux2014_armv6l(self, monkeypatch): + def test_linux_platforms_manylinux2014_armv6l( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: monkeypatch.setattr(sysconfig, "get_platform", lambda: "linux_armv6l") monkeypatch.setattr(os, "confstr", lambda _: "glibc 2.20", raising=False) platforms = list(tags._linux_platforms(is_32bit=True)) expected = ["linux_armv6l"] assert platforms == expected - def test_skip_manylinux_2014(self, monkeypatch, manylinux_module): + def test_skip_manylinux_2014( + self, monkeypatch: pytest.MonkeyPatch, manylinux_module: types.ModuleType + ) -> None: monkeypatch.setattr(sysconfig, "get_platform", lambda: "linux_ppc64") - monkeypatch.setattr(tags._manylinux, "_get_glibc_version", lambda: (2, 20)) + monkeypatch.setattr(tags._manylinux, "_get_glibc_version", lambda: (2, 20)) # type: ignore[attr-defined] monkeypatch.setattr( manylinux_module, "manylinux2014_compatible", False, raising=False ) @@ -1456,8 +1544,8 @@ def test_skip_manylinux_2014(self, monkeypatch, manylinux_module): [("x86_64", "x32", "i686"), ("armv7l", "armel", "armv7l")], ) def test_linux_platforms_not_manylinux_abi( - self, monkeypatch, machine, abi, alt_machine - ): + self, monkeypatch: pytest.MonkeyPatch, machine: str, abi: str, alt_machine: str + ) -> None: monkeypatch.setattr(sysconfig, "get_platform", lambda: f"linux_{machine}") monkeypatch.setattr( sys, @@ -1477,15 +1565,21 @@ def test_linux_platforms_not_manylinux_abi( [("x86_64", 2, 20, False), ("s390x", 2, 22, True)], ) def test_linux_use_manylinux_compatible( - self, monkeypatch, manylinux_module, machine, major, minor, tf - ): - def manylinux_compatible(tag_major, tag_minor, tag_arch): + self, + monkeypatch: pytest.MonkeyPatch, + manylinux_module: types.ModuleType, + machine: str, + major: int, + minor: int, + tf: bool, + ) -> None: + def manylinux_compatible(tag_major: int, tag_minor: int, tag_arch: str) -> bool: if tag_major == 2 and tag_minor == 22: return tag_arch == "s390x" return False monkeypatch.setattr( - tags._manylinux, + tags._manylinux, # type: ignore[attr-defined] "_get_glibc_version", lambda: (major, minor), ) @@ -1501,13 +1595,19 @@ def manylinux_compatible(tag_major, tag_minor, tag_arch): expected.append(f"linux_{machine}") assert platforms == expected - def test_linux_use_manylinux_compatible_none(self, monkeypatch, manylinux_module): - def manylinux_compatible(tag_major, tag_minor, tag_arch): # noqa: ARG001 + def test_linux_use_manylinux_compatible_none( + self, monkeypatch: pytest.MonkeyPatch, manylinux_module: types.ModuleType + ) -> None: + def manylinux_compatible( + tag_major: int, + tag_minor: int, + tag_arch: str, # noqa: ARG001 + ) -> bool | None: if tag_major == 2 and tag_minor < 25: return False return None - monkeypatch.setattr(tags._manylinux, "_get_glibc_version", lambda: (2, 30)) + monkeypatch.setattr(tags._manylinux, "_get_glibc_version", lambda: (2, 30)) # type: ignore[attr-defined] monkeypatch.setattr(sysconfig, "get_platform", lambda: "linux_x86_64") monkeypatch.setattr( manylinux_module, @@ -1527,7 +1627,7 @@ def manylinux_compatible(tag_major, tag_minor, tag_arch): # noqa: ARG001 ] assert platforms == expected - def test_pypy_first_none_any_tag(self, monkeypatch): + def test_pypy_first_none_any_tag(self, monkeypatch: pytest.MonkeyPatch) -> None: # When building the complete list of pypy tags, make sure the first # -none-any one is pp3-none-any monkeypatch.setattr(tags, "interpreter_name", lambda: "pp") @@ -1538,7 +1638,7 @@ def test_pypy_first_none_any_tag(self, monkeypatch): assert tag == tags.Tag("pp3", "none", "any") - def test_cpython_first_none_any_tag(self, monkeypatch): + def test_cpython_first_none_any_tag(self, monkeypatch: pytest.MonkeyPatch) -> None: # When building the complete list of cpython tags, make sure the first # -none-any one is cpxx-none-any monkeypatch.setattr(tags, "interpreter_name", lambda: "cp") @@ -1553,7 +1653,7 @@ def test_cpython_first_none_any_tag(self, monkeypatch): class TestBitness: - def teardown_method(self): + def teardown_method(self) -> None: importlib.reload(tags) @pytest.mark.parametrize( @@ -1567,8 +1667,14 @@ def teardown_method(self): (2147483647, 8, False), ], ) - def test_32bit_interpreter(self, maxsize, sizeof_voidp, expected, monkeypatch): - def _calcsize(fmt): + def test_32bit_interpreter( + self, + maxsize: int, + sizeof_voidp: int, + expected: bool, + monkeypatch: pytest.MonkeyPatch, + ) -> None: + def _calcsize(fmt: str) -> int: assert fmt == "P" return sizeof_voidp @@ -1578,7 +1684,7 @@ def _calcsize(fmt): assert expected == tags._32_BIT_INTERPRETER -def test_pickle(): +def test_pickle() -> None: # Make sure equality works between a pickle/unpickle round trip. tag = tags.Tag("py3", "none", "any") assert pickle.loads(pickle.dumps(tag)) == tag diff --git a/tests/test_utils.py b/tests/test_utils.py index d19776a5b..2f269edc1 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -2,6 +2,8 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. +from __future__ import annotations + import pytest from packaging.tags import Tag @@ -33,7 +35,7 @@ ("foo----bar", "foo-bar"), ], ) -def test_canonicalize_name(name, expected): +def test_canonicalize_name(name: str, expected: str) -> None: assert canonicalize_name(name) == expected @@ -49,7 +51,7 @@ def test_canonicalize_name(name, expected): ("h\ri", "h\ri"), ], ) -def test_canonicalize_name_invalid(name, expected): +def test_canonicalize_name_invalid(name: str, expected: str) -> None: with pytest.raises(InvalidName): canonicalize_name(name, validate=True) assert canonicalize_name(name) == expected @@ -70,7 +72,7 @@ def test_canonicalize_name_invalid(name, expected): ("foo----bar", "foo-bar"), ], ) -def test_is_normalized_name(name, expected): +def test_is_normalized_name(name: str, expected: str) -> None: assert is_normalized_name(expected) if name != expected: assert not is_normalized_name(name) @@ -95,12 +97,12 @@ def test_is_normalized_name(name, expected): ("1.0.1-test7", "1.0.1-test7"), ], ) -def test_canonicalize_version(version, expected): +def test_canonicalize_version(version: str, expected: str) -> None: assert canonicalize_version(version) == expected @pytest.mark.parametrize(("version"), ["1.4.0", "1.0"]) -def test_canonicalize_version_no_strip_trailing_zero(version): +def test_canonicalize_version_no_strip_trailing_zero(version: str) -> None: assert canonicalize_version(version, strip_trailing_zero=False) == version @@ -151,8 +153,10 @@ def test_canonicalize_version_no_strip_trailing_zero(version): ), ], ) -def test_parse_wheel_filename(filename, name, version, build, tags): - assert parse_wheel_filename(filename) == (name, version, build, tags) +def test_parse_wheel_filename( + filename: str, name: str, version: Version, build: tuple[int, str], tags: set[Tag] +) -> None: + assert parse_wheel_filename(filename) == (name, version, build, frozenset(tags)) @pytest.mark.parametrize( @@ -168,7 +172,7 @@ def test_parse_wheel_filename(filename, name, version, build, tags): ("foo-1.0-200-py3-none-any-junk.whl"), # Too many dashes (`-junk`) ], ) -def test_parse_wheel_invalid_filename(filename): +def test_parse_wheel_invalid_filename(filename: str) -> None: with pytest.raises(InvalidWheelFilename): parse_wheel_filename(filename) @@ -177,7 +181,7 @@ def test_parse_wheel_invalid_filename(filename): ("filename", "name", "version"), [("foo-1.0.tar.gz", "foo", Version("1.0")), ("foo-1.0.zip", "foo", Version("1.0"))], ) -def test_parse_sdist_filename(filename, name, version): +def test_parse_sdist_filename(filename: str, name: str, version: Version) -> None: assert parse_sdist_filename(filename) == (name, version) @@ -189,6 +193,6 @@ def test_parse_sdist_filename(filename, name, version): ("foo-1.x.tar.gz"), # Invalid version ], ) -def test_parse_sdist_invalid_filename(filename): +def test_parse_sdist_invalid_filename(filename: str) -> None: with pytest.raises(InvalidSdistFilename): parse_sdist_filename(filename) diff --git a/tests/test_version.py b/tests/test_version.py index 42e1934e0..61350fbb8 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -2,20 +2,26 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. +from __future__ import annotations + import itertools import operator +import typing import pretend import pytest from packaging.version import InvalidVersion, Version, parse +if typing.TYPE_CHECKING: + from collections.abc import Callable + -def test_parse(): +def test_parse() -> None: assert isinstance(parse("1.0"), Version) -def test_parse_raises(): +def test_parse_raises() -> None: with pytest.raises(InvalidVersion): parse("lolwat") @@ -83,7 +89,7 @@ def test_parse_raises(): class TestVersion: @pytest.mark.parametrize("version", VERSIONS) - def test_valid_versions(self, version): + def test_valid_versions(self, version: str) -> None: Version(version) @pytest.mark.parametrize( @@ -99,7 +105,7 @@ def test_valid_versions(self, version): "1.0+1+1", ], ) - def test_invalid_versions(self, version): + def test_invalid_versions(self, version: str) -> None: with pytest.raises(InvalidVersion): Version(version) @@ -217,7 +223,7 @@ def test_invalid_versions(self, version): (" v1.0\t\n", "1.0"), ], ) - def test_normalized_versions(self, version, normalized): + def test_normalized_versions(self, version: str, normalized: str) -> None: assert str(Version(version)) == normalized @pytest.mark.parametrize( @@ -272,15 +278,15 @@ def test_normalized_versions(self, version, normalized): ("7!1.1.dev1", "7!1.1.dev1"), ], ) - def test_version_str_repr(self, version, expected): + def test_version_str_repr(self, version: str, expected: str) -> None: assert str(Version(version)) == expected assert repr(Version(version)) == f"" - def test_version_rc_and_c_equals(self): + def test_version_rc_and_c_equals(self) -> None: assert Version("1.0rc1") == Version("1.0c1") @pytest.mark.parametrize("version", VERSIONS) - def test_version_hash(self, version): + def test_version_hash(self, version: str) -> None: assert hash(Version(version)) == hash(Version(version)) @pytest.mark.parametrize( @@ -317,7 +323,7 @@ def test_version_hash(self, version): ("1!1.0.post5+deadbeef", "1!1.0.post5"), ], ) - def test_version_public(self, version, public): + def test_version_public(self, version: str, public: str) -> None: assert Version(version).public == public @pytest.mark.parametrize( @@ -354,7 +360,7 @@ def test_version_public(self, version, public): ("1!1.0.post5+deadbeef", "1!1.0"), ], ) - def test_version_base_version(self, version, base_version): + def test_version_base_version(self, version: str, base_version: str) -> None: assert Version(version).base_version == base_version @pytest.mark.parametrize( @@ -391,7 +397,7 @@ def test_version_base_version(self, version, base_version): ("1!1.0.post5+deadbeef", 1), ], ) - def test_version_epoch(self, version, epoch): + def test_version_epoch(self, version: str, epoch: int) -> None: assert Version(version).epoch == epoch @pytest.mark.parametrize( @@ -428,7 +434,7 @@ def test_version_epoch(self, version, epoch): ("1!1.0.post5+deadbeef", (1, 0)), ], ) - def test_version_release(self, version, release): + def test_version_release(self, version: str, release: tuple[int, int]) -> None: assert Version(version).release == release @pytest.mark.parametrize( @@ -465,7 +471,7 @@ def test_version_release(self, version, release): ("1!1.0.post5+deadbeef", "deadbeef"), ], ) - def test_version_local(self, version, local): + def test_version_local(self, version: str, local: str | None) -> None: assert Version(version).local == local @pytest.mark.parametrize( @@ -502,7 +508,7 @@ def test_version_local(self, version, local): ("1!1.0.post5+deadbeef", None), ], ) - def test_version_pre(self, version, pre): + def test_version_pre(self, version: str, pre: None | tuple[str, int]) -> None: assert Version(version).pre == pre @pytest.mark.parametrize( @@ -532,7 +538,7 @@ def test_version_pre(self, version, pre): ("1.0.post1+dev", False), ], ) - def test_version_is_prerelease(self, version, expected): + def test_version_is_prerelease(self, version: str, expected: bool) -> None: assert Version(version).is_prerelease is expected @pytest.mark.parametrize( @@ -569,7 +575,7 @@ def test_version_is_prerelease(self, version, expected): ("1!1.0.post5+deadbeef", None), ], ) - def test_version_dev(self, version, dev): + def test_version_dev(self, version: str, dev: int | None) -> None: assert Version(version).dev == dev @pytest.mark.parametrize( @@ -606,7 +612,7 @@ def test_version_dev(self, version, dev): ("1!1.0.post5+deadbeef", False), ], ) - def test_version_is_devrelease(self, version, expected): + def test_version_is_devrelease(self, version: str, expected: bool) -> None: assert Version(version).is_devrelease is expected @pytest.mark.parametrize( @@ -643,7 +649,7 @@ def test_version_is_devrelease(self, version, expected): ("1!1.0.post5+deadbeef", 5), ], ) - def test_version_post(self, version, post): + def test_version_post(self, version: str, post: int | None) -> None: assert Version(version).post == post @pytest.mark.parametrize( @@ -656,7 +662,7 @@ def test_version_post(self, version, post): ("1.0.post1", True), ], ) - def test_version_is_postrelease(self, version, expected): + def test_version_is_postrelease(self, version: str, expected: bool) -> None: assert Version(version).is_postrelease is expected @pytest.mark.parametrize( @@ -698,7 +704,9 @@ def test_version_is_postrelease(self, version, expected): ] ), ) - def test_comparison_true(self, left, right, op): + def test_comparison_true( + self, left: str, right: str, op: Callable[[Version, Version], bool] + ) -> None: assert op(Version(left), Version(right)) @pytest.mark.parametrize( @@ -740,28 +748,30 @@ def test_comparison_true(self, left, right, op): ] ), ) - def test_comparison_false(self, left, right, op): + def test_comparison_false( + self, left: str, right: str, op: Callable[[Version, Version], bool] + ) -> None: assert not op(Version(left), Version(right)) @pytest.mark.parametrize("op", ["lt", "le", "eq", "ge", "gt", "ne"]) - def test_dunder_op_returns_notimplemented(self, op): + def test_dunder_op_returns_notimplemented(self, op: str) -> None: method = getattr(Version, f"__{op}__") assert method(Version("1"), 1) is NotImplemented @pytest.mark.parametrize(("op", "expected"), [("eq", False), ("ne", True)]) - def test_compare_other(self, op, expected): + def test_compare_other(self, op: str, expected: bool) -> None: other = pretend.stub(**{f"__{op}__": lambda _: NotImplemented}) assert getattr(operator, op)(Version("1"), other) is expected - def test_major_version(self): + def test_major_version(self) -> None: assert Version("2.1.0").major == 2 - def test_minor_version(self): + def test_minor_version(self) -> None: assert Version("2.1.0").minor == 1 assert Version("2").minor == 0 - def test_micro_version(self): + def test_micro_version(self) -> None: assert Version("2.1.3").micro == 3 assert Version("2.1").micro == 0 assert Version("2").micro == 0