Skip to content

Commit a80bd00

Browse files
committed
Fix handling of continuation lines in Description metadata field
Fixes #1218.
1 parent aa3a910 commit a80bd00

File tree

3 files changed

+83
-0
lines changed

3 files changed

+83
-0
lines changed

changelog/1220.bugfix.rst

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix handling of multiline ``Description`` metadata fields.

tests/test_package.py

+55
Original file line numberDiff line numberDiff line change
@@ -463,3 +463,58 @@ def test_setuptools_license_file(read_data, filtered, monkeypatch):
463463
package = package_file.PackageFile.from_filename(filename, comment=None)
464464
meta = package.metadata_dictionary()
465465
assert filtered != ("license_file" in meta)
466+
467+
468+
@pytest.mark.parametrize("description,expected",
469+
[
470+
# fmt: off
471+
pytest.param(
472+
"\n"
473+
"Two\n"
474+
"Lines\n",
475+
"Two\nLines\n",
476+
id="body",
477+
),
478+
pytest.param(
479+
"Description: Two\n"
480+
" |Lines\n"
481+
" |\n",
482+
"Two\nLines\n",
483+
id="multiline-header",
484+
),
485+
pytest.param(
486+
"Description: Two\n"
487+
" Lines\n"
488+
" \n",
489+
"Two\nLines\n",
490+
id="multiline-header-setuptools",
491+
),
492+
pytest.param(
493+
"Description: Two\n"
494+
" Lines\n"
495+
" Maybe Three",
496+
"Two\n Lines\n Maybe Three",
497+
id="multiline-inconsistent",
498+
),
499+
pytest.param(
500+
"Description: Two\n"
501+
" |Lines\n"
502+
" Maybe Three",
503+
"Two\n |Lines\n Maybe Three",
504+
id="multiline-mixed",
505+
),
506+
# fmt: on
507+
],
508+
)
509+
def test_description_field_continuation(description, expected, monkeypatch):
510+
"""License-File metadata entries are kept when Metadata-Version is 2.4."""
511+
read_data = (
512+
"Metadata-Version: 2.4\n"
513+
"Name: test-package\n"
514+
"Version: 1.0.0\n" +
515+
description
516+
)
517+
monkeypatch.setattr(package_file.wheel.Wheel, "read", lambda _: read_data)
518+
filename = "tests/fixtures/twine-1.5.0-py2.py3-none-any.whl"
519+
package = package_file.PackageFile.from_filename(filename, comment=None)
520+
assert package.metadata["description"] == expected

twine/package.py

+27
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,28 @@ def _safe_name(name: str) -> str:
6565
return re.sub("[^A-Za-z0-9.]+", "-", name)
6666

6767

68+
def _dedent(string: str) -> str:
69+
"""Remove line continuation suffix used for the ``Description`` metadata field.
70+
71+
The metadata standard prescribes that continuation lines should be
72+
prefixed with 7 spaces followed by a pipe character, however, setuptools
73+
used to prefix continuation lines with 8 spaces. Handle both here.
74+
75+
If not all lines following the first start with either of the accepted
76+
prefixes, the string is returned without modifications.
77+
"""
78+
lines = string.splitlines()
79+
if (
80+
False # This is here only to force black into something sensible.
81+
or all(line.startswith(" |") for line in lines[1:])
82+
or all(line.startswith(" ") for line in lines[1:])
83+
):
84+
for i in range(1, len(lines)):
85+
lines[i] = lines[i][8:]
86+
return "\n".join(lines)
87+
return string
88+
89+
6890
# Map ``metadata.RawMetadata`` fields to ``PackageMetadata`` fields. Some
6991
# fields are renamed to match the names expected in the upload form.
7092
_RAW_TO_PACKAGE_METADATA = {
@@ -232,6 +254,11 @@ def from_filename(cls, filename: str, comment: Optional[str]) -> "PackageFile":
232254
# than 2.4.
233255
if version.Version(meta.get("metadata_version", "0")) < version.Version("2.4"):
234256
meta.pop("license_files", None)
257+
258+
description = meta.get("description", None)
259+
if description is not None:
260+
meta["description"] = _dedent(description)
261+
235262
try:
236263
metadata.Metadata.from_raw(meta)
237264
except metadata.ExceptionGroup as group:

0 commit comments

Comments
 (0)