setuptools
Advanced tools
| # Ensure that checkers who see this as a separate namespace package still understand it's typed. |
@@ -22,3 +22,3 @@ ========== | ||
| You can also :doc:`install build <build:installation>` using :pypi:`pip`:: | ||
| You can also :doc:`install build <build:how-to/install>` using :pypi:`pip`:: | ||
@@ -25,0 +25,0 @@ pip install --upgrade build |
+6
-6
| Metadata-Version: 2.4 | ||
| Name: setuptools | ||
| Version: 82.0.1 | ||
| Version: 83.0.0 | ||
| Summary: Most extensible Python build backend with support for C/C++ extension modules | ||
@@ -19,3 +19,3 @@ Author-email: Python Packaging Authority <distutils-sig@python.org> | ||
| Classifier: Topic :: Utilities | ||
| Requires-Python: >=3.9 | ||
| Requires-Python: >=3.10 | ||
| Description-Content-Type: text/x-rst | ||
@@ -70,3 +70,3 @@ License-File: LICENSE | ||
| Provides-Extra: check | ||
| Requires-Dist: pytest-checkdocs>=2.4; extra == "check" | ||
| Requires-Dist: pytest-checkdocs>=2.14; extra == "check" | ||
| Requires-Dist: pytest-ruff>=0.2.1; sys_platform != "cygwin" and extra == "check" | ||
@@ -77,5 +77,5 @@ Requires-Dist: ruff>=0.13.0; sys_platform != "cygwin" and extra == "check" | ||
| Provides-Extra: enabler | ||
| Requires-Dist: pytest-enabler>=2.2; extra == "enabler" | ||
| Requires-Dist: pytest-enabler>=3.4; extra == "enabler" | ||
| Provides-Extra: type | ||
| Requires-Dist: pytest-mypy; extra == "type" | ||
| Requires-Dist: pytest-mypy>=1.0.1; platform_python_implementation != "PyPy" and extra == "type" | ||
| Requires-Dist: mypy==1.18.*; extra == "type" | ||
@@ -102,3 +102,3 @@ Requires-Dist: importlib_metadata>=7.0.2; python_version < "3.10" and extra == "type" | ||
| .. |skeleton-badge| image:: https://img.shields.io/badge/skeleton-2025-informational | ||
| .. |skeleton-badge| image:: https://img.shields.io/badge/skeleton-2026-informational | ||
| :target: https://blog.jaraco.com/skeleton | ||
@@ -105,0 +105,0 @@ |
+7
-5
@@ -13,3 +13,3 @@ [build-system] | ||
| name = "setuptools" | ||
| version = "82.0.1" | ||
| version = "83.0.0" | ||
| authors = [ | ||
@@ -30,3 +30,3 @@ { name = "Python Packaging Authority", email = "distutils-sig@python.org" }, | ||
| ] | ||
| requires-python = ">=3.9" | ||
| requires-python = ">=3.10" | ||
| license = "MIT" | ||
@@ -114,3 +114,3 @@ dependencies = [ | ||
| # upstream | ||
| "pytest-checkdocs >= 2.4", | ||
| "pytest-checkdocs >= 2.14", | ||
| "pytest-ruff >= 0.2.1; sys_platform != 'cygwin'", | ||
@@ -129,3 +129,3 @@ | ||
| enabler = [ | ||
| "pytest-enabler >= 2.2", | ||
| "pytest-enabler >= 3.4", | ||
| ] | ||
@@ -135,3 +135,5 @@ | ||
| # upstream | ||
| "pytest-mypy", | ||
| # Exclude PyPy from type checks (python/mypy#20454 jaraco/skeleton#187) | ||
| "pytest-mypy >= 1.0.1; platform_python_implementation != 'PyPy'", | ||
@@ -138,0 +140,0 @@ # local |
+1
-1
@@ -17,3 +17,3 @@ .. |pypi-version| image:: https://img.shields.io/pypi/v/setuptools.svg | ||
| .. |skeleton-badge| image:: https://img.shields.io/badge/skeleton-2025-informational | ||
| .. |skeleton-badge| image:: https://img.shields.io/badge/skeleton-2026-informational | ||
| :target: https://blog.jaraco.com/skeleton | ||
@@ -20,0 +20,0 @@ |
| Metadata-Version: 2.4 | ||
| Name: setuptools | ||
| Version: 82.0.1 | ||
| Version: 83.0.0 | ||
| Summary: Most extensible Python build backend with support for C/C++ extension modules | ||
@@ -19,3 +19,3 @@ Author-email: Python Packaging Authority <distutils-sig@python.org> | ||
| Classifier: Topic :: Utilities | ||
| Requires-Python: >=3.9 | ||
| Requires-Python: >=3.10 | ||
| Description-Content-Type: text/x-rst | ||
@@ -70,3 +70,3 @@ License-File: LICENSE | ||
| Provides-Extra: check | ||
| Requires-Dist: pytest-checkdocs>=2.4; extra == "check" | ||
| Requires-Dist: pytest-checkdocs>=2.14; extra == "check" | ||
| Requires-Dist: pytest-ruff>=0.2.1; sys_platform != "cygwin" and extra == "check" | ||
@@ -77,5 +77,5 @@ Requires-Dist: ruff>=0.13.0; sys_platform != "cygwin" and extra == "check" | ||
| Provides-Extra: enabler | ||
| Requires-Dist: pytest-enabler>=2.2; extra == "enabler" | ||
| Requires-Dist: pytest-enabler>=3.4; extra == "enabler" | ||
| Provides-Extra: type | ||
| Requires-Dist: pytest-mypy; extra == "type" | ||
| Requires-Dist: pytest-mypy>=1.0.1; platform_python_implementation != "PyPy" and extra == "type" | ||
| Requires-Dist: mypy==1.18.*; extra == "type" | ||
@@ -102,3 +102,3 @@ Requires-Dist: importlib_metadata>=7.0.2; python_version < "3.10" and extra == "type" | ||
| .. |skeleton-badge| image:: https://img.shields.io/badge/skeleton-2025-informational | ||
| .. |skeleton-badge| image:: https://img.shields.io/badge/skeleton-2026-informational | ||
| :target: https://blog.jaraco.com/skeleton | ||
@@ -105,0 +105,0 @@ |
@@ -5,3 +5,3 @@ | ||
| [check] | ||
| pytest-checkdocs>=2.4 | ||
| pytest-checkdocs>=2.14 | ||
@@ -46,3 +46,3 @@ [check:sys_platform != "cygwin"] | ||
| [enabler] | ||
| pytest-enabler>=2.2 | ||
| pytest-enabler>=3.4 | ||
@@ -77,5 +77,7 @@ [ssl] | ||
| [type] | ||
| pytest-mypy | ||
| mypy==1.18.* | ||
| [type:platform_python_implementation != "PyPy"] | ||
| pytest-mypy>=1.0.1 | ||
| [type:python_version < "3.10"] | ||
@@ -82,0 +84,0 @@ importlib_metadata>=7.0.2 |
@@ -138,2 +138,3 @@ LICENSE | ||
| setuptools/_distutils/log.py | ||
| setuptools/_distutils/py.typed | ||
| setuptools/_distutils/spawn.py | ||
@@ -174,2 +175,3 @@ setuptools/_distutils/sysconfig.py | ||
| setuptools/_distutils/compilers/C/msvc.py | ||
| setuptools/_distutils/compilers/C/py.typed | ||
| setuptools/_distutils/compilers/C/unix.py | ||
@@ -176,0 +178,0 @@ setuptools/_distutils/compilers/C/zos.py |
@@ -61,3 +61,3 @@ """Timestamp comparison of files and groups of files.""" | ||
| newer_pairs = filter(splat(newer), zip_strict(sources, targets)) | ||
| return tuple(map(list, zip(*newer_pairs))) or ([], []) | ||
| return tuple(map(list, zip(*newer_pairs, strict=False))) or ([], []) | ||
@@ -64,0 +64,0 @@ |
@@ -502,3 +502,3 @@ """distutils.command.build_ext | ||
| ] | ||
| for ext, fut in zip(self.extensions, futures): | ||
| for ext, fut in zip(self.extensions, futures, strict=False): | ||
| with self._filter_build_errors(ext): | ||
@@ -505,0 +505,0 @@ fut.result() |
@@ -139,12 +139,12 @@ """distutils.command.build | ||
| def has_pure_modules(self): | ||
| def has_pure_modules(self) -> bool: | ||
| return self.distribution.has_pure_modules() | ||
| def has_c_libraries(self): | ||
| def has_c_libraries(self) -> bool: | ||
| return self.distribution.has_c_libraries() | ||
| def has_ext_modules(self): | ||
| def has_ext_modules(self) -> bool: | ||
| return self.distribution.has_ext_modules() | ||
| def has_scripts(self): | ||
| def has_scripts(self) -> bool: | ||
| return self.distribution.has_scripts() | ||
@@ -151,0 +151,0 @@ |
@@ -62,5 +62,5 @@ """ | ||
| # The following routines are taken from setuptools' pkg_resources module and | ||
| # can be replaced by importing them from pkg_resources once it is included | ||
| # in the stdlib. | ||
| # The following routines were originally copied from setuptools' pkg_resources | ||
| # module and intended to be replaced by stdlib versions. They're now just legacy | ||
| # cruft. | ||
@@ -67,0 +67,0 @@ |
@@ -267,8 +267,8 @@ """distutils.command.install | ||
| # that installation scheme. | ||
| self.install_purelib = None # for pure module distributions | ||
| self.install_platlib = None # non-pure (dists w/ extensions) | ||
| self.install_headers = None # for C/C++ headers | ||
| self.install_purelib: str | None = None # for pure module distributions | ||
| self.install_platlib: str | None = None # non-pure (dists w/ extensions) | ||
| self.install_headers: str | None = None # for C/C++ headers | ||
| self.install_lib: str | None = None # set to either purelib or platlib | ||
| self.install_scripts = None | ||
| self.install_data = None | ||
| self.install_scripts: str | None = None | ||
| self.install_data: str | None = None | ||
| self.install_userbase = USER_BASE | ||
@@ -776,3 +776,3 @@ self.install_usersite = USER_SITE | ||
| def has_lib(self): | ||
| def has_lib(self) -> bool: | ||
| """Returns true if the current distribution has any Python | ||
@@ -784,3 +784,3 @@ modules to install.""" | ||
| def has_headers(self): | ||
| def has_headers(self) -> bool: | ||
| """Returns true if the current distribution has any headers to | ||
@@ -790,3 +790,3 @@ install.""" | ||
| def has_scripts(self): | ||
| def has_scripts(self) -> bool: | ||
| """Returns true if the current distribution has any scripts to. | ||
@@ -796,3 +796,3 @@ install.""" | ||
| def has_data(self): | ||
| def has_data(self) -> bool: | ||
| """Returns true if the current distribution has any data to. | ||
@@ -799,0 +799,0 @@ install.""" |
@@ -57,3 +57,3 @@ import functools | ||
| # All sizes are equal, we can use the built-in zip. | ||
| return zip(*iterables) | ||
| return zip(*iterables, strict=False) | ||
| # If any one of the iterables didn't have a length, start reading | ||
@@ -60,0 +60,0 @@ # them until one runs out. |
@@ -19,3 +19,2 @@ """distutils.ccompiler | ||
| TypeVar, | ||
| Union, | ||
| overload, | ||
@@ -43,7 +42,9 @@ ) | ||
| if TYPE_CHECKING: | ||
| from typing_extensions import TypeAlias, TypeVarTuple, Unpack | ||
| from typing import TypeAlias | ||
| from typing_extensions import TypeVarTuple, Unpack | ||
| _Ts = TypeVarTuple("_Ts") | ||
| _Macro: TypeAlias = Union[tuple[str], tuple[str, Union[str, None]]] | ||
| _Macro: TypeAlias = tuple[str] | tuple[str, str | None] | ||
| _StrPathT = TypeVar("_StrPathT", bound="str | os.PathLike[str]") | ||
@@ -75,3 +76,3 @@ _BytesPathT = TypeVar("_BytesPathT", bound="bytes | os.PathLike[bytes]") | ||
| # responsible for updating 'compiler_class'! | ||
| compiler_type: ClassVar[str] = None # type: ignore[assignment] | ||
| compiler_type: ClassVar[str] = None | ||
@@ -864,3 +865,3 @@ # XXX things not handled by this compiler abstraction model: | ||
| def runtime_library_dir_option(self, dir: str) -> str: | ||
| def runtime_library_dir_option(self, dir: str) -> str | list[str]: | ||
| """Return the compiler option to add 'dir' to the list of | ||
@@ -867,0 +868,0 @@ directories searched for runtime libraries. |
@@ -9,2 +9,4 @@ """distutils.cygwinccompiler | ||
| from __future__ import annotations | ||
| import copy | ||
@@ -331,3 +333,3 @@ import os | ||
| def is_cygwincc(cc): | ||
| def is_cygwincc(cc: str | shlex._ShlexInstream) -> bool: | ||
| """Try to determine if the compiler that would be used is from cygwin.""" | ||
@@ -334,0 +336,0 @@ out_string = check_output(shlex.split(cc) + ['-dumpmachine']) |
@@ -98,3 +98,4 @@ """distutils._msvccompiler | ||
| path = ( | ||
| subprocess.check_output([ | ||
| subprocess | ||
| .check_output([ | ||
| os.path.join( | ||
@@ -101,0 +102,0 @@ root, "Microsoft Visual Studio", "Installer", "vswhere.exe" |
@@ -21,3 +21,4 @@ import platform | ||
| payload = ( | ||
| textwrap.dedent( | ||
| textwrap | ||
| .dedent( | ||
| """ | ||
@@ -24,0 +25,0 @@ #headers |
@@ -325,3 +325,3 @@ """distutils.unixccompiler | ||
| def runtime_library_dir_option(self, dir: str) -> str | list[str]: # type: ignore[override] # Fixed in pypa/distutils#339 | ||
| def runtime_library_dir_option(self, dir: str) -> str | list[str]: | ||
| # XXX Hackish, at the very least. See Python bug #445902: | ||
@@ -328,0 +328,0 @@ # https://bugs.python.org/issue445902 |
@@ -25,3 +25,2 @@ """distutils.dist | ||
| TypeVar, | ||
| Union, | ||
| overload, | ||
@@ -44,11 +43,13 @@ ) | ||
| if TYPE_CHECKING: | ||
| from typing import TypeAlias | ||
| from _typeshed import SupportsWrite | ||
| from typing_extensions import TypeAlias | ||
| # type-only import because of mutual dependence between these modules | ||
| from .cmd import Command | ||
| from .extension import Extension | ||
| _CommandT = TypeVar("_CommandT", bound="Command") | ||
| _OptionsList: TypeAlias = list[ | ||
| Union[tuple[str, Union[str, None], str, int], tuple[str, Union[str, None], str]] | ||
| tuple[str, str | None, str, int] | tuple[str, str | None, str] | ||
| ] | ||
@@ -223,9 +224,9 @@ | ||
| # Distribution as a convenience to the developer. | ||
| self.packages = None | ||
| self.packages: list[str] | None = None | ||
| self.package_data: dict[str, list[str]] = {} | ||
| self.package_dir = None | ||
| self.py_modules = None | ||
| self.package_dir: dict[str, str] | None = None | ||
| self.py_modules: list[str] | None = None | ||
| self.libraries = None | ||
| self.headers = None | ||
| self.ext_modules = None | ||
| self.ext_modules: list[Extension] | None = None | ||
| self.ext_package = None | ||
@@ -235,3 +236,3 @@ self.include_dirs = None | ||
| self.scripts = None | ||
| self.data_files = None | ||
| self.data_files: list[str | tuple] | None = None | ||
| self.password = '' | ||
@@ -1029,9 +1030,9 @@ | ||
| def has_pure_modules(self) -> bool: | ||
| return len(self.packages or self.py_modules or []) > 0 | ||
| return bool(self.packages or self.py_modules) | ||
| def has_ext_modules(self) -> bool: | ||
| return self.ext_modules and len(self.ext_modules) > 0 | ||
| return bool(self.ext_modules) | ||
| def has_c_libraries(self) -> bool: | ||
| return self.libraries and len(self.libraries) > 0 | ||
| return bool(self.libraries) | ||
@@ -1042,9 +1043,9 @@ def has_modules(self) -> bool: | ||
| def has_headers(self) -> bool: | ||
| return self.headers and len(self.headers) > 0 | ||
| return bool(self.headers) | ||
| def has_scripts(self) -> bool: | ||
| return self.scripts and len(self.scripts) > 0 | ||
| return bool(self.scripts) | ||
| def has_data_files(self) -> bool: | ||
| return self.data_files and len(self.data_files) > 0 | ||
| return bool(self.data_files) | ||
@@ -1051,0 +1052,0 @@ def is_pure(self) -> bool: |
@@ -108,3 +108,3 @@ """distutils.fancy_getopt | ||
| def has_option(self, long_option): | ||
| def has_option(self, long_option: str) -> bool: | ||
| """Return true if the option table for this parser has an | ||
@@ -111,0 +111,0 @@ option with long name 'long_option'.""" |
@@ -1,40 +0,18 @@ | ||
| import sys | ||
| if sys.version_info >= (3, 10): | ||
| from test.support.import_helper import ( | ||
| CleanImport as CleanImport, | ||
| ) | ||
| from test.support.import_helper import ( | ||
| DirsOnSysPath as DirsOnSysPath, | ||
| ) | ||
| from test.support.os_helper import ( | ||
| EnvironmentVarGuard as EnvironmentVarGuard, | ||
| ) | ||
| from test.support.os_helper import ( | ||
| rmtree as rmtree, | ||
| ) | ||
| from test.support.os_helper import ( | ||
| skip_unless_symlink as skip_unless_symlink, | ||
| ) | ||
| from test.support.os_helper import ( | ||
| unlink as unlink, | ||
| ) | ||
| else: | ||
| from test.support import ( | ||
| CleanImport as CleanImport, | ||
| ) | ||
| from test.support import ( | ||
| DirsOnSysPath as DirsOnSysPath, | ||
| ) | ||
| from test.support import ( | ||
| EnvironmentVarGuard as EnvironmentVarGuard, | ||
| ) | ||
| from test.support import ( | ||
| rmtree as rmtree, | ||
| ) | ||
| from test.support import ( | ||
| skip_unless_symlink as skip_unless_symlink, | ||
| ) | ||
| from test.support import ( | ||
| unlink as unlink, | ||
| ) | ||
| from test.support.import_helper import ( | ||
| CleanImport as CleanImport, | ||
| ) | ||
| from test.support.import_helper import ( | ||
| DirsOnSysPath as DirsOnSysPath, | ||
| ) | ||
| from test.support.os_helper import ( | ||
| EnvironmentVarGuard as EnvironmentVarGuard, | ||
| ) | ||
| from test.support.os_helper import ( | ||
| rmtree as rmtree, | ||
| ) | ||
| from test.support.os_helper import ( | ||
| skip_unless_symlink as skip_unless_symlink, | ||
| ) | ||
| from test.support.os_helper import ( | ||
| unlink as unlink, | ||
| ) |
| """Tests for distutils.command.check.""" | ||
| import distutils.command.check as _check | ||
| import importlib | ||
| import os | ||
| import sys | ||
| import textwrap | ||
| from distutils.command.check import check | ||
| from distutils.errors import DistutilsSetupError | ||
@@ -11,11 +13,32 @@ from distutils.tests import support | ||
| try: | ||
| import pygments | ||
| except ImportError: | ||
| pygments = None | ||
| HERE = os.path.dirname(__file__) | ||
| HERE = os.path.dirname(__file__) | ||
| @pytest.fixture | ||
| def hide_pygments(monkeypatch, request): | ||
| """ | ||
| Clear docutils and hide the presence of pygments. | ||
| """ | ||
| clear_docutils(monkeypatch) | ||
| monkeypatch.setitem(sys.modules, 'pygments', None) | ||
| reload_check() | ||
| # restore 'check' to its normal state after monkeypatch is undone | ||
| request.addfinalizer(reload_check) | ||
| def clear_docutils(monkeypatch): | ||
| docutils_names = [ | ||
| name for name in sys.modules if name.partition('.')[0] == 'docutils' | ||
| ] | ||
| for name in docutils_names: | ||
| monkeypatch.delitem(sys.modules, name) | ||
| def reload_check(): | ||
| """ | ||
| Reload the 'check' command module to reflect the import state. | ||
| """ | ||
| importlib.reload(_check) | ||
| @support.combine_markers | ||
@@ -30,3 +53,3 @@ class TestCheck(support.TempdirManager): | ||
| pkg_info, dist = self.create_dist(**metadata) | ||
| cmd = check(dist) | ||
| cmd = _check.check(dist) | ||
| cmd.initialize_options() | ||
@@ -108,5 +131,4 @@ for name, value in options.items(): | ||
| def test_check_document(self): | ||
| pytest.importorskip('docutils') | ||
| pkg_info, dist = self.create_dist() | ||
| cmd = check(dist) | ||
| cmd = _check.check(dist) | ||
@@ -124,7 +146,6 @@ # let's see if it detects broken rest | ||
| def test_check_restructuredtext(self): | ||
| pytest.importorskip('docutils') | ||
| # let's see if it detects broken rest in long_description | ||
| broken_rest = 'title\n===\n\ntest' | ||
| pkg_info, dist = self.create_dist(long_description=broken_rest) | ||
| cmd = check(dist) | ||
| cmd = _check.check(dist) | ||
| cmd.check_restructuredtext() | ||
@@ -155,12 +176,8 @@ assert cmd._warnings == 1 | ||
| def test_check_restructuredtext_with_syntax_highlight(self): | ||
| pytest.importorskip('docutils') | ||
| # Don't fail if there is a `code` or `code-block` directive | ||
| example_rst_docs = [ | ||
| textwrap.dedent( | ||
| """\ | ||
| code_examples = [ | ||
| textwrap.dedent( | ||
| f""" | ||
| Here's some code: | ||
| .. code:: python | ||
| .. {directive}:: python | ||
@@ -170,28 +187,21 @@ def foo(): | ||
| """ | ||
| ), | ||
| textwrap.dedent( | ||
| """\ | ||
| Here's some code: | ||
| ).lstrip() | ||
| for directive in ['code', 'code-block'] | ||
| ] | ||
| .. code-block:: python | ||
| def check_rst_data(self, descr): | ||
| pkg_info, dist = self.create_dist(long_description=descr) | ||
| cmd = _check.check(dist) | ||
| cmd.check_restructuredtext() | ||
| return cmd._check_rst_data(descr) | ||
| def foo(): | ||
| pass | ||
| """ | ||
| ), | ||
| ] | ||
| @pytest.mark.parametrize('descr', code_examples) | ||
| def test_check_rst_with_syntax_highlight_pygments(self, descr): | ||
| assert self.check_rst_data(descr) == [] | ||
| for rest_with_code in example_rst_docs: | ||
| pkg_info, dist = self.create_dist(long_description=rest_with_code) | ||
| cmd = check(dist) | ||
| cmd.check_restructuredtext() | ||
| msgs = cmd._check_rst_data(rest_with_code) | ||
| if pygments is not None: | ||
| assert len(msgs) == 0 | ||
| else: | ||
| assert len(msgs) == 1 | ||
| assert ( | ||
| str(msgs[0][1]) | ||
| == 'Cannot analyze code. Pygments package not found.' | ||
| ) | ||
| @pytest.mark.parametrize('descr', code_examples) | ||
| def test_check_rst_with_syntax_highlight_no_pygments(self, descr, hide_pygments): | ||
| (msg,) = self.check_rst_data(descr) | ||
| _, exc, _, _ = msg | ||
| assert str(exc) == 'Cannot analyze code. Pygments package not found.' | ||
@@ -198,0 +208,0 @@ def test_check_all(self): |
@@ -6,2 +6,3 @@ """Tests for distutils.filelist.""" | ||
| import re | ||
| import sys | ||
| from distutils import debug, filelist | ||
@@ -49,18 +50,18 @@ from distutils.errors import DistutilsTemplateError | ||
| def test_glob_to_re(self): | ||
| sep = os.sep | ||
| if os.sep == '\\': | ||
| sep = re.escape(os.sep) | ||
| sep = re.escape(os.sep) | ||
| # https://docs.python.org/3/whatsnew/3.14.html#re | ||
| end_of_str_metachar = r"\z" if sys.version_info >= (3, 14) else r"\Z" | ||
| for glob, regex in ( | ||
| # simple cases | ||
| ('foo*', r'(?s:foo[^%(sep)s]*)\Z'), | ||
| ('foo?', r'(?s:foo[^%(sep)s])\Z'), | ||
| ('foo??', r'(?s:foo[^%(sep)s][^%(sep)s])\Z'), | ||
| ('foo*', r'(?s:foo[^%(sep)s]*)%(eos)s'), | ||
| ('foo?', r'(?s:foo[^%(sep)s])%(eos)s'), | ||
| ('foo??', r'(?s:foo[^%(sep)s][^%(sep)s])%(eos)s'), | ||
| # special cases | ||
| (r'foo\\*', r'(?s:foo\\\\[^%(sep)s]*)\Z'), | ||
| (r'foo\\\*', r'(?s:foo\\\\\\[^%(sep)s]*)\Z'), | ||
| ('foo????', r'(?s:foo[^%(sep)s][^%(sep)s][^%(sep)s][^%(sep)s])\Z'), | ||
| (r'foo\\??', r'(?s:foo\\\\[^%(sep)s][^%(sep)s])\Z'), | ||
| (r'foo\\*', r'(?s:foo\\\\[^%(sep)s]*)%(eos)s'), | ||
| (r'foo\\\*', r'(?s:foo\\\\\\[^%(sep)s]*)%(eos)s'), | ||
| ('foo????', r'(?s:foo[^%(sep)s][^%(sep)s][^%(sep)s][^%(sep)s])%(eos)s'), | ||
| (r'foo\\??', r'(?s:foo\\\\[^%(sep)s][^%(sep)s])%(eos)s'), | ||
| ): | ||
| regex = regex % {'sep': sep} | ||
| regex = regex % {'sep': sep, 'eos': end_of_str_metachar} | ||
| assert glob_to_re(glob) == regex | ||
@@ -67,0 +68,0 @@ |
@@ -46,11 +46,4 @@ """Tests for distutils.util.""" | ||
| def test_get_host_platform(self): | ||
| with mock.patch('os.name', 'nt'): | ||
| with mock.patch('sys.version', '... [... (ARM64)]'): | ||
| assert get_host_platform() == 'win-arm64' | ||
| with mock.patch('sys.version', '... [... (ARM)]'): | ||
| assert get_host_platform() == 'win-arm32' | ||
| assert get_host_platform() == stdlib_sysconfig.get_platform() | ||
| with mock.patch('sys.version_info', (3, 9, 0, 'final', 0)): | ||
| assert get_host_platform() == stdlib_sysconfig.get_platform() | ||
| def test_get_platform(self): | ||
@@ -57,0 +50,0 @@ with mock.patch('os.name', 'nt'): |
@@ -504,4 +504,4 @@ """distutils.util | ||
| def is_freethreaded(): | ||
| def is_freethreaded() -> bool: | ||
| """Return True if the Python interpreter is built with free threading support.""" | ||
| return bool(sysconfig.get_config_var('Py_GIL_DISABLED')) |
@@ -1,9 +0,2 @@ | ||
| import sys | ||
| if sys.version_info < (3, 10): | ||
| import importlib_metadata as metadata # pragma: no cover | ||
| else: | ||
| import importlib.metadata as metadata # noqa: F401 | ||
| import importlib.metadata as metadata # noqa: F401 | ||
| import importlib.resources as resources # noqa: F401 |
@@ -6,3 +6,3 @@ from __future__ import annotations | ||
| import sys | ||
| from typing import TYPE_CHECKING, TypeVar, Union | ||
| from typing import TYPE_CHECKING, TypeVar | ||
@@ -12,6 +12,6 @@ from more_itertools import unique_everseen | ||
| if TYPE_CHECKING: | ||
| from typing_extensions import TypeAlias | ||
| from typing import TypeAlias | ||
| StrPath: TypeAlias = Union[str, os.PathLike[str]] # Same as _typeshed.StrPath | ||
| StrPathT = TypeVar("StrPathT", bound=Union[str, os.PathLike[str]]) | ||
| StrPath: TypeAlias = str | os.PathLike[str] # Same as _typeshed.StrPath | ||
| StrPathT = TypeVar("StrPathT", bound=str | os.PathLike[str]) | ||
@@ -18,0 +18,0 @@ |
| from __future__ import annotations | ||
| from collections.abc import Iterable, Iterator | ||
| from collections.abc import Callable, Iterable, Iterator | ||
| from functools import lru_cache | ||
| from typing import TYPE_CHECKING, Callable, TypeVar, Union, overload | ||
| from typing import TYPE_CHECKING, TypeVar, overload | ||
@@ -11,6 +11,6 @@ import jaraco.text as text | ||
| if TYPE_CHECKING: | ||
| from typing_extensions import TypeAlias | ||
| from typing import TypeAlias | ||
| _T = TypeVar("_T") | ||
| _StrOrIter: TypeAlias = Union[str, Iterable[str]] | ||
| _StrOrIter: TypeAlias = str | Iterable[str] | ||
@@ -17,0 +17,0 @@ |
@@ -5,3 +5,4 @@ """Convenience layer on top of stdlib's shutil and os""" | ||
| import stat | ||
| from typing import Callable, TypeVar | ||
| from collections.abc import Callable | ||
| from typing import TypeVar | ||
@@ -8,0 +9,0 @@ from .compat import py311 |
@@ -42,3 +42,3 @@ """A PEP 517 interface to setuptools | ||
| from pathlib import Path | ||
| from typing import TYPE_CHECKING, NoReturn, Union | ||
| from typing import TYPE_CHECKING, NoReturn | ||
@@ -56,3 +56,3 @@ import setuptools | ||
| if TYPE_CHECKING: | ||
| from typing_extensions import TypeAlias | ||
| from typing import TypeAlias | ||
@@ -149,3 +149,3 @@ __all__ = [ | ||
| _ConfigSettings: TypeAlias = Union[Mapping[str, Union[str, list[str], None]], None] | ||
| _ConfigSettings: TypeAlias = Mapping[str, str | list[str] | None] | None | ||
| """ | ||
@@ -152,0 +152,0 @@ Currently the user can run:: |
@@ -26,4 +26,5 @@ """setuptools.command.bdist_egg | ||
| if TYPE_CHECKING: | ||
| from typing import TypeAlias | ||
| from _typeshed import GenericPath | ||
| from typing_extensions import TypeAlias | ||
@@ -30,0 +31,0 @@ # Same as zipfile._ZipFileMode from typeshed |
@@ -40,2 +40,23 @@ """setuptools.command.egg_info | ||
| class _NormalizedMatcher: | ||
| """ | ||
| Wrap a compiled pattern so that matching is insensitive to Unicode | ||
| normalization form. | ||
| File names walked from disk (NFD on macOS APFS/HFS+) and patterns from | ||
| ``MANIFEST.in`` (typically NFC) can denote the same file while differing | ||
| byte-for-byte. Normalizing both sides before matching keeps an exclusion | ||
| (or inclusion) from silently failing. See GHSA-h35f-9h28-mq5c. | ||
| """ | ||
| def __init__(self, pattern: re.Pattern) -> None: | ||
| self._pattern = pattern | ||
| def match(self, path): | ||
| return self._pattern.match(unicode_utils.normalize(path)) | ||
| def search(self, path): | ||
| return self._pattern.search(unicode_utils.normalize(path)) | ||
| def translate_pattern(glob): # noqa: C901 # is too complex (14) # FIXME | ||
@@ -50,2 +71,7 @@ """ | ||
| # Normalize the pattern so it matches paths regardless of the Unicode | ||
| # normalization form used on disk (GHSA-h35f-9h28-mq5c). Candidate paths | ||
| # are normalized to the same form by ``_NormalizedMatcher``. | ||
| glob = unicode_utils.normalize(glob) | ||
| # This will split on '/' within [character classes]. This is deliberate. | ||
@@ -122,3 +148,3 @@ chunks = glob.split(os.path.sep) | ||
| pat += r'\Z' | ||
| return re.compile(pat, flags=re.MULTILINE | re.DOTALL) | ||
| return _NormalizedMatcher(re.compile(pat, flags=re.MULTILINE | re.DOTALL)) | ||
@@ -125,0 +151,0 @@ |
@@ -5,7 +5,9 @@ from __future__ import annotations | ||
| import sys | ||
| from typing import TYPE_CHECKING, Any, Callable | ||
| from collections.abc import Callable | ||
| from typing import TYPE_CHECKING, Any | ||
| if TYPE_CHECKING: | ||
| from typing import TypeAlias | ||
| from _typeshed import ExcInfo, StrOrBytesPath | ||
| from typing_extensions import TypeAlias | ||
@@ -12,0 +14,0 @@ # Same as shutil._OnExcCallback from typeshed |
@@ -5,4 +5,5 @@ """For backward compatibility, expose main functions from | ||
| from collections.abc import Callable | ||
| from functools import wraps | ||
| from typing import Callable, TypeVar, cast | ||
| from typing import TypeVar, cast | ||
@@ -9,0 +10,0 @@ from ..warnings import SetuptoolsDeprecationWarning |
@@ -15,3 +15,3 @@ """Translation layer between pyproject config and setuptools distribution and | ||
| import os | ||
| from collections.abc import Mapping | ||
| from collections.abc import Callable, Mapping | ||
| from email.headerregistry import Address | ||
@@ -22,3 +22,3 @@ from functools import partial, reduce | ||
| from types import MappingProxyType | ||
| from typing import TYPE_CHECKING, Any, Callable, TypeVar, Union | ||
| from typing import TYPE_CHECKING, Any, TypeVar | ||
@@ -32,3 +32,3 @@ from .. import _static | ||
| if TYPE_CHECKING: | ||
| from typing_extensions import TypeAlias | ||
| from typing import TypeAlias | ||
@@ -42,4 +42,4 @@ from setuptools._importlib import metadata | ||
| EMPTY: Mapping = MappingProxyType({}) # Immutable dict-like | ||
| _ProjectReadmeValue: TypeAlias = Union[str, dict[str, str]] | ||
| _Correspondence: TypeAlias = Callable[["Distribution", Any, Union[StrPath, None]], None] | ||
| _ProjectReadmeValue: TypeAlias = str | dict[str, str] | ||
| _Correspondence: TypeAlias = Callable[["Distribution", Any, StrPath | None], None] | ||
| _T = TypeVar("_T") | ||
@@ -46,0 +46,0 @@ |
@@ -28,3 +28,3 @@ """Utility functions to expand configuration directives or special values | ||
| import sys | ||
| from collections.abc import Iterable, Iterator, Mapping | ||
| from collections.abc import Callable, Iterable, Iterator, Mapping | ||
| from configparser import ConfigParser | ||
@@ -36,3 +36,3 @@ from glob import iglob | ||
| from types import ModuleType, TracebackType | ||
| from typing import TYPE_CHECKING, Any, Callable, TypeVar | ||
| from typing import TYPE_CHECKING, Any, TypeVar | ||
@@ -39,0 +39,0 @@ from .. import _static |
@@ -16,7 +16,7 @@ """ | ||
| import os | ||
| from collections.abc import Mapping | ||
| from collections.abc import Callable, Mapping | ||
| from contextlib import contextmanager | ||
| from functools import partial | ||
| from types import TracebackType | ||
| from typing import TYPE_CHECKING, Any, Callable | ||
| from typing import TYPE_CHECKING, Any | ||
@@ -23,0 +23,0 @@ from .._path import StrPath |
@@ -19,5 +19,5 @@ """ | ||
| from collections import defaultdict | ||
| from collections.abc import Iterable, Iterator | ||
| from collections.abc import Callable, Iterable, Iterator | ||
| from functools import partial, wraps | ||
| from typing import TYPE_CHECKING, Any, Callable, ClassVar, Generic, TypeVar, cast | ||
| from typing import TYPE_CHECKING, Any, ClassVar, Generic, TypeVar, cast | ||
@@ -35,3 +35,3 @@ from packaging.markers import default_environment as marker_env | ||
| if TYPE_CHECKING: | ||
| from typing_extensions import TypeAlias | ||
| from typing import TypeAlias | ||
@@ -38,0 +38,0 @@ from setuptools.dist import Distribution |
@@ -13,3 +13,3 @@ from __future__ import annotations | ||
| from pathlib import Path | ||
| from typing import TYPE_CHECKING, Any, Union | ||
| from typing import TYPE_CHECKING, Any | ||
@@ -48,3 +48,3 @@ from more_itertools import partition, unique_everseen | ||
| if TYPE_CHECKING: | ||
| from typing_extensions import TypeAlias | ||
| from typing import TypeAlias | ||
@@ -64,6 +64,6 @@ | ||
| """ | ||
| _Sequence: TypeAlias = Union[tuple[str, ...], list[str]] | ||
| _Sequence: TypeAlias = tuple[str, ...] | list[str] | ||
| # This is how stringifying _Sequence would look in Python 3.10 | ||
| _sequence_type_repr = "tuple[str, ...] | list[str]" | ||
| _OrderedStrSequence: TypeAlias = Union[str, dict[str, Any], Sequence[str]] | ||
| _OrderedStrSequence: TypeAlias = str | dict[str, Any] | Sequence[str] | ||
| """ | ||
@@ -70,0 +70,0 @@ :meta private: |
@@ -10,5 +10,6 @@ import contextlib | ||
| import warnings | ||
| from collections.abc import Callable | ||
| from concurrent import futures | ||
| from pathlib import Path | ||
| from typing import Any, Callable | ||
| from typing import Any | ||
| from zipfile import ZipFile | ||
@@ -15,0 +16,0 @@ |
| import os | ||
| import sys | ||
| from configparser import ConfigParser | ||
| from itertools import product | ||
| from typing import cast | ||
@@ -163,10 +162,5 @@ | ||
| @pytest.mark.parametrize( | ||
| ("config_file", "param", "circumstance"), | ||
| product( | ||
| ["setup.cfg", "setup.py", "pyproject.toml"], | ||
| ["packages", "py_modules"], | ||
| FILES.keys(), | ||
| ), | ||
| ) | ||
| @pytest.mark.parametrize("config_file", ["setup.cfg", "setup.py", "pyproject.toml"]) | ||
| @pytest.mark.parametrize("param", ["packages", "py_modules"]) | ||
| @pytest.mark.parametrize("circumstance", FILES.keys()) | ||
| def test_purposefully_empty(self, tmp_path, config_file, param, circumstance): | ||
@@ -173,0 +167,0 @@ files = self.FILES[circumstance] + ["mod.py", "other.py", "src/pkg/__init__.py"] |
@@ -13,2 +13,3 @@ """sdist tests""" | ||
| import tempfile | ||
| import unicodedata | ||
@@ -161,2 +162,17 @@ import pytest | ||
| def test_translate_pattern_unicode_normalization(): | ||
| """ | ||
| Matching is insensitive to Unicode normalization form: a pattern authored | ||
| in one form matches a path stored on disk in another (and vice versa), so | ||
| that an exclusion cannot be bypassed by an NFC/NFD mismatch. | ||
| Regression test for GHSA-h35f-9h28-mq5c. | ||
| """ | ||
| nfc = unicodedata.normalize('NFC', 'café.txt') # 'café.txt' composed | ||
| nfd = unicodedata.normalize('NFD', 'café.txt') # 'café.txt' decomposed | ||
| assert nfc != nfd # the two byte forms genuinely differ | ||
| assert translate_pattern(nfc).match(nfd) | ||
| assert translate_pattern(nfd).match(nfc) | ||
| class TempDirTestCase: | ||
@@ -336,3 +352,32 @@ def setup_method(self, method): | ||
| def test_global_exclude_unicode_normalization(self): | ||
| """ | ||
| A ``global-exclude`` authored NFC must drop a file whose on-disk name | ||
| is NFD: on macOS APFS/HFS+ the two are the same file, and even on | ||
| case/normalization-exact filesystems the decomposed name can be | ||
| committed and reach the build. Otherwise the file is published in the | ||
| sdist despite the exclusion. | ||
| Regression test for GHSA-h35f-9h28-mq5c. | ||
| """ | ||
| nfc_name = unicodedata.normalize('NFC', 'café.txt') | ||
| nfd_name = unicodedata.normalize('NFD', 'café.txt') | ||
| assert nfc_name != nfd_name | ||
| # write the file under its decomposed (NFD) name ... | ||
| touch(os.path.join(self.temp_dir, 'app', nfd_name)) | ||
| # ... and exclude it with the composed (NFC) form. | ||
| self.make_manifest( | ||
| f""" | ||
| global-include *.txt | ||
| global-exclude {nfc_name} | ||
| """ | ||
| ) | ||
| leaked = { | ||
| f | ||
| for f in self.get_files() | ||
| if unicodedata.normalize('NFC', os.path.basename(f)) == nfc_name | ||
| } | ||
| assert not leaked, f"excluded file leaked into manifest: {leaked}" | ||
| class TestFileListTest(TempDirTestCase): | ||
@@ -339,0 +384,0 @@ """ |
@@ -22,2 +22,16 @@ import sys | ||
| def normalize(text): | ||
| """ | ||
| Return *text* in a canonical Unicode form (NFC) so that names which are | ||
| visually identical but encoded differently compare equal. | ||
| macOS APFS/HFS+ store file names in decomposed form (NFD), while patterns | ||
| in ``MANIFEST.in`` are typically authored composed (NFC). The two denote | ||
| the same file but differ byte-for-byte, so matching them directly lets an | ||
| exclusion silently fail. Normalizing both the walked path and the pattern | ||
| to a single form before matching avoids that (GHSA-h35f-9h28-mq5c). | ||
| """ | ||
| return unicodedata.normalize('NFC', text) if isinstance(text, str) else text | ||
| def filesys_decode(path): | ||
@@ -24,0 +38,0 @@ """ |
@@ -18,3 +18,3 @@ """Provide basic warnings used by setuptools modules. | ||
| if TYPE_CHECKING: | ||
| from typing_extensions import TypeAlias | ||
| from typing import TypeAlias | ||
@@ -21,0 +21,0 @@ _DueDate: TypeAlias = tuple[int, int, int] # time tuple |
+1
-1
@@ -47,3 +47,3 @@ [testenv] | ||
| pytest {posargs} --cov-report xml | ||
| diff-cover coverage.xml --compare-branch=origin/main --html-report diffcov.html | ||
| diff-cover coverage.xml --compare-branch=origin/main --format html:diffcov.html | ||
| diff-cover coverage.xml --compare-branch=origin/main --fail-under=100 | ||
@@ -50,0 +50,0 @@ |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
4439900
0.09%512
0.39%70223
0.07%