Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit f9b0e50

Browse files
committedMar 20, 2025·
Convert error for ../ in license paths into deprecation warning (#4896)
2 parents 5fe9c32 + 685778d commit f9b0e50

File tree

5 files changed

+74
-7
lines changed

5 files changed

+74
-7
lines changed
 

‎newsfragments/4896.bugfix.rst

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Temporarily convert error for license glob patterns containing ``../`` into a deprecation warning
2+
to allow an accomodation period.

‎setuptools/_core_metadata.py

+10-1
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,8 @@ def write_field(key, value):
207207
if self.long_description_content_type:
208208
write_field('Description-Content-Type', self.long_description_content_type)
209209

210-
self._write_list(file, 'License-File', self.license_files or [])
210+
safe_license_files = map(_safe_license_file, self.license_files or [])
211+
self._write_list(file, 'License-File', safe_license_files)
211212
_write_requirements(self, file)
212213

213214
for field, attr in _POSSIBLE_DYNAMIC_FIELDS.items():
@@ -293,6 +294,14 @@ def _distribution_fullname(name: str, version: str) -> str:
293294
)
294295

295296

297+
def _safe_license_file(file):
298+
# XXX: Do we need this after the deprecation discussed in #4892, #4896??
299+
normalized = os.path.normpath(file).replace(os.sep, "/")
300+
if "../" in normalized:
301+
return os.path.basename(normalized) # Temporarily restore pre PEP639 behaviour
302+
return normalized
303+
304+
296305
_POSSIBLE_DYNAMIC_FIELDS = {
297306
# Core Metadata Field x related Distribution attribute
298307
"author": "author",

‎setuptools/command/bdist_wheel.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from wheel.wheelfile import WheelFile
2424

2525
from .. import Command, __version__, _shutil
26+
from .._core_metadata import _safe_license_file
2627
from .._normalization import safer_name
2728
from ..warnings import SetuptoolsDeprecationWarning
2829
from .egg_info import egg_info as egg_info_cls
@@ -582,7 +583,8 @@ def adios(p: str) -> None:
582583

583584
licenses_folder_path = os.path.join(distinfo_path, "licenses")
584585
for license_path in self.license_paths:
585-
dist_info_license_path = os.path.join(licenses_folder_path, license_path)
586+
safe_path = _safe_license_file(license_path)
587+
dist_info_license_path = os.path.join(licenses_folder_path, safe_path)
586588
os.makedirs(os.path.dirname(dist_info_license_path), exist_ok=True)
587589
shutil.copy(license_path, dist_info_license_path)
588590

‎setuptools/dist.py

+14-5
Original file line numberDiff line numberDiff line change
@@ -496,26 +496,35 @@ def _find_pattern(pattern: str, enforce_match: bool = True) -> list[str]:
496496
>>> Distribution._find_pattern("../LICENSE.MIT")
497497
Traceback (most recent call last):
498498
...
499-
setuptools.errors.InvalidConfigError: ...Pattern '../LICENSE.MIT' cannot contain '..'
499+
setuptools.warnings.SetuptoolsDeprecationWarning: ...Pattern '../LICENSE.MIT' cannot contain '..'...
500500
>>> Distribution._find_pattern("LICEN{CSE*")
501501
Traceback (most recent call last):
502502
...
503503
setuptools.warnings.SetuptoolsDeprecationWarning: ...Pattern 'LICEN{CSE*' contains invalid characters...
504504
"""
505+
pypa_guides = "specifications/glob-patterns/"
505506
if ".." in pattern:
506-
raise InvalidConfigError(f"Pattern {pattern!r} cannot contain '..'")
507+
SetuptoolsDeprecationWarning.emit(
508+
f"Pattern {pattern!r} cannot contain '..'",
509+
"""
510+
Please ensure the files specified are contained by the root
511+
of the Python package (normally marked by `pyproject.toml`).
512+
""",
513+
see_url=f"https://packaging.python.org/en/latest/{pypa_guides}",
514+
due_date=(2026, 3, 20), # Introduced in 2025-03-20
515+
# Replace with InvalidConfigError after deprecation
516+
)
507517
if pattern.startswith((os.sep, "/")) or ":\\" in pattern:
508518
raise InvalidConfigError(
509519
f"Pattern {pattern!r} should be relative and must not start with '/'"
510520
)
511521
if re.match(r'^[\w\-\.\/\*\?\[\]]+$', pattern) is None:
512-
pypa_guides = "specifications/glob-patterns/"
513522
SetuptoolsDeprecationWarning.emit(
514523
"Please provide a valid glob pattern.",
515524
"Pattern {pattern!r} contains invalid characters.",
516525
pattern=pattern,
517526
see_url=f"https://packaging.python.org/en/latest/{pypa_guides}",
518-
due_date=(2026, 2, 20), # Introduced in 2025-02-20
527+
due_date=(2026, 3, 20), # Introduced in 2025-02-20
519528
)
520529

521530
found = glob(pattern, recursive=True)
@@ -525,7 +534,7 @@ def _find_pattern(pattern: str, enforce_match: bool = True) -> list[str]:
525534
"Cannot find any files for the given pattern.",
526535
"Pattern {pattern!r} did not match any files.",
527536
pattern=pattern,
528-
due_date=(2026, 2, 20), # Introduced in 2025-02-20
537+
due_date=(2026, 3, 20), # Introduced in 2025-02-20
529538
# PEP 639 requires us to error, but as a transition period
530539
# we will only issue a warning to give people time to prepare.
531540
# After the transition, this should raise an InvalidConfigError.

‎setuptools/tests/test_bdist_wheel.py

+45
Original file line numberDiff line numberDiff line change
@@ -661,3 +661,48 @@ def test_dist_info_provided(dummy_dist, monkeypatch, tmp_path):
661661
assert expected - files_found == set()
662662
# Make sure there is no accidental egg-info bleeding into the wheel.
663663
assert not [path for path in files_found if 'egg-info' in str(path)]
664+
665+
666+
def test_allow_grace_period_parent_directory_license(monkeypatch, tmp_path):
667+
# Motivation: https://github.com/pypa/setuptools/issues/4892
668+
# TODO: Remove this test after deprecation period is over
669+
files = {
670+
"LICENSE.txt": "parent license", # <---- the license files are outside
671+
"NOTICE.txt": "parent notice",
672+
"python": {
673+
"pyproject.toml": cleandoc(
674+
"""
675+
[project]
676+
name = "test-proj"
677+
dynamic = ["version"] # <---- testing dynamic will not break
678+
[tool.setuptools.dynamic]
679+
version.file = "VERSION"
680+
"""
681+
),
682+
"setup.cfg": cleandoc(
683+
"""
684+
[metadata]
685+
license_files =
686+
../LICENSE.txt
687+
../NOTICE.txt
688+
"""
689+
),
690+
"VERSION": "42",
691+
},
692+
}
693+
jaraco.path.build(files, prefix=str(tmp_path))
694+
monkeypatch.chdir(tmp_path / "python")
695+
msg = "Pattern '../.*.txt' cannot contain '..'"
696+
with pytest.warns(SetuptoolsDeprecationWarning, match=msg):
697+
bdist_wheel_cmd().run()
698+
with ZipFile("dist/test_proj-42-py3-none-any.whl") as wf:
699+
files_found = set(wf.namelist())
700+
expected_files = {
701+
"test_proj-42.dist-info/licenses/LICENSE.txt",
702+
"test_proj-42.dist-info/licenses/NOTICE.txt",
703+
}
704+
assert expected_files <= files_found
705+
706+
metadata = wf.read("test_proj-42.dist-info/METADATA").decode("utf8")
707+
assert "License-File: LICENSE.txt" in metadata
708+
assert "License-File: NOTICE.txt" in metadata

0 commit comments

Comments
 (0)
Please sign in to comment.