Skip to content

Update init & new commands for PEP 639 (License)#10787

Merged
radoering merged 2 commits intopython-poetry:mainfrom
dunkmann00:init-cmd-license
Mar 29, 2026
Merged

Update init & new commands for PEP 639 (License)#10787
radoering merged 2 commits intopython-poetry:mainfrom
dunkmann00:init-cmd-license

Conversation

@dunkmann00
Copy link
Copy Markdown
Contributor

@dunkmann00 dunkmann00 commented Mar 28, 2026

Pull Request Check List

Resolves: N/A

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

Poetry was already updated to support this in
#10413. But the layout needed to be updated for it to get generated in the init and new commands.

This also updates the relevant tests.

Summary by Sourcery

Align project layout and init/new command outputs with PEP 639 license field expectations.

Bug Fixes:

  • Update generated pyproject.toml license field to use a simple string instead of a nested table for license text.
  • Adjust interactive init prompt to remove the empty default indicator for the license question.

Tests:

  • Update init and new command tests to assert the new license string format in generated pyproject.toml files.

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai bot commented Mar 28, 2026

Reviewer's Guide

Updates the project layout and tests so that init and new commands generate a PEP 639-compliant license field as a simple string instead of an object, and adjusts the interactive license prompt text.

Class diagram for pyproject layout license field PEP 639 update

classDiagram
  class PyProjectLayoutBefore {
    string description
    list authors
    dict license
    string readme
    string requires_python
    list dependencies
    generate_project_content(project_content)
  }

  class PyProjectLayoutAfter {
    string description
    list authors
    string license
    string readme
    string requires_python
    list dependencies
    generate_project_content(project_content)
  }

  PyProjectLayoutBefore <|-- PyProjectLayoutAfter

  class GenerateProjectContentBefore {
    +apply_license(project_content)
  }
  class GenerateProjectContentAfter {
    +apply_license(project_content)
  }

  PyProjectLayoutBefore --> GenerateProjectContentBefore
  PyProjectLayoutAfter --> GenerateProjectContentAfter

  GenerateProjectContentBefore : license_field_type dict
  GenerateProjectContentBefore : project_content[license][text] = _license

  GenerateProjectContentAfter : license_field_type string
  GenerateProjectContentAfter : project_content[license] = _license
Loading

File-Level Changes

Change Details Files
Align license field generation with PEP 639 string form in layouts and init/new tests.
  • Change default layout template to define license as an empty string instead of an empty table/object.
  • Update project content generation to set project_content["license"] directly to the license string instead of nesting it under a text key.
  • Adjust all init/new-related test TOML fixtures and expectations from license = {text = "MIT"} (or similar) to license = "MIT".
src/poetry/layouts/layout.py
tests/console/commands/test_init.py
tests/console/commands/conftest.py
Tweak interactive init license prompt wording.
  • Change the interactive init command license question from License []: to License: while keeping an empty default value.
src/poetry/console/commands/init.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
Copy Markdown

@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 - I've found 2 issues, and left some high level feedback:

  • Now that license is represented as a string instead of a table, please scan for and update any remaining code paths that still assume project_content['license'] is a mapping (e.g., accessing ['text']) to avoid subtle runtime errors.
  • Given license is now initialized as an empty string in the layout, consider whether using None or omitting the key until a value is set would better convey the absence of a license and simplify checks where you currently rely on truthiness.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Now that `license` is represented as a string instead of a table, please scan for and update any remaining code paths that still assume `project_content['license']` is a mapping (e.g., accessing `['text']`) to avoid subtle runtime errors.
- Given `license` is now initialized as an empty string in the layout, consider whether using `None` or omitting the key until a value is set would better convey the absence of a license and simplify checks where you currently rely on truthiness.

## Individual Comments

### Comment 1
<location path="tests/console/commands/test_init.py" line_range="148" />
<code_context>
     {name = "Your Name",email = "[email protected]"}
 ]
-license = {text = "MIT"}
+license = "MIT"
 readme = "README.md"
 requires-python = ">=3.6"
</code_context>
<issue_to_address>
**suggestion (testing):** Add a test case for the `--license` CLI option to ensure it writes the new scalar license format

These expectations only cover the interactive `init` flow. Please also add coverage for the non-interactive path (e.g., using the `--license` flag and/or a preconfigured license), and assert that the generated `pyproject.toml` also uses the scalar `license = "..."` format. This will help catch any divergence between interactive and non-interactive handling.

Suggested implementation:

```python
authors = [
    {name = "Your Name",email = "[email protected]"}
]
license = "MIT"
requires-python = ">=3.6"
dependencies = [
    "pendulum (>=2.0.0,<3.0.0)",
authors = [
    {name = "Your Name",email = "[email protected]"}
]
license = "MIT"
requires-python = ">=3.6"
"""

```

I can only see a small literal snippet of the test file, so to fully implement your request the following concrete additions are needed elsewhere in `tests/console/commands/test_init.py`:

1. **Add an expected PyProject string for the non-interactive path**  
   Define a new constant (mirroring how the interactive expected content is stored), for example:
   ```python
   EXPECTED_PYPROJECT_NON_INTERACTIVE_LICENSE = """[project]
   name = "example"
   version = "0.1.0"
   description = ""
   authors = [
       {name = "Your Name", email = "[email protected]"},
   ]
   license = "MIT"
   requires-python = ">=3.6"
   dependencies = []
   """
   ```
   Make sure the `license` line is **scalar**, i.e. `license = "MIT"` (not a table or `{text = ...}`).

2. **Add a non-interactive init test that uses the `--license` flag**  
   Create a new test function using the same CLI harness and patterns as the other tests in this file, for example:
   ```python
   def test_init_non_interactive_license_scalar(tmp_path, cli_runner):
       project_dir = tmp_path / "example"
       result = cli_runner.invoke(
           app,
           [
               "init",
               "--name", "example",
               "--author", "Your Name <[email protected]>",
               "--license", "MIT",
               "--no-interaction",
               "--path", str(project_dir),
           ],
       )
       assert result.exit_code == 0

       pyproject = (project_dir / "pyproject.toml").read_text()
       assert 'license = "MIT"' in pyproject
       # If your other tests do full-string comparisons, also:
       # assert pyproject.strip() == EXPECTED_PYPROJECT_NON_INTERACTIVE_LICENSE.strip()
   ```
   Adjust `app`, `cli_runner`, and flags to match how the rest of the tests in this file invoke the `init` command (e.g., `poetry` vs `app`, `--no-interaction` vs `-n`, etc.).

3. **Cover preconfigured license (if there is such a code path)**  
   If the `init` command also reads a default/preconfigured license (e.g., from a config file or environment variable) without the `--license` flag:
   - Add a second test that sets up that configuration (using fixtures or monkeypatching, consistent with the existing tests).
   - Run `init` non-interactively without `--license`.
   - Assert again that the generated `pyproject.toml` contains `license = "..."` as a scalar, not the old `{text = ...}` table format.

4. **Keep parity with interactive expectations**  
   Ensure both interactive and non-interactive tests assert the same scalar `license` format so that any future regression (e.g., one path reverting to `{text = "MIT"}`) will be caught.
</issue_to_address>

### Comment 2
<location path="tests/console/commands/conftest.py" line_range="33" />
<code_context>
     {name = "Your Name",email = "[email protected]"}
 ]
-license = {text = "MIT"}
+license = "MIT"
 readme = "README.md"
 requires-python = ">=3.6"
</code_context>
<issue_to_address>
**suggestion (testing):** Consider adding a fixture (and tests) that cover the case where no license is chosen to ensure the `license` field is omitted

To fully exercise the PEP 639 behavior, it’d be useful to add a counterpart fixture (e.g. `init_no_license_toml` / `new_no_license_toml`) and tests that simulate skipping the license in `init`/`new`, then assert that the `license` key is completely absent from `pyproject.toml`. That would validate both the default layout and interactive command handling for the "no license" path.

Suggested implementation:

```python
INIT_TOML = """
authors = [
    {name = "Your Name",email = "[email protected]"}
]
license = "MIT"
readme = "README.md"
requires-python = ">=3.6"
"""

NEW_TOML = """
authors = [
    {name = "Your Name",email = "[email protected]"}
]
license = "MIT"
readme = "README.md"
requires-python = ">=3.6"
dependencies = [
"""

INIT_NO_LICENSE_TOML = """
authors = [
    {name = "Your Name",email = "[email protected]"}
]
readme = "README.md"
requires-python = ">=3.6"
"""

NEW_NO_LICENSE_TOML = """
authors = [
    {name = "Your Name",email = "[email protected]"}
]
readme = "README.md"
requires-python = ">=3.6"
dependencies = [
"""

```

1. If this file currently defines fixtures that use the original TOML literals (e.g. `init_toml`, `new_toml`), update them to use `INIT_TOML` / `NEW_TOML` instead.
2. Add counterpart fixtures (e.g. `init_no_license_toml`, `new_no_license_toml`) that write `INIT_NO_LICENSE_TOML` / `NEW_NO_LICENSE_TOML` into `pyproject.toml` in their respective temporary directories.
3. Add tests for the `init` and `new` commands that:
   - Simulate skipping license selection in the interactive prompts (or however “no license” is currently represented).
   - Use the new fixtures and assert that the resulting `pyproject.toml` does **not** contain a `license` key at all.
4. If the surrounding code already uses different constant names or patterns, align the new constants/fixtures with that existing naming convention.
</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.

@dosubot
Copy link
Copy Markdown

dosubot bot commented Mar 28, 2026

Related Documentation

2 document(s) may need updating based on files changed in this PR:

Python Poetry

basic-usage /poetry/blob/main/docs/basic-usage.md — ⏳ Awaiting Merge
pyproject /poetry/blob/main/docs/pyproject.md — ⏳ Awaiting Merge

Note: You must be authenticated to accept/decline updates.

How did I do? Any feedback?  Join Discord

Poetry was already updated to support this in
python-poetry#10413. But the layout
needed to be updated for it to get generated in the init and new
commands.

This also updates the relevant tests.
@radoering radoering enabled auto-merge (squash) March 29, 2026 05:47
@radoering radoering merged commit afb12f6 into python-poetry:main Mar 29, 2026
54 checks passed
radoering pushed a commit that referenced this pull request Mar 29, 2026
@dunkmann00 dunkmann00 deleted the init-cmd-license branch March 29, 2026 20:37
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