diff --git a/changelog/833.feature.rst b/changelog/833.feature.rst new file mode 100644 index 00000000..ad7dd6aa --- /dev/null +++ b/changelog/833.feature.rst @@ -0,0 +1 @@ +Add support for core metadata version 2.2, defined in PEP 643. diff --git a/setup.cfg b/setup.cfg index 412eb84b..293aaa24 100644 --- a/setup.cfg +++ b/setup.cfg @@ -36,7 +36,7 @@ packages = twine.commands python_requires = >=3.6 install_requires= - pkginfo >= 1.4.2 + pkginfo >= 1.8.1 readme_renderer >= 21.0 requests >= 2.20 requests-toolbelt >= 0.8.0, != 0.9.0 diff --git a/tests/test_package.py b/tests/test_package.py index a9cd220d..62108b5d 100644 --- a/tests/test_package.py +++ b/tests/test_package.py @@ -19,6 +19,8 @@ from twine import exceptions from twine import package as package_file +from . import helpers + def test_sign_file(monkeypatch): replaced_check_call = pretend.call_recorder(lambda args: None) @@ -134,8 +136,59 @@ def test_package_safe_name_is_correct(pkg_name, expected_name): assert package.safe_name == expected_name +def test_metadata_dictionary_keys(): + """Merge multiple sources of metadata into a single dictionary.""" + package = package_file.PackageFile.from_filename(helpers.SDIST_FIXTURE, None) + assert set(package.metadata_dictionary()) == set( + [ + # identify release + "name", + "version", + # file content + "filetype", + "pyversion", + # additional meta-data + "metadata_version", + "summary", + "home_page", + "author", + "author_email", + "maintainer", + "maintainer_email", + "license", + "description", + "keywords", + "platform", + "classifiers", + "download_url", + "supported_platform", + "comment", + "md5_digest", + "sha256_digest", + "blake2_256_digest", + # PEP 314 + "provides", + "requires", + "obsoletes", + # Metadata 1.2 + "project_urls", + "provides_dist", + "obsoletes_dist", + "requires_dist", + "requires_external", + "requires_python", + # Metadata 2.1 + "provides_extras", + "description_content_type", + # Metadata 2.2 + "dynamic", + ] + ) + + @pytest.mark.parametrize("gpg_signature", [(None), (pretend.stub())]) -def test_metadata_dictionary(gpg_signature): +def test_metadata_dictionary_values(gpg_signature): + """Pass values from pkginfo.Distribution through to dictionary.""" meta = pretend.stub( name="whatever", version=pretend.stub(), @@ -164,6 +217,7 @@ def test_metadata_dictionary(gpg_signature): requires_python=pretend.stub(), provides_extras=pretend.stub(), description_content_type=pretend.stub(), + dynamic=pretend.stub(), ) package = package_file.PackageFile( @@ -219,6 +273,9 @@ def test_metadata_dictionary(gpg_signature): assert result["provides_extras"] == meta.provides_extras assert result["description_content_type"] == meta.description_content_type + # Metadata 2.2 + assert result["dynamic"] == meta.dynamic + # GPG signature assert result.get("gpg_signature") == gpg_signature diff --git a/twine/package.py b/twine/package.py index ca212a8e..da468a89 100644 --- a/twine/package.py +++ b/twine/package.py @@ -128,6 +128,10 @@ def from_filename(cls, filename: str, comment: Optional[str]) -> "PackageFile": return cls(filename, comment, meta, py_version, dtype) def metadata_dictionary(self) -> Dict[str, MetadataValue]: + """Merge multiple sources of metadata into a single dictionary. + + Includes values from filename, PKG-INFO, hashers, and signature. + """ meta = self.metadata data = { # identify release @@ -167,6 +171,8 @@ def metadata_dictionary(self) -> Dict[str, MetadataValue]: # Metadata 2.1 "provides_extras": meta.provides_extras, "description_content_type": meta.description_content_type, + # Metadata 2.2 + "dynamic": meta.dynamic, } if self.gpg_signature is not None: