Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-pyproject-metadata for 
openSUSE:Factory checked in at 2026-01-27 16:06:53
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-pyproject-metadata (Old)
 and      /work/SRC/openSUSE:Factory/.python-pyproject-metadata.new.1928 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-pyproject-metadata"

Tue Jan 27 16:06:53 2026 rev:6 rq:1329163 version:0.10.0

Changes:
--------
--- 
/work/SRC/openSUSE:Factory/python-pyproject-metadata/python-pyproject-metadata.changes
      2025-03-16 18:58:38.568610389 +0100
+++ 
/work/SRC/openSUSE:Factory/.python-pyproject-metadata.new.1928/python-pyproject-metadata.changes
    2026-01-27 16:07:02.032805235 +0100
@@ -1,0 +2,17 @@
+Mon Jan 26 08:23:32 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 0.10.0:
+  * This release adds support for PEP 794 (METADATA 2.5),
+    the new import-names(paces) fields. Support hasn't rolled out
+    in other packages yet, but once it does, you can be ready for
+    it with this release.
+  * As usual, nothing changes if you don't specify the new fields
+    or the new METADATA version.
+  * Add PyPy 3.11 testing
+  * Add Python 3.14 classifier
+  * Use PEP 639 license
+  * Use dependency groups
+  * Enable branch coverage
+  * Enabled most Ruff linting rules on codebase
+
+-------------------------------------------------------------------

Old:
----
  pyproject-metadata-0.9.1.tar.gz

New:
----
  pyproject-metadata-0.10.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-pyproject-metadata.spec ++++++
--- /var/tmp/diff_new_pack.kbJVkX/_old  2026-01-27 16:07:02.612829696 +0100
+++ /var/tmp/diff_new_pack.kbJVkX/_new  2026-01-27 16:07:02.616829864 +0100
@@ -1,7 +1,7 @@
 #
 # spec file for package python-pyproject-metadata
 #
-# Copyright (c) 2025 SUSE LLC
+# Copyright (c) 2026 SUSE LLC and contributors
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -18,7 +18,7 @@
 
 %{?sle15_python_module_pythons}
 Name:           python-pyproject-metadata
-Version:        0.9.1
+Version:        0.10.0
 Release:        0
 Summary:        PEP 621 metadata parsing
 License:        MIT

++++++ pyproject-metadata-0.9.1.tar.gz -> pyproject-metadata-0.10.0.tar.gz 
++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyproject-metadata-0.9.1/.github/release.yml 
new/pyproject-metadata-0.10.0/.github/release.yml
--- old/pyproject-metadata-0.9.1/.github/release.yml    2025-03-10 
23:10:43.000000000 +0100
+++ new/pyproject-metadata-0.10.0/.github/release.yml   2025-11-21 
16:26:50.000000000 +0100
@@ -1,5 +1,5 @@
 changelog:
   exclude:
     authors:
-      - dependabot
-      - pre-commit-ci
+      - dependabot[bot]
+      - pre-commit-ci[bot]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pyproject-metadata-0.9.1/.github/workflows/checks.yml 
new/pyproject-metadata-0.10.0/.github/workflows/checks.yml
--- old/pyproject-metadata-0.9.1/.github/workflows/checks.yml   2025-03-10 
23:10:43.000000000 +0100
+++ new/pyproject-metadata-0.10.0/.github/workflows/checks.yml  2025-11-21 
16:26:50.000000000 +0100
@@ -10,15 +10,14 @@
   mypy:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v4
-
-      - name: Install nox
-        run: pipx install nox
+      - uses: actions/checkout@v5
 
       - name: Setup Python
-        uses: actions/setup-python@v5
+        uses: actions/setup-python@v6
         with:
           python-version: 3.8
 
+      - uses: astral-sh/setup-uv@v7
+
       - name: Run check for type
-        run: nox -s mypy
+        run: uv run noxfile.py -s mypy
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pyproject-metadata-0.9.1/.github/workflows/release.yml 
new/pyproject-metadata-0.10.0/.github/workflows/release.yml
--- old/pyproject-metadata-0.9.1/.github/workflows/release.yml  2025-03-10 
23:10:43.000000000 +0100
+++ new/pyproject-metadata-0.10.0/.github/workflows/release.yml 2025-11-21 
16:26:50.000000000 +0100
@@ -18,7 +18,7 @@
     runs-on: ubuntu-latest
 
     steps:
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v5
       - uses: hynek/build-and-inspect-python-package@v2
 
   # Upload to real PyPI on GitHub Releases.
@@ -34,13 +34,13 @@
 
     steps:
       - name: Download packages built by build-and-inspect-python-package
-        uses: actions/download-artifact@v4
+        uses: actions/download-artifact@v6
         with:
           name: Packages
           path: dist
 
       - name: Generate artifact attestation for sdist and wheel
-        uses: 
actions/attest-build-provenance@bd77c077858b8d561b7a36cbe48ef4cc642ca39d # 
v2.2.2
+        uses: 
actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # 
v3.0.0
         with:
           subject-path: "dist/pyproject*"
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyproject-metadata-0.9.1/.github/workflows/tests.yml 
new/pyproject-metadata-0.10.0/.github/workflows/tests.yml
--- old/pyproject-metadata-0.9.1/.github/workflows/tests.yml    2025-03-10 
23:10:43.000000000 +0100
+++ new/pyproject-metadata-0.10.0/.github/workflows/tests.yml   2025-11-21 
16:26:50.000000000 +0100
@@ -28,41 +28,38 @@
           - "3.13"
           - "3.14"
         include:
-          - os: macos-13
-            python: "3.7"
+          - os: macos-15-intel
+            python: "3.8"
           - os: macos-14
             python: "3.12"
           - os: ubuntu-latest
-            python: "pypy-3.10"
+            python: "pypy-3.11"
           - os: windows-latest
             python: "3.8"
           - os: windows-latest
             python: "3.11"
           - os: windows-latest
             python: "3.13"
-          - os: ubuntu-22.04
-            python: "3.8"
 
     steps:
       - name: Checkout
-        uses: actions/checkout@v4
+        uses: actions/checkout@v5
 
       - name: Set up target Python
-        uses: actions/setup-python@v5
+        uses: actions/setup-python@v6
         with:
           python-version: ${{ matrix.python }}
           allow-prereleases: true
 
       - name: Install the latest version of uv
-        uses: astral-sh/setup-uv@v5
-
-      - name: Limit virtualenv on 3.7
-        if: matrix.python == '3.7'
-        run: pipx inject --force nox 'virtualenv<20.27.0'
+        uses: astral-sh/setup-uv@v7
 
       - name: Run tests
         run: uv run noxfile.py -s test-${{ matrix.python }}
 
+      - name: Run minimum tests
+        run: uv run noxfile.py -s minimums-${{ matrix.python }}
+
       - name: Send coverage report
         uses: codecov/codecov-action@v5
         env:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyproject-metadata-0.9.1/.gitignore 
new/pyproject-metadata-0.10.0/.gitignore
--- old/pyproject-metadata-0.9.1/.gitignore     2025-03-10 23:10:43.000000000 
+0100
+++ new/pyproject-metadata-0.10.0/.gitignore    2025-11-21 16:26:50.000000000 
+0100
@@ -158,3 +158,6 @@
 #  and can be added to the global gitignore or merged into this file.  For a 
more nuclear
 #  option (not recommended) you can uncomment the following to ignore the 
entire idea folder.
 #.idea/
+
+*pylock.toml
+uv.lock
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyproject-metadata-0.9.1/.pre-commit-config.yaml 
new/pyproject-metadata-0.10.0/.pre-commit-config.yaml
--- old/pyproject-metadata-0.9.1/.pre-commit-config.yaml        2025-03-10 
23:10:43.000000000 +0100
+++ new/pyproject-metadata-0.10.0/.pre-commit-config.yaml       2025-11-21 
16:26:50.000000000 +0100
@@ -6,7 +6,7 @@
 
 repos:
   - repo: https://github.com/pre-commit/pre-commit-hooks
-    rev: v5.0.0
+    rev: v6.0.0
     hooks:
       - id: check-ast
       - id: check-builtin-literals
@@ -19,9 +19,9 @@
       - id: trailing-whitespace
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: "v0.9.10"
+    rev: "v0.14.5"
     hooks:
-      - id: ruff
+      - id: ruff-check
         args: ["--fix", "--show-fixes"]
       - id: ruff-format
 
@@ -33,20 +33,20 @@
       - id: rst-inline-touching-normal
 
   - repo: https://github.com/adamchainz/blacken-docs
-    rev: 1.19.1
+    rev: 1.20.0
     hooks:
       - id: blacken-docs
-        additional_dependencies: [black==24.*]
+        additional_dependencies: [black==25.*]
 
   - repo: https://github.com/rbubley/mirrors-prettier
-    rev: "v3.5.3"
+    rev: "v3.6.2"
     hooks:
       - id: prettier
         types_or: [yaml, markdown, html, css, scss, javascript, json]
         args: [--prose-wrap=always]
 
   - repo: https://github.com/henryiii/check-sdist
-    rev: "v1.2.0"
+    rev: "v1.3.0"
     hooks:
       - id: check-sdist
         args: [--inject-junk]
@@ -60,17 +60,17 @@
         exclude: 
^(LICENSE$|src/scikit_build_core/resources/find_python|tests/test_skbuild_settings.py$)
 
   - repo: https://github.com/shellcheck-py/shellcheck-py
-    rev: v0.10.0.1
+    rev: v0.11.0.1
     hooks:
       - id: shellcheck
 
   - repo: https://github.com/henryiii/validate-pyproject-schema-store
-    rev: 2025.03.03
+    rev: 2025.11.14
     hooks:
       - id: validate-pyproject
 
   - repo: https://github.com/python-jsonschema/check-jsonschema
-    rev: 0.31.3
+    rev: 0.35.0
     hooks:
       - id: check-dependabot
       - id: check-github-workflows
@@ -79,6 +79,6 @@
         files: \.schema\.json
 
   - repo: https://github.com/scientific-python/cookie
-    rev: 2025.01.22
+    rev: 2025.11.10
     hooks:
       - id: sp-repo-review
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyproject-metadata-0.9.1/.readthedocs.yml 
new/pyproject-metadata-0.10.0/.readthedocs.yml
--- old/pyproject-metadata-0.9.1/.readthedocs.yml       2025-03-10 
23:10:43.000000000 +0100
+++ new/pyproject-metadata-0.10.0/.readthedocs.yml      2025-11-21 
16:26:50.000000000 +0100
@@ -1,12 +1,15 @@
 version: 2
 
+sphinx:
+  configuration: docs/conf.py
+
 build:
-  os: ubuntu-22.04
+  os: "ubuntu-22.04"
   tools:
     python: "3.12"
-
-python:
-  install:
-    - method: pip
-      path: .
-      extra_requirements: [docs]
+  commands:
+    - asdf plugin add uv
+    - asdf install uv latest
+    - asdf global uv latest
+    - uv run --group docs sphinx-build -T -b html -d docs/_build/doctrees -D
+      language=en docs $READTHEDOCS_OUTPUT/html
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyproject-metadata-0.9.1/README.md 
new/pyproject-metadata-0.10.0/README.md
--- old/pyproject-metadata-0.9.1/README.md      2025-03-10 23:10:43.000000000 
+0100
+++ new/pyproject-metadata-0.10.0/README.md     2025-11-21 16:26:50.000000000 
+0100
@@ -47,7 +47,7 @@
     )
 ```
 
-A backend is also expected to copy entries from `project.licence_files`, which
+A backend is also expected to copy entries from `project.license_files`, which
 are paths relative to the project directory, into the `dist-info/licenses`
 folder, preserving the original source structure.
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyproject-metadata-0.9.1/docs/changelog.md 
new/pyproject-metadata-0.10.0/docs/changelog.md
--- old/pyproject-metadata-0.9.1/docs/changelog.md      2025-03-10 
23:10:43.000000000 +0100
+++ new/pyproject-metadata-0.10.0/docs/changelog.md     2025-11-21 
16:26:50.000000000 +0100
@@ -1,9 +1,31 @@
 # Changelog
 
-## Unreleased
+## 0.10.0 (11-21-2025)
 
+This release adds support for [PEP 794](https://peps.python.org/pep-0794/)
+(METADATA 2.5), the new import-names(paces) fields. Support hasn't rolled out 
in
+other packages yet, but once it does, you can be ready for it with this 
release.
+As usual, nothing changes if you don't specify the new fields or the new
+METADATA version.
+
+Features:
+
+- Support `import-names(paces)`
 - Remove Python 3.7 support
 
+Fixes:
+
+- Minimum supported version of packaging corrected (now tested)
+
+Internal and CI:
+
+- Add PyPy 3.11 testing
+- Add Python 3.14 classifier
+- Use PEP 639 license
+- Use dependency groups
+- Enable branch coverage
+- Enabled most Ruff linting rules on codebase
+
 ## 0.9.1 (10-03-2024)
 
 This release fixes form feeds in License files using pre-PEP 639 syntax when
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyproject-metadata-0.9.1/noxfile.py 
new/pyproject-metadata-0.10.0/noxfile.py
--- old/pyproject-metadata-0.9.1/noxfile.py     2025-03-10 23:10:43.000000000 
+0100
+++ new/pyproject-metadata-0.10.0/noxfile.py    2025-11-21 16:26:50.000000000 
+0100
@@ -1,3 +1,4 @@
+#!/usr/bin/env -S uv run --script
 # SPDX-License-Identifier: MIT
 
 # /// script
@@ -5,17 +6,16 @@
 # ///
 
 import argparse
-import os
-import os.path
+from pathlib import Path
 
 import nox
 
 nox.needs_version = ">=2025.2.9"
-nox.options.reuse_existing_virtualenvs = True
 nox.options.default_venv_backend = "uv|virtualenv"
 
-ALL_PYTHONS = 
nox.project.python_versions(nox.project.load_toml("pyproject.toml"))
-ALL_PYTHONS += ["3.14", "pypy-3.10"]
+PYPROJECT = nox.project.load_toml("pyproject.toml")
+ALL_PYTHONS = nox.project.python_versions(PYPROJECT)
+ALL_PYTHONS += ["pypy-3.11"]
 
 
 @nox.session(python="3.8")
@@ -23,9 +23,8 @@
     """
     Run a type checker.
     """
-    session.install(".", "mypy", "nox", "pytest")
-
-    session.run("mypy", "pyproject_metadata", "tests", "noxfile.py")
+    session.install("-e.", "mypy", "nox", "pytest")
+    session.run("mypy")
 
 
 @nox.session(python=ALL_PYTHONS)
@@ -33,12 +32,11 @@
     """
     Run the test suite.
     """
-    htmlcov_output = os.path.join(session.virtualenv.location, "htmlcov")
-    xmlcov_output = os.path.join(
-        session.virtualenv.location, f"coverage-{session.python}.xml"
-    )
+    htmlcov_output = Path(session.virtualenv.location) / "htmlcov"
+    xmlcov_output = Path(session.virtualenv.location) / 
f"coverage-{session.python}.xml"
 
-    session.install("-e.[test]")
+    test_grp = nox.project.dependency_groups(PYPROJECT, "test")
+    session.install("-e.", *test_grp)
 
     session.run(
         "pytest",
@@ -52,12 +50,34 @@
     )
 
 
[email protected](venv_backend="uv", default=False, python=ALL_PYTHONS)
+def minimums(session: nox.Session) -> None:
+    """
+    Check minimum requirements.
+    """
+    test_grp = nox.project.dependency_groups(PYPROJECT, "test")
+    session.install("-e.", "--resolution=lowest-direct", *test_grp, 
silent=False)
+
+    xmlcov_output = (
+        Path(session.virtualenv.location) / 
f"coverage-{session.python}-min.xml"
+    )
+
+    session.run(
+        "pytest",
+        "--cov",
+        f"--cov-report=xml:{xmlcov_output}",
+        "--cov-report=term-missing",
+        "--cov-context=test",
+        "tests/",
+        *session.posargs,
+    )
+
+
 @nox.session(default=False)
 def docs(session: nox.Session) -> None:
     """
     Build the docs. Use "--non-interactive" to avoid serving. Pass "-b 
linkcheck" to check links.
     """
-
     parser = argparse.ArgumentParser()
     parser.add_argument(
         "-b", dest="builder", default="html", help="Build target (default: 
html)"
@@ -66,7 +86,8 @@
 
     serve = args.builder == "html" and session.interactive
     extra_installs = ["sphinx-autobuild"] if serve else []
-    session.install("-e.[docs]", *extra_installs)
+    docs_grp = nox.project.dependency_groups(PYPROJECT, "docs")
+    session.install("-e.", *docs_grp, *extra_installs)
 
     session.chdir("docs")
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyproject-metadata-0.9.1/pyproject.toml 
new/pyproject-metadata-0.10.0/pyproject.toml
--- old/pyproject-metadata-0.9.1/pyproject.toml 2025-03-10 23:10:43.000000000 
+0100
+++ new/pyproject-metadata-0.10.0/pyproject.toml        2025-11-21 
16:26:50.000000000 +0100
@@ -1,5 +1,5 @@
 [build-system]
-requires = ["flit-core"]
+requires = ["flit-core>=3.11"]
 build-backend = "flit_core.buildapi"
 
 [project]
@@ -7,28 +7,32 @@
 dynamic = ["version"]
 description = "PEP 621 metadata parsing"
 readme = "README.md"
-requires-python = ">=3.7"
+requires-python = ">=3.8"
+license = "MIT"
+license-files = ["LICENSE"]
 authors = [
     { name = "Filipe Laíns", email = "[email protected]" },
 ]
 classifiers = [
-    "License :: OSI Approved :: MIT License",
     "Programming Language :: Python :: 3",
     "Programming Language :: Python :: 3 :: Only",
-    "Programming Language :: Python :: 3.7",
     "Programming Language :: Python :: 3.8",
     "Programming Language :: Python :: 3.9",
     "Programming Language :: Python :: 3.10",
     "Programming Language :: Python :: 3.11",
     "Programming Language :: Python :: 3.12",
     "Programming Language :: Python :: 3.13",
+    "Programming Language :: Python :: 3.14",
 ]
 dependencies = [
-    "packaging>=19.0",
-    "typing_extensions; python_version<'3.8'",
+    "packaging>=23.2",
 ]
 
-[project.optional-dependencies]
+[project.urls]
+changelog = "https://pep621.readthedocs.io/en/stable/changelog.html";
+homepage = "https://github.com/pypa/pyproject-metadata";
+
+[dependency-groups]
 docs = [
     "furo>=2023.9.10",
     "sphinx-autodoc-typehints>=1.10.0",
@@ -37,32 +41,25 @@
     "myst-parser",
 ]
 test = [
-    "pytest-cov[toml]>=2",
-    "pytest>=6.2.4",
-    'tomli>=1.0.0;python_version<"3.11"',
-    'exceptiongroup;python_version<"3.11"',  # Optional
+    "pytest-cov>=4",
+    "pytest>=7.4; python_version>='3.12'",
+    "pytest>=7; python_version<'3.12'",
+    'tomli>=1.1;python_version<"3.11"',
+    'exceptiongroup>=1.0;python_version<"3.11"',  # Optional
 ]
+dev = [{include-group = "test"}]
 
-[project.urls]
-changelog = "https://pep621.readthedocs.io/en/stable/changelog.html";
-homepage = "https://github.com/pypa/pyproject-metadata";
 
 [tool.flit.sdist]
 include = ["LICENSE", "tests/**", "docs/**", ".gitignore"]
 
-[tool.uv]
-dev-dependencies = ["pyproject-metadata[test]"]
-environments = [
-  "python_version >= '3.10'",
-]
-
 
 [tool.pytest.ini_options]
-minversion = "6.0"
+minversion = "7.0"
 addopts = ["-ra", "--showlocals", "--strict-markers", "--strict-config"]
 xfail_strict = true
 filterwarnings = ["error"]
-log_cli_level = "info"
+log_level = "INFO"
 testpaths = ["tests"]
 
 
@@ -70,44 +67,49 @@
 strict = true
 warn_unreachable = false
 enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"]
+files = ["pyproject_metadata", "tests", "noxfile.py"]
 
 
+[tool.ruff]
+show-fixes = true
+
 [tool.ruff.lint]
-extend-select = [
-  "C90",         # mccabe
-  "B",           # flake8-bugbear
-  "I",           # isort
-  "ARG",         # flake8-unused-arguments
-  "C4",          # flake8-comprehensions
-  "ICN",         # flake8-import-conventions
-  "ISC",         # flake8-implicit-str-concat
-  "EM",          # flake8-errmsg
-  "G",           # flake8-logging-format
-  "PGH",         # pygrep-hooks
-  "PIE",         # flake8-pie
-  "PL",          # pylint
-  "PT",          # flake8-pytest-style
-  "RET",         # flake8-return
-  "RUF",         # Ruff-specific
-  "SIM",         # flake8-simplify
-  "T20",         # flake8-print
-  "UP",          # pyupgrade
-  "YTT",         # flake8-2020
-  "EXE",         # flake8-executable
-  "NPY",         # NumPy specific rules
-  "PD",          # pandas-vet
-]
+select = ["ALL"]
 ignore = [
-  "ISC001",  # conflicts with formatter
+  "S101",    # Asserts are used by mypy and pytest
   "PLR09",   # Design related (too many X)
   "PLR2004", # Magic value in comparison
+  "COM812",  # Trailing commas inform the formatter
+  "E501",    # Not worried about long lines in strings
+  "D200",    # Don't force single line docstring (for now)
+  "D205",    # Some of our summaries are more than a line (for now)
+]
+flake8-builtins.ignorelist = ["copyright"]
+pydocstyle.convention = "pep257"
+
+[tool.ruff.lint.per-file-ignores]
+"tests/**" = [
+  "PTH123",  # Path.open not needed for simple files
+  "D",       # Tests don't need docs
+  "INP001",  # Tests don't need an __init__
+  "FBT001",  # Bools are fine in test fixutres/params
+  "SLF001",  # Tests can access private members
+]
+"docs/**" = [
+  "INP001",  # Docs don't need an __init__
+  "ERA001",  # Commented out code in conf.py
+  "D",
 ]
+"noxfile.py" = ["D"]
 
 [tool.ruff.format]
 docstring-code-format = true
 
 [tool.coverage]
+run.branch = true
+run.source_pkgs = ["pyproject_metadata"]
 html.show_contexts = true
+report.show_missing = true
 report.exclude_also = [
     "if typing.TYPE_CHECKING:",
 ]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pyproject-metadata-0.9.1/pyproject_metadata/__init__.py 
new/pyproject-metadata-0.10.0/pyproject_metadata/__init__.py
--- old/pyproject-metadata-0.9.1/pyproject_metadata/__init__.py 2025-03-10 
23:10:43.000000000 +0100
+++ new/pyproject-metadata-0.10.0/pyproject_metadata/__init__.py        
2025-11-21 16:26:50.000000000 +0100
@@ -37,6 +37,8 @@
 import email.message
 import email.policy
 import email.utils
+import itertools
+import keyword
 import os
 import os.path
 import pathlib
@@ -50,7 +52,7 @@
 from .pyproject import License, PyProjectReader, Readme
 
 if typing.TYPE_CHECKING:
-    from collections.abc import Mapping
+    from collections.abc import Generator, Mapping
     from typing import Any
 
     from packaging.requirements import Requirement
@@ -74,7 +76,7 @@
     RE_EOL_BYTES = re.compile(rb"[\r\n]+")
 
 
-__version__ = "0.9.1"
+__version__ = "0.10.0"
 
 __all__ = [
     "ConfigurationError",
@@ -137,7 +139,7 @@
     message: email.message.Message
 
     def __setitem__(self, name: str, value: str | None) -> None:
-        if not value:
+        if value is None:
             return
         self.message[name] = value
 
@@ -186,6 +188,9 @@
     max_line_length = 0
 
     def header_store_parse(self, name: str, value: str) -> tuple[str, str]:
+        """
+        Require known headers, and replace newlines with spaces.
+        """
         if name.lower() not in constants.KNOWN_METADATA_FIELDS:
             msg = f"Unknown field {name!r}"
             raise ConfigurationError(msg, key=name)
@@ -196,7 +201,10 @@
     if sys.version_info < (3, 12, 4):
         # Work around Python bug 
https://github.com/python/cpython/issues/117313
         def _fold(
-            self, name: str, value: Any, refold_binary: bool = False
+            self,
+            name: str,
+            value: Any,  # noqa: ANN401
+            refold_binary: bool = False,  # noqa: FBT001, FBT002
         ) -> str:  # pragma: no cover
             if hasattr(value, "name"):
                 return value.fold(policy=self)  # type: ignore[no-any-return]
@@ -205,7 +213,6 @@
             # this is from the library version, and it improperly breaks on 
chars like 0x0c, treating
             # them as 'form feed' etc.
             # we need to ensure that only CR/LF is used as end of line
-            # lines = value.splitlines()
 
             # this is a workaround which splits only on CR/LF characters
             if isinstance(value, bytes):
@@ -220,11 +227,52 @@
                     or any(len(x) > maxlen for x in lines[1:])
                 )
             )
-            if refold or (refold_binary and 
email.policy._has_surrogates(value)):  # type: ignore[attr-defined]
+            if refold or (
+                refold_binary and email.policy._has_surrogates(value)  # type: 
ignore[attr-defined] # noqa: SLF001
+            ):
                 return self.header_factory(name, 
"".join(lines)).fold(policy=self)  # type: ignore[arg-type,no-any-return]
             return name + ": " + self.linesep.join(lines) + self.linesep  # 
type: ignore[arg-type]
 
 
+def _validate_import_names(
+    names: list[str], key: str, *, errors: ErrorCollector
+) -> Generator[str, None, None]:
+    """
+    Return normalized names for comparisons.
+    """
+    for fullname in names:
+        name, simicolon, private = fullname.partition(";")
+        if simicolon and private.lstrip() != "private":
+            msg = "{key} contains an ending tag other than '; private', got 
{value!r}"
+            errors.config_error(msg, key=key, value=fullname)
+        name = name.rstrip()
+
+        for ident in name.split("."):
+            if not ident.isidentifier():
+                msg = "{key} contains {value!r}, which is not a valid 
identifier"
+                errors.config_error(msg, key=key, value=fullname)
+
+            elif keyword.iskeyword(ident):
+                msg = "{key} contains a Python keyword, which is not a valid 
import name, got {value!r}"
+                errors.config_error(msg, key=key, value=fullname)
+
+        yield name
+
+
+def _validate_dotted_names(names: set[str], *, errors: ErrorCollector) -> None:
+    """
+    Check to make sure every name is accounted for. Takes the union of 
de-tagged names.
+    """
+    for name in names:
+        for parent in itertools.accumulate(
+            name.split(".")[:-1], lambda a, b: f"{a}.{b}"
+        ):
+            if parent not in names:
+                msg = "{key} is missing {value!r}, but submodules are present 
elsewhere"
+                errors.config_error(msg, key="project.import-namespaces", 
value=parent)
+                continue
+
+
 class RFC822Message(email.message.EmailMessage):
     """
     This is :class:`email.message.EmailMessage` with two small changes: it 
defaults to
@@ -233,13 +281,18 @@
     """
 
     def __init__(self) -> None:
+        """
+        Create a new message with RFC822Policy.
+        """
         super().__init__(policy=RFC822Policy())
 
     def as_bytes(
-        self, unixfrom: bool = False, policy: email.policy.Policy | None = None
+        self,
+        unixfrom: bool = False,  # noqa: FBT001, FBT002
+        policy: email.policy.Policy | None = None,
     ) -> bytes:
         """
-        This handles unicode encoding.
+        Will always handle unicode encoding.
         """
         return self.as_string(unixfrom, policy=policy).encode("utf-8")
 
@@ -271,6 +324,8 @@
     keywords: list[str] = dataclasses.field(default_factory=list)
     scripts: dict[str, str] = dataclasses.field(default_factory=dict)
     gui_scripts: dict[str, str] = dataclasses.field(default_factory=dict)
+    import_names: list[str] | None = None
+    import_namespaces: list[str] | None = None
     dynamic: list[Dynamic] = dataclasses.field(default_factory=list)
     """
     This field is used to track dynamic fields. You can't set a field not in 
this list.
@@ -290,6 +345,9 @@
     """
 
     def __post_init__(self) -> None:
+        """
+        Validate the fields on construction.
+        """
         self.validate()
 
     @property
@@ -301,6 +359,8 @@
         if self.metadata_version is not None:
             return self.metadata_version
 
+        if self.import_names is not None or self.import_namespaces is not None:
+            return "2.5"
         if isinstance(self.license, str) or self.license_files is not None:
             return "2.4"
         if self.dynamic_metadata:
@@ -460,6 +520,12 @@
                     project.get("gui-scripts", {}), "project.gui-scripts"
                 )
                 or {},
+                import_names=pyproject.ensure_list(
+                    project.get("import-names", None), "project.import-names"
+                ),
+                import_namespaces=pyproject.ensure_list(
+                    project.get("import-namespaces", None), 
"project.import-namespaces"
+                ),
                 dynamic=dynamic,
                 dynamic_metadata=dynamic_metadata or [],
                 metadata_version=metadata_version,
@@ -490,9 +556,11 @@
 
     def validate(self, *, warn: bool = True) -> None:  # noqa: C901
         """
-        Validate metadata for consistency and correctness. Will also produce
-        warnings if ``warn`` is given. Respects ``all_errors``. This is called
-        when loading a pyproject.toml, and when making metadata. Checks:
+        Validate metadata for consistency and correctness.
+
+        Will also produce warnings if ``warn`` is given. Respects
+        ``all_errors``. This is called when loading a pyproject.toml, and when
+        making metadata. Checks:
 
         - ``metadata_version`` is a known version or None
         - ``name`` is a valid project name
@@ -504,6 +572,9 @@
         - ``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
+        - ``import-name(paces)s`` is only supported on metadata_version >= 2.5
+        - ``import-name(space)s`` must be valid names, optionally with ``; 
private``
+        - ``import-names`` and ``import-namespaces`` cannot overlap.
         """
         errors = ErrorCollector(collect_errors=self.all_errors)
 
@@ -555,14 +626,14 @@
             isinstance(self.license, str)
             and self.auto_metadata_version in 
constants.PRE_SPDX_METADATA_VERSIONS
         ):
-            msg = "Setting {key} to an SPDX license expression is supported 
only when emitting metadata version >= 2.4"
+            msg = "Setting {key} to an SPDX license expression is only 
supported when emitting metadata version >= 2.4"
             errors.config_error(msg, key="project.license")
 
         if (
             self.license_files is not None
             and self.auto_metadata_version in 
constants.PRE_SPDX_METADATA_VERSIONS
         ):
-            msg = "{key} is supported only when emitting metadata version >= 
2.4"
+            msg = "{key} is only supported when emitting metadata version >= 
2.4"
             errors.config_error(msg, key="project.license-files")
 
         for name in self.urls:
@@ -570,6 +641,37 @@
                 msg = "{key} names cannot be more than 32 characters long"
                 errors.config_error(msg, key="project.urls", got=name)
 
+        if (
+            self.import_names is not None
+            and self.auto_metadata_version in 
constants.PRE_2_5_METADATA_VERSIONS
+        ):
+            msg = "{key} is only supported when emitting metadata version >= 
2.5"
+            errors.config_error(msg, key="project.import-names")
+
+        if (
+            self.import_namespaces is not None
+            and self.auto_metadata_version in 
constants.PRE_2_5_METADATA_VERSIONS
+        ):
+            msg = "{key} is only supported when emitting metadata version >= 
2.5"
+            errors.config_error(msg, key="project.import-namespaces")
+
+        import_names = set(
+            _validate_import_names(
+                self.import_names or [], "import-names", errors=errors
+            )
+        )
+        import_namespaces = set(
+            _validate_import_names(
+                self.import_namespaces or [], "import-namespaces", 
errors=errors
+            )
+        )
+        in_both = import_names & import_namespaces
+        if in_both:
+            msg = "{key} overlaps with 'project.import-namespaces': {in_both}"
+            errors.config_error(msg, key="project.import-names", 
in_both=in_both)
+
+        _validate_dotted_names(import_names | import_namespaces, errors=errors)
+
         errors.finalize("Metadata validation failed")
 
     def _write_metadata(  # noqa: C901
@@ -634,9 +736,16 @@
                     _build_extra_req(norm_extra, requirement)
                 )
         if self.readme:
-            if self.readme.content_type:
-                smart_message["Description-Content-Type"] = 
self.readme.content_type
+            assert self.readme.content_type  # verified earlier
+            smart_message["Description-Content-Type"] = 
self.readme.content_type
             smart_message.set_payload(self.readme.text)
+        for import_name in self.import_names or []:
+            smart_message["Import-Name"] = import_name
+        for import_namespace in self.import_namespaces or []:
+            smart_message["Import-Namespace"] = import_namespace
+        # Special case for empty import-names
+        if self.import_names is not None and not self.import_names:
+            smart_message["Import-Name"] = ""
         # Core Metadata 2.2
         if self.auto_metadata_version != "2.1":
             for field in self.dynamic_metadata:
@@ -679,7 +788,7 @@
     """
     requirement = copy.copy(requirement)
     if requirement.marker:
-        if "or" in requirement.marker._markers:
+        if "or" in requirement.marker._markers:  # noqa: SLF001
             requirement.marker = packaging.markers.Marker(
                 f"({requirement.marker}) and extra == {extra!r}"
             )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pyproject-metadata-0.9.1/pyproject_metadata/constants.py 
new/pyproject-metadata-0.10.0/pyproject_metadata/constants.py
--- old/pyproject-metadata-0.9.1/pyproject_metadata/constants.py        
2025-03-10 23:10:43.000000000 +0100
+++ new/pyproject-metadata-0.10.0/pyproject_metadata/constants.py       
2025-11-21 16:26:50.000000000 +0100
@@ -24,8 +24,9 @@
     return __all__
 
 
-KNOWN_METADATA_VERSIONS = {"2.1", "2.2", "2.3", "2.4"}
+KNOWN_METADATA_VERSIONS = {"2.1", "2.2", "2.3", "2.4", "2.5"}
 PRE_SPDX_METADATA_VERSIONS = {"2.1", "2.2", "2.3"}
+PRE_2_5_METADATA_VERSIONS = {"2.1", "2.2", "2.3", "2.4"}
 
 PROJECT_TO_METADATA = {
     "authors": frozenset(["Author", "Author-Email"]),
@@ -46,6 +47,8 @@
     "scripts": frozenset(),
     "urls": frozenset(["Project-URL"]),
     "version": frozenset(["Version"]),
+    "import-names": frozenset(["Import-Name"]),
+    "import-namespaces": frozenset(["Import-Namespaces"]),
 }
 
 KNOWN_TOPLEVEL_FIELDS = {"build-system", "project", "tool", 
"dependency-groups"}
@@ -83,6 +86,8 @@
     "summary",
     "supported-platform",  # Not specified via pyproject standards
     "version",  # Can't be in dynamic
+    "import-name",
+    "import-namespace",
 }
 
 KNOWN_MULTIUSE = {
@@ -100,4 +105,6 @@
     "requires",  # Deprecated
     "obsoletes",  # Deprecated
     "provides",  # Deprecated
+    "import-name",
+    "import-namespace",
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pyproject-metadata-0.9.1/pyproject_metadata/errors.py 
new/pyproject-metadata-0.10.0/pyproject_metadata/errors.py
--- old/pyproject-metadata-0.9.1/pyproject_metadata/errors.py   2025-03-10 
23:10:43.000000000 +0100
+++ new/pyproject-metadata-0.10.0/pyproject_metadata/errors.py  2025-11-21 
16:26:50.000000000 +0100
@@ -28,15 +28,25 @@
 
 
 class ConfigurationError(Exception):
-    """Error in the backend metadata. Has an optional key attribute, which 
will be non-None
-    if the error is related to a single key in the pyproject.toml file."""
+    """
+    Error in the backend metadata.
+
+    Has an optional key attribute, which will be non-None if the error is
+    related to a single key in the pyproject.toml file.
+    """
 
-    def __init__(self, msg: str, *, key: str | None = None):
+    def __init__(self, msg: str, *, key: str | None = None) -> None:
+        """
+        Create a new error with a key (can be None).
+        """
         super().__init__(msg)
         self._key = key
 
     @property
     def key(self) -> str | None:  # pragma: no cover
+        """
+        Return the stored key.
+        """
         return self._key
 
 
@@ -48,7 +58,7 @@
     ExceptionGroup = builtins.ExceptionGroup
 else:
 
-    class ExceptionGroup(Exception):
+    class ExceptionGroup(Exception):  # noqa: N818
         """A minimal implementation of `ExceptionGroup` from Python 3.11.
 
         Users can replace this with a more complete implementation, such as 
from
@@ -61,10 +71,16 @@
         exceptions: list[Exception]
 
         def __init__(self, message: str, exceptions: list[Exception]) -> None:
+            """
+            Create a new group with a message and a list of exceptions.
+            """
             self.message = message
             self.exceptions = exceptions
 
         def __repr__(self) -> str:
+            """
+            Return a repr similar to the stdlib ExceptionGroup.
+            """
             return f"{self.__class__.__name__}({self.message!r}, 
{self.exceptions!r})"
 
 
@@ -83,10 +99,10 @@
         msg: str,
         *,
         key: str | None = None,
-        got: typing.Any = None,
+        got: object = None,
         got_type: type[typing.Any] | None = None,
         warn: bool = False,
-        **kwargs: typing.Any,
+        **kwargs: object,
     ) -> None:
         """Raise a configuration error, or add it to the error list."""
         msg = msg.format(key=f'"{key}"', **kwargs)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pyproject-metadata-0.9.1/pyproject_metadata/project_table.py 
new/pyproject-metadata-0.10.0/pyproject_metadata/project_table.py
--- old/pyproject-metadata-0.9.1/pyproject_metadata/project_table.py    
2025-03-10 23:10:43.000000000 +0100
+++ new/pyproject-metadata-0.10.0/pyproject_metadata/project_table.py   
2025-11-21 16:26:50.000000000 +0100
@@ -14,16 +14,12 @@
 import typing
 from typing import Any, Dict, List, Union
 
-if sys.version_info < (3, 11):
+if sys.version_info < (3, 11):  # pragma: nocover
     from typing_extensions import Required
 else:
     from typing import Required
 
-if sys.version_info < (3, 8):
-    from typing_extensions import Literal, TypedDict
-else:
-    from typing import Literal, TypedDict
-
+from typing import Literal, TypedDict
 
 __all__ = [
     "BuildSystemTable",
@@ -42,11 +38,19 @@
 
 
 class ContactTable(TypedDict, total=False):
+    """
+    Can have either name or email.
+    """
+
     name: str
     email: str
 
 
 class LicenseTable(TypedDict, total=False):
+    """
+    Can have either text or file. Legacy.
+    """
+
     text: str
     file: str
 
@@ -72,6 +76,8 @@
     "scripts",
     "urls",
     "version",
+    "import-names",
+    "import-namespaces",
 ]
 
 ProjectTable = TypedDict(
@@ -94,6 +100,8 @@
         "keywords": List[str],
         "scripts": Dict[str, str],
         "gui-scripts": Dict[str, str],
+        "import-names": List[str],
+        "import-namespaces": List[str],
         "dynamic": List[Dynamic],
     },
     total=False,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pyproject-metadata-0.9.1/pyproject_metadata/pyproject.py 
new/pyproject-metadata-0.10.0/pyproject_metadata/pyproject.py
--- old/pyproject-metadata-0.9.1/pyproject_metadata/pyproject.py        
2025-03-10 23:10:43.000000000 +0100
+++ new/pyproject-metadata-0.10.0/pyproject_metadata/pyproject.py       
2025-11-21 16:26:50.000000000 +0100
@@ -9,7 +9,6 @@
 from __future__ import annotations
 
 import dataclasses
-import pathlib
 import re
 import typing
 
@@ -18,6 +17,7 @@
 from .errors import ErrorCollector
 
 if typing.TYPE_CHECKING:
+    import pathlib
     from collections.abc import Generator, Iterable, Sequence
 
     from packaging.requirements import Requirement
@@ -81,8 +81,10 @@
         self.config_error(msg, key=key, got_type=type(value))
         return None
 
-    def ensure_list(self, val: list[T], key: str) -> list[T] | None:
+    def ensure_list(self, val: list[T] | None, key: str) -> list[T] | None:
         """Ensure that a value is a list of strings."""
+        if val is None:
+            return None
         if not isinstance(val, list):
             msg = "Field {key} has an invalid type, expecting a list of 
strings"
             self.config_error(msg, key=key, got_type=type(val))
@@ -299,7 +301,6 @@
 
     def get_dependencies(self, project: ProjectTable) -> list[Requirement]:
         """Get the dependencies from the project table."""
-
         requirement_strings: list[str] | None = None
         requirement_strings_raw = project.get("dependencies")
         if requirement_strings_raw is not None:
@@ -310,13 +311,13 @@
             return []
 
         requirements: list[Requirement] = []
-        for req in requirement_strings:
-            try:
+        try:
+            for req in requirement_strings:
                 requirements.append(packaging.requirements.Requirement(req))
-            except packaging.requirements.InvalidRequirement as e:
-                msg = "Field {key} contains an invalid PEP 508 requirement 
string {req!r} ({error!r})"
-                self.config_error(msg, key="project.dependencies", req=req, 
error=e)
-                return []
+        except packaging.requirements.InvalidRequirement as e:
+            msg = "Field {key} contains an invalid PEP 508 requirement string 
{req!r} ({error!r})"
+            self.config_error(msg, key="project.dependencies", req=req, 
error=e)
+            return []
         return requirements
 
     def get_optional_dependencies(
@@ -324,7 +325,6 @@
         project: ProjectTable,
     ) -> dict[str, list[Requirement]]:
         """Get the optional dependencies from the project table."""
-
         val = project.get("optional-dependencies")
         if not val:
             return {}
@@ -376,7 +376,6 @@
 
     def get_entrypoints(self, project: ProjectTable) -> dict[str, dict[str, 
str]]:
         """Get the entrypoints from the project table."""
-
         val = project.get("entry-points", None)
         if val is None:
             return {}
@@ -435,7 +434,6 @@
         self, project_dir: pathlib.Path, globs: Iterable[str]
     ) -> Generator[pathlib.Path, None, None]:
         """Given a list of globs, get files that match."""
-
         for glob in globs:
             if glob.startswith(("..", "/")):
                 msg = "{glob!r} is an invalid {key} glob: the pattern must 
match files within the project directory"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pyproject-metadata-0.9.1/tests/packages/full-metadata/pyproject.toml 
new/pyproject-metadata-0.10.0/tests/packages/full-metadata/pyproject.toml
--- old/pyproject-metadata-0.9.1/tests/packages/full-metadata/pyproject.toml    
2025-03-10 23:10:43.000000000 +0100
+++ new/pyproject-metadata-0.10.0/tests/packages/full-metadata/pyproject.toml   
2025-11-21 16:26:50.000000000 +0100
@@ -10,6 +10,7 @@
     { name = 'Example!' },
 ]
 maintainers = [
+    { name = 'Emailless' },
     { name = 'Other Example', email = '[email protected]' },
 ]
 classifiers = [
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pyproject-metadata-0.9.1/tests/packages/metadata-2.5/LICENSE 
new/pyproject-metadata-0.10.0/tests/packages/metadata-2.5/LICENSE
--- old/pyproject-metadata-0.9.1/tests/packages/metadata-2.5/LICENSE    
1970-01-01 01:00:00.000000000 +0100
+++ new/pyproject-metadata-0.10.0/tests/packages/metadata-2.5/LICENSE   
2025-11-21 16:26:50.000000000 +0100
@@ -0,0 +1,20 @@
+Copyright © 2019 Filipe Laíns <[email protected]>
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice (including the next
+paragraph) shall be included in all copies or substantial portions of the
+Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pyproject-metadata-0.9.1/tests/packages/metadata-2.5/README.md 
new/pyproject-metadata-0.10.0/tests/packages/metadata-2.5/README.md
--- old/pyproject-metadata-0.9.1/tests/packages/metadata-2.5/README.md  
1970-01-01 01:00:00.000000000 +0100
+++ new/pyproject-metadata-0.10.0/tests/packages/metadata-2.5/README.md 
2025-11-21 16:26:50.000000000 +0100
@@ -0,0 +1 @@
+some readme 👋
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pyproject-metadata-0.9.1/tests/packages/metadata-2.5/pyproject.toml 
new/pyproject-metadata-0.10.0/tests/packages/metadata-2.5/pyproject.toml
--- old/pyproject-metadata-0.9.1/tests/packages/metadata-2.5/pyproject.toml     
1970-01-01 01:00:00.000000000 +0100
+++ new/pyproject-metadata-0.10.0/tests/packages/metadata-2.5/pyproject.toml    
2025-11-21 16:26:50.000000000 +0100
@@ -0,0 +1,51 @@
+[project]
+name = 'metadata25'
+version = '3.2.1'
+description = 'A package with all the metadata :)'
+readme = 'README.md'
+license = "MIT"
+license-files = ["LICENSE"]
+keywords = ['trampolim', 'is', 'interesting']
+authors = [
+    { email = '[email protected]' },
+    { name = 'Example!' },
+]
+maintainers = [
+    { name = 'Other Example', email = '[email protected]' },
+]
+classifiers = [
+    'Development Status :: 4 - Beta',
+    'Programming Language :: Python',
+]
+
+requires-python = '>=3.8'
+dependencies = [
+    'dependency1',
+    'dependency2>1.0.0',
+    'dependency3[extra]',
+    'dependency4; os_name != "nt"',
+    'dependency5[other-extra]>1.0; os_name == "nt"',
+]
+import-names = ["metadata25"]
+
+[project.optional-dependencies]
+test = [
+    'test_dependency',
+    'test_dependency[test_extra]',
+    'test_dependency[test_extra2] > 3.0; os_name == "nt"',
+]
+
+[project.urls]
+homepage = 'example.com'
+documentation = 'readthedocs.org'
+repository = 'github.com/some/repo'
+changelog = 'github.com/some/repo/blob/master/CHANGELOG.rst'
+
+[project.scripts]
+full-metadata = 'full_metadata:main_cli'
+
+[project.gui-scripts]
+full-metadata-gui = 'full_metadata:main_gui'
+
+[project.entry-points.custom]
+full-metadata = 'full_metadata:main_custom'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyproject-metadata-0.9.1/tests/test_internals.py 
new/pyproject-metadata-0.10.0/tests/test_internals.py
--- old/pyproject-metadata-0.9.1/tests/test_internals.py        2025-03-10 
23:10:43.000000000 +0100
+++ new/pyproject-metadata-0.10.0/tests/test_internals.py       2025-11-21 
16:26:50.000000000 +0100
@@ -18,6 +18,6 @@
 def test_project_table_all() -> None:
     if sys.version_info < (3, 11):
         pytest.importorskip("typing_extensions")
-    import pyproject_metadata.project_table
+    import pyproject_metadata.project_table  # noqa: PLC0415
 
     assert "annotations" not in dir(pyproject_metadata.project_table)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pyproject-metadata-0.9.1/tests/test_standard_metadata.py 
new/pyproject-metadata-0.10.0/tests/test_standard_metadata.py
--- old/pyproject-metadata-0.9.1/tests/test_standard_metadata.py        
2025-03-10 23:10:43.000000000 +0100
+++ new/pyproject-metadata-0.10.0/tests/test_standard_metadata.py       
2025-11-21 16:26:50.000000000 +0100
@@ -319,6 +319,16 @@
                 [project]
                 name = "test"
                 version = "0.1.0"
+                readme = { file = "pyproject.toml" }
+            """,
+            'Field "project.readme.content-type" missing',
+            id="Missing content-type for readme file",
+        ),
+        pytest.param(
+            """
+                [project]
+                name = "test"
+                version = "0.1.0"
                 description = true
             """,
             'Field "project.description" has an invalid type, expecting a 
string (got bool)',
@@ -780,6 +790,78 @@
             "Setting \"project.license\" to an SPDX license expression is not 
compatible with 'License ::' classifiers",
             id="SPDX license and License trove classifiers",
         ),
+        pytest.param(
+            """
+                [project]
+                name = "test"
+                version = "0.1.0"
+                import-names = ["is"]
+            """,
+            "\"import-names\" contains a Python keyword, which is not a valid 
import name, got 'is'",
+            id="Setting import-names to keyword",
+        ),
+        pytest.param(
+            """
+                [project]
+                name = "test"
+                version = "0.1.0"
+                import-namespaces = ["from"]
+            """,
+            "\"import-namespaces\" contains a Python keyword, which is not a 
valid import name, got 'from'",
+            id="Setting import-namespaces to keyword",
+        ),
+        pytest.param(
+            """
+                [project]
+                name = "test"
+                version = "0.1.0"
+                import-names = ["2two"]
+            """,
+            "\"import-names\" contains '2two', which is not a valid 
identifier",
+            id="Setting import-names invalid identifier",
+        ),
+        pytest.param(
+            """
+                [project]
+                name = "test"
+                version = "0.1.0"
+                import-namespaces = ["3"]
+            """,
+            "\"import-namespaces\" contains '3', which is not a valid 
identifier",
+            id="Setting import-namespaces to invalid identifier",
+        ),
+        pytest.param(
+            """
+                [project]
+                name = "test"
+                version = "0.1.0"
+                import-names = ["one", "two"]
+                import-namespaces = ["one", "three"]
+            """,
+            "\"project.import-names\" overlaps with 
'project.import-namespaces': {'one'}",
+            id="Matching entry in import-names and import-namespaces",
+        ),
+        pytest.param(
+            """
+                [project]
+                name = "test"
+                version = "0.1.0"
+                import-names = ["one; private", "two"]
+                import-namespaces = ["one", "three    ;   private"]
+            """,
+            "\"project.import-names\" overlaps with 
'project.import-namespaces': {'one'}",
+            id="Matching entry in import-names and import-namespaces with 
private tags",
+        ),
+        pytest.param(
+            """
+                [project]
+                name = "test"
+                version = "0.1.0"
+                import-names = ["one.two"]
+            """,
+            "\"project.import-namespaces\" is missing 'one', but submodules 
are present elsewhere",
+            id="Matching entry in import-names and import-namespaces",
+        ),
     ],
 )
 def test_load(
@@ -886,6 +968,22 @@
             ],
             id="Four errors including extra keys",
         ),
+        pytest.param(
+            """
+                [project]
+                name = 'test'
+                version = "0.1.0"
+                import-names = ["test", "other"]
+                import-namespaces = ["other.one.two", "invalid name", "not; 
public"]
+            """,
+            [
+                "\"import-namespaces\" contains 'invalid name', which is not a 
valid identifier",
+                "\"import-namespaces\" contains an ending tag other than '; 
private', got 'not; public'",
+                "\"import-namespaces\" contains a Python keyword, which is not 
a valid import name, got 'not; public'",
+                "\"project.import-namespaces\" is missing 'other.one', but 
submodules are present elsewhere",
+            ],
+            id="Multiple errors related to names/namespaces",
+        ),
     ],
 )
 def test_load_multierror(
@@ -928,7 +1026,7 @@
                 version = "0.1.0"
                 license = 'MIT'
             """,
-            'Setting "project.license" to an SPDX license expression is 
supported only when emitting metadata version >= 2.4',
+            'Setting "project.license" to an SPDX license expression is only 
supported when emitting metadata version >= 2.4',
             "2.3",
             id="SPDX with metadata_version 2.3",
         ),
@@ -939,10 +1037,32 @@
                 version = "0.1.0"
                 license-files = ['README.md']
             """,
-            '"project.license-files" is supported only when emitting metadata 
version >= 2.4',
+            '"project.license-files" is only supported when emitting metadata 
version >= 2.4',
             "2.3",
             id="license-files with metadata_version 2.3",
         ),
+        pytest.param(
+            """
+                [project]
+                name = "test"
+                version = "0.1.0"
+                import-names = ['one']
+            """,
+            '"project.import-names" is only supported when emitting metadata 
version >= 2.5',
+            "2.4",
+            id="import-names with metadata_version 2.4",
+        ),
+        pytest.param(
+            """
+                [project]
+                name = "test"
+                version = "0.1.0"
+                import-namespaces = ['one']
+            """,
+            '"project.import-namespaces" is only supported when emitting 
metadata version >= 2.5',
+            "2.4",
+            id="import-names with metadata_version 2.4",
+        ),
     ],
 )
 def test_load_with_metadata_version(
@@ -1021,6 +1141,7 @@
         ("Example!", None),
     ]
     assert metadata.maintainers == [
+        ("Emailless", None),
         ("Other Example", "[email protected]"),
     ]
     assert metadata.keywords == ["trampolim", "is", "interesting"]
@@ -1060,6 +1181,25 @@
     ]
 
 
[email protected]("after_rfc", [False, True])
+def test_value_25(after_rfc: bool, monkeypatch: pytest.MonkeyPatch) -> None:
+    monkeypatch.chdir(DIR / "packages/metadata-2.5")
+    with open("pyproject.toml", "rb") as f:
+        metadata = 
pyproject_metadata.StandardMetadata.from_pyproject(tomllib.load(f))
+
+    if after_rfc:
+        metadata.as_rfc822()
+
+    assert metadata.auto_metadata_version == "2.5"
+
+    assert isinstance(metadata.license, str)
+    assert metadata.license == "MIT"
+    assert metadata.license_files == [pathlib.Path("LICENSE")]
+
+    assert metadata.import_names == ["metadata25"]
+    assert metadata.import_namespaces is None
+
+
 def test_read_license(monkeypatch: pytest.MonkeyPatch) -> None:
     monkeypatch.chdir(DIR / "packages/full-metadata2")
     with open("pyproject.toml", "rb") as f:
@@ -1117,6 +1257,7 @@
         "description_content_type": "text/markdown",
         "keywords": ["trampolim", "is", "interesting"],
         "license": "some license text",
+        "maintainer": "Emailless",
         "maintainer_email": "Other Example <[email protected]>",
         "metadata_version": "2.1",
         "name": "full_metadata",
@@ -1143,6 +1284,20 @@
     }
 
 
+def test_readme_text() -> None:
+    pyproject = pyproject_metadata.StandardMetadata.from_pyproject(
+        {
+            "project": {
+                "name": "foo",
+                "version": "1.2.3",
+                "readme": {"text": "onetwothree", "content-type": 
"text/plain"},
+            }
+        }
+    )
+    assert pyproject.readme
+    assert pyproject.readme.text == "onetwothree"
+
+
 def test_as_rfc822(monkeypatch: pytest.MonkeyPatch) -> None:
     monkeypatch.chdir(DIR / "packages/full-metadata")
 
@@ -1157,6 +1312,7 @@
         ("Keywords", "trampolim,is,interesting"),
         ("Author", "Example!"),
         ("Author-Email", "Unknown <[email protected]>"),
+        ("Maintainer", "Emailless"),
         ("Maintainer-Email", "Other Example <[email protected]>"),
         ("License", "some license text"),
         ("Classifier", "Development Status :: 4 - Beta"),
@@ -1183,11 +1339,53 @@
     assert core_metadata.get_payload() == "some readme 👋\n"
 
 
+def test_rfc822_empty_import_name() -> None:
+    metadata = pyproject_metadata.StandardMetadata.from_pyproject(
+        {"project": {"name": "test", "version": "0.1.0", "import-names": []}}
+    )
+    assert metadata.import_names == []
+    assert metadata.import_namespaces is None
+
+    core_metadata = metadata.as_rfc822()
+    assert core_metadata.items() == [
+        ("Metadata-Version", "2.5"),
+        ("Name", "test"),
+        ("Version", "0.1.0"),
+        ("Import-Name", ""),
+    ]
+
+
+def test_rfc822_full_import_name() -> None:
+    metadata = pyproject_metadata.StandardMetadata.from_pyproject(
+        {
+            "project": {
+                "name": "test",
+                "version": "0.1.0",
+                "import-names": ["one", "two"],
+                "import-namespaces": ["three"],
+            }
+        }
+    )
+    assert metadata.import_names == ["one", "two"]
+    assert metadata.import_namespaces == ["three"]
+
+    core_metadata = metadata.as_rfc822()
+    assert core_metadata.items() == [
+        ("Metadata-Version", "2.5"),
+        ("Name", "test"),
+        ("Version", "0.1.0"),
+        ("Import-Name", "one"),
+        ("Import-Name", "two"),
+        ("Import-Namespace", "three"),
+    ]
+
+
 def test_as_json_spdx(monkeypatch: pytest.MonkeyPatch) -> None:
     monkeypatch.chdir(DIR / "packages/spdx")
 
     with open("pyproject.toml", "rb") as f:
         metadata = 
pyproject_metadata.StandardMetadata.from_pyproject(tomllib.load(f))
+
     core_metadata = metadata.as_json()
     assert core_metadata == {
         "license_expression": "MIT OR GPL-2.0-or-later OR (FSFUL AND 
BSD-2-Clause)",
@@ -1259,11 +1457,12 @@
     pre_spdx = (
         metadata_version in 
pyproject_metadata.constants.PRE_SPDX_METADATA_VERSIONS
     )
-    with (
+    ctx = (
         contextlib.nullcontext()
         if pre_spdx
         else pytest.warns(pyproject_metadata.errors.ConfigurationWarning)
-    ):
+    )
+    with ctx:
         metadata = pyproject_metadata.StandardMetadata.from_pyproject(
             {
                 "project": {
@@ -1443,7 +1642,8 @@
 def test_as_rfc822_missing_version() -> None:
     metadata = pyproject_metadata.StandardMetadata(name="something")
     with pytest.raises(
-        pyproject_metadata.ConfigurationError, match='Field "project.version" 
missing'
+        pyproject_metadata.ConfigurationError,
+        match=re.escape('Field "project.version" missing'),
     ):
         metadata.as_rfc822()
 
@@ -1451,7 +1651,9 @@
 def test_statically_defined_dynamic_field() -> None:
     with pytest.raises(
         pyproject_metadata.ConfigurationError,
-        match='Field "project.version" declared as dynamic in 
"project.dynamic" but is defined',
+        match=re.escape(
+            'Field "project.version" declared as dynamic in "project.dynamic" 
but is defined'
+        ),
     ):
         pyproject_metadata.StandardMetadata.from_pyproject(
             {
@@ -1470,8 +1672,8 @@
     "value",
     [
         "<3.10",
-        ">3.7,<3.11",
-        ">3.7,<3.11,!=3.8.4",
+        ">3.8,<3.11",
+        ">3.8,<3.11,!=3.8.4",
         "~=3.10,!=3.10.3",
     ],
 )

Reply via email to