Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Add Import-Namespace
  • Loading branch information
brettcannon committed Jul 15, 2025
commit a5b1bcaa541adc426901a0d2b8c7c3528af5a454
7 changes: 7 additions & 0 deletions src/packaging/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ class RawMetadata(TypedDict, total=False):
license_files: list[str]

# Metadata 2.5 - PEP 794
import_namespaces: list[str]
import_names: list[str]


Expand Down Expand Up @@ -171,6 +172,7 @@ class RawMetadata(TypedDict, total=False):
"requires_dist",
"requires_external",
"supported_platforms",
"import_namespaces",
"import_names",
}

Expand Down Expand Up @@ -264,6 +266,7 @@ def _get_payload(msg: email.message.Message, source: bytes | str) -> str:
"download-url": "download_url",
"dynamic": "dynamic",
"home-page": "home_page",
"import-namespace": "import_namespaces",
"import-name": "import_names",
"keywords": "keywords",
"license": "license",
Expand Down Expand Up @@ -704,6 +707,8 @@ def _process_import_names(self, value: list[str]) -> list[str]:
)
return value

_process_import_namespaces = _process_import_names


class Metadata:
"""Representation of distribution metadata.
Expand Down Expand Up @@ -875,6 +880,8 @@ def from_email(cls, data: bytes | str, *, validate: bool = True) -> Metadata:
""":external:ref:`core-metadata-provides-dist`"""
obsoletes_dist: _Validator[list[str] | None] = _Validator(added="1.2")
""":external:ref:`core-metadata-obsoletes-dist`"""
import_namespaces: _Validator[list[str] | None] = _Validator(added="2.5")
""":external:ref:`XXX`"""
import_names: _Validator[list[str] | None] = _Validator(added="2.5")
""":external:ref:`XXX`"""
requires: _Validator[list[str] | None] = _Validator(added="1.1")
Expand Down
4 changes: 4 additions & 0 deletions tests/metadata/everything.metadata
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ Provides-Dist: OtherProject
Provides-Dist: AnotherProject (3.4)
Provides-Dist: virtual_package; python_version >= "3.4"
Dynamic: Obsoletes-Dist
Import-Namespace: spam
Import-Namespace: bacon
Import-Name: spam.eggs
Import-Name: bacon.spam
Import-Name: beaglevote
Import-Name: _beaglevote
ThisIsNotReal: Hello!
Expand Down
27 changes: 16 additions & 11 deletions tests/test_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ def test_complete(self):
raw, unparsed = metadata.parse_email(metadata_contents)
assert len(unparsed) == 1
assert unparsed["thisisnotreal"] == ["Hello!"]
assert len(raw) == 27
assert len(raw) == 28
assert raw["metadata_version"] == "2.4"
assert raw["name"] == "BeagleVote"
assert raw["version"] == "1.0a2"
Expand Down Expand Up @@ -246,7 +246,9 @@ def test_complete(self):
]
assert raw["dynamic"] == ["Obsoletes-Dist"]
assert raw["description"] == "This description intentionally left blank.\n"
assert raw["import_names"] == ["beaglevote", "_beaglevote"]
assert raw["import_namespaces"] == ["spam", "bacon"]
assert raw["import_names"] == ["spam.eggs", "bacon.spam", "beaglevote",
"_beaglevote"]


class TestExceptionGroup:
Expand Down Expand Up @@ -774,28 +776,31 @@ def test_invalid_license_files(self, license_files):
with pytest.raises(metadata.InvalidMetadata):
meta.license_files # noqa: B018

def test_valid_import_names(self):
@pytest.mark.parametrize("key", ["import_namespaces", "import_names"])
def test_valid_import_names(self, key):
import_names = ["packaging", "packaging.metadata"]
meta = metadata.Metadata.from_raw(
{"import_names": import_names}, validate=False
{key: import_names}, validate=False
)

assert meta.import_names == import_names
assert getattr(meta, key) == import_names

def test_invalid_import_names_identifier(self):
@pytest.mark.parametrize("key", ["import_namespaces", "import_names"])
def test_invalid_import_names_identifier(self, key):
import_names = ["not-valid"]
meta = metadata.Metadata.from_raw(
{"import_names": import_names}, validate=False
{key: import_names}, validate=False
)

with pytest.raises(metadata.InvalidMetadata):
meta.import_names # noqa: B018
getattr(meta, key) # noqa: B018

def test_invalid_import_names_keyword(self):
@pytest.mark.parametrize("key", ["import_namespaces", "import_names"])
def test_invalid_import_names_keyword(self, key):
import_names = ["class"]
meta = metadata.Metadata.from_raw(
{"import_names": import_names}, validate=False
{key: import_names}, validate=False
)

with pytest.raises(metadata.InvalidMetadata):
meta.import_names # noqa: B018
getattr(meta, key) # noqa: B018