diff --git a/src/pdm/backend/_vendor/pyproject_metadata/__init__.py b/src/pdm/backend/_vendor/pyproject_metadata/__init__.py index 22fe253..5e1e001 100644 --- a/src/pdm/backend/_vendor/pyproject_metadata/__init__.py +++ b/src/pdm/backend/_vendor/pyproject_metadata/__init__.py @@ -69,7 +69,7 @@ import pdm.backend._vendor.packaging.utils import pdm.backend._vendor.packaging.version -__version__ = "0.9.0b7" +__version__ = "0.9.0" __all__ = [ "ConfigurationError", @@ -127,7 +127,6 @@ class _SmartMessageSetter: reduce boilerplate. If a value is None, do nothing. - If a value contains a newline, indent it (may produce a warning in the future). """ message: email.message.Message @@ -253,23 +252,10 @@ class StandardMetadata: """ If True, all errors will be collected and raised in an ExceptionGroup. """ - _locked_metadata: bool = False - """ - Internal flag to prevent setting non-dynamic fields after initialization. - """ def __post_init__(self) -> None: self.validate() - def __setattr__(self, name: str, value: Any) -> None: - if self._locked_metadata: - metadata_name = name.replace("_", "-") - locked_fields = constants.KNOWN_METADATA_FIELDS - set(self.dynamic) - if metadata_name in locked_fields: - msg = f"Field {name!r} is not dynamic" - raise AttributeError(msg) - super().__setattr__(name, value) - @property def auto_metadata_version(self) -> str: """ @@ -443,7 +429,6 @@ def from_pyproject( # noqa: C901 metadata_version=metadata_version, all_errors=all_errors, ) - self._locked_metadata = True pyproject.finalize("Failed to parse pyproject.toml") assert self is not None @@ -482,6 +467,7 @@ def validate(self, *, warn: bool = True) -> None: # noqa: C901 - License classifiers deprecated for metadata_version >= 2.4 (warning) - ``license`` is an SPDX license expression if metadata_version >= 2.4 - ``license_files`` is supported only for metadata_version >= 2.4 + - ``project_url`` can't contain keys over 32 characters """ errors = ErrorCollector(collect_errors=self.all_errors) @@ -545,6 +531,11 @@ def validate(self, *, warn: bool = True) -> None: # noqa: C901 msg = "{key} is supported only when emitting metadata version >= 2.4" errors.config_error(msg, key="project.license-files") + for name in self.urls: + if len(name) > 32: + msg = "{key} names cannot be more than 32 characters long" + errors.config_error(msg, key="project.urls", got=name) + errors.finalize("Metadata validation failed") def _write_metadata( # noqa: C901 @@ -566,8 +557,7 @@ def _write_metadata( # noqa: C901 if self.description: smart_message["Summary"] = self.description smart_message["Keywords"] = ",".join(self.keywords) or None - if "homepage" in self.urls: - smart_message["Home-page"] = self.urls["homepage"] + # skip 'Home-page' # skip 'Download-URL' smart_message["Author"] = _name_list(self.authors) smart_message["Author-Email"] = _email_list(self.authors) @@ -582,6 +572,12 @@ def _write_metadata( # noqa: C901 if self.license_files is not None: for license_file in sorted(set(self.license_files)): smart_message["License-File"] = os.fspath(license_file.as_posix()) + elif ( + self.auto_metadata_version not in constants.PRE_SPDX_METADATA_VERSIONS + and isinstance(self.license, License) + and self.license.file + ): + smart_message["License-File"] = os.fspath(self.license.file.as_posix()) for classifier in self.classifiers: smart_message["Classifier"] = classifier @@ -589,7 +585,7 @@ def _write_metadata( # noqa: C901 # skip 'Obsoletes-Dist' # skip 'Requires-External' for name, url in self.urls.items(): - smart_message["Project-URL"] = f"{name.capitalize()}, {url}" + smart_message["Project-URL"] = f"{name}, {url}" if self.requires_python: smart_message["Requires-Python"] = str(self.requires_python) for dep in self.dependencies: diff --git a/src/pdm/backend/_vendor/pyproject_metadata/constants.py b/src/pdm/backend/_vendor/pyproject_metadata/constants.py index afa1185..afe4281 100644 --- a/src/pdm/backend/_vendor/pyproject_metadata/constants.py +++ b/src/pdm/backend/_vendor/pyproject_metadata/constants.py @@ -48,7 +48,7 @@ def __dir__() -> list[str]: "version": frozenset(["Version"]), } -KNOWN_TOPLEVEL_FIELDS = {"build-system", "project", "tool"} +KNOWN_TOPLEVEL_FIELDS = {"build-system", "project", "tool", "dependency-groups"} KNOWN_BUILD_SYSTEM_FIELDS = {"backend-path", "build-backend", "requires"} KNOWN_PROJECT_FIELDS = set(PROJECT_TO_METADATA) @@ -58,9 +58,9 @@ def __dir__() -> list[str]: "classifier", "description", "description-content-type", - "download-url", # Not specified via pyproject standards + "download-url", # Not specified via pyproject standards, deprecated by PEP 753 "dynamic", # Can't be in dynamic - "home-page", # Not specified via pyproject standards + "home-page", # Not specified via pyproject standards, deprecated by PEP 753 "keywords", "license", "license-expression", diff --git a/src/pdm/backend/_vendor/pyproject_metadata/project_table.py b/src/pdm/backend/_vendor/pyproject_metadata/project_table.py index 84d21df..a92d66d 100644 --- a/src/pdm/backend/_vendor/pyproject_metadata/project_table.py +++ b/src/pdm/backend/_vendor/pyproject_metadata/project_table.py @@ -11,6 +11,7 @@ from __future__ import annotations import sys +import typing from typing import Any, Dict, List, Union if sys.version_info < (3, 11): @@ -28,6 +29,7 @@ "BuildSystemTable", "ContactTable", "Dynamic", + "IncludeGroupTable", "LicenseTable", "ProjectTable", "PyProjectTable", @@ -107,12 +109,44 @@ class LicenseTable(TypedDict, total=False): total=False, ) +# total=False here because this could be +# extended in the future +IncludeGroupTable = TypedDict( + "IncludeGroupTable", + {"include-group": str}, + total=False, +) + PyProjectTable = TypedDict( "PyProjectTable", { "build-system": BuildSystemTable, "project": ProjectTable, "tool": Dict[str, Any], + "dependency-groups": Dict[str, List[Union[str, IncludeGroupTable]]], }, total=False, ) + +# Tests for type checking +if typing.TYPE_CHECKING: + PyProjectTable( + { + "build-system": BuildSystemTable( + {"build-backend": "one", "requires": ["two"]} + ), + "project": ProjectTable( + { + "name": "one", + "version": "0.1.0", + } + ), + "tool": {"thing": object()}, + "dependency-groups": { + "one": [ + "one", + IncludeGroupTable({"include-group": "two"}), + ] + }, + } + ) diff --git a/src/pdm/backend/_vendor/vendor.txt b/src/pdm/backend/_vendor/vendor.txt index 9555374..3b92126 100644 --- a/src/pdm/backend/_vendor/vendor.txt +++ b/src/pdm/backend/_vendor/vendor.txt @@ -1,5 +1,5 @@ packaging==24.1 tomli==2.0.1 tomli_w==1.0.0 -pyproject-metadata==0.9.0b7 +pyproject-metadata==0.9.0 editables==0.5