From 979596a4e00e57ea183532e8036d8cbe49cb9a09 Mon Sep 17 00:00:00 2001 From: Anderson Bravalheri Date: Wed, 23 Feb 2022 02:57:49 +0000 Subject: [PATCH] Back-fill Description-Content-Type according to readme suffix According to PEP 621, the backend should fill-in the content-type if the `readme` field is passed as a string. The value is derived from the extension of the file (an error should be raised when that is not possible). Previously all READMEs were wrongly assumed rst. This error was reported in: https://discuss.python.org/t/help-testing-experimental-features-in-setuptools/13821/4 --- setuptools/config/_apply_pyprojecttoml.py | 25 ++++++++++- .../tests/config/test_apply_pyprojecttoml.py | 41 +++++++++++++++++-- 2 files changed, 61 insertions(+), 5 deletions(-) diff --git a/setuptools/config/_apply_pyprojecttoml.py b/setuptools/config/_apply_pyprojecttoml.py index 0d2ead8832..f711c8a2ec 100644 --- a/setuptools/config/_apply_pyprojecttoml.py +++ b/setuptools/config/_apply_pyprojecttoml.py @@ -74,18 +74,39 @@ def _set_config(dist: "Distribution", field: str, value: Any): setattr(dist, field, value) +_CONTENT_TYPES = { + ".md": "text/markdown", + ".rst": "text/x-rst", + ".txt": "text/plain", +} + + +def _guess_content_type(file: str) -> Optional[str]: + _, ext = os.path.splitext(file.lower()) + if not ext: + return None + + if ext in _CONTENT_TYPES: + return _CONTENT_TYPES[ext] + + valid = ", ".join(f"{k} ({v})" for k, v in _CONTENT_TYPES.items()) + msg = f"only the following file extensions are recognized: {valid}." + raise ValueError(f"Undefined content type for {file}, {msg}") + + def _long_description(dist: "Distribution", val: _DictOrStr, root_dir: _Path): from setuptools.config import expand if isinstance(val, str): text = expand.read_files(val, root_dir) - ctype = "text/x-rst" + ctype = _guess_content_type(val) else: text = val.get("text") or expand.read_files(val.get("file", []), root_dir) ctype = val["content-type"] _set_config(dist, "long_description", text) - _set_config(dist, "long_description_content_type", ctype) + if ctype: + _set_config(dist, "long_description_content_type", ctype) def _license(dist: "Distribution", val: Union[str, dict], _root_dir): diff --git a/setuptools/tests/config/test_apply_pyprojecttoml.py b/setuptools/tests/config/test_apply_pyprojecttoml.py index 4d9c8c5f62..5b5a8dfa7f 100644 --- a/setuptools/tests/config/test_apply_pyprojecttoml.py +++ b/setuptools/tests/config/test_apply_pyprojecttoml.py @@ -129,18 +129,53 @@ def main_tomatoes(): pass """ -def test_pep621_example(tmp_path): - """Make sure the example in PEP 621 works""" +def _pep621_example_project(tmp_path, readme="README.rst"): pyproject = tmp_path / "pyproject.toml" - pyproject.write_text(PEP621_EXAMPLE) + text = PEP621_EXAMPLE + replacements = {'readme = "README.rst"': f'readme = "{readme}"'} + for orig, subst in replacements.items(): + text = text.replace(orig, subst) + pyproject.write_text(text) + (tmp_path / "README.rst").write_text("hello world") (tmp_path / "LICENSE.txt").write_text("--- LICENSE stub ---") (tmp_path / "spam.py").write_text(PEP621_EXAMPLE_SCRIPT) + return pyproject + +def test_pep621_example(tmp_path): + """Make sure the example in PEP 621 works""" + pyproject = _pep621_example_project(tmp_path) dist = pyprojecttoml.apply_configuration(Distribution(), pyproject) assert set(dist.metadata.license_files) == {"LICENSE.txt"} +@pytest.mark.parametrize( + "readme, ctype", + [ + ("Readme.txt", "text/plain"), + ("readme.md", "text/markdown"), + ("text.rst", "text/x-rst"), + ] +) +def test_readme_content_type(tmp_path, readme, ctype): + pyproject = _pep621_example_project(tmp_path, readme) + dist = pyprojecttoml.apply_configuration(Distribution(), pyproject) + assert dist.metadata.long_description_content_type == ctype + + +def test_undefined_content_type(tmp_path): + pyproject = _pep621_example_project(tmp_path, "README.tex") + with pytest.raises(ValueError, match="Undefined content type for README.tex"): + pyprojecttoml.apply_configuration(Distribution(), pyproject) + + +def test_no_explicit_content_type_for_missing_extension(tmp_path): + pyproject = _pep621_example_project(tmp_path, "README") + dist = pyprojecttoml.apply_configuration(Distribution(), pyproject) + assert dist.metadata.long_description_content_type is None + + # --- Auxiliary Functions ---