Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix "Wheel naming is not following PEP 491 convention" #4766

Merged
merged 14 commits into from
Feb 2, 2025
1 change: 1 addition & 0 deletions newsfragments/4766.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix wheel file naming to follow binary distribution specification -- by :user:`di`
8 changes: 7 additions & 1 deletion setuptools/_normalization.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,13 @@ def filename_component_broken(value: str) -> str:
def safer_name(value: str) -> str:
"""Like ``safe_name`` but can be used as filename component for wheel"""
# See bdist_wheel.safer_name
return filename_component(safe_name(value))
return (
# Per https://packaging.python.org/en/latest/specifications/name-normalization/#name-normalization
re.sub(r"[-_.]+", "-", safe_name(value))
.lower()
# Per https://packaging.python.org/en/latest/specifications/binary-distribution-format/#escaping-and-unicode
.replace("-", "_")
)


def safer_best_effort_version(value: str) -> str:
Expand Down
12 changes: 1 addition & 11 deletions setuptools/command/bdist_wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,13 @@
from wheel.wheelfile import WheelFile

from .. import Command, __version__, _shutil
from .._normalization import safer_name
from ..warnings import SetuptoolsDeprecationWarning
from .egg_info import egg_info as egg_info_cls

from distutils import log


def safe_name(name: str) -> str:
"""Convert an arbitrary string to a standard distribution name
Any runs of non-alphanumeric/. characters are replaced with a single '-'.
"""
return re.sub("[^A-Za-z0-9.]+", "-", name)


def safe_version(version: str) -> str:
"""
Convert an arbitrary string to a standard version string
Expand Down Expand Up @@ -133,10 +127,6 @@ def get_abi_tag() -> str | None:
return abi


def safer_name(name: str) -> str:
return safe_name(name).replace("-", "_")


def safer_version(version: str) -> str:
return safe_version(version).replace("-", "_")

Expand Down
4 changes: 2 additions & 2 deletions setuptools/tests/test_bdist_wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,9 +246,9 @@ def test_no_scripts(wheel_paths):


def test_unicode_record(wheel_paths):
path = next(path for path in wheel_paths if "unicode.dist" in path)
path = next(path for path in wheel_paths if "unicode_dist" in path)
with ZipFile(path) as zf:
record = zf.read("unicode.dist-0.1.dist-info/RECORD")
record = zf.read("unicode_dist-0.1.dist-info/RECORD")

assert "åäö_日本語.py".encode() in record

Expand Down
4 changes: 2 additions & 2 deletions setuptools/tests/test_dist.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from setuptools import Distribution
from setuptools.dist import check_package_data, check_specifier

from .test_easy_install import make_nspkg_sdist
from .test_easy_install import make_trivial_sdist
from .test_find_packages import ensure_files
from .textwrap import DALS

Expand All @@ -25,7 +25,7 @@ def test_dist_fetch_build_egg(tmpdir):
def sdist_with_index(distname, version):
dist_dir = index.mkdir(distname)
dist_sdist = f'{distname}-{version}.tar.gz'
make_nspkg_sdist(str(dist_dir.join(dist_sdist)), distname, version)
make_trivial_sdist(str(dist_dir.join(dist_sdist)), distname, version)
with dist_dir.join('index.html').open('w') as fp:
fp.write(
DALS(
Expand Down
2 changes: 1 addition & 1 deletion setuptools/tests/test_dist_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ def test_dist_info_is_the_same_as_in_wheel(
dist_info = next(tmp_path.glob("dir_dist/*.dist-info"))

assert dist_info.name == wheel_dist_info.name
assert dist_info.name.startswith(f"{name.replace('-', '_')}-{version}{suffix}")
assert dist_info.name.startswith(f"my_proj-{version}{suffix}")
for file in "METADATA", "entry_points.txt":
assert read(dist_info / file) == read(wheel_dist_info / file)

Expand Down
18 changes: 11 additions & 7 deletions setuptools/tests/test_easy_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import setuptools.command.easy_install as ei
from pkg_resources import Distribution as PRDistribution, normalize_path, working_set
from setuptools import sandbox
from setuptools._normalization import safer_name
from setuptools.command.easy_install import PthDistributions
from setuptools.dist import Distribution
from setuptools.sandbox import run_setup
Expand Down Expand Up @@ -670,11 +671,11 @@ def test_setup_requires_override_nspkg(self, use_setup_cfg):

with contexts.save_pkg_resources_state():
with contexts.tempdir() as temp_dir:
foobar_1_archive = os.path.join(temp_dir, 'foo.bar-0.1.tar.gz')
foobar_1_archive = os.path.join(temp_dir, 'foo_bar-0.1.tar.gz')
make_nspkg_sdist(foobar_1_archive, 'foo.bar', '0.1')
# Now actually go ahead an extract to the temp dir and add the
# extracted path to sys.path so foo.bar v0.1 is importable
foobar_1_dir = os.path.join(temp_dir, 'foo.bar-0.1')
foobar_1_dir = os.path.join(temp_dir, 'foo_bar-0.1')
os.mkdir(foobar_1_dir)
with tarfile.open(foobar_1_archive) as tf:
tf.extraction_filter = lambda member, path: member
Expand All @@ -697,7 +698,7 @@ def test_setup_requires_override_nspkg(self, use_setup_cfg):
len(foo.__path__) == 2):
print('FAIL')

if 'foo.bar-0.2' not in foo.__path__[0]:
if 'foo_bar-0.2' not in foo.__path__[0]:
print('FAIL')
"""
)
Expand All @@ -718,8 +719,8 @@ def test_setup_requires_override_nspkg(self, use_setup_cfg):
# Don't even need to install the package, just
# running the setup.py at all is sufficient
run_setup(test_setup_py, ['--name'])
except pkg_resources.VersionConflict:
self.fail(
except pkg_resources.VersionConflict: # pragma: nocover
pytest.fail(
'Installing setup.py requirements caused a VersionConflict'
)

Expand Down Expand Up @@ -1120,6 +1121,8 @@ def make_nspkg_sdist(dist_path, distname, version):
package with the same name as distname. The top-level package is
designated a namespace package).
"""
# Assert that the distname contains at least one period
assert '.' in distname

parts = distname.split('.')
nspackage = parts[0]
Expand Down Expand Up @@ -1207,10 +1210,11 @@ def create_setup_requires_package(
package itself is just 'test_pkg'.
"""

normalized_distname = safer_name(distname)
test_setup_attrs = {
'name': 'test_pkg',
'version': '0.0',
'setup_requires': [f'{distname}=={version}'],
'setup_requires': [f'{normalized_distname}=={version}'],
'dependency_links': [os.path.abspath(path)],
}
if setup_attrs:
Expand Down Expand Up @@ -1259,7 +1263,7 @@ def create_setup_requires_package(
with open(os.path.join(test_pkg, 'setup.py'), 'w', encoding="utf-8") as f:
f.write(setup_py_template % test_setup_attrs)

foobar_path = os.path.join(path, f'{distname}-{version}.tar.gz')
foobar_path = os.path.join(path, f'{normalized_distname}-{version}.tar.gz')
make_package(foobar_path, distname, version)

return test_pkg
Expand Down
Loading