From 6bfc7bd2e221f4331b62a8fbf02dce26148b322f Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Fri, 8 Nov 2019 16:53:30 -0800 Subject: [PATCH 01/58] Update to mypy 0.740 --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 367e62e4f..936a21233 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,7 +8,7 @@ repos: - id: trailing-whitespace - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.720 + rev: v0.740 hooks: - id: mypy exclude: '^(docs|tasks|tests)|setup\.py' From d703c2d3ffa73545bde30e69bf541908fb207726 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Fri, 8 Nov 2019 16:55:48 -0800 Subject: [PATCH 02/58] Change `Optional[bool]` to `bool` as `None` is not expected --- packaging/tags.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packaging/tags.py b/packaging/tags.py index 73e253147..5211a83ec 100644 --- a/packaging/tags.py +++ b/packaging/tags.py @@ -106,7 +106,7 @@ def parse_tag(tag): def _get_config_var(name, warn=False): - # type: (str, Optional[bool]) -> Union[int, str, None] + # type: (str, bool) -> Union[int, str, None] value = sysconfig.get_config_var(name) if value is None and warn: logger.debug( @@ -127,7 +127,7 @@ def _cpython_interpreter(py_version): def _cpython_abis(py_version, warn=False): - # type: (PythonVersion, Optional[bool]) -> List[str] + # type: (PythonVersion, bool) -> List[str] abis = [] version = "{}{}".format(*py_version[:2]) debug = pymalloc = ucs4 = "" @@ -460,15 +460,14 @@ def _interpreter_name(): def _generic_interpreter(name, py_version, warn=False): - # type: (str, PythonVersion, Optional[bool]) -> str + # type: (str, PythonVersion, bool) -> str version = _get_config_var("py_version_nodot", warn) if not version: version = "".join(map(str, py_version[:2])) return "{name}{version}".format(name=name, version=version) -def sys_tags(warn=False): - # type: (Optional[bool]) -> Iterator[Tag] + # type: (bool) -> Iterator[Tag] """ Returns the sequence of tag triples for the running interpreter. From 0b9e724e7cb0e77b9261f256fa44e8216cba88b4 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Fri, 8 Nov 2019 16:56:54 -0800 Subject: [PATCH 03/58] Tweak docsstrings to follow the rest of the file --- packaging/tags.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packaging/tags.py b/packaging/tags.py index 5211a83ec..cfc7d4035 100644 --- a/packaging/tags.py +++ b/packaging/tags.py @@ -235,7 +235,7 @@ def _py_interpreter_range(py_version): def _independent_tags(interpreter, py_version, platforms): # type: (str, PythonVersion, Iterable[str]) -> Iterator[Tag] """ - Return the sequence of tags that are consistent across implementations. + Yield the sequence of tags that are consistent across implementations. The tags consist of: - py*-none- @@ -341,7 +341,9 @@ def _glibc_version_string(): def _glibc_version_string_confstr(): # type: () -> Optional[str] - "Primary implementation of glibc_version_string using os.confstr." + """ + Primary implementation of glibc_version_string using os.confstr. + """ # os.confstr is quite a bit faster than ctypes.DLL. It's also less likely # to be broken or missing. This strategy is used in the standard library # platform module. @@ -359,7 +361,9 @@ def _glibc_version_string_confstr(): def _glibc_version_string_ctypes(): # type: () -> Optional[str] - "Fallback implementation of glibc_version_string using ctypes." + """ + Fallback implementation of glibc_version_string using ctypes. + """ try: import ctypes except ImportError: @@ -469,7 +473,7 @@ def _generic_interpreter(name, py_version, warn=False): # type: (bool) -> Iterator[Tag] """ - Returns the sequence of tag triples for the running interpreter. + Return the sequence of tag triples for the running interpreter. The order of the sequence corresponds to priority order for the interpreter, from most to least important. From c722ee0e1cf5d72c2b76922f3d70bfe13114db02 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Fri, 8 Nov 2019 16:57:31 -0800 Subject: [PATCH 04/58] Make linters happy --- packaging/tags.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packaging/tags.py b/packaging/tags.py index cfc7d4035..3c8ea794f 100644 --- a/packaging/tags.py +++ b/packaging/tags.py @@ -323,7 +323,7 @@ def _is_manylinux_compatible(name, glibc_version): # type: (str, GlibcVersion) -> bool # Check for presence of _manylinux module. try: - import _manylinux + import _manylinux # noqa return bool(getattr(_manylinux, name + "_compatible")) except (ImportError, AttributeError): @@ -456,7 +456,7 @@ def _generic_platforms(): def _interpreter_name(): # type: () -> str try: - name = sys.implementation.name + name = sys.implementation.name # type: ignore except AttributeError: # pragma: no cover # Python 2.7 compatibility. name = platform.python_implementation().lower() From 8c2c701a49f63c257e5706025d68f26caec39fe9 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Fri, 8 Nov 2019 22:48:44 -0800 Subject: [PATCH 05/58] Initial implementation of public *_tags() functions --- packaging/tags.py | 189 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 163 insertions(+), 26 deletions(-) diff --git a/packaging/tags.py b/packaging/tags.py index 3c8ea794f..b6bed08fb 100644 --- a/packaging/tags.py +++ b/packaging/tags.py @@ -105,6 +105,22 @@ def parse_tag(tag): return frozenset(tags) +def _warn_parameter(func_name, kwargs): + # type: (str, Dict[str, bool]) -> bool + """ + Backwards-compatibility with Python 2.7 to allow treating 'warn' as keyword-only. + """ + if not kwargs: + return False + elif len(kwargs) > 1 or "warn" not in kwargs: + kwargs.pop("warn", None) + arg = next(iter(kwargs.keys())) + raise TypeError( + "{}() got an unexpected keyword argument {!r}".format(func_name, arg) + ) + return kwargs["warn"] + + def _get_config_var(name, warn=False): # type: (str, bool) -> Union[int, str, None] value = sysconfig.get_config_var(name) @@ -180,6 +196,58 @@ def _cpython_tags(py_version, interpreter, abis, platforms): yield Tag(interpreter, "abi3", platform_) +def cpython_tags( + python_version=sys.version_info[:2], abis=None, platforms=None, **kwargs +): + # type: (PythonVersion, Optional[Iterable[str]], Optional[Iterable[str]], bool) -> Iterator[Tag] # noqa + """ + Yield the tags for a CPython interpreter. + + The tags consist of: + - cp-- + - cp-abi3- + - cp-none- + - cp-abi3- # Older Python versions down to 3.2. + + If 'abi3' or 'none' are specified in 'abis' then they will be yielded at + their normal position and not at the beginning. + """ + warn = _warn_parameter("cpython_tags", kwargs) + interpreter = "cp{}{}".format(*python_version) + if not abis: + abis = _cpython_abis(python_version, warn) + if not platforms: + platforms = _platforms() + platforms = list(platforms) + abis = list(abis) + # 'abi3' and 'none' are explicitly handled later. + try: + abis.remove("abi3") + except ValueError: + pass + try: + abis.remove("none") + except ValueError: + pass + for abi in abis: + for platform_ in platforms: + yield Tag(interpreter, abi, platform_) + # Not worrying about the case of Python 3.2 or older being specified and + # thus having redundant tags thanks to the abi3 in-fill later on as + # 'packaging' doesn't directly support Python that far back. + for tag in (Tag(interpreter, "abi3", platform_) for platform_ in platforms): + yield tag + for tag in (Tag(interpreter, "none", platform_) for platform_ in platforms): + yield tag + # PEP 384 was first implemented in Python 3.2. + for minor_version in range(python_version[1] - 1, 1, -1): + for platform_ in platforms: + interpreter = "cp{major}{minor}".format( + major=python_version[0], minor=minor_version + ) + yield Tag(interpreter, "abi3", platform_) + + def _pypy_interpreter(): # type: () -> str # Ignoring sys.pypy_version_info for type checking due to typeshed lacking @@ -208,6 +276,29 @@ def _pypy_tags(py_version, interpreter, abi, platforms): yield tag +def pypy_tags(interpreter=None, abis=None, platforms=None): + # type: (Optional[str], Optional[Iterable[str]], Optional[Iterable[str]]) -> Iterator[Tag] # noqa + """ + Yield the tags for a PyPy interpreter. + + The tags consist of what is yielded by generic_tags() with a calcuated value + for 'interpreter' if it is not specified. + """ + # The 'interpreter' parameter is necessary to allow the user to distinguish + # between PyPy and PyPy3 (pp and pp3, respectively). + if not interpreter: + interpreter = _pypy_interpreter() + for tag in generic_tags(interpreter, abis, platforms): + yield tag + + +def _generic_interpreter(warn=False): + version = _get_config_var("py_version_nodot", warn) + if not version: + version = "".join(map(str, sys.version_info[:2])) + return "{name}{version}".format(name=_interpreter_name(), version=version) + + def _generic_tags(interpreter, py_version, abi, platforms): # type: (str, PythonVersion, str, Iterable[str]) -> Iterator[Tag] for tag in (Tag(interpreter, abi, platform) for platform in platforms): @@ -218,6 +309,34 @@ def _generic_tags(interpreter, py_version, abi, platforms): yield tag +def generic_tags(interpreter=None, abis=None, platforms=None, **kwargs): + # type: (Optional[str], Optional[Iterable[str]], Optional[Iterable[str]], bool) -> Iterator[Tag] # noqa + """ + Yield the tags for a generic interpreter. + + The tags consist of: + - -- + - -none- # If 'none' is not specified in 'abis'. + + """ + warn = _warn_parameter("generic_tags", kwargs) + if not interpreter: + interpreter = _generic_interpreter(warn=warn) + if not abis: + abis = [_generic_abi()] + if not platforms: + platforms = _platforms() + else: + platforms = list(platforms) + abis = list(abis) + for abi in abis: + for platform_ in platforms: + yield Tag(interpreter, abi, platform_) + if "none" not in abis: + for platform_ in platforms: + yield Tag(interpreter, "none", platform_) + + def _py_interpreter_range(py_version): # type: (PythonVersion) -> Iterator[str] """ @@ -250,6 +369,29 @@ def _independent_tags(interpreter, py_version, platforms): yield Tag(version, "none", "any") +def compatible_tags( + python_version=sys.version_info[:2], interpreter=None, platforms=None +): + # type: (PythonVersion, Optional[str], Optional[Iterable[str]]) -> Iterator[Tag] + """ + Yield the sequence of tags that are compatible with a specific version of Python. + + The tags consist of: + - py*-none- + - -none-any # If provided. + - py*-none-any + """ + if not platforms: + platforms = _platforms() + for version in _py_interpreter_range(python_version): + for platform_ in platforms: + yield Tag(version, "none", platform_) + if interpreter: + yield Tag(interpreter, "none", "any") + for version in _py_interpreter_range(python_version): + yield Tag(version, "none", "any") + + def _mac_arch(arch, is_32bit=_32_BIT_INTERPRETER): # type: (str, bool) -> str if not is_32bit: @@ -453,6 +595,19 @@ def _generic_platforms(): return [platform] +def _platforms(): + # type: () -> List[str] + """ + Provide the platform tags for this installation. + """ + if platform.system() == "Darwin": + return _mac_platforms() + elif platform.system() == "Linux": + return _linux_platforms() + else: + return _generic_platforms() + + def _interpreter_name(): # type: () -> str try: @@ -463,14 +618,7 @@ def _interpreter_name(): return INTERPRETER_SHORT_NAMES.get(name) or name -def _generic_interpreter(name, py_version, warn=False): - # type: (str, PythonVersion, bool) -> str - version = _get_config_var("py_version_nodot", warn) - if not version: - version = "".join(map(str, py_version[:2])) - return "{name}{version}".format(name=name, version=version) - - +def sys_tags(**kwargs): # type: (bool) -> Iterator[Tag] """ Return the sequence of tag triples for the running interpreter. @@ -478,29 +626,18 @@ def _generic_interpreter(name, py_version, warn=False): The order of the sequence corresponds to priority order for the interpreter, from most to least important. """ - py_version = sys.version_info[:2] - interpreter_name = _interpreter_name() - if platform.system() == "Darwin": - platforms = _mac_platforms() - elif platform.system() == "Linux": - platforms = _linux_platforms() - else: - platforms = _generic_platforms() + warn = _warn_parameter("sys_tags", kwargs) + interpreter_name = _interpreter_name() if interpreter_name == "cp": - interpreter = _cpython_interpreter(py_version) - abis = _cpython_abis(py_version, warn) - for tag in _cpython_tags(py_version, interpreter, abis, platforms): + for tag in cpython_tags(warn=warn): yield tag elif interpreter_name == "pp": - interpreter = _pypy_interpreter() - abi = _generic_abi() - for tag in _pypy_tags(py_version, interpreter, abi, platforms): + for tag in pypy_tags(): yield tag else: - interpreter = _generic_interpreter(interpreter_name, py_version, warn) - abi = _generic_abi() - for tag in _generic_tags(interpreter, py_version, abi, platforms): + for tag in generic_tags(): yield tag - for tag in _independent_tags(interpreter, py_version, platforms): + + for tag in compatible_tags(): yield tag From fb58d28998afe2680b6ee50ae5aa07edda4151eb Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Fri, 8 Nov 2019 22:52:36 -0800 Subject: [PATCH 06/58] Tweak a test name --- tests/test_tags.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_tags.py b/tests/test_tags.py index c45ccd3a1..cb04ed671 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -384,7 +384,7 @@ def test_pypy_interpreter(monkeypatch): assert expected == tags._pypy_interpreter() -def test_pypy_tags(mock_interpreter_name, monkeypatch): +def test__pypy_tags(mock_interpreter_name, monkeypatch): if mock_interpreter_name("PyPy"): monkeypatch.setattr(tags, "_pypy_interpreter", lambda: "pp360") interpreter = tags._pypy_interpreter() From 4494b1c973ee15a5915d6903dcb0347a0c83816c Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Fri, 8 Nov 2019 22:53:06 -0800 Subject: [PATCH 07/58] Add tests for cpython_tags() --- tests/test_tags.py | 75 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/tests/test_tags.py b/tests/test_tags.py index cb04ed671..cecc82e5e 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -680,3 +680,78 @@ def test_generic_sys_tags(monkeypatch): result = list(tags.sys_tags()) expected = tags.Tag("py{}0".format(sys.version_info[0]), "none", "any") assert result[-1] == expected + + +def test_warn_parameters(): + assert not tags._warn_parameter("test_warn_parameters", {}) + assert not tags._warn_parameter("test_warn_parameters", {"warn": False}) + assert tags._warn_parameter("test_warn_parameters", {"warn": True}) + message_re = re.compile(r"too_many.+{!r}".format("whatever")) + with pytest.raises(TypeError, match=message_re): + tags._warn_parameter("too_many", {"warn": True, "whatever": True}) + message_re = re.compile(r"missing.+{!r}".format("unexpected")) + with pytest.raises(TypeError, match=message_re): + tags._warn_parameter("missing", {"unexpected": True}) + + +def test_cpython_tags_all_args(): + result = list(tags.cpython_tags((3, 8), ["cp38d", "cp38"], ["plat1", "plat2"])) + assert result == [ + tags.Tag("cp38", "cp38d", "plat1"), + tags.Tag("cp38", "cp38d", "plat2"), + tags.Tag("cp38", "cp38", "plat1"), + tags.Tag("cp38", "cp38", "plat2"), + tags.Tag("cp38", "abi3", "plat1"), + tags.Tag("cp38", "abi3", "plat2"), + tags.Tag("cp38", "none", "plat1"), + tags.Tag("cp38", "none", "plat2"), + tags.Tag("cp37", "abi3", "plat1"), + tags.Tag("cp37", "abi3", "plat2"), + tags.Tag("cp36", "abi3", "plat1"), + tags.Tag("cp36", "abi3", "plat2"), + tags.Tag("cp35", "abi3", "plat1"), + tags.Tag("cp35", "abi3", "plat2"), + tags.Tag("cp34", "abi3", "plat1"), + tags.Tag("cp34", "abi3", "plat2"), + tags.Tag("cp33", "abi3", "plat1"), + tags.Tag("cp33", "abi3", "plat2"), + tags.Tag("cp32", "abi3", "plat1"), + tags.Tag("cp32", "abi3", "plat2"), + ] + result = list(tags.cpython_tags((3, 3), ["cp33m"], ["plat1", "plat2"])) + assert result == [ + tags.Tag("cp33", "cp33m", "plat1"), + tags.Tag("cp33", "cp33m", "plat2"), + tags.Tag("cp33", "abi3", "plat1"), + tags.Tag("cp33", "abi3", "plat2"), + tags.Tag("cp33", "none", "plat1"), + tags.Tag("cp33", "none", "plat2"), + tags.Tag("cp32", "abi3", "plat1"), + tags.Tag("cp32", "abi3", "plat2"), + ] + + +def test_cpython_tags_defaults(monkeypatch): + # python_version + tag = next(tags.cpython_tags(abis=["abi3"], platforms=["any"])) + interpreter = "cp{}{}".format(*sys.version_info[:2]) + assert tag == tags.Tag(interpreter, "abi3", "any") + # abis + with monkeypatch.context() as m: + m.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 + # platforms + with monkeypatch.context() as m: + m.setattr(tags, "_platforms", lambda: ["plat1"]) + result = list(tags.cpython_tags((3, 8), abis=["whatever"])) + assert tags.Tag("cp38", "whatever", "plat1") in result + + +@pytest.mark.parametrize("abis", [["abi3"], ["none"]]) +def test_cpython_tags_skip_redundant_abis(abis): + results = list(tags.cpython_tags((3, 0), abis=abis, platforms=["any"])) + assert results == [tags.Tag("cp30", "abi3", "any"), tags.Tag("cp30", "none", "any")] + From d5f8730f30c08e548ca398f11c4c581fb7c0c7d0 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Fri, 8 Nov 2019 22:53:19 -0800 Subject: [PATCH 08/58] Add tests for pypy_tags() --- tests/test_tags.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/test_tags.py b/tests/test_tags.py index cecc82e5e..322ed3118 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -755,3 +755,12 @@ def test_cpython_tags_skip_redundant_abis(abis): results = list(tags.cpython_tags((3, 0), abis=abis, platforms=["any"])) assert results == [tags.Tag("cp30", "abi3", "any"), tags.Tag("cp30", "none", "any")] + +def test_pypy_tags(monkeypatch): + monkeypatch.setattr(tags, "_pypy_interpreter", lambda: "pp370") + result = list(tags.pypy_tags(abis=["pp370"], platforms=["plat1"])) + assert result == [ + tags.Tag("pp370", "pp370", "plat1"), + tags.Tag("pp370", "none", "plat1"), + ] + From 2495a36252d2555b02ad00cb355a1211d426dbbf Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Fri, 8 Nov 2019 22:53:58 -0800 Subject: [PATCH 09/58] Add tests for generic_tags() --- tests/test_tags.py | 78 +++++++++++++++++++++++++++++----------------- 1 file changed, 49 insertions(+), 29 deletions(-) diff --git a/tests/test_tags.py b/tests/test_tags.py index 322ed3118..f00217738 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -411,41 +411,12 @@ def test_sys_tags_on_mac_pypy(mock_interpreter_name, monkeypatch): assert result[-1] == tags.Tag("py{}0".format(sys.version_info[0]), "none", "any") -def test_generic_interpreter(): - version = sysconfig.get_config_var("py_version_nodot") - if not version: - version = "".join(sys.version_info[:2]) - result = tags._generic_interpreter("sillywalk", sys.version_info[:2]) - assert result == "sillywalk{version}".format(version=version) - - -def test_generic_interpreter_no_config_var(monkeypatch): - monkeypatch.setattr(sysconfig, "get_config_var", lambda _: None) - assert tags._generic_interpreter("sillywalk", (3, 6)) == "sillywalk36" - - def test_generic_platforms(): platform = distutils.util.get_platform().replace("-", "_") platform = platform.replace(".", "_") assert tags._generic_platforms() == [platform] -def test_generic_tags(): - result = list(tags._generic_tags("sillywalk33", (3, 3), "abi", ["plat1", "plat2"])) - assert result == [ - tags.Tag("sillywalk33", "abi", "plat1"), - tags.Tag("sillywalk33", "abi", "plat2"), - tags.Tag("sillywalk33", "none", "plat1"), - tags.Tag("sillywalk33", "none", "plat2"), - ] - - no_abi = tags._generic_tags("sillywalk34", (3, 4), "none", ["plat1", "plat2"]) - assert list(no_abi) == [ - tags.Tag("sillywalk34", "none", "plat1"), - tags.Tag("sillywalk34", "none", "plat2"), - ] - - def test_sys_tags_on_windows_cpython(mock_interpreter_name, monkeypatch): if mock_interpreter_name("CPython"): monkeypatch.setattr(tags, "_cpython_abis", lambda *a: ["cp33m"]) @@ -764,3 +735,52 @@ def test_pypy_tags(monkeypatch): tags.Tag("pp370", "none", "plat1"), ] + +def test_generic_interpreter(monkeypatch): + monkeypatch.setattr(sysconfig, "get_config_var", lambda key: "42") + monkeypatch.setattr(tags, "_interpreter_name", lambda: "sillywalk") + assert tags._generic_interpreter() == "sillywalk42" + + +def test_generic_interpreter_no_config_var(monkeypatch): + monkeypatch.setattr(sysconfig, "get_config_var", lambda _: None) + monkeypatch.setattr(tags, "_interpreter_name", lambda: "sillywalk") + assert tags._generic_interpreter() == "sillywalk{}{}".format(*sys.version_info[:2]) + + +def test_generic_tags(): + result = list(tags.generic_tags("sillywalk33", ["abi"], ["plat1", "plat2"])) + assert result == [ + tags.Tag("sillywalk33", "abi", "plat1"), + tags.Tag("sillywalk33", "abi", "plat2"), + tags.Tag("sillywalk33", "none", "plat1"), + tags.Tag("sillywalk33", "none", "plat2"), + ] + + no_abi = list(tags.generic_tags("sillywalk34", ["none"], ["plat1", "plat2"])) + assert no_abi == [ + tags.Tag("sillywalk34", "none", "plat1"), + tags.Tag("sillywalk34", "none", "plat2"), + ] + + +def test_generic_tags_defaults(monkeypatch): + # interpreter + with monkeypatch.context() as m: + m.setattr(tags, "_generic_interpreter", lambda warn: "sillywalk") + result = list(tags.generic_tags(abis=["none"], platforms=["any"])) + assert result == [tags.Tag("sillywalk", "none", "any")] + # abis + with monkeypatch.context() as m: + m.setattr(tags, "_generic_abi", lambda: "abi") + result = list(tags.generic_tags(interpreter="sillywalk", platforms=["any"])) + assert result == [ + tags.Tag("sillywalk", "abi", "any"), + tags.Tag("sillywalk", "none", "any"), + ] + # platforms + with monkeypatch.context() as m: + m.setattr(tags, "_platforms", lambda: ["plat"]) + result = list(tags.generic_tags(interpreter="sillywalk", abis=["none"])) + assert result == [tags.Tag("sillywalk", "none", "plat")] + From 5ccac11a98996899ad5cc453a0cb83f4becfaad7 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Sat, 9 Nov 2019 12:02:20 -0800 Subject: [PATCH 10/58] Add tests for compatible_tags() --- tests/test_tags.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/test_tags.py b/tests/test_tags.py index f00217738..e9e6911ec 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -784,3 +784,25 @@ def test_generic_tags_defaults(monkeypatch): result = list(tags.generic_tags(interpreter="sillywalk", abis=["none"])) assert result == [tags.Tag("sillywalk", "none", "plat")] + +def test_compatible_tags(): + result = list(tags.compatible_tags((3, 3), "cp33", ["plat1", "plat2"])) + assert result == [ + tags.Tag("py33", "none", "plat1"), + tags.Tag("py33", "none", "plat2"), + tags.Tag("py3", "none", "plat1"), + tags.Tag("py3", "none", "plat2"), + tags.Tag("py32", "none", "plat1"), + tags.Tag("py32", "none", "plat2"), + tags.Tag("py31", "none", "plat1"), + tags.Tag("py31", "none", "plat2"), + tags.Tag("py30", "none", "plat1"), + tags.Tag("py30", "none", "plat2"), + tags.Tag("cp33", "none", "any"), + tags.Tag("py33", "none", "any"), + tags.Tag("py3", "none", "any"), + tags.Tag("py32", "none", "any"), + tags.Tag("py31", "none", "any"), + tags.Tag("py30", "none", "any"), + ] + From ff3e41f2eeefd1a0d38f6762274fcf140ef8010b Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Sat, 9 Nov 2019 12:13:58 -0800 Subject: [PATCH 11/58] Type _generic_interpreter() --- packaging/tags.py | 1 + 1 file changed, 1 insertion(+) diff --git a/packaging/tags.py b/packaging/tags.py index b6bed08fb..e88ceacc6 100644 --- a/packaging/tags.py +++ b/packaging/tags.py @@ -293,6 +293,7 @@ def pypy_tags(interpreter=None, abis=None, platforms=None): def _generic_interpreter(warn=False): + # type: (bool) -> str version = _get_config_var("py_version_nodot", warn) if not version: version = "".join(map(str, sys.version_info[:2])) From 9f398c58d5245be8ea51b21baf614cfe50204884 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Sat, 9 Nov 2019 12:20:07 -0800 Subject: [PATCH 12/58] Add tests for _platform() --- tests/test_tags.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/test_tags.py b/tests/test_tags.py index e9e6911ec..9b542c31e 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -806,3 +806,17 @@ def test_compatible_tags(): tags.Tag("py30", "none", "any"), ] + +@pytest.mark.parametrize( + "platform_name,dispatch_func", + [ + ("Darwin", "_mac_platforms"), + ("Linux", "_linux_platforms"), + ("Generic", "_generic_platforms"), + ], +) +def test__platforms(platform_name, dispatch_func, monkeypatch): + expected = ["sillywalk"] + monkeypatch.setattr(platform, "system", lambda: platform_name) + monkeypatch.setattr(tags, dispatch_func, lambda: expected) + assert tags._platforms() == expected From 8f54af00fbb97406af8a7f9b3e6378b39bf1fa99 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Sat, 9 Nov 2019 12:20:17 -0800 Subject: [PATCH 13/58] Remove now-dead code --- packaging/tags.py | 60 ------------------------------------- tests/test_tags.py | 74 ---------------------------------------------- 2 files changed, 134 deletions(-) diff --git a/packaging/tags.py b/packaging/tags.py index e88ceacc6..552cb3b0c 100644 --- a/packaging/tags.py +++ b/packaging/tags.py @@ -136,12 +136,6 @@ def _normalize_string(string): return string.replace(".", "_").replace("-", "_") -def _cpython_interpreter(py_version): - # type: (PythonVersion) -> str - # TODO: Is using py_version_nodot for interpreter version critical? - return "cp{major}{minor}".format(major=py_version[0], minor=py_version[1]) - - def _cpython_abis(py_version, warn=False): # type: (PythonVersion, bool) -> List[str] abis = [] @@ -178,24 +172,6 @@ def _cpython_abis(py_version, warn=False): return abis -def _cpython_tags(py_version, interpreter, abis, platforms): - # type: (PythonVersion, str, Iterable[str], Iterable[str]) -> Iterator[Tag] - for abi in abis: - for platform_ in platforms: - yield Tag(interpreter, abi, platform_) - for tag in (Tag(interpreter, "abi3", platform_) for platform_ in platforms): - yield tag - for tag in (Tag(interpreter, "none", platform_) for platform_ in platforms): - yield tag - # PEP 384 was first implemented in Python 3.2. - for minor_version in range(py_version[1] - 1, 1, -1): - for platform_ in platforms: - interpreter = "cp{major}{minor}".format( - major=py_version[0], minor=minor_version - ) - yield Tag(interpreter, "abi3", platform_) - - def cpython_tags( python_version=sys.version_info[:2], abis=None, platforms=None, **kwargs ): @@ -268,14 +244,6 @@ def _generic_abi(): return "none" -def _pypy_tags(py_version, interpreter, abi, platforms): - # type: (PythonVersion, str, str, Iterable[str]) -> Iterator[Tag] - for tag in (Tag(interpreter, abi, platform) for platform in platforms): - yield tag - for tag in (Tag(interpreter, "none", platform) for platform in platforms): - yield tag - - def pypy_tags(interpreter=None, abis=None, platforms=None): # type: (Optional[str], Optional[Iterable[str]], Optional[Iterable[str]]) -> Iterator[Tag] # noqa """ @@ -300,16 +268,6 @@ def _generic_interpreter(warn=False): return "{name}{version}".format(name=_interpreter_name(), version=version) -def _generic_tags(interpreter, py_version, abi, platforms): - # type: (str, PythonVersion, str, Iterable[str]) -> Iterator[Tag] - for tag in (Tag(interpreter, abi, platform) for platform in platforms): - yield tag - if abi != "none": - tags = (Tag(interpreter, "none", platform_) for platform_ in platforms) - for tag in tags: - yield tag - - def generic_tags(interpreter=None, abis=None, platforms=None, **kwargs): # type: (Optional[str], Optional[Iterable[str]], Optional[Iterable[str]], bool) -> Iterator[Tag] # noqa """ @@ -352,24 +310,6 @@ def _py_interpreter_range(py_version): yield "py{major}{minor}".format(major=py_version[0], minor=minor) -def _independent_tags(interpreter, py_version, platforms): - # type: (str, PythonVersion, Iterable[str]) -> Iterator[Tag] - """ - Yield the sequence of tags that are consistent across implementations. - - The tags consist of: - - py*-none- - - -none-any - - py*-none-any - """ - for version in _py_interpreter_range(py_version): - for platform_ in platforms: - yield Tag(version, "none", platform_) - yield Tag(interpreter, "none", "any") - for version in _py_interpreter_range(py_version): - yield Tag(version, "none", "any") - - def compatible_tags( python_version=sys.version_info[:2], interpreter=None, platforms=None ): diff --git a/tests/test_tags.py b/tests/test_tags.py index 9b542c31e..e0e5e9f59 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -275,67 +275,6 @@ def test_cpython_abis_wide_unicode( assert tags._cpython_abis(version) == expected -def test_independent_tags(): - result = list(tags._independent_tags("cp33", (3, 3), ["plat1", "plat2"])) - assert result == [ - tags.Tag("py33", "none", "plat1"), - tags.Tag("py33", "none", "plat2"), - tags.Tag("py3", "none", "plat1"), - tags.Tag("py3", "none", "plat2"), - tags.Tag("py32", "none", "plat1"), - tags.Tag("py32", "none", "plat2"), - tags.Tag("py31", "none", "plat1"), - tags.Tag("py31", "none", "plat2"), - tags.Tag("py30", "none", "plat1"), - tags.Tag("py30", "none", "plat2"), - tags.Tag("cp33", "none", "any"), - tags.Tag("py33", "none", "any"), - tags.Tag("py3", "none", "any"), - tags.Tag("py32", "none", "any"), - tags.Tag("py31", "none", "any"), - tags.Tag("py30", "none", "any"), - ] - - -def test_cpython_tags(): - result = list( - tags._cpython_tags((3, 8), "cp38", ["cp38d", "cp38"], ["plat1", "plat2"]) - ) - assert result == [ - tags.Tag("cp38", "cp38d", "plat1"), - tags.Tag("cp38", "cp38d", "plat2"), - tags.Tag("cp38", "cp38", "plat1"), - tags.Tag("cp38", "cp38", "plat2"), - tags.Tag("cp38", "abi3", "plat1"), - tags.Tag("cp38", "abi3", "plat2"), - tags.Tag("cp38", "none", "plat1"), - tags.Tag("cp38", "none", "plat2"), - tags.Tag("cp37", "abi3", "plat1"), - tags.Tag("cp37", "abi3", "plat2"), - tags.Tag("cp36", "abi3", "plat1"), - tags.Tag("cp36", "abi3", "plat2"), - tags.Tag("cp35", "abi3", "plat1"), - tags.Tag("cp35", "abi3", "plat2"), - tags.Tag("cp34", "abi3", "plat1"), - tags.Tag("cp34", "abi3", "plat2"), - tags.Tag("cp33", "abi3", "plat1"), - tags.Tag("cp33", "abi3", "plat2"), - tags.Tag("cp32", "abi3", "plat1"), - tags.Tag("cp32", "abi3", "plat2"), - ] - result = list(tags._cpython_tags((3, 3), "cp33", ["cp33m"], ["plat1", "plat2"])) - assert result == [ - tags.Tag("cp33", "cp33m", "plat1"), - tags.Tag("cp33", "cp33m", "plat2"), - tags.Tag("cp33", "abi3", "plat1"), - tags.Tag("cp33", "abi3", "plat2"), - tags.Tag("cp33", "none", "plat1"), - tags.Tag("cp33", "none", "plat2"), - tags.Tag("cp32", "abi3", "plat1"), - tags.Tag("cp32", "abi3", "plat2"), - ] - - def test_sys_tags_on_mac_cpython(mock_interpreter_name, monkeypatch): if mock_interpreter_name("CPython"): monkeypatch.setattr(tags, "_cpython_abis", lambda *a: ["cp33m"]) @@ -384,19 +323,6 @@ def test_pypy_interpreter(monkeypatch): assert expected == tags._pypy_interpreter() -def test__pypy_tags(mock_interpreter_name, monkeypatch): - if mock_interpreter_name("PyPy"): - monkeypatch.setattr(tags, "_pypy_interpreter", lambda: "pp360") - interpreter = tags._pypy_interpreter() - result = list(tags._pypy_tags((3, 3), interpreter, "pypy3_60", ["plat1", "plat2"])) - assert result == [ - tags.Tag(interpreter, "pypy3_60", "plat1"), - tags.Tag(interpreter, "pypy3_60", "plat2"), - tags.Tag(interpreter, "none", "plat1"), - tags.Tag(interpreter, "none", "plat2"), - ] - - def test_sys_tags_on_mac_pypy(mock_interpreter_name, monkeypatch): if mock_interpreter_name("PyPy"): monkeypatch.setattr(tags, "_pypy_interpreter", lambda: "pp360") From 11b9bedf78b836a67a698be1094ec205b84ee2fd Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Mon, 11 Nov 2019 09:26:35 -0800 Subject: [PATCH 14/58] Fix the docstring for _py_interpreter_range() --- packaging/tags.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packaging/tags.py b/packaging/tags.py index 552cb3b0c..236612fb1 100644 --- a/packaging/tags.py +++ b/packaging/tags.py @@ -302,7 +302,7 @@ def _py_interpreter_range(py_version): Yield Python versions in descending order. After the latest version, the major-only version will be yielded, and then - all following versions up to 'end'. + all previous versions of that major version. """ yield "py{major}{minor}".format(major=py_version[0], minor=py_version[1]) yield "py{major}".format(major=py_version[0]) From c8e90d96fea1275816247f707b252f9332f0cf93 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Mon, 11 Nov 2019 18:23:36 -0800 Subject: [PATCH 15/58] Expose mac_platforms() publicly Along the way, make all platform-related functions generators. --- packaging/tags.py | 58 ++++++++++++++++++++++------------------------- 1 file changed, 27 insertions(+), 31 deletions(-) diff --git a/packaging/tags.py b/packaging/tags.py index 236612fb1..520f3ddd3 100644 --- a/packaging/tags.py +++ b/packaging/tags.py @@ -173,9 +173,12 @@ def _cpython_abis(py_version, warn=False): def cpython_tags( - python_version=sys.version_info[:2], abis=None, platforms=None, **kwargs + python_version=sys.version_info[:2], # type: PythonVersion + abis=None, # type: Optional[Iterable[str]] + platforms=None, # type: Optional[Iterable[str]] + **kwargs # type: bool ): - # type: (PythonVersion, Optional[Iterable[str]], Optional[Iterable[str]], bool) -> Iterator[Tag] # noqa + # type: (...) -> Iterator[Tag] """ Yield the tags for a CPython interpreter. @@ -192,9 +195,7 @@ def cpython_tags( interpreter = "cp{}{}".format(*python_version) if not abis: abis = _cpython_abis(python_version, warn) - if not platforms: - platforms = _platforms() - platforms = list(platforms) + platforms = list(platforms or _platforms()) abis = list(abis) # 'abi3' and 'none' are explicitly handled later. try: @@ -283,10 +284,7 @@ def generic_tags(interpreter=None, abis=None, platforms=None, **kwargs): interpreter = _generic_interpreter(warn=warn) if not abis: abis = [_generic_abi()] - if not platforms: - platforms = _platforms() - else: - platforms = list(platforms) + platforms = list(platforms or _platforms()) abis = list(abis) for abi in abis: for platform_ in platforms: @@ -372,11 +370,16 @@ def _mac_binary_formats(version, cpu_arch): return formats -def _mac_platforms( - version=None, # type: Optional[MacVersion] - arch=None, # type: Optional[str] -): - # type: (...) -> List[str] +def mac_platforms(version=None, arch=None): + # type: (Optional[MacVersion], Optional[str]) -> Iterator[str] + """ + Yield the platform tags for a macOS system. + + The *version* parameter is a two-item tuple specifying the macOS version to + generate platform tags for. The *arch* parameter is the CPU architecture to + generate platform tags for. Both parameters default to the appropriate value + for the current system. + """ version_str, _, cpu_arch = platform.mac_ver() # type: ignore if version is None: version = cast("MacVersion", tuple(map(int, version_str.split(".")[:2]))) @@ -386,19 +389,15 @@ def _mac_platforms( arch = _mac_arch(cpu_arch) else: arch = arch - platforms = [] for minor_version in range(version[1], -1, -1): compat_version = version[0], minor_version binary_formats = _mac_binary_formats(compat_version, arch) for binary_format in binary_formats: - platforms.append( - "macosx_{major}_{minor}_{binary_format}".format( + yield "macosx_{major}_{minor}_{binary_format}".format( major=compat_version[0], minor=compat_version[1], binary_format=binary_format, ) - ) - return platforms # From PEP 513. @@ -508,7 +507,7 @@ def _have_compatible_glibc(required_major, minimum_minor): def _linux_platforms(is_32bit=_32_BIT_INTERPRETER): - # type: (bool) -> List[str] + # type: (bool) -> Iterator[str] linux = _normalize_string(distutils.util.get_platform()) if linux == "linux_x86_64" and is_32bit: linux = "linux_i686" @@ -520,29 +519,26 @@ def _linux_platforms(is_32bit=_32_BIT_INTERPRETER): manylinux_support_iter = iter(manylinux_support) for name, glibc_version in manylinux_support_iter: if _is_manylinux_compatible(name, glibc_version): - platforms = [linux.replace("linux", name)] + yield linux.replace("linux", name) break - else: - platforms = [] # Support for a later manylinux implies support for an earlier version. - platforms += [linux.replace("linux", name) for name, _ in manylinux_support_iter] - platforms.append(linux) - return platforms + for name, _ in manylinux_support_iter: + yield linux.replace("linux", name) + yield linux def _generic_platforms(): - # type: () -> List[str] - platform = _normalize_string(distutils.util.get_platform()) - return [platform] + # type: () -> Iterator[str] + yield _normalize_string(distutils.util.get_platform()) def _platforms(): - # type: () -> List[str] + # type: () -> Iterator[str] """ Provide the platform tags for this installation. """ if platform.system() == "Darwin": - return _mac_platforms() + return mac_platforms() elif platform.system() == "Linux": return _linux_platforms() else: From c0911014b2ed2e4576f69ea9927a90e13e5fb1e4 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Mon, 11 Nov 2019 18:23:58 -0800 Subject: [PATCH 16/58] Make some type hints on parameters more readable --- packaging/tags.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/packaging/tags.py b/packaging/tags.py index 520f3ddd3..344d25132 100644 --- a/packaging/tags.py +++ b/packaging/tags.py @@ -245,8 +245,12 @@ def _generic_abi(): return "none" -def pypy_tags(interpreter=None, abis=None, platforms=None): - # type: (Optional[str], Optional[Iterable[str]], Optional[Iterable[str]]) -> Iterator[Tag] # noqa +def pypy_tags( + interpreter=None, # type: Optional[str] + abis=None, # type: Optional[Iterable[str]] + platforms=None, # type: Optional[Iterable[str]] +): + # type: (...) -> Iterator[Tag] """ Yield the tags for a PyPy interpreter. @@ -269,8 +273,13 @@ def _generic_interpreter(warn=False): return "{name}{version}".format(name=_interpreter_name(), version=version) -def generic_tags(interpreter=None, abis=None, platforms=None, **kwargs): - # type: (Optional[str], Optional[Iterable[str]], Optional[Iterable[str]], bool) -> Iterator[Tag] # noqa +def generic_tags( + interpreter=None, # type: Optional[str] + abis=None, # type: Optional[Iterable[str]] + platforms=None, # type: Optional[Iterable[str]] + **kwargs # type: bool +): + # type: (...) -> Iterator[Tag] """ Yield the tags for a generic interpreter. From f50ce383e639182ac7de0d3379970a4a0023c41e Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Mon, 11 Nov 2019 18:24:16 -0800 Subject: [PATCH 17/58] Fix some whitespace --- packaging/tags.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packaging/tags.py b/packaging/tags.py index 344d25132..1280cd475 100644 --- a/packaging/tags.py +++ b/packaging/tags.py @@ -403,10 +403,10 @@ def mac_platforms(version=None, arch=None): binary_formats = _mac_binary_formats(compat_version, arch) for binary_format in binary_formats: yield "macosx_{major}_{minor}_{binary_format}".format( - major=compat_version[0], - minor=compat_version[1], - binary_format=binary_format, - ) + major=compat_version[0], + minor=compat_version[1], + binary_format=binary_format, + ) # From PEP 513. From d0b5b56c232dcec1b4804fe9b388341f7c89176b Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Mon, 11 Nov 2019 18:24:41 -0800 Subject: [PATCH 18/58] Fix up tests for mac_platforms() exposure and making platform functions generators --- tests/test_tags.py | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/tests/test_tags.py b/tests/test_tags.py index e0e5e9f59..9db0f7e39 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -174,7 +174,7 @@ def test_macos_binary_formats(version, arch, expected): def test_mac_platforms(): - platforms = tags._mac_platforms((10, 5), "x86_64") + platforms = list(tags.mac_platforms((10, 5), "x86_64")) assert platforms == [ "macosx_10_5_x86_64", "macosx_10_5_intel", @@ -188,9 +188,9 @@ def test_mac_platforms(): "macosx_10_4_universal", ] - assert len(tags._mac_platforms((10, 17), "x86_64")) == 14 * 5 + assert len(list(tags.mac_platforms((10, 17), "x86_64"))) == 14 * 5 - assert not tags._mac_platforms((10, 0), "x86_64") + assert not list(tags.mac_platforms((10, 0), "x86_64")) def test_macos_version_detection(monkeypatch): @@ -200,7 +200,7 @@ def test_macos_version_detection(monkeypatch): ) version = platform.mac_ver()[0].split(".") expected = "macosx_{major}_{minor}".format(major=version[0], minor=version[1]) - platforms = tags._mac_platforms(arch="x86_64") + platforms = list(tags.mac_platforms(arch="x86_64")) assert platforms[0].startswith(expected) @@ -209,7 +209,7 @@ def test_macos_arch_detection(arch, monkeypatch): if platform.system() != "Darwin" or platform.mac_ver()[2] != arch: monkeypatch.setattr(platform, "mac_ver", lambda: ("10.14", ("", "", ""), arch)) monkeypatch.setattr(tags, "_mac_arch", lambda *args: arch) - assert tags._mac_platforms((10, 14))[0].endswith(arch) + assert next(tags.mac_platforms((10, 14))).endswith(arch) @pytest.mark.parametrize( @@ -280,9 +280,9 @@ def test_sys_tags_on_mac_cpython(mock_interpreter_name, monkeypatch): monkeypatch.setattr(tags, "_cpython_abis", lambda *a: ["cp33m"]) if platform.system() != "Darwin": monkeypatch.setattr(platform, "system", lambda: "Darwin") - monkeypatch.setattr(tags, "_mac_platforms", lambda: ["macosx_10_5_x86_64"]) + monkeypatch.setattr(tags, "mac_platforms", lambda: ["macosx_10_5_x86_64"]) abis = tags._cpython_abis(sys.version_info[:2]) - platforms = tags._mac_platforms() + platforms = list(tags.mac_platforms()) result = list(tags.sys_tags()) assert len(abis) == 1 assert result[0] == tags.Tag( @@ -328,10 +328,10 @@ def test_sys_tags_on_mac_pypy(mock_interpreter_name, monkeypatch): monkeypatch.setattr(tags, "_pypy_interpreter", lambda: "pp360") if platform.system() != "Darwin": monkeypatch.setattr(platform, "system", lambda: "Darwin") - monkeypatch.setattr(tags, "_mac_platforms", lambda: ["macosx_10_5_x86_64"]) + monkeypatch.setattr(tags, "mac_platforms", lambda: ["macosx_10_5_x86_64"]) interpreter = tags._pypy_interpreter() abi = tags._generic_abi() - platforms = tags._mac_platforms() + platforms = list(tags.mac_platforms()) result = list(tags.sys_tags()) assert result[0] == tags.Tag(interpreter, abi, platforms[0]) assert result[-1] == tags.Tag("py{}0".format(sys.version_info[0]), "none", "any") @@ -340,7 +340,7 @@ def test_sys_tags_on_mac_pypy(mock_interpreter_name, monkeypatch): def test_generic_platforms(): platform = distutils.util.get_platform().replace("-", "_") platform = platform.replace(".", "_") - assert tags._generic_platforms() == [platform] + assert list(tags._generic_platforms()) == [platform] def test_sys_tags_on_windows_cpython(mock_interpreter_name, monkeypatch): @@ -496,7 +496,7 @@ def test_linux_platforms_64bit_on_64bit_os(is_64bit_os, is_x86, monkeypatch): if platform.system() != "Linux" or not is_64bit_os or not is_x86: monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64") monkeypatch.setattr(tags, "_is_manylinux_compatible", lambda *args: False) - linux_platform = tags._linux_platforms(is_32bit=False)[-1] + linux_platform = list(tags._linux_platforms(is_32bit=False))[-1] assert linux_platform == "linux_x86_64" @@ -504,14 +504,14 @@ def test_linux_platforms_32bit_on_64bit_os(is_64bit_os, is_x86, monkeypatch): if platform.system() != "Linux" or not is_64bit_os or not is_x86: monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64") monkeypatch.setattr(tags, "_is_manylinux_compatible", lambda *args: False) - linux_platform = tags._linux_platforms(is_32bit=True)[-1] + linux_platform = list(tags._linux_platforms(is_32bit=True))[-1] assert linux_platform == "linux_i686" def test_linux_platforms_manylinux_unsupported(monkeypatch): monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64") monkeypatch.setattr(tags, "_is_manylinux_compatible", lambda *args: False) - linux_platform = tags._linux_platforms(is_32bit=False) + linux_platform = list(tags._linux_platforms(is_32bit=False)) assert linux_platform == ["linux_x86_64"] @@ -521,7 +521,7 @@ def test_linux_platforms_manylinux1(monkeypatch): ) if platform.system() != "Linux": monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64") - platforms = tags._linux_platforms(is_32bit=False) + platforms = list(tags._linux_platforms(is_32bit=False)) assert platforms == ["manylinux1_x86_64", "linux_x86_64"] @@ -531,7 +531,7 @@ def test_linux_platforms_manylinux2010(monkeypatch): ) if platform.system() != "Linux": monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64") - platforms = tags._linux_platforms(is_32bit=False) + platforms = list(tags._linux_platforms(is_32bit=False)) expected = ["manylinux2010_x86_64", "manylinux1_x86_64", "linux_x86_64"] assert platforms == expected @@ -542,7 +542,7 @@ def test_linux_platforms_manylinux2014(monkeypatch): ) if platform.system() != "Linux": monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64") - platforms = tags._linux_platforms(is_32bit=False) + platforms = list(tags._linux_platforms(is_32bit=False)) expected = [ "manylinux2014_x86_64", "manylinux2010_x86_64", @@ -736,7 +736,7 @@ def test_compatible_tags(): @pytest.mark.parametrize( "platform_name,dispatch_func", [ - ("Darwin", "_mac_platforms"), + ("Darwin", "mac_platforms"), ("Linux", "_linux_platforms"), ("Generic", "_generic_platforms"), ], From 5ef9af04515578d27ec9a930ea1304e3fa8ec9e9 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Mon, 11 Nov 2019 18:40:10 -0800 Subject: [PATCH 19/58] Document new public functions --- docs/tags.rst | 104 +++++++++++++++++++++++++++++++++++++++++++--- packaging/tags.py | 16 +++---- 2 files changed, 106 insertions(+), 14 deletions(-) diff --git a/docs/tags.rst b/docs/tags.rst index cabe33b21..65e49e62e 100644 --- a/docs/tags.rst +++ b/docs/tags.rst @@ -65,17 +65,18 @@ Reference .. function:: parse_tag(tag) - Parse the provided *tag* into a set of :class:`Tag` instances. + Parses the provided *tag* into a set of :class:`Tag` instances. - The returning of a set is required due to the possibility that the tag is a - `compressed tag set`_, e.g. ``"py2.py3-none-any"``. + Returning a set is required due to the possibility that the tag is a + `compressed tag set`_, e.g. ``"py2.py3-none-any"`` which supports both + Python 2 and Python 3. :param str tag: The tag to parse, e.g. ``"py3-none-any"``. -.. function:: sys_tags(warn=False) +.. function:: sys_tags(*, warn=False) - Create an iterable of tags that the running interpreter supports. + Yields the tags that the running interpreter supports. The iterable is ordered so that the best-matching tag is first in the sequence. The exact preferential order to tags is interpreter-specific, but @@ -92,11 +93,102 @@ Reference The function returns an iterable in order to allow for the possible short-circuiting of tag generation if the entire sequence is not necessary - and calculating some tags happens to be expensive. + and tag calculation happens to be expensive. :param bool warn: Whether warnings should be logged. Defaults to ``False``. +.. function:: mac_platforms(version=None, arch=None) + + Yields the platforms tags for macOS. + + Specific support for macOS is provided by this module due to how multiple + versions of macOS can be supported by any one version which can be + determined statically. For Windows this information is entirely static and + thus does not require calculating older version support. For Linux, code + must be run on the system itself to determine its compatibility level and + thus cannot be calculated statically. + + :param tuple version: A two-item tuple presenting the version of macOS. + Defaults to the current system's version. + :param str arch: The CPU architecture. Defaults to the architecture of the + current system, e.g. ``"x86_64"``. + + +.. function:: compatible_tags(python_version=sys.version_info[:2], interpreter=None, platforms=None) + + Yields the tags for an interpreter compatible with the Python version + specified by *python_version*. + + The specific tags generated are: + + - ``py*-none-`` + - ``-none-any`` if *interpreter* is provided + - ``py*-none-any`` + + :param tuple python_version: A two-item tuple representing the compatible + version of Python. Defaults to + ``sys.version_info[:2]``. + :param str interpreter: The name of the interpreter (if known). + :param Iterable platforms: Iterable of compatible platforms. Defaults to the + platforms compatible with the current system. + +.. function:: cpython_tags(python_version=sys.version_info[:2], abis=None, platforms=None, *, warn=False) + + Yields the tags for the CPython interpreter. + + The specific tags generated are: + + - ``cp--`` + - ``cp-abi3-`` + - ``cp-none-`` + - ``cp-abi3-`` where "older version" is all older + minor versions down to Python 3.2 (when ``abi3`` was introduced) + + :param tuple python_version: A tuple representing the targetted Python + version. + :param Iterable abis: Iterable of compatible ABIs. Defaults to the ABIs + compatible with the current system. + :param Iterable platforms: Iterable of compatible platforms. Defaults to the + platforms compatible with the current system. + :param bool warn: Whether warnings should be logged. Defaults to ``False``. + +.. function:: pypy_tags(interpreter=None, abis=None, platforms=None) + + Yields the tags for the PyPy intrepreter. + + The specific tags are those returned by :func:`generic_tags`, but with + *interpreter* inferred for the current system if not provided. + + :param str interpreter: The name of the interpreter. Defaults the current + PyPy interpreter. + :param Iterable abis: Iterable of compatible ABIs. Defaults to the ABIs + compatible with the current system. + :param Iterable platforms: Iterable of compatible platforms. Defaults to the + platforms compatible with the current system. + +.. function:: generic_tags(interpreter=None, abis=None, platforms=None, *, warn=False) + + Yields the tags for an interpreter in a non-specialized fashion. + + This function should be used if one of the other interpreter-specific + functions is not appropriate (i.e. not calculating tags for a CPython or + PyPy intrepreter). + + The specific tags generated are: + + - ``--`` + - ``-none-`` if ``"none"`` was not provided as part of + *abis* + + :param str interpreter: The name of the interpreter. Defaults the current + PyPy interpreter. + :param Iterable abis: Iterable of compatible ABIs. Defaults to the ABIs + compatible with the current system. + :param Iterable platforms: Iterable of compatible platforms. Defaults to the + platforms compatible with the current system. + :param bool warn: Whether warnings should be logged. Defaults to ``False``. + .. _abbreviation codes: https://www.python.org/dev/peps/pep-0425/#python-tag .. _compressed tag set: https://www.python.org/dev/peps/pep-0425/#compressed-tag-sets .. _platform compatibility tags: https://packaging.python.org/specifications/platform-compatibility-tags/ diff --git a/packaging/tags.py b/packaging/tags.py index 1280cd475..8ada878ea 100644 --- a/packaging/tags.py +++ b/packaging/tags.py @@ -180,7 +180,7 @@ def cpython_tags( ): # type: (...) -> Iterator[Tag] """ - Yield the tags for a CPython interpreter. + Yields the tags for a CPython interpreter. The tags consist of: - cp-- @@ -252,7 +252,7 @@ def pypy_tags( ): # type: (...) -> Iterator[Tag] """ - Yield the tags for a PyPy interpreter. + Yields the tags for a PyPy interpreter. The tags consist of what is yielded by generic_tags() with a calcuated value for 'interpreter' if it is not specified. @@ -281,7 +281,7 @@ def generic_tags( ): # type: (...) -> Iterator[Tag] """ - Yield the tags for a generic interpreter. + Yields the tags for a generic interpreter. The tags consist of: - -- @@ -306,7 +306,7 @@ def generic_tags( def _py_interpreter_range(py_version): # type: (PythonVersion) -> Iterator[str] """ - Yield Python versions in descending order. + Yields Python versions in descending order. After the latest version, the major-only version will be yielded, and then all previous versions of that major version. @@ -322,7 +322,7 @@ def compatible_tags( ): # type: (PythonVersion, Optional[str], Optional[Iterable[str]]) -> Iterator[Tag] """ - Yield the sequence of tags that are compatible with a specific version of Python. + Yields the sequence of tags that are compatible with a specific version of Python. The tags consist of: - py*-none- @@ -382,7 +382,7 @@ def _mac_binary_formats(version, cpu_arch): def mac_platforms(version=None, arch=None): # type: (Optional[MacVersion], Optional[str]) -> Iterator[str] """ - Yield the platform tags for a macOS system. + Yields the platform tags for a macOS system. The *version* parameter is a two-item tuple specifying the macOS version to generate platform tags for. The *arch* parameter is the CPU architecture to @@ -544,7 +544,7 @@ def _generic_platforms(): def _platforms(): # type: () -> Iterator[str] """ - Provide the platform tags for this installation. + Provides the platform tags for this installation. """ if platform.system() == "Darwin": return mac_platforms() @@ -567,7 +567,7 @@ def _interpreter_name(): def sys_tags(**kwargs): # type: (bool) -> Iterator[Tag] """ - Return the sequence of tag triples for the running interpreter. + Returns the sequence of tag triples for the running interpreter. The order of the sequence corresponds to priority order for the interpreter, from most to least important. From c59e5967d898e1e8e43dc6b4e85183949a8db579 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Tue, 12 Nov 2019 13:47:45 -0800 Subject: [PATCH 20/58] Fix tests --- tests/test_tags.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/tests/test_tags.py b/tests/test_tags.py index 9db0f7e39..a0d5f7c11 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -558,8 +558,8 @@ def test_sys_tags_linux_cpython(mock_interpreter_name, monkeypatch): if platform.system() != "Linux": monkeypatch.setattr(platform, "system", lambda: "Linux") monkeypatch.setattr(tags, "_linux_platforms", lambda: ["linux_x86_64"]) - abis = tags._cpython_abis(sys.version_info[:2]) - platforms = tags._linux_platforms() + abis = list(tags._cpython_abis(sys.version_info[:2])) + platforms = list(tags._linux_platforms()) result = list(tags.sys_tags()) expected_interpreter = "cp{major}{minor}".format( major=sys.version_info[0], minor=sys.version_info[1] @@ -654,13 +654,23 @@ def test_cpython_tags_skip_redundant_abis(abis): def test_pypy_tags(monkeypatch): - monkeypatch.setattr(tags, "_pypy_interpreter", lambda: "pp370") - result = list(tags.pypy_tags(abis=["pp370"], platforms=["plat1"])) + with monkeypatch.context() as m: + m.setattr(tags, "_pypy_interpreter", lambda: "pp370") + result = list(tags.pypy_tags(abis=["pp370"], platforms=["plat1"])) assert result == [ tags.Tag("pp370", "pp370", "plat1"), tags.Tag("pp370", "none", "plat1"), ] + with monkeypatch.context() as m: + m.setattr(tags, "_pypy_interpreter", lambda: "pp370") + result = list(tags.pypy_tags("pp360", ["pp360"], ["plat1"])) + assert result == [ + tags.Tag("pp360", "pp360", "plat1"), + tags.Tag("pp360", "none", "plat1"), + ] + + def test_generic_interpreter(monkeypatch): monkeypatch.setattr(sysconfig, "get_config_var", lambda key: "42") From defe7196ea44e069a0cac792079862f92d391543 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Tue, 12 Nov 2019 16:03:51 -0800 Subject: [PATCH 21/58] Remove an extra blank line --- tests/test_tags.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_tags.py b/tests/test_tags.py index a0d5f7c11..7d8b3a399 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -671,7 +671,6 @@ def test_pypy_tags(monkeypatch): ] - def test_generic_interpreter(monkeypatch): monkeypatch.setattr(sysconfig, "get_config_var", lambda key: "42") monkeypatch.setattr(tags, "_interpreter_name", lambda: "sillywalk") From ab7cc6d3aeac48b333d718e7554f0cf573230132 Mon Sep 17 00:00:00 2001 From: Brett Cannon <54418+brettcannon@users.noreply.github.com> Date: Fri, 15 Nov 2019 10:37:10 -0800 Subject: [PATCH 22/58] Fix spelling mistake Co-Authored-By: Xavier Fernandez --- docs/tags.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tags.rst b/docs/tags.rst index 65e49e62e..25a8c42f5 100644 --- a/docs/tags.rst +++ b/docs/tags.rst @@ -178,7 +178,7 @@ Reference The specific tags generated are: - ``--`` - - ``-none-`` if ``"none"`` was not provided as part of + - ``-none-`` if ``"none"`` was not provided as part of *abis* :param str interpreter: The name of the interpreter. Defaults the current From c952f2cebd9923d305f88cf3210f59afb8b3df42 Mon Sep 17 00:00:00 2001 From: Brett Cannon <54418+brettcannon@users.noreply.github.com> Date: Fri, 15 Nov 2019 10:37:44 -0800 Subject: [PATCH 23/58] Fix a grammar mistake Co-Authored-By: Xavier Fernandez --- docs/tags.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tags.rst b/docs/tags.rst index 25a8c42f5..563dad653 100644 --- a/docs/tags.rst +++ b/docs/tags.rst @@ -181,7 +181,7 @@ Reference - ``-none-`` if ``"none"`` was not provided as part of *abis* - :param str interpreter: The name of the interpreter. Defaults the current + :param str interpreter: The name of the interpreter. Defaults to the current PyPy interpreter. :param Iterable abis: Iterable of compatible ABIs. Defaults to the ABIs compatible with the current system. From 1fbecf208563def998159a1424fb16f92f6fc09a Mon Sep 17 00:00:00 2001 From: Brett Cannon <54418+brettcannon@users.noreply.github.com> Date: Fri, 15 Nov 2019 10:38:04 -0800 Subject: [PATCH 24/58] Fix spelling mistake Co-Authored-By: Xavier Fernandez --- packaging/tags.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packaging/tags.py b/packaging/tags.py index 8ada878ea..a1cbacfbc 100644 --- a/packaging/tags.py +++ b/packaging/tags.py @@ -285,7 +285,7 @@ def generic_tags( The tags consist of: - -- - - -none- # If 'none' is not specified in 'abis'. + - -none- # If 'none' is not specified in 'abis'. """ warn = _warn_parameter("generic_tags", kwargs) From 123c1e6b2bc37a75b4e349acd4b50ba09b0ddb45 Mon Sep 17 00:00:00 2001 From: Brett Cannon <54418+brettcannon@users.noreply.github.com> Date: Fri, 15 Nov 2019 10:38:28 -0800 Subject: [PATCH 25/58] Fix a grammar mistake Co-Authored-By: Xavier Fernandez --- docs/tags.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tags.rst b/docs/tags.rst index 563dad653..13fd36e9c 100644 --- a/docs/tags.rst +++ b/docs/tags.rst @@ -160,7 +160,7 @@ Reference The specific tags are those returned by :func:`generic_tags`, but with *interpreter* inferred for the current system if not provided. - :param str interpreter: The name of the interpreter. Defaults the current + :param str interpreter: The name of the interpreter. Defaults to the current PyPy interpreter. :param Iterable abis: Iterable of compatible ABIs. Defaults to the ABIs compatible with the current system. From 818cd09377552ff017d7b9ae5712cc8f376ce9cc Mon Sep 17 00:00:00 2001 From: Brett Cannon <54418+brettcannon@users.noreply.github.com> Date: Fri, 15 Nov 2019 10:40:15 -0800 Subject: [PATCH 26/58] Fix spelling mistake Co-Authored-By: Pradyun Gedam --- docs/tags.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tags.rst b/docs/tags.rst index 13fd36e9c..8b05b0f80 100644 --- a/docs/tags.rst +++ b/docs/tags.rst @@ -173,7 +173,7 @@ Reference This function should be used if one of the other interpreter-specific functions is not appropriate (i.e. not calculating tags for a CPython or - PyPy intrepreter). + PyPy interpreter). The specific tags generated are: From b168c090345bf6c14c2de61fd41fc7a7b6d2a089 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Fri, 15 Nov 2019 10:49:59 -0800 Subject: [PATCH 27/58] Provide an interpreter name example --- docs/tags.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/tags.rst b/docs/tags.rst index 8b05b0f80..4552afc59 100644 --- a/docs/tags.rst +++ b/docs/tags.rst @@ -129,7 +129,8 @@ Reference :param tuple python_version: A two-item tuple representing the compatible version of Python. Defaults to ``sys.version_info[:2]``. - :param str interpreter: The name of the interpreter (if known). + :param str interpreter: The name of the interpreter (if known), e.g. + ``"cp38"``. :param Iterable platforms: Iterable of compatible platforms. Defaults to the platforms compatible with the current system. @@ -160,8 +161,8 @@ Reference The specific tags are those returned by :func:`generic_tags`, but with *interpreter* inferred for the current system if not provided. - :param str interpreter: The name of the interpreter. Defaults to the current - PyPy interpreter. + :param str interpreter: The name of the interpreter (if known), e.g. + ``"cp38"``. :param Iterable abis: Iterable of compatible ABIs. Defaults to the ABIs compatible with the current system. :param Iterable platforms: Iterable of compatible platforms. Defaults to the From b31be53213d743342393c5ca7bc4a46d0f92e8f3 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Fri, 15 Nov 2019 10:58:54 -0800 Subject: [PATCH 28/58] Tweak phrasing on adding "none" ABI automatically --- docs/tags.rst | 4 ++-- packaging/tags.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/tags.rst b/docs/tags.rst index 4552afc59..8518c77be 100644 --- a/docs/tags.rst +++ b/docs/tags.rst @@ -179,8 +179,8 @@ Reference The specific tags generated are: - ``--`` - - ``-none-`` if ``"none"`` was not provided as part of - *abis* + + The ``"none"`` ABI will be added if it was not explicitly provided. :param str interpreter: The name of the interpreter. Defaults to the current PyPy interpreter. diff --git a/packaging/tags.py b/packaging/tags.py index a1cbacfbc..fbfb0dd32 100644 --- a/packaging/tags.py +++ b/packaging/tags.py @@ -285,8 +285,8 @@ def generic_tags( The tags consist of: - -- - - -none- # If 'none' is not specified in 'abis'. + The "none" ABI will be added if it was not explicitly provided. """ warn = _warn_parameter("generic_tags", kwargs) if not interpreter: From 7277b5d1d1adea6ff9b1d194b9f9924b08a8482e Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Fri, 15 Nov 2019 10:59:16 -0800 Subject: [PATCH 29/58] Simplify handling of "none" ABI --- packaging/tags.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packaging/tags.py b/packaging/tags.py index fbfb0dd32..26b78b97b 100644 --- a/packaging/tags.py +++ b/packaging/tags.py @@ -295,12 +295,11 @@ def generic_tags( abis = [_generic_abi()] platforms = list(platforms or _platforms()) abis = list(abis) + if "none" not in abis: + abis.append("none") for abi in abis: for platform_ in platforms: yield Tag(interpreter, abi, platform_) - if "none" not in abis: - for platform_ in platforms: - yield Tag(interpreter, "none", platform_) def _py_interpreter_range(py_version): From 36c2d81191a984c3458e03bcdabf3283444a7609 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Fri, 15 Nov 2019 11:05:38 -0800 Subject: [PATCH 30/58] Tweak how we say mac_platforms() is special --- docs/tags.rst | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/tags.rst b/docs/tags.rst index 8518c77be..2585744dc 100644 --- a/docs/tags.rst +++ b/docs/tags.rst @@ -102,18 +102,19 @@ Reference Yields the platforms tags for macOS. - Specific support for macOS is provided by this module due to how multiple - versions of macOS can be supported by any one version which can be - determined statically. For Windows this information is entirely static and - thus does not require calculating older version support. For Linux, code - must be run on the system itself to determine its compatibility level and - thus cannot be calculated statically. - :param tuple version: A two-item tuple presenting the version of macOS. Defaults to the current system's version. :param str arch: The CPU architecture. Defaults to the architecture of the current system, e.g. ``"x86_64"``. + .. note:: + Equivalent support for the other major platforms is purposefully not + provided: + + - On Windows, platform compatibility is statically specified + - On Linux, code must be run on the system itself to determine + compatibility + .. function:: compatible_tags(python_version=sys.version_info[:2], interpreter=None, platforms=None) From df7751d119fbf44161a44742bae06a59817f8a3e Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Fri, 15 Nov 2019 11:09:37 -0800 Subject: [PATCH 31/58] Rename _platforms() to _platform_tags() --- packaging/tags.py | 8 ++++---- tests/test_tags.py | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packaging/tags.py b/packaging/tags.py index 26b78b97b..fd4c09c9f 100644 --- a/packaging/tags.py +++ b/packaging/tags.py @@ -195,7 +195,7 @@ def cpython_tags( interpreter = "cp{}{}".format(*python_version) if not abis: abis = _cpython_abis(python_version, warn) - platforms = list(platforms or _platforms()) + platforms = list(platforms or _platform_tags()) abis = list(abis) # 'abi3' and 'none' are explicitly handled later. try: @@ -293,7 +293,7 @@ def generic_tags( interpreter = _generic_interpreter(warn=warn) if not abis: abis = [_generic_abi()] - platforms = list(platforms or _platforms()) + platforms = list(platforms or _platform_tags()) abis = list(abis) if "none" not in abis: abis.append("none") @@ -329,7 +329,7 @@ def compatible_tags( - py*-none-any """ if not platforms: - platforms = _platforms() + platforms = _platform_tags() for version in _py_interpreter_range(python_version): for platform_ in platforms: yield Tag(version, "none", platform_) @@ -540,7 +540,7 @@ def _generic_platforms(): yield _normalize_string(distutils.util.get_platform()) -def _platforms(): +def _platform_tags(): # type: () -> Iterator[str] """ Provides the platform tags for this installation. diff --git a/tests/test_tags.py b/tests/test_tags.py index 7d8b3a399..cc8353e7a 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -642,7 +642,7 @@ def test_cpython_tags_defaults(monkeypatch): assert tags.Tag("cp38", "none", "any") in result # platforms with monkeypatch.context() as m: - m.setattr(tags, "_platforms", lambda: ["plat1"]) + m.setattr(tags, "_platform_tags", lambda: ["plat1"]) result = list(tags.cpython_tags((3, 8), abis=["whatever"])) assert tags.Tag("cp38", "whatever", "plat1") in result @@ -715,7 +715,7 @@ def test_generic_tags_defaults(monkeypatch): ] # platforms with monkeypatch.context() as m: - m.setattr(tags, "_platforms", lambda: ["plat"]) + m.setattr(tags, "_platform_tags", lambda: ["plat"]) result = list(tags.generic_tags(interpreter="sillywalk", abis=["none"])) assert result == [tags.Tag("sillywalk", "none", "plat")] @@ -750,8 +750,8 @@ def test_compatible_tags(): ("Generic", "_generic_platforms"), ], ) -def test__platforms(platform_name, dispatch_func, monkeypatch): +def test__platform_tags(platform_name, dispatch_func, monkeypatch): expected = ["sillywalk"] monkeypatch.setattr(platform, "system", lambda: platform_name) monkeypatch.setattr(tags, dispatch_func, lambda: expected) - assert tags._platforms() == expected + assert tags._platform_tags() == expected From 6c81919d7bb9188e77a80afa60340e13d03bfc7c Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Fri, 15 Nov 2019 11:17:56 -0800 Subject: [PATCH 32/58] Clarify what generic_tags() is for --- docs/tags.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/tags.rst b/docs/tags.rst index 2585744dc..2195c160d 100644 --- a/docs/tags.rst +++ b/docs/tags.rst @@ -171,11 +171,11 @@ Reference .. function:: generic_tags(interpreter=None, abis=None, platforms=None, *, warn=False) - Yields the tags for an interpreter in a non-specialized fashion. + Yields the tags for an interpreter in a generic fashion. This function should be used if one of the other interpreter-specific - functions is not appropriate (i.e. not calculating tags for a CPython or - PyPy interpreter). + functions provided by this module is not appropriate (i.e. not calculating + tags for a CPython interpreter). The specific tags generated are: @@ -183,8 +183,8 @@ Reference The ``"none"`` ABI will be added if it was not explicitly provided. - :param str interpreter: The name of the interpreter. Defaults to the current - PyPy interpreter. + :param str interpreter: The name of the interpreter. Defaults to being + calculated. :param Iterable abis: Iterable of compatible ABIs. Defaults to the ABIs compatible with the current system. :param Iterable platforms: Iterable of compatible platforms. Defaults to the From ff384e839a97f714647b7f2b0e603f64d1753cb2 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Fri, 15 Nov 2019 11:19:55 -0800 Subject: [PATCH 33/58] Rename _warn_parameters() --- packaging/tags.py | 8 ++++---- tests/test_tags.py | 19 +++++++++++-------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/packaging/tags.py b/packaging/tags.py index fd4c09c9f..6cf4d57f4 100644 --- a/packaging/tags.py +++ b/packaging/tags.py @@ -105,7 +105,7 @@ def parse_tag(tag): return frozenset(tags) -def _warn_parameter(func_name, kwargs): +def _warn_keyword_parameter(func_name, kwargs): # type: (str, Dict[str, bool]) -> bool """ Backwards-compatibility with Python 2.7 to allow treating 'warn' as keyword-only. @@ -191,7 +191,7 @@ def cpython_tags( If 'abi3' or 'none' are specified in 'abis' then they will be yielded at their normal position and not at the beginning. """ - warn = _warn_parameter("cpython_tags", kwargs) + warn = _warn_keyword_parameter("cpython_tags", kwargs) interpreter = "cp{}{}".format(*python_version) if not abis: abis = _cpython_abis(python_version, warn) @@ -288,7 +288,7 @@ def generic_tags( The "none" ABI will be added if it was not explicitly provided. """ - warn = _warn_parameter("generic_tags", kwargs) + warn = _warn_keyword_parameter("generic_tags", kwargs) if not interpreter: interpreter = _generic_interpreter(warn=warn) if not abis: @@ -571,7 +571,7 @@ def sys_tags(**kwargs): The order of the sequence corresponds to priority order for the interpreter, from most to least important. """ - warn = _warn_parameter("sys_tags", kwargs) + warn = _warn_keyword_parameter("sys_tags", kwargs) interpreter_name = _interpreter_name() if interpreter_name == "cp": diff --git a/tests/test_tags.py b/tests/test_tags.py index cc8353e7a..9c8732dc0 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -201,10 +201,11 @@ def test_macos_version_detection(monkeypatch): version = platform.mac_ver()[0].split(".") expected = "macosx_{major}_{minor}".format(major=version[0], minor=version[1]) platforms = list(tags.mac_platforms(arch="x86_64")) - assert platforms[0].startswith(expected) + assert platforms[0].startswi_warn_keyword_parameter @ pytest.mark.parametrize( + "arch", ["x86_64", "i386"] + ) -@pytest.mark.parametrize("arch", ["x86_64", "i386"]) def test_macos_arch_detection(arch, monkeypatch): if platform.system() != "Darwin" or platform.mac_ver()[2] != arch: monkeypatch.setattr(platform, "mac_ver", lambda: ("10.14", ("", "", ""), arch)) @@ -579,16 +580,18 @@ def test_generic_sys_tags(monkeypatch): assert result[-1] == expected -def test_warn_parameters(): - assert not tags._warn_parameter("test_warn_parameters", {}) - assert not tags._warn_parameter("test_warn_parameters", {"warn": False}) - assert tags._warn_parameter("test_warn_parameters", {"warn": True}) +def test_warn_keyword_parameters(): + assert not tags._warn_keyword_parameter("test_warn_keyword_parameters", {}) + assert not tags._warn_keyword_parameter( + "test_warn_keyword_parameters", {"warn": False} + ) + assert tags._warn_keyword_parameter("test_warn_keyword_parameters", {"warn": True}) message_re = re.compile(r"too_many.+{!r}".format("whatever")) with pytest.raises(TypeError, match=message_re): - tags._warn_parameter("too_many", {"warn": True, "whatever": True}) + tags._warn_keyword_parameter("too_many", {"warn": True, "whatever": True}) message_re = re.compile(r"missing.+{!r}".format("unexpected")) with pytest.raises(TypeError, match=message_re): - tags._warn_parameter("missing", {"unexpected": True}) + tags._warn_keyword_parameter("missing", {"unexpected": True}) def test_cpython_tags_all_args(): From 3f5746908f82d408ca58124e71fe1d0363b68d17 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Fri, 15 Nov 2019 11:20:45 -0800 Subject: [PATCH 34/58] Clarify a comment --- packaging/tags.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packaging/tags.py b/packaging/tags.py index 6cf4d57f4..09fa4b107 100644 --- a/packaging/tags.py +++ b/packaging/tags.py @@ -325,7 +325,7 @@ def compatible_tags( The tags consist of: - py*-none- - - -none-any # If provided. + - -none-any # If 'interpreter' provided. - py*-none-any """ if not platforms: From 1559ffce06e4e8585f04c94e0a4ba35b3a8a599d Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Fri, 15 Nov 2019 11:23:06 -0800 Subject: [PATCH 35/58] Use backticks instead of * for parameters --- packaging/tags.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packaging/tags.py b/packaging/tags.py index 09fa4b107..0dc2da6da 100644 --- a/packaging/tags.py +++ b/packaging/tags.py @@ -325,7 +325,7 @@ def compatible_tags( The tags consist of: - py*-none- - - -none-any # If 'interpreter' provided. + - -none-any # ... if `interpreter` is provided. - py*-none-any """ if not platforms: @@ -383,8 +383,8 @@ def mac_platforms(version=None, arch=None): """ Yields the platform tags for a macOS system. - The *version* parameter is a two-item tuple specifying the macOS version to - generate platform tags for. The *arch* parameter is the CPU architecture to + The `version` parameter is a two-item tuple specifying the macOS version to + generate platform tags for. The `arch` parameter is the CPU architecture to generate platform tags for. Both parameters default to the appropriate value for the current system. """ From bc53e904fd60ad1e375d561069a3b32a4ab3ecc2 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Fri, 15 Nov 2019 11:25:38 -0800 Subject: [PATCH 36/58] Fix a syntax hiccup --- tests/test_tags.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/test_tags.py b/tests/test_tags.py index 9c8732dc0..7c8409bd2 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -201,11 +201,10 @@ def test_macos_version_detection(monkeypatch): version = platform.mac_ver()[0].split(".") expected = "macosx_{major}_{minor}".format(major=version[0], minor=version[1]) platforms = list(tags.mac_platforms(arch="x86_64")) - assert platforms[0].startswi_warn_keyword_parameter @ pytest.mark.parametrize( - "arch", ["x86_64", "i386"] - ) + assert platforms[0].startswith(expected) +@pytest.mark.parametrize("arch", ["x86_64", "i386"]) def test_macos_arch_detection(arch, monkeypatch): if platform.system() != "Darwin" or platform.mac_ver()[2] != arch: monkeypatch.setattr(platform, "mac_ver", lambda: ("10.14", ("", "", ""), arch)) From adea500cf25a03687755cddb9e06821573b4beea Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Fri, 15 Nov 2019 11:26:59 -0800 Subject: [PATCH 37/58] Use `` over * for parameters --- docs/tags.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/tags.rst b/docs/tags.rst index 2195c160d..c574cc6dd 100644 --- a/docs/tags.rst +++ b/docs/tags.rst @@ -65,7 +65,7 @@ Reference .. function:: parse_tag(tag) - Parses the provided *tag* into a set of :class:`Tag` instances. + Parses the provided ``tag`` into a set of :class:`Tag` instances. Returning a set is required due to the possibility that the tag is a `compressed tag set`_, e.g. ``"py2.py3-none-any"`` which supports both @@ -119,12 +119,12 @@ Reference .. function:: compatible_tags(python_version=sys.version_info[:2], interpreter=None, platforms=None) Yields the tags for an interpreter compatible with the Python version - specified by *python_version*. + specified by ``python_version``. The specific tags generated are: - ``py*-none-`` - - ``-none-any`` if *interpreter* is provided + - ``-none-any`` if ``interpreter`` is provided - ``py*-none-any`` :param tuple python_version: A two-item tuple representing the compatible @@ -160,7 +160,7 @@ Reference Yields the tags for the PyPy intrepreter. The specific tags are those returned by :func:`generic_tags`, but with - *interpreter* inferred for the current system if not provided. + ``interpreter`` inferred for the current system if not provided. :param str interpreter: The name of the interpreter (if known), e.g. ``"cp38"``. From 205ee207289e0db009ef06d3bae17b840b2300c2 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Fri, 15 Nov 2019 11:53:32 -0800 Subject: [PATCH 38/58] Drop PyPy-specific code --- docs/tags.rst | 16 +-------------- packaging/tags.py | 34 ------------------------------- tests/test_tags.py | 51 +--------------------------------------------- 3 files changed, 2 insertions(+), 99 deletions(-) diff --git a/docs/tags.rst b/docs/tags.rst index c574cc6dd..1de09ebcd 100644 --- a/docs/tags.rst +++ b/docs/tags.rst @@ -155,23 +155,9 @@ Reference platforms compatible with the current system. :param bool warn: Whether warnings should be logged. Defaults to ``False``. -.. function:: pypy_tags(interpreter=None, abis=None, platforms=None) - - Yields the tags for the PyPy intrepreter. - - The specific tags are those returned by :func:`generic_tags`, but with - ``interpreter`` inferred for the current system if not provided. - - :param str interpreter: The name of the interpreter (if known), e.g. - ``"cp38"``. - :param Iterable abis: Iterable of compatible ABIs. Defaults to the ABIs - compatible with the current system. - :param Iterable platforms: Iterable of compatible platforms. Defaults to the - platforms compatible with the current system. - .. function:: generic_tags(interpreter=None, abis=None, platforms=None, *, warn=False) - Yields the tags for an interpreter in a generic fashion. + Yields the tags for an interpreter which requires no specialization. This function should be used if one of the other interpreter-specific functions provided by this module is not appropriate (i.e. not calculating diff --git a/packaging/tags.py b/packaging/tags.py index 0dc2da6da..4526aa14d 100644 --- a/packaging/tags.py +++ b/packaging/tags.py @@ -225,17 +225,6 @@ def cpython_tags( yield Tag(interpreter, "abi3", platform_) -def _pypy_interpreter(): - # type: () -> str - # Ignoring sys.pypy_version_info for type checking due to typeshed lacking - # the reference to the attribute. - return "pp{py_major}{pypy_major}{pypy_minor}".format( - py_major=sys.version_info[0], - pypy_major=sys.pypy_version_info.major, # type: ignore - pypy_minor=sys.pypy_version_info.minor, # type: ignore - ) - - def _generic_abi(): # type: () -> str abi = sysconfig.get_config_var("SOABI") @@ -245,26 +234,6 @@ def _generic_abi(): return "none" -def pypy_tags( - interpreter=None, # type: Optional[str] - abis=None, # type: Optional[Iterable[str]] - platforms=None, # type: Optional[Iterable[str]] -): - # type: (...) -> Iterator[Tag] - """ - Yields the tags for a PyPy interpreter. - - The tags consist of what is yielded by generic_tags() with a calcuated value - for 'interpreter' if it is not specified. - """ - # The 'interpreter' parameter is necessary to allow the user to distinguish - # between PyPy and PyPy3 (pp and pp3, respectively). - if not interpreter: - interpreter = _pypy_interpreter() - for tag in generic_tags(interpreter, abis, platforms): - yield tag - - def _generic_interpreter(warn=False): # type: (bool) -> str version = _get_config_var("py_version_nodot", warn) @@ -577,9 +546,6 @@ def sys_tags(**kwargs): if interpreter_name == "cp": for tag in cpython_tags(warn=warn): yield tag - elif interpreter_name == "pp": - for tag in pypy_tags(): - yield tag else: for tag in generic_tags(): yield tag diff --git a/tests/test_tags.py b/tests/test_tags.py index 7c8409bd2..2b9d37c59 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -2,8 +2,6 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -import collections - try: import ctypes except ImportError: @@ -129,7 +127,7 @@ def test_parse_tag_multi_platform(): "name,expected", [("CPython", "cp"), ("PyPy", "pp"), ("Jython", "jy"), ("IronPython", "ip")], ) -def test__interpreter_name_cpython(name, expected, mock_interpreter_name): +def test__interpreter_name(name, expected, mock_interpreter_name): mock_interpreter_name(name) assert tags._interpreter_name() == expected @@ -308,35 +306,6 @@ def test_generic_abi(monkeypatch): assert tags._generic_abi() == "none" -def test_pypy_interpreter(monkeypatch): - if hasattr(sys, "pypy_version_info"): - major, minor = sys.pypy_version_info[:2] - else: - attributes = ["major", "minor", "micro", "releaselevel", "serial"] - PyPyVersion = collections.namedtuple("version_info", attributes) - major, minor = 6, 0 - pypy_version = PyPyVersion( - major=major, minor=minor, micro=1, releaselevel="final", serial=0 - ) - monkeypatch.setattr(sys, "pypy_version_info", pypy_version, raising=False) - expected = "pp{}{}{}".format(sys.version_info[0], major, minor) - assert expected == tags._pypy_interpreter() - - -def test_sys_tags_on_mac_pypy(mock_interpreter_name, monkeypatch): - if mock_interpreter_name("PyPy"): - monkeypatch.setattr(tags, "_pypy_interpreter", lambda: "pp360") - if platform.system() != "Darwin": - monkeypatch.setattr(platform, "system", lambda: "Darwin") - monkeypatch.setattr(tags, "mac_platforms", lambda: ["macosx_10_5_x86_64"]) - interpreter = tags._pypy_interpreter() - abi = tags._generic_abi() - platforms = list(tags.mac_platforms()) - result = list(tags.sys_tags()) - assert result[0] == tags.Tag(interpreter, abi, platforms[0]) - assert result[-1] == tags.Tag("py{}0".format(sys.version_info[0]), "none", "any") - - def test_generic_platforms(): platform = distutils.util.get_platform().replace("-", "_") platform = platform.replace(".", "_") @@ -655,24 +624,6 @@ def test_cpython_tags_skip_redundant_abis(abis): assert results == [tags.Tag("cp30", "abi3", "any"), tags.Tag("cp30", "none", "any")] -def test_pypy_tags(monkeypatch): - with monkeypatch.context() as m: - m.setattr(tags, "_pypy_interpreter", lambda: "pp370") - result = list(tags.pypy_tags(abis=["pp370"], platforms=["plat1"])) - assert result == [ - tags.Tag("pp370", "pp370", "plat1"), - tags.Tag("pp370", "none", "plat1"), - ] - - with monkeypatch.context() as m: - m.setattr(tags, "_pypy_interpreter", lambda: "pp370") - result = list(tags.pypy_tags("pp360", ["pp360"], ["plat1"])) - assert result == [ - tags.Tag("pp360", "pp360", "plat1"), - tags.Tag("pp360", "none", "plat1"), - ] - - def test_generic_interpreter(monkeypatch): monkeypatch.setattr(sysconfig, "get_config_var", lambda key: "42") monkeypatch.setattr(tags, "_interpreter_name", lambda: "sillywalk") From ddc23de1ed61bae96eb23d515ea3873ae04397f8 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Fri, 15 Nov 2019 12:02:14 -0800 Subject: [PATCH 39/58] Touch up _generic_abi() --- packaging/tags.py | 8 +++----- tests/test_tags.py | 14 +++++++------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/packaging/tags.py b/packaging/tags.py index 4526aa14d..7139fae3b 100644 --- a/packaging/tags.py +++ b/packaging/tags.py @@ -226,12 +226,10 @@ def cpython_tags( def _generic_abi(): - # type: () -> str + # type: () -> Iterator[str] abi = sysconfig.get_config_var("SOABI") if abi: - return _normalize_string(abi) - else: - return "none" + yield _normalize_string(abi) def _generic_interpreter(warn=False): @@ -261,7 +259,7 @@ def generic_tags( if not interpreter: interpreter = _generic_interpreter(warn=warn) if not abis: - abis = [_generic_abi()] + abis = _generic_abi() platforms = list(platforms or _platform_tags()) abis = list(abis) if "none" not in abis: diff --git a/tests/test_tags.py b/tests/test_tags.py index 2b9d37c59..8f972dbc4 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -291,19 +291,19 @@ def test_sys_tags_on_mac_cpython(mock_interpreter_name, monkeypatch): assert result[-1] == tags.Tag("py{}0".format(sys.version_info[0]), "none", "any") -def test_generic_abi(monkeypatch): +def test__generic_abi(monkeypatch): abi = sysconfig.get_config_var("SOABI") if abi: - abi = abi.replace(".", "_").replace("-", "_") + abi = [abi.replace(".", "_").replace("-", "_")] else: - abi = "none" - assert abi == tags._generic_abi() + abi = [] + assert abi == list(tags._generic_abi()) monkeypatch.setattr(sysconfig, "get_config_var", lambda key: "cpython-37m-darwin") - assert tags._generic_abi() == "cpython_37m_darwin" + assert list(tags._generic_abi()) == ["cpython_37m_darwin"] monkeypatch.setattr(sysconfig, "get_config_var", lambda key: None) - assert tags._generic_abi() == "none" + assert not list(tags._generic_abi()) def test_generic_platforms(): @@ -660,7 +660,7 @@ def test_generic_tags_defaults(monkeypatch): assert result == [tags.Tag("sillywalk", "none", "any")] # abis with monkeypatch.context() as m: - m.setattr(tags, "_generic_abi", lambda: "abi") + m.setattr(tags, "_generic_abi", lambda: iter(["abi"])) result = list(tags.generic_tags(interpreter="sillywalk", platforms=["any"])) assert result == [ tags.Tag("sillywalk", "abi", "any"), From 8f08a0a529e93dd4c5f399223e19c26b9d696315 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Fri, 15 Nov 2019 12:06:30 -0800 Subject: [PATCH 40/58] Add tests that we are getting back iterators --- tests/test_tags.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/tests/test_tags.py b/tests/test_tags.py index 8f972dbc4..9b62deee2 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -14,6 +14,7 @@ import sys import sysconfig import types +import typing import warnings import pretend @@ -273,6 +274,10 @@ def test_cpython_abis_wide_unicode( assert tags._cpython_abis(version) == expected +def test_sys_tags_iterator(): + assert isinstance(tags.sys_tags(), typing.Iterator) + + def test_sys_tags_on_mac_cpython(mock_interpreter_name, monkeypatch): if mock_interpreter_name("CPython"): monkeypatch.setattr(tags, "_cpython_abis", lambda *a: ["cp33m"]) @@ -563,7 +568,9 @@ def test_warn_keyword_parameters(): def test_cpython_tags_all_args(): - result = list(tags.cpython_tags((3, 8), ["cp38d", "cp38"], ["plat1", "plat2"])) + result_iterator = tags.cpython_tags((3, 8), ["cp38d", "cp38"], ["plat1", "plat2"]) + assert isinstance(result_iterator, typing.Iterator) + result = list(result_iterator) assert result == [ tags.Tag("cp38", "cp38d", "plat1"), tags.Tag("cp38", "cp38d", "plat2"), @@ -637,7 +644,9 @@ def test_generic_interpreter_no_config_var(monkeypatch): def test_generic_tags(): - result = list(tags.generic_tags("sillywalk33", ["abi"], ["plat1", "plat2"])) + result_iterator = tags.generic_tags("sillywalk33", ["abi"], ["plat1", "plat2"]) + assert isinstance(result_iterator, typing.Iterator) + result = list(result_iterator) assert result == [ tags.Tag("sillywalk33", "abi", "plat1"), tags.Tag("sillywalk33", "abi", "plat2"), From 5677fc158d8835733f578c00a161151691e63b63 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Fri, 15 Nov 2019 12:37:20 -0800 Subject: [PATCH 41/58] Make Python 2.7 happy --- tests/test_tags.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/test_tags.py b/tests/test_tags.py index 9b62deee2..ca2ded66a 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -2,6 +2,11 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. +try: + import collections.abc as collections_abc +except ImportError: + import collections as collections_abc + try: import ctypes except ImportError: @@ -14,7 +19,6 @@ import sys import sysconfig import types -import typing import warnings import pretend @@ -275,7 +279,7 @@ def test_cpython_abis_wide_unicode( def test_sys_tags_iterator(): - assert isinstance(tags.sys_tags(), typing.Iterator) + assert isinstance(tags.sys_tags(), collections_abc.Iterator) def test_sys_tags_on_mac_cpython(mock_interpreter_name, monkeypatch): @@ -569,7 +573,7 @@ def test_warn_keyword_parameters(): def test_cpython_tags_all_args(): result_iterator = tags.cpython_tags((3, 8), ["cp38d", "cp38"], ["plat1", "plat2"]) - assert isinstance(result_iterator, typing.Iterator) + assert isinstance(result_iterator, collections_abc.Iterator) result = list(result_iterator) assert result == [ tags.Tag("cp38", "cp38d", "plat1"), @@ -645,7 +649,7 @@ def test_generic_interpreter_no_config_var(monkeypatch): def test_generic_tags(): result_iterator = tags.generic_tags("sillywalk33", ["abi"], ["plat1", "plat2"]) - assert isinstance(result_iterator, typing.Iterator) + assert isinstance(result_iterator, collections_abc.Iterator) result = list(result_iterator) assert result == [ tags.Tag("sillywalk33", "abi", "plat1"), From 40b2eb7615bc59ba1b1abe86328101b067f97537 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Fri, 15 Nov 2019 13:07:23 -0800 Subject: [PATCH 42/58] Group tests --- tests/test_tags.py | 1195 ++++++++++++++++++++++---------------------- 1 file changed, 590 insertions(+), 605 deletions(-) diff --git a/tests/test_tags.py b/tests/test_tags.py index ca2ded66a..85e460379 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -56,634 +56,556 @@ def mock(name): return mock -def test_tag_lowercasing(): - tag = tags.Tag("PY3", "None", "ANY") - assert tag.interpreter == "py3" - assert tag.abi == "none" - assert tag.platform == "any" - - -def test_tag_equality(): - args = "py3", "none", "any" - assert tags.Tag(*args) == tags.Tag(*args) - - -def test_tag_equality_fails_with_non_tag(): - assert not tags.Tag("py3", "none", "any") == "non-tag" - - -def test_tag_hashing(example_tag): - tags = {example_tag} # Should not raise TypeError. - assert example_tag in tags - - -def test_tag_hash_equality(example_tag): - equal_tag = tags.Tag("py3", "none", "any") - assert example_tag == equal_tag - assert example_tag.__hash__() == equal_tag.__hash__() - +class TestTag: + def test_lowercasing(self): + tag = tags.Tag("PY3", "None", "ANY") + assert tag.interpreter == "py3" + assert tag.abi == "none" + assert tag.platform == "any" + + def test_equality(self): + args = "py3", "none", "any" + assert tags.Tag(*args) == tags.Tag(*args) + + def test_equality_fails_with_non_tag(self): + assert not tags.Tag("py3", "none", "any") == "non-tag" + + def test_hashing(self, example_tag): + tags = {example_tag} # Should not raise TypeError. + assert example_tag in tags + + def test_hash_equality(self, example_tag): + equal_tag = tags.Tag("py3", "none", "any") + assert example_tag == equal_tag + assert example_tag.__hash__() == equal_tag.__hash__() + + def test_str(self, example_tag): + assert str(example_tag) == "py3-none-any" + + def test_repr(self, example_tag): + assert repr(example_tag) == "".format( + tag_id=id(example_tag) + ) -def test_tag_str(example_tag): - assert str(example_tag) == "py3-none-any" + def test_attribute_access(self, example_tag): + assert example_tag.interpreter == "py3" + assert example_tag.abi == "none" + assert example_tag.platform == "any" -def test_tag_repr(example_tag): - assert repr(example_tag) == "".format( - tag_id=id(example_tag) +def test_warn_keyword_parameters(): + assert not tags._warn_keyword_parameter("test_warn_keyword_parameters", {}) + assert not tags._warn_keyword_parameter( + "test_warn_keyword_parameters", {"warn": False} ) + assert tags._warn_keyword_parameter("test_warn_keyword_parameters", {"warn": True}) + message_re = re.compile(r"too_many.+{!r}".format("whatever")) + with pytest.raises(TypeError, match=message_re): + tags._warn_keyword_parameter("too_many", {"warn": True, "whatever": True}) + message_re = re.compile(r"missing.+{!r}".format("unexpected")) + with pytest.raises(TypeError, match=message_re): + tags._warn_keyword_parameter("missing", {"unexpected": True}) -def test_tag_attribute_access(example_tag): - assert example_tag.interpreter == "py3" - assert example_tag.abi == "none" - assert example_tag.platform == "any" - - -def test_parse_tag_simple(example_tag): - parsed_tags = tags.parse_tag(str(example_tag)) - assert parsed_tags == {example_tag} - - -def test_parse_tag_multi_interpreter(example_tag): - expected = {example_tag, tags.Tag("py2", "none", "any")} - given = tags.parse_tag("py2.py3-none-any") - assert given == expected - - -def test_parse_tag_multi_platform(): - expected = { - tags.Tag("cp37", "cp37m", platform) - for platform in ( - "macosx_10_6_intel", - "macosx_10_9_intel", - "macosx_10_9_x86_64", - "macosx_10_10_intel", - "macosx_10_10_x86_64", +class TestParseTag: + def test_simple(self, example_tag): + parsed_tags = tags.parse_tag(str(example_tag)) + assert parsed_tags == {example_tag} + + def test_multi_interpreter(self, example_tag): + expected = {example_tag, tags.Tag("py2", "none", "any")} + given = tags.parse_tag("py2.py3-none-any") + assert given == expected + + def test_multi_platform(self): + expected = { + tags.Tag("cp37", "cp37m", platform) + for platform in ( + "macosx_10_6_intel", + "macosx_10_9_intel", + "macosx_10_9_x86_64", + "macosx_10_10_intel", + "macosx_10_10_x86_64", + ) + } + given = tags.parse_tag( + "cp37-cp37m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64." + "macosx_10_10_intel.macosx_10_10_x86_64" ) - } - given = tags.parse_tag( - "cp37-cp37m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64." - "macosx_10_10_intel.macosx_10_10_x86_64" + assert given == expected + + +class TestMacOSPlatforms: + @pytest.mark.parametrize( + "arch, is_32bit, expected", + [ + ("i386", True, "i386"), + ("ppc", True, "ppc"), + ("x86_64", False, "x86_64"), + ("x86_64", True, "i386"), + ("ppc64", False, "ppc64"), + ("ppc64", True, "ppc"), + ], ) - assert given == expected - - -@pytest.mark.parametrize( - "name,expected", - [("CPython", "cp"), ("PyPy", "pp"), ("Jython", "jy"), ("IronPython", "ip")], -) -def test__interpreter_name(name, expected, mock_interpreter_name): - mock_interpreter_name(name) - assert tags._interpreter_name() == expected - - -@pytest.mark.parametrize( - "arch, is_32bit, expected", - [ - ("i386", True, "i386"), - ("ppc", True, "ppc"), - ("x86_64", False, "x86_64"), - ("x86_64", True, "i386"), - ("ppc64", False, "ppc64"), - ("ppc64", True, "ppc"), - ], -) -def test_macos_architectures(arch, is_32bit, expected): - assert tags._mac_arch(arch, is_32bit=is_32bit) == expected - - -@pytest.mark.parametrize( - "version,arch,expected", - [ - ((10, 17), "x86_64", ["x86_64", "intel", "fat64", "fat32", "universal"]), - ((10, 4), "x86_64", ["x86_64", "intel", "fat64", "fat32", "universal"]), - ((10, 3), "x86_64", []), - ((10, 17), "i386", ["i386", "intel", "fat32", "fat", "universal"]), - ((10, 4), "i386", ["i386", "intel", "fat32", "fat", "universal"]), - ((10, 3), "i386", []), - ((10, 17), "ppc64", []), - ((10, 6), "ppc64", []), - ((10, 5), "ppc64", ["ppc64", "fat64", "universal"]), - ((10, 3), "ppc64", []), - ((10, 17), "ppc", []), - ((10, 7), "ppc", []), - ((10, 6), "ppc", ["ppc", "fat32", "fat", "universal"]), - ((10, 0), "ppc", ["ppc", "fat32", "fat", "universal"]), - ((11, 0), "riscv", ["riscv", "universal"]), - ], -) -def test_macos_binary_formats(version, arch, expected): - assert tags._mac_binary_formats(version, arch) == expected - - -def test_mac_platforms(): - platforms = list(tags.mac_platforms((10, 5), "x86_64")) - assert platforms == [ - "macosx_10_5_x86_64", - "macosx_10_5_intel", - "macosx_10_5_fat64", - "macosx_10_5_fat32", - "macosx_10_5_universal", - "macosx_10_4_x86_64", - "macosx_10_4_intel", - "macosx_10_4_fat64", - "macosx_10_4_fat32", - "macosx_10_4_universal", - ] - - assert len(list(tags.mac_platforms((10, 17), "x86_64"))) == 14 * 5 - - assert not list(tags.mac_platforms((10, 0), "x86_64")) - - -def test_macos_version_detection(monkeypatch): - if platform.system() != "Darwin": + def test_architectures(self, arch, is_32bit, expected): + assert tags._mac_arch(arch, is_32bit=is_32bit) == expected + + @pytest.mark.parametrize( + "version,arch,expected", + [ + ((10, 17), "x86_64", ["x86_64", "intel", "fat64", "fat32", "universal"]), + ((10, 4), "x86_64", ["x86_64", "intel", "fat64", "fat32", "universal"]), + ((10, 3), "x86_64", []), + ((10, 17), "i386", ["i386", "intel", "fat32", "fat", "universal"]), + ((10, 4), "i386", ["i386", "intel", "fat32", "fat", "universal"]), + ((10, 3), "i386", []), + ((10, 17), "ppc64", []), + ((10, 6), "ppc64", []), + ((10, 5), "ppc64", ["ppc64", "fat64", "universal"]), + ((10, 3), "ppc64", []), + ((10, 17), "ppc", []), + ((10, 7), "ppc", []), + ((10, 6), "ppc", ["ppc", "fat32", "fat", "universal"]), + ((10, 0), "ppc", ["ppc", "fat32", "fat", "universal"]), + ((11, 0), "riscv", ["riscv", "universal"]), + ], + ) + def test_binary_formats(self, version, arch, expected): + assert tags._mac_binary_formats(version, arch) == expected + + def test_version_detection(self, monkeypatch): + if platform.system() != "Darwin": + monkeypatch.setattr( + platform, "mac_ver", lambda: ("10.14", ("", "", ""), "x86_64") + ) + version = platform.mac_ver()[0].split(".") + expected = "macosx_{major}_{minor}".format(major=version[0], minor=version[1]) + platforms = list(tags.mac_platforms(arch="x86_64")) + assert platforms[0].startswith(expected) + + @pytest.mark.parametrize("arch", ["x86_64", "i386"]) + def test_arch_detection(self, arch, monkeypatch): + if platform.system() != "Darwin" or platform.mac_ver()[2] != arch: + monkeypatch.setattr( + platform, "mac_ver", lambda: ("10.14", ("", "", ""), arch) + ) + monkeypatch.setattr(tags, "_mac_arch", lambda *args: arch) + assert next(tags.mac_platforms((10, 14))).endswith(arch) + + def test_mac_platforms(self): + platforms = list(tags.mac_platforms((10, 5), "x86_64")) + assert platforms == [ + "macosx_10_5_x86_64", + "macosx_10_5_intel", + "macosx_10_5_fat64", + "macosx_10_5_fat32", + "macosx_10_5_universal", + "macosx_10_4_x86_64", + "macosx_10_4_intel", + "macosx_10_4_fat64", + "macosx_10_4_fat32", + "macosx_10_4_universal", + ] + + assert len(list(tags.mac_platforms((10, 17), "x86_64"))) == 14 * 5 + + assert not list(tags.mac_platforms((10, 0), "x86_64")) + + +class TestManylinuxPlatform: + def test_is_manylinux_compatible_module_support(self, monkeypatch): + monkeypatch.setattr(tags, "_have_compatible_glibc", lambda *args: False) + module_name = "_manylinux" + module = types.ModuleType(module_name) + module.manylinux1_compatible = True + monkeypatch.setitem(sys.modules, module_name, module) + assert tags._is_manylinux_compatible("manylinux1", (2, 5)) + module.manylinux1_compatible = False + assert not tags._is_manylinux_compatible("manylinux1", (2, 5)) + del module.manylinux1_compatible + assert not tags._is_manylinux_compatible("manylinux1", (2, 5)) + monkeypatch.setitem(sys.modules, module_name, None) + assert not tags._is_manylinux_compatible("manylinux1", (2, 5)) + + def test_is_manylinux_compatible_glibc_support(self, monkeypatch): + monkeypatch.setitem(sys.modules, "_manylinux", None) monkeypatch.setattr( - platform, "mac_ver", lambda: ("10.14", ("", "", ""), "x86_64") + tags, + "_have_compatible_glibc", + lambda major, minor: (major, minor) <= (2, 5), ) - version = platform.mac_ver()[0].split(".") - expected = "macosx_{major}_{minor}".format(major=version[0], minor=version[1]) - platforms = list(tags.mac_platforms(arch="x86_64")) - assert platforms[0].startswith(expected) - - -@pytest.mark.parametrize("arch", ["x86_64", "i386"]) -def test_macos_arch_detection(arch, monkeypatch): - if platform.system() != "Darwin" or platform.mac_ver()[2] != arch: - monkeypatch.setattr(platform, "mac_ver", lambda: ("10.14", ("", "", ""), arch)) - monkeypatch.setattr(tags, "_mac_arch", lambda *args: arch) - assert next(tags.mac_platforms((10, 14))).endswith(arch) - - -@pytest.mark.parametrize( - "py_debug,gettotalrefcount,result", - [(1, False, True), (0, False, False), (None, True, True)], -) -def test_cpython_abis_debug(py_debug, gettotalrefcount, result, monkeypatch): - config = {"Py_DEBUG": py_debug, "WITH_PYMALLOC": 0, "Py_UNICODE_SIZE": 2} - monkeypatch.setattr(sysconfig, "get_config_var", config.__getitem__) - if gettotalrefcount: - monkeypatch.setattr(sys, "gettotalrefcount", 1, raising=False) - expected = ["cp37d" if result else "cp37"] - assert tags._cpython_abis((3, 7)) == expected - - -def test_cpython_abis_debug_file_extension(monkeypatch): - config = {"Py_DEBUG": None} - monkeypatch.setattr(sysconfig, "get_config_var", config.__getitem__) - monkeypatch.delattr(sys, "gettotalrefcount", raising=False) - monkeypatch.setattr(tags, "EXTENSION_SUFFIXES", {"_d.pyd"}) - assert tags._cpython_abis((3, 8)) == ["cp38d", "cp38"] - - -@pytest.mark.parametrize( - "debug,expected", [(True, ["cp38d", "cp38"]), (False, ["cp38"])] -) -def test_cpython_abis_debug_38(debug, expected, monkeypatch): - config = {"Py_DEBUG": debug} - monkeypatch.setattr(sysconfig, "get_config_var", config.__getitem__) - assert tags._cpython_abis((3, 8)) == expected - - -@pytest.mark.parametrize( - "pymalloc,version,result", - [(1, (3, 7), True), (0, (3, 7), False), (None, (3, 7), True), (1, (3, 8), False)], -) -def test_cpython_abis_pymalloc(pymalloc, version, result, monkeypatch): - config = {"Py_DEBUG": 0, "WITH_PYMALLOC": pymalloc, "Py_UNICODE_SIZE": 2} - monkeypatch.setattr(sysconfig, "get_config_var", config.__getitem__) - base_abi = "cp{}{}".format(version[0], version[1]) - expected = [base_abi + "m" if result else base_abi] - assert tags._cpython_abis(version) == expected - - -@pytest.mark.parametrize( - "unicode_size,maxunicode,version,result", - [ - (4, 0x10FFFF, (3, 2), True), - (2, 0xFFFF, (3, 2), False), - (None, 0x10FFFF, (3, 2), True), - (None, 0xFFFF, (3, 2), False), - (4, 0x10FFFF, (3, 3), False), - ], -) -def test_cpython_abis_wide_unicode( - unicode_size, maxunicode, version, result, monkeypatch -): - 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) - base_abi = "cp{}{}".format(version[0], version[1]) - expected = [base_abi + "u" if result else base_abi] - assert tags._cpython_abis(version) == expected - - -def test_sys_tags_iterator(): - assert isinstance(tags.sys_tags(), collections_abc.Iterator) - - -def test_sys_tags_on_mac_cpython(mock_interpreter_name, monkeypatch): - if mock_interpreter_name("CPython"): - monkeypatch.setattr(tags, "_cpython_abis", lambda *a: ["cp33m"]) - if platform.system() != "Darwin": - monkeypatch.setattr(platform, "system", lambda: "Darwin") - monkeypatch.setattr(tags, "mac_platforms", lambda: ["macosx_10_5_x86_64"]) - abis = tags._cpython_abis(sys.version_info[:2]) - platforms = list(tags.mac_platforms()) - result = list(tags.sys_tags()) - assert len(abis) == 1 - assert result[0] == tags.Tag( - "cp{major}{minor}".format(major=sys.version_info[0], minor=sys.version_info[1]), - abis[0], - platforms[0], + assert tags._is_manylinux_compatible("manylinux1", (2, 0)) + assert tags._is_manylinux_compatible("manylinux1", (2, 5)) + assert not tags._is_manylinux_compatible("manylinux1", (2, 10)) + + @pytest.mark.parametrize( + "version_str,major,minor,expected", + [ + ("2.4", 2, 4, True), + ("2.4", 2, 5, False), + ("2.4", 2, 3, True), + ("3.4", 2, 4, False), + ], ) - assert result[-1] == tags.Tag("py{}0".format(sys.version_info[0]), "none", "any") - + def test_check_glibc_version(self, version_str, major, minor, expected): + assert expected == tags._check_glibc_version(version_str, major, minor) + + @pytest.mark.parametrize("version_str", ["glibc-2.4.5", "2"]) + def test_check_glibc_version_warning(self, version_str): + with warnings.catch_warnings(record=True) as w: + tags._check_glibc_version(version_str, 2, 4) + assert len(w) == 1 + assert issubclass(w[0].category, RuntimeWarning) + + @pytest.mark.skipif(not ctypes, reason="requires ctypes") + @pytest.mark.parametrize( + "version_str,expected", + [ + # Be very explicit about bytes and Unicode for Python 2 testing. + (b"2.4", "2.4"), + (u"2.4", "2.4"), + ], + ) + def test_glibc_version_string(self, version_str, expected, monkeypatch): + class LibcVersion: + def __init__(self, version_str): + self.version_str = version_str -def test__generic_abi(monkeypatch): - abi = sysconfig.get_config_var("SOABI") - if abi: - abi = [abi.replace(".", "_").replace("-", "_")] - else: - abi = [] - assert abi == list(tags._generic_abi()) + def __call__(self): + return version_str - monkeypatch.setattr(sysconfig, "get_config_var", lambda key: "cpython-37m-darwin") - assert list(tags._generic_abi()) == ["cpython_37m_darwin"] + class ProcessNamespace: + def __init__(self, libc_version): + self.gnu_get_libc_version = libc_version - monkeypatch.setattr(sysconfig, "get_config_var", lambda key: None) - assert not list(tags._generic_abi()) + process_namespace = ProcessNamespace(LibcVersion(version_str)) + monkeypatch.setattr(ctypes, "CDLL", lambda _: process_namespace) + monkeypatch.setattr(tags, "_glibc_version_string_confstr", lambda: False) + assert tags._glibc_version_string() == expected -def test_generic_platforms(): - platform = distutils.util.get_platform().replace("-", "_") - platform = platform.replace(".", "_") - assert list(tags._generic_platforms()) == [platform] + del process_namespace.gnu_get_libc_version + assert tags._glibc_version_string() is None + def test_glibc_version_string_confstr(self, monkeypatch): + monkeypatch.setattr(os, "confstr", lambda x: "glibc 2.20", raising=False) + assert tags._glibc_version_string_confstr() == "2.20" -def test_sys_tags_on_windows_cpython(mock_interpreter_name, monkeypatch): - if mock_interpreter_name("CPython"): - monkeypatch.setattr(tags, "_cpython_abis", lambda *a: ["cp33m"]) - if platform.system() != "Windows": - monkeypatch.setattr(platform, "system", lambda: "Windows") - monkeypatch.setattr(tags, "_generic_platforms", lambda: ["win_amd64"]) - abis = tags._cpython_abis(sys.version_info[:2]) - platforms = tags._generic_platforms() - result = list(tags.sys_tags()) - interpreter = "cp{major}{minor}".format( - major=sys.version_info[0], minor=sys.version_info[1] + @pytest.mark.parametrize( + "failure", + [pretend.raiser(ValueError), pretend.raiser(OSError), lambda x: "XXX"], ) - assert len(abis) == 1 - expected = tags.Tag(interpreter, abis[0], platforms[0]) - assert result[0] == expected - expected = tags.Tag("py{}0".format(sys.version_info[0]), "none", "any") - assert result[-1] == expected - - -def test_is_manylinux_compatible_module_support(monkeypatch): - monkeypatch.setattr(tags, "_have_compatible_glibc", lambda *args: False) - module_name = "_manylinux" - module = types.ModuleType(module_name) - module.manylinux1_compatible = True - monkeypatch.setitem(sys.modules, module_name, module) - assert tags._is_manylinux_compatible("manylinux1", (2, 5)) - module.manylinux1_compatible = False - assert not tags._is_manylinux_compatible("manylinux1", (2, 5)) - del module.manylinux1_compatible - assert not tags._is_manylinux_compatible("manylinux1", (2, 5)) - monkeypatch.setitem(sys.modules, module_name, None) - assert not tags._is_manylinux_compatible("manylinux1", (2, 5)) - - -def test_is_manylinux_compatible_glibc_support(monkeypatch): - monkeypatch.setitem(sys.modules, "_manylinux", None) - monkeypatch.setattr( - tags, "_have_compatible_glibc", lambda major, minor: (major, minor) <= (2, 5) - ) - assert tags._is_manylinux_compatible("manylinux1", (2, 0)) - assert tags._is_manylinux_compatible("manylinux1", (2, 5)) - assert not tags._is_manylinux_compatible("manylinux1", (2, 10)) - + def test_glibc_version_string_confstr_fail(self, monkeypatch, failure): + monkeypatch.setattr(os, "confstr", failure, raising=False) + assert tags._glibc_version_string_confstr() is None + + def test_glibc_version_string_confstr_missing(self, monkeypatch): + monkeypatch.delattr(os, "confstr", raising=False) + assert tags._glibc_version_string_confstr() is None + + def test_glibc_version_string_ctypes_missing(self, monkeypatch): + monkeypatch.setitem(sys.modules, "ctypes", None) + assert tags._glibc_version_string_ctypes() is None + + def test_get_config_var_does_not_log(self, monkeypatch): + 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): + debug = pretend.call_recorder(lambda *a: None) + monkeypatch.setattr(tags.logger, "debug", debug) + tags._get_config_var("missing", warn=True) + assert debug.calls == [ + pretend.call( + "Config variable '%s' is unset, Python ABI tag may be incorrect", + "missing", + ) + ] + + def test_have_compatible_glibc(self, monkeypatch): + if platform.system() == "Linux": + # Assuming no one is running this test with a version of glibc released in + # 1997. + assert tags._have_compatible_glibc(2, 0) + else: + monkeypatch.setattr(tags, "_glibc_version_string", lambda: "2.4") + assert tags._have_compatible_glibc(2, 4) + monkeypatch.setattr(tags, "_glibc_version_string", lambda: None) + assert not tags._have_compatible_glibc(2, 4) + + def test_linux_platforms_64bit_on_64bit_os(self, is_64bit_os, is_x86, monkeypatch): + if platform.system() != "Linux" or not is_64bit_os or not is_x86: + monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64") + monkeypatch.setattr(tags, "_is_manylinux_compatible", lambda *args: False) + linux_platform = list(tags._linux_platforms(is_32bit=False))[-1] + assert linux_platform == "linux_x86_64" + + def test_linux_platforms_32bit_on_64bit_os(self, is_64bit_os, is_x86, monkeypatch): + if platform.system() != "Linux" or not is_64bit_os or not is_x86: + monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64") + monkeypatch.setattr(tags, "_is_manylinux_compatible", lambda *args: False) + linux_platform = list(tags._linux_platforms(is_32bit=True))[-1] + assert linux_platform == "linux_i686" + + def test_linux_platforms_manylinux_unsupported(self, monkeypatch): + monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64") + monkeypatch.setattr(tags, "_is_manylinux_compatible", lambda *args: False) + linux_platform = list(tags._linux_platforms(is_32bit=False)) + assert linux_platform == ["linux_x86_64"] -@pytest.mark.parametrize( - "version_str,major,minor,expected", - [ - ("2.4", 2, 4, True), - ("2.4", 2, 5, False), - ("2.4", 2, 3, True), - ("3.4", 2, 4, False), - ], -) -def test_check_glibc_version(version_str, major, minor, expected): - assert expected == tags._check_glibc_version(version_str, major, minor) + def test_linux_platforms_manylinux1(self, monkeypatch): + monkeypatch.setattr( + tags, "_is_manylinux_compatible", lambda name, _: name == "manylinux1" + ) + if platform.system() != "Linux": + monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64") + platforms = list(tags._linux_platforms(is_32bit=False)) + assert platforms == ["manylinux1_x86_64", "linux_x86_64"] + def test_linux_platforms_manylinux2010(self, monkeypatch): + monkeypatch.setattr( + tags, "_is_manylinux_compatible", lambda name, _: name == "manylinux2010" + ) + if platform.system() != "Linux": + monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64") + platforms = list(tags._linux_platforms(is_32bit=False)) + expected = ["manylinux2010_x86_64", "manylinux1_x86_64", "linux_x86_64"] + assert platforms == expected -@pytest.mark.parametrize("version_str", ["glibc-2.4.5", "2"]) -def test_check_glibc_version_warning(version_str): - with warnings.catch_warnings(record=True) as w: - tags._check_glibc_version(version_str, 2, 4) - assert len(w) == 1 - assert issubclass(w[0].category, RuntimeWarning) + def test_linux_platforms_manylinux2014(self, monkeypatch): + monkeypatch.setattr( + tags, "_is_manylinux_compatible", lambda name, _: name == "manylinux2014" + ) + if platform.system() != "Linux": + monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64") + platforms = list(tags._linux_platforms(is_32bit=False)) + expected = [ + "manylinux2014_x86_64", + "manylinux2010_x86_64", + "manylinux1_x86_64", + "linux_x86_64", + ] + assert platforms == expected -@pytest.mark.skipif(not ctypes, reason="requires ctypes") @pytest.mark.parametrize( - "version_str,expected", + "platform_name,dispatch_func", [ - # Be very explicit about bytes and Unicode for Python 2 testing. - (b"2.4", "2.4"), - (u"2.4", "2.4"), + ("Darwin", "mac_platforms"), + ("Linux", "_linux_platforms"), + ("Generic", "_generic_platforms"), ], ) -def test_glibc_version_string(version_str, expected, monkeypatch): - class LibcVersion: - def __init__(self, version_str): - self.version_str = version_str - - def __call__(self): - return version_str - - class ProcessNamespace: - def __init__(self, libc_version): - self.gnu_get_libc_version = libc_version - - process_namespace = ProcessNamespace(LibcVersion(version_str)) - monkeypatch.setattr(ctypes, "CDLL", lambda _: process_namespace) - monkeypatch.setattr(tags, "_glibc_version_string_confstr", lambda: False) - - assert tags._glibc_version_string() == expected - - del process_namespace.gnu_get_libc_version - assert tags._glibc_version_string() is None - - -def test_glibc_version_string_confstr(monkeypatch): - monkeypatch.setattr(os, "confstr", lambda x: "glibc 2.20", raising=False) - assert tags._glibc_version_string_confstr() == "2.20" - - -@pytest.mark.parametrize( - "failure", [pretend.raiser(ValueError), pretend.raiser(OSError), lambda x: "XXX"] -) -def test_glibc_version_string_confstr_fail(monkeypatch, failure): - monkeypatch.setattr(os, "confstr", failure, raising=False) - assert tags._glibc_version_string_confstr() is None - - -def test_glibc_version_string_confstr_missing(monkeypatch): - monkeypatch.delattr(os, "confstr", raising=False) - assert tags._glibc_version_string_confstr() is None - - -def test_glibc_version_string_ctypes_missing(monkeypatch): - monkeypatch.setitem(sys.modules, "ctypes", None) - assert tags._glibc_version_string_ctypes() is None - - -def test_get_config_var_does_not_log(monkeypatch): - 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(monkeypatch): - debug = pretend.call_recorder(lambda *a: None) - monkeypatch.setattr(tags.logger, "debug", debug) - tags._get_config_var("missing", warn=True) - assert debug.calls == [ - pretend.call( - "Config variable '%s' is unset, Python ABI tag may be incorrect", "missing" - ) - ] - - -def test_have_compatible_glibc(monkeypatch): - if platform.system() == "Linux": - # Assuming no one is running this test with a version of glibc released in - # 1997. - assert tags._have_compatible_glibc(2, 0) - else: - monkeypatch.setattr(tags, "_glibc_version_string", lambda: "2.4") - assert tags._have_compatible_glibc(2, 4) - monkeypatch.setattr(tags, "_glibc_version_string", lambda: None) - assert not tags._have_compatible_glibc(2, 4) - - -def test_linux_platforms_64bit_on_64bit_os(is_64bit_os, is_x86, monkeypatch): - if platform.system() != "Linux" or not is_64bit_os or not is_x86: - monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64") - monkeypatch.setattr(tags, "_is_manylinux_compatible", lambda *args: False) - linux_platform = list(tags._linux_platforms(is_32bit=False))[-1] - assert linux_platform == "linux_x86_64" - - -def test_linux_platforms_32bit_on_64bit_os(is_64bit_os, is_x86, monkeypatch): - if platform.system() != "Linux" or not is_64bit_os or not is_x86: - monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64") - monkeypatch.setattr(tags, "_is_manylinux_compatible", lambda *args: False) - linux_platform = list(tags._linux_platforms(is_32bit=True))[-1] - assert linux_platform == "linux_i686" - - -def test_linux_platforms_manylinux_unsupported(monkeypatch): - monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64") - monkeypatch.setattr(tags, "_is_manylinux_compatible", lambda *args: False) - linux_platform = list(tags._linux_platforms(is_32bit=False)) - assert linux_platform == ["linux_x86_64"] - - -def test_linux_platforms_manylinux1(monkeypatch): - monkeypatch.setattr( - tags, "_is_manylinux_compatible", lambda name, _: name == "manylinux1" - ) - if platform.system() != "Linux": - monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64") - platforms = list(tags._linux_platforms(is_32bit=False)) - assert platforms == ["manylinux1_x86_64", "linux_x86_64"] +def test__platform_tags(platform_name, dispatch_func, monkeypatch): + expected = ["sillywalk"] + monkeypatch.setattr(platform, "system", lambda: platform_name) + monkeypatch.setattr(tags, dispatch_func, lambda: expected) + assert tags._platform_tags() == expected -def test_linux_platforms_manylinux2010(monkeypatch): - monkeypatch.setattr( - tags, "_is_manylinux_compatible", lambda name, _: name == "manylinux2010" +class TestCPythonABI: + @pytest.mark.parametrize( + "py_debug,gettotalrefcount,result", + [(1, False, True), (0, False, False), (None, True, True)], ) - if platform.system() != "Linux": - monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64") - platforms = list(tags._linux_platforms(is_32bit=False)) - expected = ["manylinux2010_x86_64", "manylinux1_x86_64", "linux_x86_64"] - assert platforms == expected - - -def test_linux_platforms_manylinux2014(monkeypatch): - monkeypatch.setattr( - tags, "_is_manylinux_compatible", lambda name, _: name == "manylinux2014" + def test_debug(self, py_debug, gettotalrefcount, result, monkeypatch): + config = {"Py_DEBUG": py_debug, "WITH_PYMALLOC": 0, "Py_UNICODE_SIZE": 2} + monkeypatch.setattr(sysconfig, "get_config_var", config.__getitem__) + if gettotalrefcount: + monkeypatch.setattr(sys, "gettotalrefcount", 1, raising=False) + expected = ["cp37d" if result else "cp37"] + assert tags._cpython_abis((3, 7)) == expected + + def test_debug_file_extension(self, monkeypatch): + config = {"Py_DEBUG": None} + monkeypatch.setattr(sysconfig, "get_config_var", config.__getitem__) + monkeypatch.delattr(sys, "gettotalrefcount", raising=False) + monkeypatch.setattr(tags, "EXTENSION_SUFFIXES", {"_d.pyd"}) + assert tags._cpython_abis((3, 8)) == ["cp38d", "cp38"] + + @pytest.mark.parametrize( + "debug,expected", [(True, ["cp38d", "cp38"]), (False, ["cp38"])] ) - if platform.system() != "Linux": - monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64") - platforms = list(tags._linux_platforms(is_32bit=False)) - expected = [ - "manylinux2014_x86_64", - "manylinux2010_x86_64", - "manylinux1_x86_64", - "linux_x86_64", - ] - assert platforms == expected - - -def test_sys_tags_linux_cpython(mock_interpreter_name, monkeypatch): - if mock_interpreter_name("CPython"): - monkeypatch.setattr(tags, "_cpython_abis", lambda *a: ["cp33m"]) - if platform.system() != "Linux": - monkeypatch.setattr(platform, "system", lambda: "Linux") - monkeypatch.setattr(tags, "_linux_platforms", lambda: ["linux_x86_64"]) - abis = list(tags._cpython_abis(sys.version_info[:2])) - platforms = list(tags._linux_platforms()) - result = list(tags.sys_tags()) - expected_interpreter = "cp{major}{minor}".format( - major=sys.version_info[0], minor=sys.version_info[1] + def test__debug_cp38(self, debug, expected, monkeypatch): + config = {"Py_DEBUG": debug} + monkeypatch.setattr(sysconfig, "get_config_var", config.__getitem__) + assert tags._cpython_abis((3, 8)) == expected + + @pytest.mark.parametrize( + "pymalloc,version,result", + [ + (1, (3, 7), True), + (0, (3, 7), False), + (None, (3, 7), True), + (1, (3, 8), False), + ], ) - assert len(abis) == 1 - assert result[0] == tags.Tag(expected_interpreter, abis[0], platforms[0]) - expected = tags.Tag("py{}0".format(sys.version_info[0]), "none", "any") - assert result[-1] == expected - - -def test_generic_sys_tags(monkeypatch): - monkeypatch.setattr(platform, "system", lambda: "Generic") - monkeypatch.setattr(tags, "_interpreter_name", lambda: "generic") - - result = list(tags.sys_tags()) - expected = tags.Tag("py{}0".format(sys.version_info[0]), "none", "any") - assert result[-1] == expected - - -def test_warn_keyword_parameters(): - assert not tags._warn_keyword_parameter("test_warn_keyword_parameters", {}) - assert not tags._warn_keyword_parameter( - "test_warn_keyword_parameters", {"warn": False} + def test_pymalloc(self, pymalloc, version, result, monkeypatch): + config = {"Py_DEBUG": 0, "WITH_PYMALLOC": pymalloc, "Py_UNICODE_SIZE": 2} + monkeypatch.setattr(sysconfig, "get_config_var", config.__getitem__) + base_abi = "cp{}{}".format(version[0], version[1]) + expected = [base_abi + "m" if result else base_abi] + assert tags._cpython_abis(version) == expected + + @pytest.mark.parametrize( + "unicode_size,maxunicode,version,result", + [ + (4, 0x10FFFF, (3, 2), True), + (2, 0xFFFF, (3, 2), False), + (None, 0x10FFFF, (3, 2), True), + (None, 0xFFFF, (3, 2), False), + (4, 0x10FFFF, (3, 3), False), + ], ) - assert tags._warn_keyword_parameter("test_warn_keyword_parameters", {"warn": True}) - message_re = re.compile(r"too_many.+{!r}".format("whatever")) - with pytest.raises(TypeError, match=message_re): - tags._warn_keyword_parameter("too_many", {"warn": True, "whatever": True}) - message_re = re.compile(r"missing.+{!r}".format("unexpected")) - with pytest.raises(TypeError, match=message_re): - tags._warn_keyword_parameter("missing", {"unexpected": True}) - - -def test_cpython_tags_all_args(): - result_iterator = tags.cpython_tags((3, 8), ["cp38d", "cp38"], ["plat1", "plat2"]) - assert isinstance(result_iterator, collections_abc.Iterator) - result = list(result_iterator) - assert result == [ - tags.Tag("cp38", "cp38d", "plat1"), - tags.Tag("cp38", "cp38d", "plat2"), - tags.Tag("cp38", "cp38", "plat1"), - tags.Tag("cp38", "cp38", "plat2"), - tags.Tag("cp38", "abi3", "plat1"), - tags.Tag("cp38", "abi3", "plat2"), - tags.Tag("cp38", "none", "plat1"), - tags.Tag("cp38", "none", "plat2"), - tags.Tag("cp37", "abi3", "plat1"), - tags.Tag("cp37", "abi3", "plat2"), - tags.Tag("cp36", "abi3", "plat1"), - tags.Tag("cp36", "abi3", "plat2"), - tags.Tag("cp35", "abi3", "plat1"), - tags.Tag("cp35", "abi3", "plat2"), - tags.Tag("cp34", "abi3", "plat1"), - tags.Tag("cp34", "abi3", "plat2"), - tags.Tag("cp33", "abi3", "plat1"), - tags.Tag("cp33", "abi3", "plat2"), - tags.Tag("cp32", "abi3", "plat1"), - tags.Tag("cp32", "abi3", "plat2"), - ] - result = list(tags.cpython_tags((3, 3), ["cp33m"], ["plat1", "plat2"])) - assert result == [ - tags.Tag("cp33", "cp33m", "plat1"), - tags.Tag("cp33", "cp33m", "plat2"), - tags.Tag("cp33", "abi3", "plat1"), - tags.Tag("cp33", "abi3", "plat2"), - tags.Tag("cp33", "none", "plat1"), - tags.Tag("cp33", "none", "plat2"), - tags.Tag("cp32", "abi3", "plat1"), - tags.Tag("cp32", "abi3", "plat2"), - ] - - -def test_cpython_tags_defaults(monkeypatch): - # python_version - tag = next(tags.cpython_tags(abis=["abi3"], platforms=["any"])) - interpreter = "cp{}{}".format(*sys.version_info[:2]) - assert tag == tags.Tag(interpreter, "abi3", "any") - # abis - with monkeypatch.context() as m: - m.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 - # platforms - with monkeypatch.context() as m: - m.setattr(tags, "_platform_tags", lambda: ["plat1"]) - result = list(tags.cpython_tags((3, 8), abis=["whatever"])) - assert tags.Tag("cp38", "whatever", "plat1") in result - - -@pytest.mark.parametrize("abis", [["abi3"], ["none"]]) -def test_cpython_tags_skip_redundant_abis(abis): - results = list(tags.cpython_tags((3, 0), abis=abis, platforms=["any"])) - assert results == [tags.Tag("cp30", "abi3", "any"), tags.Tag("cp30", "none", "any")] - - -def test_generic_interpreter(monkeypatch): - monkeypatch.setattr(sysconfig, "get_config_var", lambda key: "42") - monkeypatch.setattr(tags, "_interpreter_name", lambda: "sillywalk") - assert tags._generic_interpreter() == "sillywalk42" - - -def test_generic_interpreter_no_config_var(monkeypatch): - monkeypatch.setattr(sysconfig, "get_config_var", lambda _: None) - monkeypatch.setattr(tags, "_interpreter_name", lambda: "sillywalk") - assert tags._generic_interpreter() == "sillywalk{}{}".format(*sys.version_info[:2]) - - -def test_generic_tags(): - result_iterator = tags.generic_tags("sillywalk33", ["abi"], ["plat1", "plat2"]) - assert isinstance(result_iterator, collections_abc.Iterator) - result = list(result_iterator) - assert result == [ - tags.Tag("sillywalk33", "abi", "plat1"), - tags.Tag("sillywalk33", "abi", "plat2"), - tags.Tag("sillywalk33", "none", "plat1"), - tags.Tag("sillywalk33", "none", "plat2"), - ] - - no_abi = list(tags.generic_tags("sillywalk34", ["none"], ["plat1", "plat2"])) - assert no_abi == [ - tags.Tag("sillywalk34", "none", "plat1"), - tags.Tag("sillywalk34", "none", "plat2"), - ] + def test_wide_unicode(self, unicode_size, maxunicode, version, result, monkeypatch): + 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) + base_abi = "cp{}{}".format(version[0], version[1]) + expected = [base_abi + "u" if result else base_abi] + assert tags._cpython_abis(version) == expected + + +class TestCPythonTags: + def test_cpython_tags_all_args(self): + result_iterator = tags.cpython_tags( + (3, 8), ["cp38d", "cp38"], ["plat1", "plat2"] + ) + assert isinstance(result_iterator, collections_abc.Iterator) + result = list(result_iterator) + assert result == [ + tags.Tag("cp38", "cp38d", "plat1"), + tags.Tag("cp38", "cp38d", "plat2"), + tags.Tag("cp38", "cp38", "plat1"), + tags.Tag("cp38", "cp38", "plat2"), + tags.Tag("cp38", "abi3", "plat1"), + tags.Tag("cp38", "abi3", "plat2"), + tags.Tag("cp38", "none", "plat1"), + tags.Tag("cp38", "none", "plat2"), + tags.Tag("cp37", "abi3", "plat1"), + tags.Tag("cp37", "abi3", "plat2"), + tags.Tag("cp36", "abi3", "plat1"), + tags.Tag("cp36", "abi3", "plat2"), + tags.Tag("cp35", "abi3", "plat1"), + tags.Tag("cp35", "abi3", "plat2"), + tags.Tag("cp34", "abi3", "plat1"), + tags.Tag("cp34", "abi3", "plat2"), + tags.Tag("cp33", "abi3", "plat1"), + tags.Tag("cp33", "abi3", "plat2"), + tags.Tag("cp32", "abi3", "plat1"), + tags.Tag("cp32", "abi3", "plat2"), + ] + result = list(tags.cpython_tags((3, 3), ["cp33m"], ["plat1", "plat2"])) + assert result == [ + tags.Tag("cp33", "cp33m", "plat1"), + tags.Tag("cp33", "cp33m", "plat2"), + tags.Tag("cp33", "abi3", "plat1"), + tags.Tag("cp33", "abi3", "plat2"), + tags.Tag("cp33", "none", "plat1"), + tags.Tag("cp33", "none", "plat2"), + tags.Tag("cp32", "abi3", "plat1"), + tags.Tag("cp32", "abi3", "plat2"), + ] + + def test_cpython_tags_defaults(self, monkeypatch): + # python_version + tag = next(tags.cpython_tags(abis=["abi3"], platforms=["any"])) + interpreter = "cp{}{}".format(*sys.version_info[:2]) + assert tag == tags.Tag(interpreter, "abi3", "any") + # abis + with monkeypatch.context() as m: + m.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 + # platforms + with monkeypatch.context() as m: + m.setattr(tags, "_platform_tags", lambda: ["plat1"]) + result = list(tags.cpython_tags((3, 8), abis=["whatever"])) + assert tags.Tag("cp38", "whatever", "plat1") in result + + @pytest.mark.parametrize("abis", [["abi3"], ["none"]]) + def test_cpython_tags_skip_redundant_abis(self, abis): + results = list(tags.cpython_tags((3, 0), abis=abis, platforms=["any"])) + assert results == [ + tags.Tag("cp30", "abi3", "any"), + tags.Tag("cp30", "none", "any"), + ] + + +class TestGenericTags: + def test_generic_interpreter(self, monkeypatch): + monkeypatch.setattr(sysconfig, "get_config_var", lambda key: "42") + monkeypatch.setattr(tags, "_interpreter_name", lambda: "sillywalk") + assert tags._generic_interpreter() == "sillywalk42" + + def test_generic_interpreter_no_config_var(self, monkeypatch): + monkeypatch.setattr(sysconfig, "get_config_var", lambda _: None) + monkeypatch.setattr(tags, "_interpreter_name", lambda: "sillywalk") + assert tags._generic_interpreter() == "sillywalk{}{}".format( + *sys.version_info[:2] + ) + def test__generic_abi(self, monkeypatch): + abi = sysconfig.get_config_var("SOABI") + if abi: + abi = [abi.replace(".", "_").replace("-", "_")] + else: + abi = [] + assert abi == list(tags._generic_abi()) -def test_generic_tags_defaults(monkeypatch): - # interpreter - with monkeypatch.context() as m: - m.setattr(tags, "_generic_interpreter", lambda warn: "sillywalk") - result = list(tags.generic_tags(abis=["none"], platforms=["any"])) - assert result == [tags.Tag("sillywalk", "none", "any")] - # abis - with monkeypatch.context() as m: - m.setattr(tags, "_generic_abi", lambda: iter(["abi"])) - result = list(tags.generic_tags(interpreter="sillywalk", platforms=["any"])) - assert result == [ - tags.Tag("sillywalk", "abi", "any"), - tags.Tag("sillywalk", "none", "any"), - ] - # platforms - with monkeypatch.context() as m: - m.setattr(tags, "_platform_tags", lambda: ["plat"]) - result = list(tags.generic_tags(interpreter="sillywalk", abis=["none"])) - assert result == [tags.Tag("sillywalk", "none", "plat")] + monkeypatch.setattr( + sysconfig, "get_config_var", lambda key: "cpython-37m-darwin" + ) + assert list(tags._generic_abi()) == ["cpython_37m_darwin"] + + monkeypatch.setattr(sysconfig, "get_config_var", lambda key: None) + assert not list(tags._generic_abi()) + + def test_generic_platforms(self): + platform = distutils.util.get_platform().replace("-", "_") + platform = platform.replace(".", "_") + assert list(tags._generic_platforms()) == [platform] + + def test_generic_tags(self): + result_iterator = tags.generic_tags("sillywalk33", ["abi"], ["plat1", "plat2"]) + assert isinstance(result_iterator, collections_abc.Iterator) + result = list(result_iterator) + assert result == [ + tags.Tag("sillywalk33", "abi", "plat1"), + tags.Tag("sillywalk33", "abi", "plat2"), + tags.Tag("sillywalk33", "none", "plat1"), + tags.Tag("sillywalk33", "none", "plat2"), + ] + + no_abi = list(tags.generic_tags("sillywalk34", ["none"], ["plat1", "plat2"])) + assert no_abi == [ + tags.Tag("sillywalk34", "none", "plat1"), + tags.Tag("sillywalk34", "none", "plat2"), + ] + + def test_generic_tags_defaults(self, monkeypatch): + # interpreter + with monkeypatch.context() as m: + m.setattr(tags, "_generic_interpreter", lambda warn: "sillywalk") + result = list(tags.generic_tags(abis=["none"], platforms=["any"])) + assert result == [tags.Tag("sillywalk", "none", "any")] + # abis + with monkeypatch.context() as m: + m.setattr(tags, "_generic_abi", lambda: iter(["abi"])) + result = list(tags.generic_tags(interpreter="sillywalk", platforms=["any"])) + assert result == [ + tags.Tag("sillywalk", "abi", "any"), + tags.Tag("sillywalk", "none", "any"), + ] + # platforms + with monkeypatch.context() as m: + m.setattr(tags, "_platform_tags", lambda: ["plat"]) + result = list(tags.generic_tags(interpreter="sillywalk", abis=["none"])) + assert result == [tags.Tag("sillywalk", "none", "plat")] def test_compatible_tags(): @@ -708,16 +630,79 @@ def test_compatible_tags(): ] -@pytest.mark.parametrize( - "platform_name,dispatch_func", - [ - ("Darwin", "mac_platforms"), - ("Linux", "_linux_platforms"), - ("Generic", "_generic_platforms"), - ], -) -def test__platform_tags(platform_name, dispatch_func, monkeypatch): - expected = ["sillywalk"] - monkeypatch.setattr(platform, "system", lambda: platform_name) - monkeypatch.setattr(tags, dispatch_func, lambda: expected) - assert tags._platform_tags() == expected +class TestSysTags: + @pytest.mark.parametrize( + "name,expected", + [("CPython", "cp"), ("PyPy", "pp"), ("Jython", "jy"), ("IronPython", "ip")], + ) + def test__interpreter_name(self, name, expected, mock_interpreter_name): + mock_interpreter_name(name) + assert tags._interpreter_name() == expected + + def test_iterator(self): + assert isinstance(tags.sys_tags(), collections_abc.Iterator) + + def test_mac_cpython(self, mock_interpreter_name, monkeypatch): + if mock_interpreter_name("CPython"): + monkeypatch.setattr(tags, "_cpython_abis", lambda *a: ["cp33m"]) + if platform.system() != "Darwin": + monkeypatch.setattr(platform, "system", lambda: "Darwin") + monkeypatch.setattr(tags, "mac_platforms", lambda: ["macosx_10_5_x86_64"]) + abis = tags._cpython_abis(sys.version_info[:2]) + platforms = list(tags.mac_platforms()) + result = list(tags.sys_tags()) + assert len(abis) == 1 + assert result[0] == tags.Tag( + "cp{major}{minor}".format( + major=sys.version_info[0], minor=sys.version_info[1] + ), + abis[0], + platforms[0], + ) + assert result[-1] == tags.Tag( + "py{}0".format(sys.version_info[0]), "none", "any" + ) + + def test_windows_cpython(self, mock_interpreter_name, monkeypatch): + if mock_interpreter_name("CPython"): + monkeypatch.setattr(tags, "_cpython_abis", lambda *a: ["cp33m"]) + if platform.system() != "Windows": + monkeypatch.setattr(platform, "system", lambda: "Windows") + monkeypatch.setattr(tags, "_generic_platforms", lambda: ["win_amd64"]) + abis = tags._cpython_abis(sys.version_info[:2]) + platforms = tags._generic_platforms() + result = list(tags.sys_tags()) + interpreter = "cp{major}{minor}".format( + major=sys.version_info[0], minor=sys.version_info[1] + ) + assert len(abis) == 1 + expected = tags.Tag(interpreter, abis[0], platforms[0]) + assert result[0] == expected + expected = tags.Tag("py{}0".format(sys.version_info[0]), "none", "any") + assert result[-1] == expected + + def test_linux_cpython(self, mock_interpreter_name, monkeypatch): + if mock_interpreter_name("CPython"): + monkeypatch.setattr(tags, "_cpython_abis", lambda *a: ["cp33m"]) + if platform.system() != "Linux": + monkeypatch.setattr(platform, "system", lambda: "Linux") + monkeypatch.setattr(tags, "_linux_platforms", lambda: ["linux_x86_64"]) + abis = list(tags._cpython_abis(sys.version_info[:2])) + platforms = list(tags._linux_platforms()) + result = list(tags.sys_tags()) + expected_interpreter = "cp{major}{minor}".format( + major=sys.version_info[0], minor=sys.version_info[1] + ) + assert len(abis) == 1 + assert result[0] == tags.Tag(expected_interpreter, abis[0], platforms[0]) + expected = tags.Tag("py{}0".format(sys.version_info[0]), "none", "any") + assert result[-1] == expected + + def test_generic(self, monkeypatch): + monkeypatch.setattr(platform, "system", lambda: "Generic") + monkeypatch.setattr(tags, "_interpreter_name", lambda: "generic") + + result = list(tags.sys_tags()) + expected = tags.Tag("py{}0".format(sys.version_info[0]), "none", "any") + assert result[-1] == expected + From 443a20986f1674ffcd0126bbb65732d01ea5b1c8 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Fri, 15 Nov 2019 13:33:10 -0800 Subject: [PATCH 43/58] Remove unnecessary trailing newline --- tests/test_tags.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_tags.py b/tests/test_tags.py index 85e460379..b705e57e9 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -705,4 +705,3 @@ def test_generic(self, monkeypatch): result = list(tags.sys_tags()) expected = tags.Tag("py{}0".format(sys.version_info[0]), "none", "any") assert result[-1] == expected - From 20311bb076f514f9964c1a2827da5ce400c720dd Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Fri, 15 Nov 2019 14:31:43 -0800 Subject: [PATCH 44/58] Make tests more granular --- tests/test_tags.py | 174 ++++++++++++++++++++++++++++----------------- 1 file changed, 107 insertions(+), 67 deletions(-) diff --git a/tests/test_tags.py b/tests/test_tags.py index b705e57e9..1ebf52c68 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -42,6 +42,15 @@ def is_64bit_os(): return platform.architecture()[0] == "64bit" +@pytest.fixture +def manylinux_module(monkeypatch): + monkeypatch.setattr(tags, "_have_compatible_glibc", lambda *args: False) + module_name = "_manylinux" + module = types.ModuleType(module_name) + monkeypatch.setitem(sys.modules, module_name, module) + return module + + @pytest.fixture def mock_interpreter_name(monkeypatch): def mock(name): @@ -76,7 +85,7 @@ def test_hashing(self, example_tag): def test_hash_equality(self, example_tag): equal_tag = tags.Tag("py3", "none", "any") - assert example_tag == equal_tag + assert example_tag == equal_tag # Sanity check. assert example_tag.__hash__() == equal_tag.__hash__() def test_str(self, example_tag): @@ -93,18 +102,29 @@ def test_attribute_access(self, example_tag): assert example_tag.platform == "any" -def test_warn_keyword_parameters(): - assert not tags._warn_keyword_parameter("test_warn_keyword_parameters", {}) - assert not tags._warn_keyword_parameter( - "test_warn_keyword_parameters", {"warn": False} - ) - assert tags._warn_keyword_parameter("test_warn_keyword_parameters", {"warn": True}) - message_re = re.compile(r"too_many.+{!r}".format("whatever")) - with pytest.raises(TypeError, match=message_re): - tags._warn_keyword_parameter("too_many", {"warn": True, "whatever": True}) - message_re = re.compile(r"missing.+{!r}".format("unexpected")) - with pytest.raises(TypeError, match=message_re): - tags._warn_keyword_parameter("missing", {"unexpected": True}) +class TestWarnKeywordOnlyParameter: + def test_no_argument(self): + assert not tags._warn_keyword_parameter("test_warn_keyword_parameters", {}) + + def test_false(self): + assert not tags._warn_keyword_parameter( + "test_warn_keyword_parameters", {"warn": False} + ) + + def test_true(self): + assert tags._warn_keyword_parameter( + "test_warn_keyword_parameters", {"warn": True} + ) + + def test_too_many_arguments(self): + message_re = re.compile(r"too_many.+{!r}".format("whatever")) + with pytest.raises(TypeError, match=message_re): + tags._warn_keyword_parameter("too_many", {"warn": True, "whatever": True}) + + def test_wrong_argument(self): + message_re = re.compile(r"missing.+{!r}".format("unexpected")) + with pytest.raises(TypeError, match=message_re): + tags._warn_keyword_parameter("missing", {"unexpected": True}) class TestParseTag: @@ -213,30 +233,40 @@ def test_mac_platforms(self): class TestManylinuxPlatform: - def test_is_manylinux_compatible_module_support(self, monkeypatch): - monkeypatch.setattr(tags, "_have_compatible_glibc", lambda *args: False) - module_name = "_manylinux" - module = types.ModuleType(module_name) - module.manylinux1_compatible = True - monkeypatch.setitem(sys.modules, module_name, module) + def test_module_declaration_true(self, manylinux_module): + manylinux_module.manylinux1_compatible = True assert tags._is_manylinux_compatible("manylinux1", (2, 5)) - module.manylinux1_compatible = False + + def test_module_declaration_false(self, manylinux_module): + manylinux_module.manylinux1_compatible = False assert not tags._is_manylinux_compatible("manylinux1", (2, 5)) - del module.manylinux1_compatible + + def test_module_declaration_missing_attribute(self, manylinux_module): + try: + del manylinux_module.manylinux1_compatible + except AttributeError: + pass assert not tags._is_manylinux_compatible("manylinux1", (2, 5)) - monkeypatch.setitem(sys.modules, module_name, None) + + def test_is_manylinux_compatible_module_support( + self, manylinux_module, monkeypatch + ): + monkeypatch.setitem(sys.modules, manylinux_module.__name__, None) assert not tags._is_manylinux_compatible("manylinux1", (2, 5)) - def test_is_manylinux_compatible_glibc_support(self, monkeypatch): + @pytest.mark.parametrize( + "version,compatible", (((2, 0), True), ((2, 5), True), ((2, 10), False)) + ) + def test_is_manylinux_compatible_glibc_support( + self, version, compatible, monkeypatch + ): monkeypatch.setitem(sys.modules, "_manylinux", None) monkeypatch.setattr( tags, "_have_compatible_glibc", lambda major, minor: (major, minor) <= (2, 5), ) - assert tags._is_manylinux_compatible("manylinux1", (2, 0)) - assert tags._is_manylinux_compatible("manylinux1", (2, 5)) - assert not tags._is_manylinux_compatible("manylinux1", (2, 10)) + assert bool(tags._is_manylinux_compatible("manylinux1", version)) == compatible @pytest.mark.parametrize( "version_str,major,minor,expected", @@ -324,14 +354,17 @@ def test_get_config_var_does_log(self, monkeypatch): ) ] + @pytest.mark.skipif(platform.system() != "Linux", reason="requires Linux") + def test_have_compatible_glibc_linux(self): + # Assuming no one is running this test with a version of glibc released in + # 1997. + assert tags._have_compatible_glibc(2, 0) + def test_have_compatible_glibc(self, monkeypatch): - if platform.system() == "Linux": - # Assuming no one is running this test with a version of glibc released in - # 1997. - assert tags._have_compatible_glibc(2, 0) - else: - monkeypatch.setattr(tags, "_glibc_version_string", lambda: "2.4") - assert tags._have_compatible_glibc(2, 4) + monkeypatch.setattr(tags, "_glibc_version_string", lambda: "2.4") + assert tags._have_compatible_glibc(2, 4) + + def test_glibc_version_string_none(self, monkeypatch): monkeypatch.setattr(tags, "_glibc_version_string", lambda: None) assert not tags._have_compatible_glibc(2, 4) @@ -469,11 +502,16 @@ def test_wide_unicode(self, unicode_size, maxunicode, version, result, monkeypat class TestCPythonTags: - def test_cpython_tags_all_args(self): + def test_iterator_returned(self): + result_iterator = tags.cpython_tags( + (3, 8), ["cp38d", "cp38"], ["plat1", "plat2"] + ) + isinstance(result_iterator, collections_abc.Iterator) + + def test_all_args(self): result_iterator = tags.cpython_tags( (3, 8), ["cp38d", "cp38"], ["plat1", "plat2"] ) - assert isinstance(result_iterator, collections_abc.Iterator) result = list(result_iterator) assert result == [ tags.Tag("cp38", "cp38d", "plat1"), @@ -509,26 +547,25 @@ def test_cpython_tags_all_args(self): tags.Tag("cp32", "abi3", "plat2"), ] - def test_cpython_tags_defaults(self, monkeypatch): - # python_version + def test_python_version_defaults(self): tag = next(tags.cpython_tags(abis=["abi3"], platforms=["any"])) interpreter = "cp{}{}".format(*sys.version_info[:2]) assert tag == tags.Tag(interpreter, "abi3", "any") - # abis - with monkeypatch.context() as m: - m.setattr(tags, "_cpython_abis", lambda _1, _2: ["cp38"]) - result = list(tags.cpython_tags((3, 8), platforms=["any"])) + + def test_abi_defaults(self, monkeypatch): + 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 - # platforms - with monkeypatch.context() as m: - m.setattr(tags, "_platform_tags", lambda: ["plat1"]) - result = list(tags.cpython_tags((3, 8), abis=["whatever"])) + + def test_platforms_defaults(self, monkeypatch): + monkeypatch.setattr(tags, "_platform_tags", lambda: ["plat1"]) + result = list(tags.cpython_tags((3, 8), abis=["whatever"])) assert tags.Tag("cp38", "whatever", "plat1") in result @pytest.mark.parametrize("abis", [["abi3"], ["none"]]) - def test_cpython_tags_skip_redundant_abis(self, abis): + def test_skip_redundant_abis(self, abis): results = list(tags.cpython_tags((3, 0), abis=abis, platforms=["any"])) assert results == [ tags.Tag("cp30", "abi3", "any"), @@ -549,19 +586,20 @@ def test_generic_interpreter_no_config_var(self, monkeypatch): *sys.version_info[:2] ) - def test__generic_abi(self, monkeypatch): - abi = sysconfig.get_config_var("SOABI") - if abi: - abi = [abi.replace(".", "_").replace("-", "_")] - else: - abi = [] - assert abi == list(tags._generic_abi()) + @pytest.mark.skipif( + not sysconfig.get_config_var("SOABI"), reason="SOABI not defined" + ) + def test__generic_abi_soabi_provided(self): + abi = sysconfig.get_config_var("SOABI").replace(".", "_").replace("-", "_") + assert [abi] == list(tags._generic_abi()) + def test__generic_abi(self, monkeypatch): monkeypatch.setattr( sysconfig, "get_config_var", lambda key: "cpython-37m-darwin" ) assert list(tags._generic_abi()) == ["cpython_37m_darwin"] + def test__generic_abi_no_soabi(self, monkeypatch): monkeypatch.setattr(sysconfig, "get_config_var", lambda key: None) assert not list(tags._generic_abi()) @@ -570,9 +608,12 @@ def test_generic_platforms(self): platform = platform.replace(".", "_") assert list(tags._generic_platforms()) == [platform] - def test_generic_tags(self): + def test_iterator_returned(self): result_iterator = tags.generic_tags("sillywalk33", ["abi"], ["plat1", "plat2"]) assert isinstance(result_iterator, collections_abc.Iterator) + + def test_all_args(self): + result_iterator = tags.generic_tags("sillywalk33", ["abi"], ["plat1", "plat2"]) result = list(result_iterator) assert result == [ tags.Tag("sillywalk33", "abi", "plat1"), @@ -581,30 +622,29 @@ def test_generic_tags(self): tags.Tag("sillywalk33", "none", "plat2"), ] + def test_none_abi_provided(self): no_abi = list(tags.generic_tags("sillywalk34", ["none"], ["plat1", "plat2"])) assert no_abi == [ tags.Tag("sillywalk34", "none", "plat1"), tags.Tag("sillywalk34", "none", "plat2"), ] - def test_generic_tags_defaults(self, monkeypatch): - # interpreter - with monkeypatch.context() as m: - m.setattr(tags, "_generic_interpreter", lambda warn: "sillywalk") - result = list(tags.generic_tags(abis=["none"], platforms=["any"])) + def test_interpreter_default(self, monkeypatch): + monkeypatch.setattr(tags, "_generic_interpreter", lambda warn: "sillywalk") + result = list(tags.generic_tags(abis=["none"], platforms=["any"])) assert result == [tags.Tag("sillywalk", "none", "any")] - # abis - with monkeypatch.context() as m: - m.setattr(tags, "_generic_abi", lambda: iter(["abi"])) - result = list(tags.generic_tags(interpreter="sillywalk", platforms=["any"])) + + def test_abis_default(self, monkeypatch): + monkeypatch.setattr(tags, "_generic_abi", lambda: iter(["abi"])) + result = list(tags.generic_tags(interpreter="sillywalk", platforms=["any"])) assert result == [ tags.Tag("sillywalk", "abi", "any"), tags.Tag("sillywalk", "none", "any"), ] - # platforms - with monkeypatch.context() as m: - m.setattr(tags, "_platform_tags", lambda: ["plat"]) - result = list(tags.generic_tags(interpreter="sillywalk", abis=["none"])) + + def test_platforms_default(self, monkeypatch): + monkeypatch.setattr(tags, "_platform_tags", lambda: ["plat"]) + result = list(tags.generic_tags(interpreter="sillywalk", abis=["none"])) assert result == [tags.Tag("sillywalk", "none", "plat")] From bb54afdfb0a44fb3e0257de630958245f109462c Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Fri, 22 Nov 2019 12:08:43 -0800 Subject: [PATCH 45/58] Touch up the docs --- docs/tags.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/tags.rst b/docs/tags.rst index 1de09ebcd..988718659 100644 --- a/docs/tags.rst +++ b/docs/tags.rst @@ -38,6 +38,7 @@ Reference A dictionary mapping interpreter names to their `abbreviation codes`_ (e.g. ``"cpython"`` is ``"cp"``). All interpreter names are lower-case. + .. class:: Tag(interpreter, abi, platform) A representation of the tag triple for a wheel. Instances are considered @@ -100,7 +101,7 @@ Reference .. function:: mac_platforms(version=None, arch=None) - Yields the platforms tags for macOS. + Yields the :attr:`~Tag.platform` tags for macOS. :param tuple version: A two-item tuple presenting the version of macOS. Defaults to the current system's version. @@ -131,7 +132,7 @@ Reference version of Python. Defaults to ``sys.version_info[:2]``. :param str interpreter: The name of the interpreter (if known), e.g. - ``"cp38"``. + ``"cp38"``. Defaults to the current interpreter. :param Iterable platforms: Iterable of compatible platforms. Defaults to the platforms compatible with the current system. From 53290722794d172cbeec87253afd22c90c835c3e Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Fri, 22 Nov 2019 12:09:24 -0800 Subject: [PATCH 46/58] Expose interpreter_name() --- docs/tags.rst | 7 +++++++ packaging/tags.py | 8 ++++---- tests/test_tags.py | 10 +++++----- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/docs/tags.rst b/docs/tags.rst index 988718659..ed11d6343 100644 --- a/docs/tags.rst +++ b/docs/tags.rst @@ -99,6 +99,13 @@ Reference :param bool warn: Whether warnings should be logged. Defaults to ``False``. +.. function:: interpreter_name() + + Returns the running interpreter's name. + + This typically acts as the prefix to the :attr:`~Tag.interpreter` tag. + + .. function:: mac_platforms(version=None, arch=None) Yields the :attr:`~Tag.platform` tags for macOS. diff --git a/packaging/tags.py b/packaging/tags.py index 7139fae3b..9d2ebd456 100644 --- a/packaging/tags.py +++ b/packaging/tags.py @@ -237,7 +237,7 @@ def _generic_interpreter(warn=False): version = _get_config_var("py_version_nodot", warn) if not version: version = "".join(map(str, sys.version_info[:2])) - return "{name}{version}".format(name=_interpreter_name(), version=version) + return "{name}{version}".format(name=interpreter_name(), version=version) def generic_tags( @@ -520,7 +520,7 @@ def _platform_tags(): return _generic_platforms() -def _interpreter_name(): +def interpreter_name(): # type: () -> str try: name = sys.implementation.name # type: ignore @@ -540,8 +540,8 @@ def sys_tags(**kwargs): """ warn = _warn_keyword_parameter("sys_tags", kwargs) - interpreter_name = _interpreter_name() - if interpreter_name == "cp": + interp_name = interpreter_name() + if interp_name == "cp": for tag in cpython_tags(warn=warn): yield tag else: diff --git a/tests/test_tags.py b/tests/test_tags.py index 1ebf52c68..75573e4ab 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -576,12 +576,12 @@ def test_skip_redundant_abis(self, abis): class TestGenericTags: def test_generic_interpreter(self, monkeypatch): monkeypatch.setattr(sysconfig, "get_config_var", lambda key: "42") - monkeypatch.setattr(tags, "_interpreter_name", lambda: "sillywalk") + monkeypatch.setattr(tags, "interpreter_name", lambda: "sillywalk") assert tags._generic_interpreter() == "sillywalk42" def test_generic_interpreter_no_config_var(self, monkeypatch): monkeypatch.setattr(sysconfig, "get_config_var", lambda _: None) - monkeypatch.setattr(tags, "_interpreter_name", lambda: "sillywalk") + monkeypatch.setattr(tags, "interpreter_name", lambda: "sillywalk") assert tags._generic_interpreter() == "sillywalk{}{}".format( *sys.version_info[:2] ) @@ -675,9 +675,9 @@ class TestSysTags: "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, expected, mock_interpreter_name): mock_interpreter_name(name) - assert tags._interpreter_name() == expected + assert tags.interpreter_name() == expected def test_iterator(self): assert isinstance(tags.sys_tags(), collections_abc.Iterator) @@ -740,7 +740,7 @@ def test_linux_cpython(self, mock_interpreter_name, monkeypatch): def test_generic(self, monkeypatch): monkeypatch.setattr(platform, "system", lambda: "Generic") - monkeypatch.setattr(tags, "_interpreter_name", lambda: "generic") + monkeypatch.setattr(tags, "interpreter_name", lambda: "generic") result = list(tags.sys_tags()) expected = tags.Tag("py{}0".format(sys.version_info[0]), "none", "any") From c51ca1542cd6eb91bb41f26f6b03b20fc5bfe06f Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Fri, 22 Nov 2019 13:39:29 -0800 Subject: [PATCH 47/58] Be more strict about default ABIs --- packaging/tags.py | 18 ++++++++---------- tests/test_tags.py | 7 ++++--- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/packaging/tags.py b/packaging/tags.py index 9d2ebd456..f87aae8db 100644 --- a/packaging/tags.py +++ b/packaging/tags.py @@ -193,19 +193,17 @@ def cpython_tags( """ warn = _warn_keyword_parameter("cpython_tags", kwargs) interpreter = "cp{}{}".format(*python_version) - if not abis: + if abis is None: abis = _cpython_abis(python_version, warn) platforms = list(platforms or _platform_tags()) abis = list(abis) # 'abi3' and 'none' are explicitly handled later. - try: - abis.remove("abi3") - except ValueError: - pass - try: - abis.remove("none") - except ValueError: - pass + for explicit_abi in ("abi3", "none"): + try: + abis.remove(explicit_abi) + except ValueError: + pass + for abi in abis: for platform_ in platforms: yield Tag(interpreter, abi, platform_) @@ -258,7 +256,7 @@ def generic_tags( warn = _warn_keyword_parameter("generic_tags", kwargs) if not interpreter: interpreter = _generic_interpreter(warn=warn) - if not abis: + if abis is None: abis = _generic_abi() platforms = list(platforms or _platform_tags()) abis = list(abis) diff --git a/tests/test_tags.py b/tests/test_tags.py index 75573e4ab..99e0f9946 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -564,7 +564,7 @@ def test_platforms_defaults(self, monkeypatch): result = list(tags.cpython_tags((3, 8), abis=["whatever"])) assert tags.Tag("cp38", "whatever", "plat1") in result - @pytest.mark.parametrize("abis", [["abi3"], ["none"]]) + @pytest.mark.parametrize("abis", [[], ["abi3"], ["none"]]) def test_skip_redundant_abis(self, abis): results = list(tags.cpython_tags((3, 0), abis=abis, platforms=["any"])) assert results == [ @@ -622,8 +622,9 @@ def test_all_args(self): tags.Tag("sillywalk33", "none", "plat2"), ] - def test_none_abi_provided(self): - no_abi = list(tags.generic_tags("sillywalk34", ["none"], ["plat1", "plat2"])) + @pytest.mark.parametrize("abi", [[], ["none"]]) + def test_abi_unspecified(self, abi): + no_abi = list(tags.generic_tags("sillywalk34", abi, ["plat1", "plat2"])) assert no_abi == [ tags.Tag("sillywalk34", "none", "plat1"), tags.Tag("sillywalk34", "none", "plat2"), From 7dc1b66d06c65fda01fd11877703f6b9d7beac67 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Fri, 22 Nov 2019 13:43:37 -0800 Subject: [PATCH 48/58] Use `None` as the default for python_version --- docs/tags.rst | 6 +++--- packaging/tags.py | 12 +++++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/docs/tags.rst b/docs/tags.rst index ed11d6343..633d7fe26 100644 --- a/docs/tags.rst +++ b/docs/tags.rst @@ -124,7 +124,7 @@ Reference compatibility -.. function:: compatible_tags(python_version=sys.version_info[:2], interpreter=None, platforms=None) +.. function:: compatible_tags(python_version=None, interpreter=None, platforms=None) Yields the tags for an interpreter compatible with the Python version specified by ``python_version``. @@ -143,7 +143,7 @@ Reference :param Iterable platforms: Iterable of compatible platforms. Defaults to the platforms compatible with the current system. -.. function:: cpython_tags(python_version=sys.version_info[:2], abis=None, platforms=None, *, warn=False) +.. function:: cpython_tags(python_version=None, abis=None, platforms=None, *, warn=False) Yields the tags for the CPython interpreter. @@ -156,7 +156,7 @@ Reference minor versions down to Python 3.2 (when ``abi3`` was introduced) :param tuple python_version: A tuple representing the targetted Python - version. + version. Defaults to ``sys.version_info[:2]``. :param Iterable abis: Iterable of compatible ABIs. Defaults to the ABIs compatible with the current system. :param Iterable platforms: Iterable of compatible platforms. Defaults to the diff --git a/packaging/tags.py b/packaging/tags.py index f87aae8db..9ca613eeb 100644 --- a/packaging/tags.py +++ b/packaging/tags.py @@ -173,7 +173,7 @@ def _cpython_abis(py_version, warn=False): def cpython_tags( - python_version=sys.version_info[:2], # type: PythonVersion + python_version=None, # type: Optional[PythonVersion] abis=None, # type: Optional[Iterable[str]] platforms=None, # type: Optional[Iterable[str]] **kwargs # type: bool @@ -192,6 +192,8 @@ def cpython_tags( their normal position and not at the beginning. """ warn = _warn_keyword_parameter("cpython_tags", kwargs) + if not python_version: + python_version = sys.version_info[:2] interpreter = "cp{}{}".format(*python_version) if abis is None: abis = _cpython_abis(python_version, warn) @@ -282,9 +284,11 @@ def _py_interpreter_range(py_version): def compatible_tags( - python_version=sys.version_info[:2], interpreter=None, platforms=None + python_version=None, # type: Optional[PythonVersion] + interpreter=None, # type: Optional[str] + platforms=None, # type: Optional[str] ): - # type: (PythonVersion, Optional[str], Optional[Iterable[str]]) -> Iterator[Tag] + # type: (...) -> Iterator[Tag] """ Yields the sequence of tags that are compatible with a specific version of Python. @@ -293,6 +297,8 @@ def compatible_tags( - -none-any # ... if `interpreter` is provided. - py*-none-any """ + if not python_version: + python_version = sys.version_info[:2] if not platforms: platforms = _platform_tags() for version in _py_interpreter_range(python_version): From 1350516925d2cf7522b46e5d9740e56896ec9da8 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Fri, 22 Nov 2019 13:49:09 -0800 Subject: [PATCH 49/58] Fix a typing typo --- packaging/tags.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packaging/tags.py b/packaging/tags.py index 9ca613eeb..c69cc3f04 100644 --- a/packaging/tags.py +++ b/packaging/tags.py @@ -286,7 +286,7 @@ def _py_interpreter_range(py_version): def compatible_tags( python_version=None, # type: Optional[PythonVersion] interpreter=None, # type: Optional[str] - platforms=None, # type: Optional[str] + platforms=None, # type: Optional[Iterator[str]] ): # type: (...) -> Iterator[Tag] """ From 68ea56f667c8b87f9ff84896b0af119f4ea17f17 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Fri, 22 Nov 2019 14:43:17 -0800 Subject: [PATCH 50/58] Accept major-only Python versions --- docs/tags.rst | 12 ++++--- packaging/tags.py | 58 ++++++++++++++++++++++--------- tests/test_tags.py | 86 +++++++++++++++++++++++++++++++++++----------- 3 files changed, 116 insertions(+), 40 deletions(-) diff --git a/docs/tags.rst b/docs/tags.rst index 633d7fe26..a6690ec54 100644 --- a/docs/tags.rst +++ b/docs/tags.rst @@ -135,8 +135,8 @@ Reference - ``-none-any`` if ``interpreter`` is provided - ``py*-none-any`` - :param tuple python_version: A two-item tuple representing the compatible - version of Python. Defaults to + :param Sequence python_version: A one- or two-item sequence representing the + compatible version of Python. Defaults to ``sys.version_info[:2]``. :param str interpreter: The name of the interpreter (if known), e.g. ``"cp38"``. Defaults to the current interpreter. @@ -155,8 +155,12 @@ Reference - ``cp-abi3-`` where "older version" is all older minor versions down to Python 3.2 (when ``abi3`` was introduced) - :param tuple python_version: A tuple representing the targetted Python - version. Defaults to ``sys.version_info[:2]``. + If ``python_version`` only provides a major-only version then only + user-provided ABIs via ``abis`` and the ``none`` ABI will be used. + + :param Sequence python_version: A one- or two-item sequence representing the + targetted Python version. Defaults to + ``sys.version_info[:2]``. :param Iterable abis: Iterable of compatible ABIs. Defaults to the ABIs compatible with the current system. :param Iterable platforms: Iterable of compatible platforms. Defaults to the diff --git a/packaging/tags.py b/packaging/tags.py index c69cc3f04..026e11a90 100644 --- a/packaging/tags.py +++ b/packaging/tags.py @@ -24,9 +24,19 @@ from ._typing import MYPY_CHECK_RUNNING, cast if MYPY_CHECK_RUNNING: # pragma: no cover - from typing import Dict, FrozenSet, Iterable, Iterator, List, Optional, Tuple, Union + from typing import ( + Dict, + FrozenSet, + Iterable, + Iterator, + List, + Optional, + Sequence, + Tuple, + Union, + ) - PythonVersion = Tuple[int, int] + PythonVersion = Sequence[int] MacVersion = Tuple[int, int] GlibcVersion = Tuple[int, int] @@ -138,6 +148,7 @@ def _normalize_string(string): def _cpython_abis(py_version, warn=False): # type: (PythonVersion, bool) -> List[str] + py_version = tuple(py_version) # To allow for version comparison. abis = [] version = "{}{}".format(*py_version[:2]) debug = pymalloc = ucs4 = "" @@ -188,16 +199,26 @@ def cpython_tags( - cp-none- - cp-abi3- # Older Python versions down to 3.2. + If python_version only specifies a major version then user-provided ABIs and + the 'none' ABItag will be used. + If 'abi3' or 'none' are specified in 'abis' then they will be yielded at their normal position and not at the beginning. """ warn = _warn_keyword_parameter("cpython_tags", kwargs) if not python_version: python_version = sys.version_info[:2] - interpreter = "cp{}{}".format(*python_version) + + if len(python_version) < 2: + interpreter = "cp{}".format(python_version[0]) + else: + interpreter = "cp{}{}".format(*python_version[:2]) + if abis is None: - abis = _cpython_abis(python_version, warn) - platforms = list(platforms or _platform_tags()) + if len(python_version) > 1: + abis = _cpython_abis(python_version, warn) + else: + abis = [] abis = list(abis) # 'abi3' and 'none' are explicitly handled later. for explicit_abi in ("abi3", "none"): @@ -206,23 +227,26 @@ def cpython_tags( except ValueError: pass + platforms = list(platforms or _platform_tags()) for abi in abis: for platform_ in platforms: yield Tag(interpreter, abi, platform_) # Not worrying about the case of Python 3.2 or older being specified and # thus having redundant tags thanks to the abi3 in-fill later on as # 'packaging' doesn't directly support Python that far back. - for tag in (Tag(interpreter, "abi3", platform_) for platform_ in platforms): - yield tag + if len(python_version) > 1: + for tag in (Tag(interpreter, "abi3", platform_) for platform_ in platforms): + yield tag for tag in (Tag(interpreter, "none", platform_) for platform_ in platforms): yield tag # PEP 384 was first implemented in Python 3.2. - for minor_version in range(python_version[1] - 1, 1, -1): - for platform_ in platforms: - interpreter = "cp{major}{minor}".format( - major=python_version[0], minor=minor_version - ) - yield Tag(interpreter, "abi3", platform_) + if len(python_version) > 1: + for minor_version in range(python_version[1] - 1, 1, -1): + for platform_ in platforms: + interpreter = "cp{major}{minor}".format( + major=python_version[0], minor=minor_version + ) + yield Tag(interpreter, "abi3", platform_) def _generic_abi(): @@ -277,10 +301,12 @@ def _py_interpreter_range(py_version): After the latest version, the major-only version will be yielded, and then all previous versions of that major version. """ - yield "py{major}{minor}".format(major=py_version[0], minor=py_version[1]) + if len(py_version) > 1: + yield "py{major}{minor}".format(major=py_version[0], minor=py_version[1]) yield "py{major}".format(major=py_version[0]) - for minor in range(py_version[1] - 1, -1, -1): - yield "py{major}{minor}".format(major=py_version[0], minor=minor) + if len(py_version) > 1: + for minor in range(py_version[1] - 1, -1, -1): + yield "py{major}{minor}".format(major=py_version[0], minor=minor) def compatible_tags( diff --git a/tests/test_tags.py b/tests/test_tags.py index 99e0f9946..de00b9e21 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -564,6 +564,13 @@ def test_platforms_defaults(self, monkeypatch): result = list(tags.cpython_tags((3, 8), abis=["whatever"])) assert tags.Tag("cp38", "whatever", "plat1") in result + def test_major_only_python_version(self): + result = list(tags.cpython_tags((3,), ["abi"], ["plat"])) + assert result == [ + tags.Tag("cp3", "abi", "plat"), + tags.Tag("cp3", "none", "plat"), + ] + @pytest.mark.parametrize("abis", [[], ["abi3"], ["none"]]) def test_skip_redundant_abis(self, abis): results = list(tags.cpython_tags((3, 0), abis=abis, platforms=["any"])) @@ -649,26 +656,65 @@ def test_platforms_default(self, monkeypatch): assert result == [tags.Tag("sillywalk", "none", "plat")] -def test_compatible_tags(): - result = list(tags.compatible_tags((3, 3), "cp33", ["plat1", "plat2"])) - assert result == [ - tags.Tag("py33", "none", "plat1"), - tags.Tag("py33", "none", "plat2"), - tags.Tag("py3", "none", "plat1"), - tags.Tag("py3", "none", "plat2"), - tags.Tag("py32", "none", "plat1"), - tags.Tag("py32", "none", "plat2"), - tags.Tag("py31", "none", "plat1"), - tags.Tag("py31", "none", "plat2"), - tags.Tag("py30", "none", "plat1"), - tags.Tag("py30", "none", "plat2"), - tags.Tag("cp33", "none", "any"), - tags.Tag("py33", "none", "any"), - tags.Tag("py3", "none", "any"), - tags.Tag("py32", "none", "any"), - tags.Tag("py31", "none", "any"), - tags.Tag("py30", "none", "any"), - ] +class CompatibleTagsTests: + def test_all_args(self): + result = list(tags.compatible_tags((3, 3), "cp33", ["plat1", "plat2"])) + assert result == [ + tags.Tag("py33", "none", "plat1"), + tags.Tag("py33", "none", "plat2"), + tags.Tag("py3", "none", "plat1"), + tags.Tag("py3", "none", "plat2"), + tags.Tag("py32", "none", "plat1"), + tags.Tag("py32", "none", "plat2"), + tags.Tag("py31", "none", "plat1"), + tags.Tag("py31", "none", "plat2"), + tags.Tag("py30", "none", "plat1"), + tags.Tag("py30", "none", "plat2"), + tags.Tag("cp33", "none", "any"), + tags.Tag("py33", "none", "any"), + tags.Tag("py3", "none", "any"), + tags.Tag("py32", "none", "any"), + tags.Tag("py31", "none", "any"), + tags.Tag("py30", "none", "any"), + ] + + def test_major_only_python_version(self): + result = list(tags.compatible_tags((3,), "cp33", ["plat"])) + assert result == [ + tags.Tag("py3", "none", "plat"), + tags.Tag("cp33", "none", "plat"), + tags.Tag("py3", "none", "any"), + ] + + def test_default_python_version(self, monkeypatch): + monkeypatch.setattr(sys, "version_info", (3, 1)) + result = list(tags.compatible_tags(interpreter="cp31", platforms=["plat"])) + assert result == [ + tags.Tag("py31", "none", "plat"), + tags.Tag("py3", "none", "plat"), + tags.Tag("py30", "none", "plat"), + tags.tag("cp31", "none", "any"), + tags.Tag("py30", "none", "any"), + ] + + def test_default_interpreter(self): + result = list(tags.compatible_tags((3, 1), platforms=["plat"])) + assert result == [ + tags.Tag("py31", "none", "plat"), + tags.Tag("py3", "none", "plat"), + tags.Tag("py30", "none", "plat"), + tags.Tag("py30", "none", "any"), + ] + + def test_default_platforms(self, monkeypatch): + monkeypatch.setattr(tags, "_platform_tags", lambda: ["plat"]) + result = list(tags.compatible_tags((3, 1), "cp31")) + assert result == [ + tags.Tag("py31", "none", "plat"), + tags.Tag("py3", "none", "plat"), + tags.Tag("py30", "none", "plat"), + tags.Tag("py30", "none", "any"), + ] class TestSysTags: From a295307560d53b1a47b4a40b78d6def85469fa9a Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Fri, 22 Nov 2019 14:56:38 -0800 Subject: [PATCH 51/58] Fix compatibility_tags() tests to actually run --- tests/test_tags.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/tests/test_tags.py b/tests/test_tags.py index de00b9e21..c796d4cce 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -656,7 +656,7 @@ def test_platforms_default(self, monkeypatch): assert result == [tags.Tag("sillywalk", "none", "plat")] -class CompatibleTagsTests: +class TestCompatibleTags: def test_all_args(self): result = list(tags.compatible_tags((3, 3), "cp33", ["plat1", "plat2"])) assert result == [ @@ -682,7 +682,7 @@ def test_major_only_python_version(self): result = list(tags.compatible_tags((3,), "cp33", ["plat"])) assert result == [ tags.Tag("py3", "none", "plat"), - tags.Tag("cp33", "none", "plat"), + tags.Tag("cp33", "none", "any"), tags.Tag("py3", "none", "any"), ] @@ -693,7 +693,9 @@ def test_default_python_version(self, monkeypatch): tags.Tag("py31", "none", "plat"), tags.Tag("py3", "none", "plat"), tags.Tag("py30", "none", "plat"), - tags.tag("cp31", "none", "any"), + tags.Tag("cp31", "none", "any"), + tags.Tag("py31", "none", "any"), + tags.Tag("py3", "none", "any"), tags.Tag("py30", "none", "any"), ] @@ -703,6 +705,8 @@ def test_default_interpreter(self): tags.Tag("py31", "none", "plat"), tags.Tag("py3", "none", "plat"), tags.Tag("py30", "none", "plat"), + tags.Tag("py31", "none", "any"), + tags.Tag("py3", "none", "any"), tags.Tag("py30", "none", "any"), ] @@ -713,6 +717,9 @@ def test_default_platforms(self, monkeypatch): tags.Tag("py31", "none", "plat"), tags.Tag("py3", "none", "plat"), tags.Tag("py30", "none", "plat"), + tags.Tag("cp31", "none", "any"), + tags.Tag("py31", "none", "any"), + tags.Tag("py3", "none", "any"), tags.Tag("py30", "none", "any"), ] From 4331ec54ba5c97b0785ec50c7865a48eaadb6542 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Fri, 22 Nov 2019 15:03:04 -0800 Subject: [PATCH 52/58] Add a test case for cpython_tags() w/ default ABI and major-only Python version --- tests/test_tags.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/test_tags.py b/tests/test_tags.py index c796d4cce..867eee819 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -571,6 +571,12 @@ def test_major_only_python_version(self): tags.Tag("cp3", "none", "plat"), ] + def test_major_only_python_version_with_default_abis(self): + 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): results = list(tags.cpython_tags((3, 0), abis=abis, platforms=["any"])) From 7603a49653ac4cbb7d012081bcef5f471511495d Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Fri, 22 Nov 2019 15:06:06 -0800 Subject: [PATCH 53/58] Run Black --- tests/test_tags.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/test_tags.py b/tests/test_tags.py index 867eee819..e99e622cd 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -573,9 +573,7 @@ def test_major_only_python_version(self): def test_major_only_python_version_with_default_abis(self): result = list(tags.cpython_tags((3,), platforms=["plat"])) - assert result == [ - tags.Tag("cp3", "none", "plat"), - ] + assert result == [tags.Tag("cp3", "none", "plat")] @pytest.mark.parametrize("abis", [[], ["abi3"], ["none"]]) def test_skip_redundant_abis(self, abis): From e62f138033bd75574e9da58efb91c12e10abdb7c Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Sat, 23 Nov 2019 20:24:10 -0800 Subject: [PATCH 54/58] Add interpreter_version() --- docs/tags.rst | 7 ++++++ packaging/tags.py | 27 ++++++++++++++-------- tests/test_tags.py | 56 ++++++++++++++++++++++++++++++++++------------ 3 files changed, 67 insertions(+), 23 deletions(-) diff --git a/docs/tags.rst b/docs/tags.rst index a6690ec54..0851d7893 100644 --- a/docs/tags.rst +++ b/docs/tags.rst @@ -106,6 +106,13 @@ Reference This typically acts as the prefix to the :attr:`~Tag.interpreter` tag. +.. function:: interpreter_version(*, warn=False) + + Returns the running interpreter's version. + + This typically acts as the suffix to the :attr:`~Tag.interpreter` tag. + + .. function:: mac_platforms(version=None, arch=None) Yields the :attr:`~Tag.platform` tags for macOS. diff --git a/packaging/tags.py b/packaging/tags.py index 026e11a90..97ac8dda1 100644 --- a/packaging/tags.py +++ b/packaging/tags.py @@ -256,14 +256,6 @@ def _generic_abi(): yield _normalize_string(abi) -def _generic_interpreter(warn=False): - # type: (bool) -> str - version = _get_config_var("py_version_nodot", warn) - if not version: - version = "".join(map(str, sys.version_info[:2])) - return "{name}{version}".format(name=interpreter_name(), version=version) - - def generic_tags( interpreter=None, # type: Optional[str] abis=None, # type: Optional[Iterable[str]] @@ -281,7 +273,9 @@ def generic_tags( """ warn = _warn_keyword_parameter("generic_tags", kwargs) if not interpreter: - interpreter = _generic_interpreter(warn=warn) + interp_name = interpreter_name() + interp_version = interpreter_version(warn=warn) + interpreter = "".join([interp_name, interp_version]) if abis is None: abis = _generic_abi() platforms = list(platforms or _platform_tags()) @@ -552,6 +546,9 @@ def _platform_tags(): def interpreter_name(): # type: () -> str + """ + Returns the name of the running interpreter. + """ try: name = sys.implementation.name # type: ignore except AttributeError: # pragma: no cover @@ -560,6 +557,18 @@ def interpreter_name(): return INTERPRETER_SHORT_NAMES.get(name) or name +def interpreter_version(**kwargs): + # type: (bool) -> str + """ + Returns the version of the running interpreter. + """ + warn = _warn_keyword_parameter("interpreter_version", kwargs) + version = _get_config_var("py_version_nodot", warn=warn) + if not version: + version = "".join(map(str, sys.version_info[:2])) + return version + + def sys_tags(**kwargs): # type: (bool) -> Iterator[Tag] """ diff --git a/tests/test_tags.py b/tests/test_tags.py index e99e622cd..ef91cf987 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -155,6 +155,45 @@ def test_multi_platform(self): assert given == expected +class TestInterpreterName: + def test_sys_implementation_name(self, monkeypatch): + monkeypatch.setattr(sys.implementation, "name", "sillywalk") + assert tags.interpreter_name() == "sillywalk" + + def test_platform(self, monkeypatch): + monkeypatch.delattr(sys.implementation, "name") + name = "SillyWalk" + monkeypatch.setattr(platform, "python_implementation", lambda: name) + assert tags.interpreter_name() == name.lower() + + def test_interpreter_short_names(self, monkeypatch): + monkeypatch.setattr(sys.implementation, "name", "cpython") + assert tags.interpreter_name() == "cp" + + +class TestInterpreterVersion: + def test_warn(self, monkeypatch): + called_with_warn = False + + def get_config_var(var, warn): + nonlocal called_with_warn + called_with_warn = warn + return "38" + + monkeypatch.setattr(tags, "_get_config_var", get_config_var) + tags.interpreter_version(warn=True) + assert called_with_warn + + def test_python_version_nodot(self, monkeypatch): + monkeypatch.setattr(tags, "_get_config_var", lambda var, warn: "NN") + assert tags.interpreter_version() == "NN" + + def test_sys_version_info(self, monkeypatch): + monkeypatch.setattr(tags, "_get_config_var", lambda *args, **kwargs: None) + monkeypatch.setattr(sys, "version_info", ("L", "M", "N")) + assert tags.interpreter_version() == "LM" + + class TestMacOSPlatforms: @pytest.mark.parametrize( "arch, is_32bit, expected", @@ -585,18 +624,6 @@ def test_skip_redundant_abis(self, abis): class TestGenericTags: - def test_generic_interpreter(self, monkeypatch): - monkeypatch.setattr(sysconfig, "get_config_var", lambda key: "42") - monkeypatch.setattr(tags, "interpreter_name", lambda: "sillywalk") - assert tags._generic_interpreter() == "sillywalk42" - - def test_generic_interpreter_no_config_var(self, monkeypatch): - monkeypatch.setattr(sysconfig, "get_config_var", lambda _: None) - monkeypatch.setattr(tags, "interpreter_name", lambda: "sillywalk") - assert tags._generic_interpreter() == "sillywalk{}{}".format( - *sys.version_info[:2] - ) - @pytest.mark.skipif( not sysconfig.get_config_var("SOABI"), reason="SOABI not defined" ) @@ -642,9 +669,10 @@ def test_abi_unspecified(self, abi): ] def test_interpreter_default(self, monkeypatch): - monkeypatch.setattr(tags, "_generic_interpreter", lambda warn: "sillywalk") + monkeypatch.setattr(tags, "interpreter_name", lambda: "sillywalk") + monkeypatch.setattr(tags, "interpreter_version", lambda warn: "NN") result = list(tags.generic_tags(abis=["none"], platforms=["any"])) - assert result == [tags.Tag("sillywalk", "none", "any")] + assert result == [tags.Tag("sillywalkNN", "none", "any")] def test_abis_default(self, monkeypatch): monkeypatch.setattr(tags, "_generic_abi", lambda: iter(["abi"])) From 61666fad5a5c9095a38aea729eff52fefb23576c Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Sat, 23 Nov 2019 20:40:38 -0800 Subject: [PATCH 55/58] Deal with a type ambiguity --- packaging/tags.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packaging/tags.py b/packaging/tags.py index 97ac8dda1..a719d4e73 100644 --- a/packaging/tags.py +++ b/packaging/tags.py @@ -564,7 +564,9 @@ def interpreter_version(**kwargs): """ warn = _warn_keyword_parameter("interpreter_version", kwargs) version = _get_config_var("py_version_nodot", warn=warn) - if not version: + if version: + version = str(version) + else: version = "".join(map(str, sys.version_info[:2])) return version From 42814d1474746efe5c33d73434f3bddbf8bd1b1b Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Sat, 23 Nov 2019 20:40:47 -0800 Subject: [PATCH 56/58] Make Python 2.7 happy --- tests/test_tags.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/tests/test_tags.py b/tests/test_tags.py index ef91cf987..9df1d7db3 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -173,16 +173,20 @@ def test_interpreter_short_names(self, monkeypatch): class TestInterpreterVersion: def test_warn(self, monkeypatch): - called_with_warn = False + class MockConfigVar(object): - def get_config_var(var, warn): - nonlocal called_with_warn - called_with_warn = warn - return "38" + def __init__(self, return_): + self.warn = None + self._return = return_ - monkeypatch.setattr(tags, "_get_config_var", get_config_var) + def __call__(self, name, warn): + self.warn = warn + return self._return + + mock_config_var = MockConfigVar("38") + monkeypatch.setattr(tags, "_get_config_var", mock_config_var) tags.interpreter_version(warn=True) - assert called_with_warn + assert mock_config_var.warn def test_python_version_nodot(self, monkeypatch): monkeypatch.setattr(tags, "_get_config_var", lambda var, warn: "NN") From b7e61cda9e8c3a6a6aabc9419d3df0d341181c71 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Sat, 23 Nov 2019 20:43:53 -0800 Subject: [PATCH 57/58] Run Black --- tests/test_tags.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_tags.py b/tests/test_tags.py index 9df1d7db3..516669ba1 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -174,7 +174,6 @@ def test_interpreter_short_names(self, monkeypatch): class TestInterpreterVersion: def test_warn(self, monkeypatch): class MockConfigVar(object): - def __init__(self, return_): self.warn = None self._return = return_ From 66f13d94ada09926f8c671fbe33382429e8755b3 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Sat, 23 Nov 2019 21:01:45 -0800 Subject: [PATCH 58/58] Make Python 2.7 happy some more --- tests/test_tags.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tests/test_tags.py b/tests/test_tags.py index 516669ba1..0a89991e1 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -157,17 +157,22 @@ def test_multi_platform(self): class TestInterpreterName: def test_sys_implementation_name(self, monkeypatch): - monkeypatch.setattr(sys.implementation, "name", "sillywalk") + class MockImplementation(object): + pass + + mock_implementation = MockImplementation() + mock_implementation.name = "sillywalk" + monkeypatch.setattr(sys, "implementation", mock_implementation, raising=False) assert tags.interpreter_name() == "sillywalk" def test_platform(self, monkeypatch): - monkeypatch.delattr(sys.implementation, "name") + monkeypatch.delattr(sys, "implementation", raising=False) name = "SillyWalk" monkeypatch.setattr(platform, "python_implementation", lambda: name) assert tags.interpreter_name() == name.lower() - def test_interpreter_short_names(self, monkeypatch): - monkeypatch.setattr(sys.implementation, "name", "cpython") + def test_interpreter_short_names(self, mock_interpreter_name, monkeypatch): + mock_interpreter_name("cpython") assert tags.interpreter_name() == "cp"