Skip to content

Comments

feat(check): validate [project.readme] field as per PEP 621#10604

Merged
radoering merged 5 commits intopython-poetry:mainfrom
Kuchizu:patch-2
Nov 1, 2025
Merged

feat(check): validate [project.readme] field as per PEP 621#10604
radoering merged 5 commits intopython-poetry:mainfrom
Kuchizu:patch-2

Conversation

@Kuchizu
Copy link
Contributor

@Kuchizu Kuchizu commented Oct 29, 2025

This adds validation for [project.readme] defined according to PEP 621. Previously, Poetry only validated [tool.poetry.readme]. The new logic ensures referenced files in [project.readme] exist and are accessible.

Pull Request Check List

Resolves: #issue-number-here

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

Summary by Sourcery

Extend the check command to validate the existence and format of files referenced in the PEP 621 [project.readme] field in addition to the existing [tool.poetry.readme] validation.

New Features:

  • Validate PEP 621 [project.readme] field alongside [tool.poetry.readme]
  • Support both string and table formats for the project.readme field

Enhancements:

  • Accumulate readme validation errors centrally and report invalid format entries

@sourcery-ai
Copy link

sourcery-ai bot commented Oct 29, 2025

Reviewer's Guide

Refactors the 'check' command to extend readme validation beyond tool.poetry to PEP 621's [project.readme], consolidating error collection and handling multiple formats with existence checks and format error reporting.

Class diagram for updated readme validation in check command

classDiagram
    class CheckCommand {
        +handle()
        +_validate_readme(readme, poetry_file)
        +_validate_dependencies_source(config)
    }
    CheckCommand : +handle() updated to validate [project.readme] per PEP 621
    CheckCommand : +handle() now supports dict and str formats for [project.readme]
    CheckCommand : +handle() reports format errors for invalid [project.readme]
Loading

File-Level Changes

Change Details Files
Consolidated and extended readme validation to support PEP 621 [project.readme]
  • Introduced a readme_errors list instead of directly extending errors
  • Validated existing tool.poetry.readme entries via _validate_readme
  • Parsed project.readme: extracted 'file' for dict inputs, accepted string inputs, and flagged unsupported types
  • Replaced the old single-field validation snippet with unified error aggregation and extension
src/poetry/console/commands/check.py

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

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 and they look great!

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location> `src/poetry/console/commands/check.py:157-168` </location>
<code_context>
+        if "readme" in poetry_config:
+            readme_errors += self._validate_readme(poetry_config["readme"], poetry_file)
+
+        project_readme = project.get("readme")
+        if project_readme:
+            if isinstance(project_readme, dict):
+                readme_path = project_readme.get("file")
+                if readme_path:
+                    readme_errors += self._validate_readme(readme_path, poetry_file)
+            elif isinstance(project_readme, str):
+                readme_errors += self._validate_readme(project_readme, poetry_file)
+            else:
+                readme_errors.append(
+                    f"Invalid format for [project.readme]: {project_readme!r}"
+                )
</code_context>

<issue_to_address>
**suggestion:** Consider handling multiple readme files in [project.readme] if specified as a list.

PEP 621 permits [project.readme] to be a list of strings. Please update the logic to handle lists and validate each file's existence.

```suggestion
        project_readme = project.get("readme")
        if project_readme:
            if isinstance(project_readme, dict):
                readme_path = project_readme.get("file")
                if readme_path:
                    readme_errors += self._validate_readme(readme_path, poetry_file)
            elif isinstance(project_readme, str):
                readme_errors += self._validate_readme(project_readme, poetry_file)
            elif isinstance(project_readme, list):
                for readme_item in project_readme:
                    if isinstance(readme_item, str):
                        readme_errors += self._validate_readme(readme_item, poetry_file)
                    else:
                        readme_errors.append(
                            f"Invalid entry in [project.readme] list: {readme_item!r}"
                        )
            else:
                readme_errors.append(
                    f"Invalid format for [project.readme]: {project_readme!r}"
                )
```
</issue_to_address>

### Comment 2
<location> `src/poetry/console/commands/check.py:166` </location>
<code_context>
+                readme_errors += self._validate_readme(project_readme, poetry_file)
+            else:
+                readme_errors.append(
+                    f"Invalid format for [project.readme]: {project_readme!r}"
+                )
+
</code_context>

<issue_to_address>
**suggestion:** Consider providing guidance on valid [project.readme] formats in the error message.

Include the accepted formats (string, dict with 'file', or list of strings) in the error message to help users fix configuration issues.

```suggestion
                readme_errors.append(
                    f"Invalid format for [project.readme]: {project_readme!r}. "
                    "Accepted formats are: a string (filename), a dict with a 'file' key, or a list of strings (filenames)."
                )
```
</issue_to_address>

### Comment 3
<location> `src/poetry/console/commands/check.py:134` </location>
<code_context>
    def handle(self) -> int:
        from poetry.core.pyproject.toml import PyProjectTOML
        from poetry.factory import Factory

        # Load poetry config and display errors, if any
        poetry_file = self.poetry.file.path
        toml_data = PyProjectTOML(poetry_file).data
        check_result = Factory.validate(toml_data, strict=True)

        project = toml_data.get("project", {})
        poetry_config = toml_data["tool"]["poetry"]

        # Validate trove classifiers
        project_classifiers = set(
            project.get("classifiers") or poetry_config.get("classifiers", [])
        )
        errors, warnings = self._validate_classifiers(project_classifiers)
        check_result["errors"].extend(errors)
        check_result["warnings"].extend(warnings)

        readme_errors = []

        # Check [tool.poetry.readme]
        if "readme" in poetry_config:
            readme_errors += self._validate_readme(poetry_config["readme"], poetry_file)

        project_readme = project.get("readme")
        if project_readme:
            if isinstance(project_readme, dict):
                readme_path = project_readme.get("file")
                if readme_path:
                    readme_errors += self._validate_readme(readme_path, poetry_file)
            elif isinstance(project_readme, str):
                readme_errors += self._validate_readme(project_readme, poetry_file)
            else:
                readme_errors.append(
                    f"Invalid format for [project.readme]: {project_readme!r}"
                )

        check_result["errors"].extend(readme_errors)

        # Validate dependencies' sources
        check_result["errors"] += self._validate_dependencies_source(poetry_config)

        # Verify that lock file is consistent
        if self.option("lock") and not self.poetry.locker.is_locked():
            check_result["errors"] += ["poetry.lock was not found."]
        if self.poetry.locker.is_locked() and not self.poetry.locker.is_fresh():
            check_result["errors"] += [
                "pyproject.toml changed significantly since poetry.lock was last generated. "
                "Run `poetry lock` to fix the lock file."
            ]

        return_code = 0

        if check_result["errors"] or (
            check_result["warnings"] and self.option("strict")
        ):
            return_code = 1

        if not check_result["errors"] and not check_result["warnings"]:
            self.info("All set!")

        for error in check_result["errors"]:
            self.line_error(f"<error>Error: {error}</error>")

        for error in check_result["warnings"]:
            self.line_error(f"<warning>Warning: {error}</warning>")

        return return_code

</code_context>

<issue_to_address>
**issue (code-quality):** Use named expression to simplify assignment and conditional [×2] ([`use-named-expression`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/use-named-expression/))
</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.

Can you add tests in test_check.py, please?

@Kuchizu Kuchizu requested a review from radoering November 1, 2025 02:14
@radoering
Copy link
Member

pre-commit.ci autofix

Kuchizu and others added 5 commits November 1, 2025 10:27
This adds validation for [project.readme] defined according to PEP 621.
Previously, Poetry only validated [tool.poetry.readme].
The new logic ensures referenced files in [project.readme] exist and are accessible.
@radoering radoering enabled auto-merge (squash) November 1, 2025 09:38
@radoering radoering merged commit c0dae78 into python-poetry:main Nov 1, 2025
63 checks passed
@github-actions
Copy link

github-actions bot commented Dec 2, 2025

This pull request has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Dec 2, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants