🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

setuptools

Package Overview
Dependencies
Maintainers
3
Versions
624
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

setuptools - pypi Package Compare versions

Comparing version
82.0.1
to
83.0.0
+1
setuptools/_distutils/compilers/C/py.typed
# Ensure that checkers who see this as a separate namespace package still understand it's typed.
+1
-1

@@ -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

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 @@

@@ -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

@@ -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

@@ -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