Skip to content

Commit 13fa2ab

Browse files
jaracosigmavirus24chadlwilson
authored
Restore support for pkginfo 1.11 (#1123)
* Split test for no_metadata and unrecognized_version. * Pin to pkginfo<1.11 to address test failures. Ref #1116 * Add logic capturing when the declared metadata version is not supported. * Only emit the supported metadata versions notice for older pkginfo versions. * Remove workaround now that pkginfo 1.11 is supported. * Cast name and version to strings. * Cast the Distribution to a CheckedDistribution, capturing the new type signature. * tests: Validate custom error message for pkginfo < 1.11 Signed-off-by: Chad Wilson <[email protected]> --------- Signed-off-by: Chad Wilson <[email protected]> Co-authored-by: Ian Stapleton Cordasco <[email protected]> Co-authored-by: Chad Wilson <[email protected]>
1 parent 03bcad6 commit 13fa2ab

File tree

5 files changed

+86
-26
lines changed

5 files changed

+86
-26
lines changed

docs/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,7 @@
291291
# TODO: Try to add these to intersphinx_mapping
292292
nitpick_ignore_regex = [
293293
(r"py:.*", r"pkginfo.*"),
294+
("py:class", r"warnings\.WarningMessage"),
294295
]
295296

296297
# -- Options for apidoc output ------------------------------------------------

pyproject.toml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,7 @@ dependencies = [
4040
"keyring >= 15.1; platform_machine != 'ppc64le' and platform_machine != 's390x'",
4141
"rfc3986 >= 1.4.0",
4242
"rich >= 12.0.0",
43-
44-
# workaround for #1116
45-
"pkginfo < 1.11",
43+
"packaging",
4644
]
4745
dynamic = ["version"]
4846

tests/test_package.py

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -383,11 +383,6 @@ def test_fips_metadata_excludes_md5_and_blake2(monkeypatch):
383383
@pytest.mark.parametrize(
384384
"read_data, missing_fields",
385385
[
386-
pytest.param(
387-
b"Metadata-Version: 102.3\nName: test-package\nVersion: 1.0.0\n",
388-
"Name, Version",
389-
id="unsupported Metadata-Version",
390-
),
391386
pytest.param(
392387
b"Metadata-Version: 2.3\nName: UNKNOWN\nVersion: UNKNOWN\n",
393388
"Name, Version",
@@ -421,10 +416,7 @@ def test_fips_metadata_excludes_md5_and_blake2(monkeypatch):
421416
],
422417
)
423418
def test_pkginfo_returns_no_metadata(read_data, missing_fields, monkeypatch):
424-
"""Raise an exception when pkginfo can't interpret the metadata.
425-
426-
This could be caused by a version number or format it doesn't support yet.
427-
"""
419+
"""Raise an exception when pkginfo can't interpret the metadata."""
428420
monkeypatch.setattr(package_file.wheel.Wheel, "read", lambda _: read_data)
429421
filename = "tests/fixtures/twine-1.5.0-py2.py3-none-any.whl"
430422

@@ -434,9 +426,33 @@ def test_pkginfo_returns_no_metadata(read_data, missing_fields, monkeypatch):
434426
assert (
435427
f"Metadata is missing required fields: {missing_fields}." in err.value.args[0]
436428
)
429+
430+
431+
def test_pkginfo_unrecognized_version(monkeypatch):
432+
"""Raise an exception when pkginfo doesn't recognize the version."""
433+
data = b"Metadata-Version: 102.3\nName: test-package\nVersion: 1.0.0\n"
434+
monkeypatch.setattr(package_file.wheel.Wheel, "read", lambda _: data)
435+
filename = "tests/fixtures/twine-1.5.0-py2.py3-none-any.whl"
436+
437+
with pytest.raises(exceptions.InvalidDistribution) as err:
438+
package_file.PackageFile.from_filename(filename, comment=None)
439+
437440
assert "1.0, 1.1, 1.2, 2.0, 2.1, 2.2" in err.value.args[0]
438441

439442

443+
def test_pkginfo_returns_no_metadata_py_below_1_11(monkeypatch):
444+
"""Raise special msg when pkginfo can't interpret metadata on pkginfo < 1.11."""
445+
data = b"Metadata-Version: 2.2\nName: UNKNOWN\nVersion: 1.0.0\n"
446+
monkeypatch.setattr(package_file.wheel.Wheel, "read", lambda _: data)
447+
monkeypatch.setattr(package_file.importlib_metadata, "version", lambda pkg: "1.10")
448+
filename = "tests/fixtures/twine-1.5.0-py2.py3-none-any.whl"
449+
450+
with pytest.raises(exceptions.InvalidDistribution) as err:
451+
package_file.PackageFile.from_filename(filename, comment=None)
452+
453+
assert "Make sure the distribution includes" in err.value.args[0]
454+
455+
440456
def test_malformed_from_file(monkeypatch):
441457
"""Raise an exception when malformed package file triggers EOFError."""
442458
filename = "tests/fixtures/malformed.tar.gz"

twine/package.py

Lines changed: 55 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,26 @@
1919
import re
2020
import subprocess
2121
import sys
22-
from typing import Any, Dict, List, NamedTuple, Optional, Sequence, Tuple, Union, cast
22+
import warnings
23+
from typing import (
24+
Any,
25+
Dict,
26+
Iterable,
27+
List,
28+
NamedTuple,
29+
Optional,
30+
Sequence,
31+
Tuple,
32+
Union,
33+
cast,
34+
)
2335

2436
if sys.version_info >= (3, 10):
2537
import importlib.metadata as importlib_metadata
2638
else:
2739
import importlib_metadata
2840

41+
import packaging.version
2942
import pkginfo
3043
from rich import print
3144

@@ -65,12 +78,19 @@ def _safe_name(name: str) -> str:
6578
return re.sub("[^A-Za-z0-9.]+", "-", name)
6679

6780

81+
class CheckedDistribution(pkginfo.Distribution):
82+
"""A Distribution whose name and version are confirmed to be defined."""
83+
84+
name: str
85+
version: str
86+
87+
6888
class PackageFile:
6989
def __init__(
7090
self,
7191
filename: str,
7292
comment: Optional[str],
73-
metadata: pkginfo.Distribution,
93+
metadata: CheckedDistribution,
7494
python_version: Optional[str],
7595
filetype: Optional[str],
7696
) -> None:
@@ -100,7 +120,8 @@ def from_filename(cls, filename: str, comment: Optional[str]) -> "PackageFile":
100120
for ext, dtype in DIST_EXTENSIONS.items():
101121
if filename.endswith(ext):
102122
try:
103-
meta = DIST_TYPES[dtype](filename)
123+
with warnings.catch_warnings(record=True) as captured:
124+
meta = DIST_TYPES[dtype](filename)
104125
except EOFError:
105126
raise exceptions.InvalidDistribution(
106127
"Invalid distribution file: '%s'" % os.path.basename(filename)
@@ -112,22 +133,29 @@ def from_filename(cls, filename: str, comment: Optional[str]) -> "PackageFile":
112133
"Unknown distribution format: '%s'" % os.path.basename(filename)
113134
)
114135

115-
# If pkginfo encounters a metadata version it doesn't support, it may give us
136+
supported_metadata = list(pkginfo.distribution.HEADER_ATTRS)
137+
if cls._is_unknown_metadata_version(captured):
138+
raise exceptions.InvalidDistribution(
139+
"Make sure the distribution is using a supported Metadata-Version: "
140+
f"{', '.join(supported_metadata)}."
141+
)
142+
# If pkginfo <1.11 encounters a metadata version it doesn't support, it may give
116143
# back empty metadata. At the very least, we should have a name and version,
117144
# which could also be empty if, for example, a MANIFEST.in doesn't include
118145
# setup.cfg.
119146
missing_fields = [
120147
f.capitalize() for f in ["name", "version"] if not getattr(meta, f)
121148
]
122149
if missing_fields:
123-
supported_metadata = list(pkginfo.distribution.HEADER_ATTRS)
124-
raise exceptions.InvalidDistribution(
125-
"Metadata is missing required fields: "
126-
f"{', '.join(missing_fields)}.\n"
127-
"Make sure the distribution includes the files where those fields "
128-
"are specified, and is using a supported Metadata-Version: "
129-
f"{', '.join(supported_metadata)}."
130-
)
150+
msg = f"Metadata is missing required fields: {', '.join(missing_fields)}."
151+
if cls._pkginfo_before_1_11():
152+
msg += (
153+
"\n"
154+
"Make sure the distribution includes the files where those fields "
155+
"are specified, and is using a supported Metadata-Version: "
156+
f"{', '.join(supported_metadata)}."
157+
)
158+
raise exceptions.InvalidDistribution(msg)
131159

132160
py_version: Optional[str]
133161
if dtype == "bdist_egg":
@@ -140,7 +168,21 @@ def from_filename(cls, filename: str, comment: Optional[str]) -> "PackageFile":
140168
else:
141169
py_version = None
142170

143-
return cls(filename, comment, meta, py_version, dtype)
171+
return cls(
172+
filename, comment, cast(CheckedDistribution, meta), py_version, dtype
173+
)
174+
175+
@staticmethod
176+
def _is_unknown_metadata_version(
177+
captured: Iterable[warnings.WarningMessage],
178+
) -> bool:
179+
NMV = getattr(pkginfo.distribution, "NewMetadataVersion", None)
180+
return any(warning.category is NMV for warning in captured)
181+
182+
@staticmethod
183+
def _pkginfo_before_1_11() -> bool:
184+
ver = packaging.version.Version(importlib_metadata.version("pkginfo"))
185+
return ver < packaging.version.Version("1.11")
144186

145187
def metadata_dictionary(self) -> Dict[str, MetadataValue]:
146188
"""Merge multiple sources of metadata into a single dictionary.

twine/repository.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,10 @@ def _upload(self, package: package_file.PackageFile) -> requests.Response:
142142

143143
with open(package.filename, "rb") as fp:
144144
data_to_send.append(
145-
("content", (package.basefilename, fp, "application/octet-stream"))
145+
(
146+
"content",
147+
(package.basefilename, fp, "application/octet-stream"),
148+
)
146149
)
147150
encoder = requests_toolbelt.MultipartEncoder(data_to_send)
148151

0 commit comments

Comments
 (0)