Skip to content

Conversation

@finswimmer
Copy link
Member

@finswimmer finswimmer commented Oct 30, 2025

  • Added tests for changed code.
  • Updated documentation for changed code.

Summary by Sourcery

Implement PEP-794 support for import-names and import-namespaces by extending package metadata models, updating the builder to emit the new fields, bumping the metadata version to 2.5, and adding corresponding tests.

New Features:

  • Add import_names and import_namespaces attributes to ProjectPackage and Metadata to support PEP-794

Enhancements:

  • Bump metadata version to 2.5 using a METADATA_VERSION constant
  • Emit Import-Name and Import-Namespace fields in the generated METADATA content

Tests:

  • Add fixture and tests for parsing and round-tripping import_names and import_namespaces
  • Update existing tests to use the new metadata version constant and validate import fields

Summary by Sourcery

Implement PEP-794 import-names support by extending package metadata, builder, and factory logic, bump metadata version to 2.5, and add tests for import-names and import-namespaces.

New Features:

  • Support PEP-794 import-names and import-namespaces in project metadata

Enhancements:

  • Bump METADATA version to 2.5 using a METADATA_VERSION constant
  • Emit Import-Name and Import-Namespace fields in generated METADATA files
  • Validate and prevent overlapping values between import-names and import-namespaces in the factory

Tests:

  • Add fixtures and tests for parsing and round-tripping import-names and import-namespaces
  • Update existing tests to use the new METADATA_VERSION constant and validate import fields

@sourcery-ai
Copy link

sourcery-ai bot commented Oct 30, 2025

Reviewer's Guide

This PR adds PEP-794 import-names and import-namespaces support by extending package and metadata models, bumping the metadata version to 2.5, wiring those fields through the build process, validating them in the factory, and updating tests and fixtures accordingly.

Sequence diagram for validation of import_names and import_namespaces in Factory

sequenceDiagram
    participant Factory
    participant ProjectPackage
    participant Error
    Factory->>ProjectPackage: get("import-names")
    Factory->>ProjectPackage: get("import-namespaces")
    Factory->>Factory: _validate_import_names(project)
    alt Duplicate names found
        Factory->>Error: raise PoetryCoreError
    end
Loading

Entity relationship diagram for import_names and import_namespaces fields in package metadata

erDiagram
    PROJECT_PACKAGE {
        string name
        string version
        list import_names
        list import_namespaces
    }
    METADATA {
        string metadata_version
        list import_names
        list import_namespaces
    }
    PROJECT_PACKAGE ||--|| METADATA: "generates"
Loading

Class diagram for updated ProjectPackage and Metadata classes (PEP-794 support)

classDiagram
    class ProjectPackage {
        +import_names: list[str] | None
        +import_namespaces: list[str] | None
    }
    class Metadata {
        +metadata_version: str
        +import_names: list[str] | None
        +import_namespaces: list[str] | None
        +from_package(package: ProjectPackage): Metadata
    }
    ProjectPackage <--> Metadata: used by
Loading

File-Level Changes

Change Details Files
Extend package and metadata classes to carry import-names and import-namespaces
  • Add import_names and import_namespaces attributes to Package and ProjectPackage
  • Introduce METADATA_VERSION constant and set Metadata.metadata_version to it
  • Populate Metadata.import_names and import_namespaces in Metadata.from_package
src/poetry/core/packages/package.py
src/poetry/core/masonry/metadata.py
Emit Import-Name and Import-Namespace fields in generated METADATA
  • Append Import-Name entries (including empty lists) to metadata content
  • Append Import-Namespace entries (including empty lists) to metadata content
src/poetry/core/masonry/builders/builder.py
Parse and validate import-names/-namespaces in factory
  • Add _validate_import_names to detect overlap between names and namespaces
  • Invoke validation in _configure_package_metadata
  • Assign project import-names and import-namespaces to Package
src/poetry/core/factory.py
Bump metadata version and expand tests/fixtures for import-names support
  • Replace hardcoded "2.4" with METADATA_VERSION in existing tests
  • Add tests for prepare_metadata_with_import_names, factory import-names behavior, and Metadata.from_package import fields
  • Include new pyproject.toml fixtures for import-names scenarios
tests/masonry/test_api.py
tests/test_factory.py
tests/masonry/test_metadata.py
tests/masonry/builders/test_builder.py
tests/masonry/builders/test_complete.py
tests/fixtures/sample_project_new_with_import_names/pyproject.toml
tests/masonry/builders/fixtures/with-import-names/pyproject.toml

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@finswimmer finswimmer force-pushed the pep794-import-names branch 2 times, most recently from eb61027 to 6497c57 Compare October 31, 2025 10:56
@finswimmer finswimmer marked this pull request as ready for review October 31, 2025 16:49
@finswimmer finswimmer requested a review from a team October 31, 2025 16:49
Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes - here's some feedback:

  • You didn’t update the JSON project schema (project-schema.json) to allow import-names and import-namespaces under the [project] table, so schema validation will fail when parsing those keys.
  • In Builder.get_metadata_content you special-case empty import lists to emit blank headers—consider removing that branch and only emitting Import-Name/Import-Namespace for non-empty entries.
  • Factory._validate_import_names strips off any qualifiers (the part after “;”) before checking duplicates, which can produce misleading error messages; consider validating against the full strings or adjusting the error text.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- You didn’t update the JSON project schema (project-schema.json) to allow import-names and import-namespaces under the [project] table, so schema validation will fail when parsing those keys.
- In Builder.get_metadata_content you special-case empty import lists to emit blank headers—consider removing that branch and only emitting Import-Name/Import-Namespace for non-empty entries.
- Factory._validate_import_names strips off any qualifiers (the part after “;”) before checking duplicates, which can produce misleading error messages; consider validating against the full strings or adjusting the error text.

## Individual Comments

### Comment 1
<location> `src/poetry/core/masonry/builders/builder.py:293-298` </location>
<code_context>
             content += f"\n{self._meta.description}\n"

+        if self._meta.import_names is not None:
+            if len(self._meta.import_names) == 0:
+                content += "Import-Name: \n"
+
+            for import_name in self._meta.import_names:
</code_context>

<issue_to_address>
**suggestion:** Writing an empty Import-Name field may be unnecessary.

Omit 'Import-Name:' when the list is empty to keep the metadata cleaner.

```suggestion
        if self._meta.import_names is not None:
            for import_name in self._meta.import_names:
                content += f"Import-Name: {import_name}\n"
```
</issue_to_address>

### Comment 2
<location> `src/poetry/core/masonry/builders/builder.py:300-305` </location>
<code_context>
+                content += f"Import-Name: {import_name}\n"
+
+        if self._meta.import_namespaces is not None:
+            if len(self._meta.import_namespaces) == 0:
+                content += "Import-Namespace: \n"
+
+            for namespace in self._meta.import_namespaces:
</code_context>

<issue_to_address>
**suggestion:** Empty Import-Namespace field may be omitted for clarity.

Consider omitting the Import-Namespace field entirely when the list is empty, as is done for Import-Name, to improve metadata clarity.

```suggestion
        if self._meta.import_namespaces is not None:
            for namespace in self._meta.import_namespaces:
                content += f"Import-Namespace: {namespace}\n"
```
</issue_to_address>

### Comment 3
<location> `tests/masonry/test_metadata.py:208-217` </location>
<code_context>
     assert metadata.description == "Description 1\nDescription 2"


[email protected](
+    ["import_names", "import_namespaces"],
+    [
+        (["my_package.a", "my_package.b"], ["my_package"]),
+        (None, None),
+        ([], []),
+    ],
+)
+def test_from_package_import_names(
+    import_names: list[str] | None,
+    import_namespaces: list[str] | None,
+    package: ProjectPackage,
+) -> None:
+    package.import_names = import_names
+    package.import_namespaces = import_namespaces
+
+    metadata = Metadata.from_package(package)
+
+    assert metadata.import_names == import_names
+    assert metadata.import_namespaces == import_namespaces
+
+
</code_context>

<issue_to_address>
**suggestion (testing):** Consider adding a test for invalid or malformed import-names/import-namespaces values.

Adding tests for malformed or unexpected values, such as non-string items, whitespace-only strings, or duplicates, will help verify that the Metadata model correctly handles or rejects these cases.

Suggested implementation:

```python
@pytest.mark.parametrize(
    ["import_names", "import_namespaces"],
    [
        (["my_package.a", "my_package.b"], ["my_package"]),
        (None, None),
        ([], []),
    ],
)
def test_from_package_import_names(
    import_names: list[str] | None,
    import_namespaces: list[str] | None,
    package: ProjectPackage,
) -> None:
    package.import_names = import_names
    package.import_namespaces = import_namespaces

    metadata = Metadata.from_package(package)

    assert metadata.import_names == import_names
    assert metadata.import_namespaces == import_namespaces


@pytest.mark.parametrize(
    ["import_names", "import_namespaces", "expected_exception"],
    [
        ([123, "my_package.b"], ["my_package"], TypeError),  # Non-string in import_names
        (["   ", "my_package.b"], ["my_package"], None),     # Whitespace-only string
        (["my_package.a", "my_package.a"], ["my_package"], None),  # Duplicate entries
        (["my_package.a"], [None], TypeError),               # Non-list in import_namespaces
        (["my_package.a"], ["   "], None),                   # Whitespace-only string in namespaces
    ],
)
def test_from_package_import_names_malformed(
    import_names,
    import_namespaces,
    expected_exception,
    package: ProjectPackage,
) -> None:
    package.import_names = import_names
    package.import_namespaces = import_namespaces

    if expected_exception:
        with pytest.raises(expected_exception):
            Metadata.from_package(package)
    else:
        metadata = Metadata.from_package(package)
        # Check for duplicates and whitespace-only strings
        if import_names is not None:
            assert all(isinstance(name, str) for name in metadata.import_names)
            assert all(name.strip() != "" for name in metadata.import_names)
            assert len(set(metadata.import_names)) == len(metadata.import_names)
        if import_namespaces is not None:
            assert all(isinstance(ns, str) for ns in metadata.import_namespaces)
            assert all(ns.strip() != "" for ns in metadata.import_namespaces)
            assert len(set(metadata.import_namespaces)) == len(metadata.import_namespaces)

```

If the `Metadata.from_package` method does not currently raise exceptions or handle these cases, you will need to update its implementation to validate input types, remove whitespace-only strings, and deduplicate entries. Adjust the assertions in the test as needed to match the actual behavior.
</issue_to_address>

### Comment 4
<location> `tests/masonry/builders/test_builder.py:180-184` </location>
<code_context>
         "Repository, https://github.com/python-poetry/poetry",
     ]

+    import_names = parsed.get_all("Import-Name", False)
+    assert import_names is False
+
+    import_namespaces = parsed.get_all("Import-Namespace", False)
+    assert import_namespaces is False
+

</code_context>

<issue_to_address>
**suggestion (testing):** Consider adding a test case where import-names and import-namespaces are present in the metadata.

Adding such a test will ensure correct handling and parsing when these fields are present.

```suggestion
    import_names = parsed.get_all("Import-Name", False)
    assert import_names is False

    import_namespaces = parsed.get_all("Import-Namespace", False)
    assert import_namespaces is False

    # Test case: metadata with Import-Name and Import-Namespace present
    metadata_with_imports = (
        "Metadata-Version: 2.1\n"
        "Name: my-package\n"
        "Version: 1.2.3\n"
        "Summary: Some description.\n"
        "Import-Name: my_package\n"
        "Import-Name: another_package\n"
        "Import-Namespace: my_namespace\n"
        "Import-Namespace: another_namespace\n"
    )
    parsed_with_imports = p.parsestr(metadata_with_imports)
    import_names_present = parsed_with_imports.get_all("Import-Name")
    import_namespaces_present = parsed_with_imports.get_all("Import-Namespace")
    assert import_names_present == ["my_package", "another_package"]
    assert import_namespaces_present == ["my_namespace", "another_namespace"]
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Copy link
Member

@radoering radoering left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apart from some pedantic feedback in the separate comments I wonder if we should determine the core metadata fields from other information we have if the import names are not defined in the project section or if we do not want to go down this rabbit hole (or at least defer it)?

@radoering
Copy link
Member

I wonder whether we should wait for a packaging release with pypa/packaging#948. There is already an issue (pypa/packaging#950).

At the moment, packaging can parse raw metadata (packaging.metadata.parse_email()) but bails out in Metadata.from_raw() (or Metadata.from_email()) with '2.5' is not a valid metadata version.

By the way, pkginfo does not support 2.5 either, but handles it more gracefully: New metadata version (2.5) higher than latest supported version: parsing as 2.4

@radoering
Copy link
Member

Blocker: pypi/warehouse#19083

Attention: I was able to upload a package with metadata version 2.5 to TestPyPI by using the latest Poetry release for publishing because it claims the metadata version is 2.4 (because that would be the version if you built with the included poetry-core). A new Poetry release (or a tool that uses the actual metadata version of a build artifact) would not be able to upload a package.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants