Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/metadata.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ Usage
.. doctest::

>>> from packaging.metadata import parse_email
>>> metadata = "Metadata-Version: 2.3\nName: packaging\nVersion: 24.0"
>>> metadata = "Metadata-Version: 2.6\nName: packaging\nVersion: 24.0"
>>> raw, unparsed = parse_email(metadata)
>>> raw["metadata_version"]
'2.3'
'2.6'
>>> raw["name"]
'packaging'
>>> raw["version"]
Expand Down
18 changes: 16 additions & 2 deletions src/packaging/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@ class RawMetadata(TypedDict, total=False):
import_names: list[str]
import_namespaces: list[str]

# Metadata 2.6 - PEP 808 (no new fields, behavior change for Dynamic)


# 'keywords' is special as it's a string in the core metadata spec, but we
# represent it as a list.
Expand Down Expand Up @@ -512,8 +514,20 @@ def parse_email(data: bytes | str) -> tuple[RawMetadata, dict[str, list[str]]]:


# Keep the two values in sync.
_VALID_METADATA_VERSIONS = ["1.0", "1.1", "1.2", "2.1", "2.2", "2.3", "2.4", "2.5"]
_MetadataVersion = Literal["1.0", "1.1", "1.2", "2.1", "2.2", "2.3", "2.4", "2.5"]
_VALID_METADATA_VERSIONS = [
"1.0",
"1.1",
"1.2",
"2.1",
"2.2",
"2.3",
"2.4",
"2.5",
"2.6",
]
_MetadataVersion = Literal[
"1.0", "1.1", "1.2", "2.1", "2.2", "2.3", "2.4", "2.5", "2.6"
]

_REQUIRED_ATTRS = frozenset(["metadata_version", "name", "version"])

Expand Down
2 changes: 1 addition & 1 deletion tests/metadata/everything.metadata
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Metadata-Version: 2.5
Metadata-Version: 2.6
Name: BeagleVote
Version: 1.0a2
Platform: ObscureUnix
Expand Down
81 changes: 71 additions & 10 deletions tests/test_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ def test_complete(self) -> None:
assert len(unparsed) == 1 # "ThisIsNotReal" key
assert unparsed["thisisnotreal"] == ["Hello!"]
assert len(raw) == 28
assert raw["metadata_version"] == "2.5"
assert raw["metadata_version"] == "2.6"
assert raw["name"] == "BeagleVote"
assert raw["version"] == "1.0a2"
assert raw["platforms"] == ["ObscureUnix", "RareDOS"]
Expand Down Expand Up @@ -274,7 +274,7 @@ def test_repr(self) -> None:


_RAW_EXAMPLE: RawMetadata = {
"metadata_version": "2.5",
"metadata_version": "2.6",
"name": "packaging",
"version": "2023.0.0",
}
Expand All @@ -301,7 +301,7 @@ def _invalid_with_cause(
assert isinstance(exc.__cause__, cause)

def test_from_email(self) -> None:
metadata_version = "2.5"
metadata_version = "2.6"
meta = metadata.Metadata.from_email(
f"Metadata-Version: {metadata_version}", validate=False
)
Expand All @@ -311,7 +311,7 @@ def test_from_email(self) -> None:

def test_from_email_empty_import_name(self) -> None:
meta = metadata.Metadata.from_email(
"Metadata-Version: 2.5\nImport-Name:\n", validate=False
"Metadata-Version: 2.6\nImport-Name:\n", validate=False
)
assert meta.import_names == []

Expand Down Expand Up @@ -447,7 +447,9 @@ def test_multi_value_unvalidated_attribute(self, attribute: str) -> None:

assert getattr(meta, attribute) == values

@pytest.mark.parametrize("version", ["1.0", "1.1", "1.2", "2.1", "2.2", "2.3"])
@pytest.mark.parametrize(
"version", ["1.0", "1.1", "1.2", "2.1", "2.2", "2.3", "2.4", "2.5", "2.6"]
)
def test_valid_metadata_version(self, version: str) -> None:
meta = metadata.Metadata.from_raw({"metadata_version": version}, validate=False)

Expand Down Expand Up @@ -644,6 +646,65 @@ def test_invalid_dynamic_value(self) -> None:
with pytest.raises(metadata.InvalidMetadata):
meta.dynamic # noqa: B018

def test_pep808_dynamic_with_static_values(self) -> None:
"""PEP 808 allows list/table fields to have static values
while their key is also listed in ``Dynamic``."""
raw: RawMetadata = {
"metadata_version": "2.6",
"name": "packaging",
"version": "1.0.0",
"classifiers": ["Development Status :: 4 - Beta"],
"requires_dist": ["pytest"],
"provides_extra": ["test"],
"license_files": ["LICENSE"],
"project_urls": {"homepage": "example.com"},
"import_names": ["packaging"],
"import_namespaces": ["pkg.ns"],
"dynamic": [
"classifier",
"requires-dist",
"provides-extra",
"license-file",
"project-url",
"import-name",
"import-namespace",
],
}

meta = metadata.Metadata.from_raw(raw, validate=True)
assert meta.classifiers == ["Development Status :: 4 - Beta"]
assert meta.requires_dist == [requirements.Requirement("pytest")]
assert meta.provides_extra == [utils.canonicalize_name("test")]
assert meta.license_files == ["LICENSE"]
assert meta.project_urls == {"homepage": "example.com"}
assert meta.import_names == ["packaging"]
assert meta.import_namespaces == ["pkg.ns"]
assert meta.dynamic == [
"classifier",
"requires-dist",
"provides-extra",
"license-file",
"project-url",
"import-name",
"import-namespace",
]

rfc822 = meta.as_rfc822()
assert ("classifier", "Development Status :: 4 - Beta") in rfc822.items()
assert ("requires-dist", "pytest") in rfc822.items()
assert ("provides-extra", "test") in rfc822.items()
assert ("license-file", "LICENSE") in rfc822.items()
assert ("project-url", "homepage, example.com") in rfc822.items()
assert ("import-name", "packaging") in rfc822.items()
assert ("import-namespace", "pkg.ns") in rfc822.items()
assert ("dynamic", "classifier") in rfc822.items()
assert ("dynamic", "requires-dist") in rfc822.items()
assert ("dynamic", "provides-extra") in rfc822.items()
assert ("dynamic", "license-file") in rfc822.items()
assert ("dynamic", "project-url") in rfc822.items()
assert ("dynamic", "import-name") in rfc822.items()
assert ("dynamic", "import-namespace") in rfc822.items()

@pytest.mark.parametrize("field_name", ["name", "version", "metadata-version"])
def test_disallowed_dynamic(self, field_name: str) -> None:
meta = metadata.Metadata.from_raw({"dynamic": [field_name]}, validate=False)
Expand Down Expand Up @@ -855,7 +916,7 @@ def test_write_metadata(self) -> None:
meta = metadata.Metadata.from_raw(_RAW_EXAMPLE)
written = meta.as_rfc822().as_string()
assert (
written == "metadata-version: 2.5\nname: packaging\nversion: 2023.0.0\n\n"
written == "metadata-version: 2.6\nname: packaging\nversion: 2023.0.0\n\n"
)

def test_write_metadata_with_description(self) -> None:
Expand Down Expand Up @@ -1005,7 +1066,7 @@ def test_modern_license(self) -> None:
def test__import_names(self) -> None:
meta = metadata.Metadata.from_raw(
{
"metadata_version": "2.5",
"metadata_version": "2.6",
"name": "full_metadata",
"version": "3.2.1",
"import_names": ["one", "two"],
Expand All @@ -1015,7 +1076,7 @@ def test__import_names(self) -> None:

core_metadata = meta.as_rfc822()
assert core_metadata.items() == [
("metadata-version", "2.5"),
("metadata-version", "2.6"),
("name", "full_metadata"),
("version", "3.2.1"),
("import-name", "one"),
Expand All @@ -1028,7 +1089,7 @@ def test__import_names(self) -> None:
def test_empty_import_names(self) -> None:
meta = metadata.Metadata.from_raw(
{
"metadata_version": "2.5",
"metadata_version": "2.6",
"name": "full_metadata",
"version": "3.2.1",
"import_names": [],
Expand All @@ -1037,7 +1098,7 @@ def test_empty_import_names(self) -> None:

core_metadata = meta.as_rfc822()
assert core_metadata.items() == [
("metadata-version", "2.5"),
("metadata-version", "2.6"),
("name", "full_metadata"),
("version", "3.2.1"),
("import-name", ""),
Expand Down
Loading