feat: add --non-package flag to init and new commands#10782
feat: add --non-package flag to init and new commands#1078220xic wants to merge 1 commit intopython-poetry:mainfrom
Conversation
Reviewer's GuideImplements a new --non-package CLI flag for Sequence diagram for poetry init --non-package project creationsequenceDiagram
actor User
participant CLI as Poetry_CLI
participant InitCmd as InitCommand
participant Layout as Layout
participant FS as FileSystem
User->>CLI: run "poetry init --non-package"
CLI->>InitCmd: execute with options(non-package=True)
InitCmd->>InitCmd: determine is_interactive
InitCmd->>InitCmd: non_package = option(non-package)
InitCmd->>InitCmd: skip interactive prompts for
InitCmd->>InitCmd: name, version, description,
InitCmd->>InitCmd: author, license, python
InitCmd->>Layout: __init__(project, author, python,
InitCmd->>Layout: dependencies, dev_dependencies,
InitCmd->>Layout: package_mode=False)
InitCmd->>Layout: create(path, with_tests, with_pyproject=True)
alt package_mode is False
Layout->>FS: _create_readme(path)
Layout->>Layout: generate_project_content()
Layout->>Layout: poetry_content.remove(packages)
Layout->>Layout: poetry_content[package-mode] = False
Layout->>Layout: skip adding build-system
end
Layout->>FS: write pyproject.toml without build-system
FS-->>User: project layout without package structure
Class diagram for updated Layout and command classesclassDiagram
class Layout {
- str _project
- Path _package_path_relative
- str _author
- str _python
- Mapping~str,Any~ _dependencies
- Mapping~str,Any~ _dev_dependencies
- bool _package_mode
+ __init__(project: str, author: str, python: str, dependencies: Mapping~str,Any~, dev_dependencies: Mapping~str,Any~, package_mode: bool)
+ create(path: Path, with_tests: bool, with_pyproject: bool) void
+ generate_project_content(with_readme: bool) TOMLDocument
+ _create_default(path: Path) void
+ _create_readme(path: Path) void
+ _create_tests(path: Path) void
}
class InitCommand {
+ options
+ _init_pyproject(project_path: Path, allow_interactive: bool) None
+ option(name: str) Any
+ create_question(prompt: str, default: str) Any
+ ask(question: Any) str
}
class NewCommand {
+ options
}
InitCommand <|-- NewCommand
class CLIOptions {
+ name: str
+ description: str
+ author: str
+ python: str
+ dependency: str
+ dev_dependency: str
+ license: str
+ non-package: bool
}
InitCommand --> Layout : creates
InitCommand --> CLIOptions : uses
NewCommand --> CLIOptions : extends with non-package
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
Related Documentation 3 document(s) may need updating based on files changed in this PR: Python Poetry basic-usage
|
There was a problem hiding this comment.
Hey - I've found 2 issues, and left some high level feedback:
- In
Layout.generate_project_content, consider guardingpoetry_content.remove("packages")with a presence check (e.g.,if "packages" in poetry_content) to avoid potentialKeyErrorif a layout variant ever omits that key. - The non-interactive/interactive tests for
--non-packagemostly assert on the generated TOML; intest_non_package_interactive_skips_package_questionsyou may want to assert that prompts like"Package name"or"Version"are absent from the output to verify the intended behavior rather than just relying on comments. - In
Layout.create,_create_defaultis completely skipped in non-package mode; if_create_defaultever gains responsibilities that are not package-specific, you might want to split out package-specific work into a separate method so that non-package projects still benefit from any common initialization.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `Layout.generate_project_content`, consider guarding `poetry_content.remove("packages")` with a presence check (e.g., `if "packages" in poetry_content`) to avoid potential `KeyError` if a layout variant ever omits that key.
- The non-interactive/interactive tests for `--non-package` mostly assert on the generated TOML; in `test_non_package_interactive_skips_package_questions` you may want to assert that prompts like `"Package name"` or `"Version"` are absent from the output to verify the intended behavior rather than just relying on comments.
- In `Layout.create`, `_create_default` is completely skipped in non-package mode; if `_create_default` ever gains responsibilities that are not package-specific, you might want to split out package-specific work into a separate method so that non-package projects still benefit from any common initialization.
## Individual Comments
### Comment 1
<location path="tests/console/commands/test_init.py" line_range="1309-1318" />
<code_context>
+ assert "[build-system]" not in toml_content
+
+
+def test_non_package_interactive_skips_package_questions(
+ tester: CommandTester,
+) -> None:
+ """Test that --non-package in interactive mode skips name/version/description/
+ author/license/python questions and goes straight to dependency questions."""
+ inputs = [
+ "n", # Interactive packages (would you like to define...)
+ "n", # Interactive dev packages
+ "\n", # Confirm generation
+ ]
+
+ tester.execute("--non-package", inputs="\n".join(inputs))
+
+ output = tester.io.fetch_output()
+
+ # Should have package-mode = false in the generated output
+ assert "package-mode = false" in output
+
+ # Should NOT have build-system
+ assert "[build-system]" not in output
+
+ # Should NOT have asked for package name (no "Package name" prompt)
+ # The output should contain the generated file but without the interactive
+ # package-specific questions
+
+
</code_context>
<issue_to_address>
**suggestion (testing):** Strengthen the interactive non-package test by explicitly asserting skipped prompts and file contents
This test currently only verifies that `package-mode = false` and the absence of `[build-system]` appear in the console output, but it doesn’t assert that the package-specific prompts are actually skipped or that the generated `pyproject.toml` reflects non-package mode. Please also:
- Assert that strings like `"Package name"`, `"Version"`, `"Description"`, and `"Author"` do **not** appear in `output`.
- Optionally read `pyproject.toml` from disk in this interactive scenario and assert it contains `[project]`, `package-mode = false`, and no `[build-system]` section, consistent with the non-interactive tests.
This makes the test truly guard the interactive `--non-package` flow rather than just checking for a single flag in the output.
Suggested implementation:
```python
def test_non_package_interactive_skips_package_questions(
tester: CommandTester,
) -> None:
"""Test that --non-package in interactive mode skips name/version/description/
author/license/python questions and goes straight to dependency questions."""
inputs = [
"n", # Interactive packages (would you like to define...)
"n", # Interactive dev packages
"\n", # Confirm generation
]
tester.execute("--non-package", inputs="\n".join(inputs))
output = tester.io.fetch_output()
# Should have package-mode = false in the generated output
assert "package-mode = false" in output
# Should NOT have build-system in the output
assert "[build-system]" not in output
# Should NOT have asked for package-specific metadata
assert "Package name" not in output
assert "Version" not in output
assert "Description" not in output
assert "Author" not in output
# Optionally protect against other package-related prompts
assert "License" not in output
assert "Python" not in output
# The generated file should reflect non-package mode as well
pyproject_content = Path("pyproject.toml").read_text(encoding="utf-8")
assert "[project]" in pyproject_content
assert "package-mode = false" in pyproject_content
assert "[build-system]" not in pyproject_content
```
This change assumes that the current working directory for the `tester` execution is where `pyproject.toml` is written (which is consistent with other tests in this file that read `pyproject.toml` directly or via `tmp_path`).
If other interactive tests in this file use a different pattern (e.g., `tmp_path` fixture combined with `chdir`), you may want to:
1. Mirror that pattern here (e.g., add a `tmp_path: Path` fixture and `os.chdir(tmp_path)` or use the helper that already does this for `tester`), and
2. Replace `Path("pyproject.toml")` with the appropriate path, such as `tmp_path / "pyproject.toml"`.
</issue_to_address>
### Comment 2
<location path="tests/console/commands/test_init.py" line_range="1279-1248" />
<code_context>
+ assert 'version = "0.1.0"' in toml_content
+
+
+def test_non_package_noninteractive_with_dependencies(
+ app: PoetryTestApplication,
+ mocker: MockerFixture,
+ poetry: Poetry,
+ repo: TestRepository,
+ tmp_path: Path,
+) -> None:
+ """Test that --non-package works with --dependency flag."""
+ command = app.find("init")
+ assert isinstance(command, InitCommand)
+ command._pool = poetry.pool
+
+ repo.add_package(get_package("requests", "2.28.0"))
+
+ p = mocker.patch("pathlib.Path.cwd")
+ p.return_value = tmp_path
+
+ tester = CommandTester(command)
+ tester.execute(
+ "--name my-project --non-package --dependency requests",
+ interactive=False,
+ )
+
+ toml_content = (tmp_path / "pyproject.toml").read_text(encoding="utf-8")
+
+ assert "package-mode = false" in toml_content
+ assert "requests" in toml_content
+ assert "[build-system]" not in toml_content
+
+
</code_context>
<issue_to_address>
**suggestion (testing):** Add a companion test for `--dev-dependency` in non-package mode
The new behavior should also be validated for `--dev-dependency` in non-package mode. Please add a similar test (e.g. `test_non_package_noninteractive_with_dev_dependencies`) that:
- Adds a package to the repo
- Runs `poetry init --non-package --dev-dependency <pkg>`
- Asserts the dev dependency is in the correct section, `package-mode = false` is present, and `[build-system]` is absent
This will fill the remaining coverage gap for non-package dependency handling.
Suggested implementation:
```python
def test_non_package_noninteractive_with_dependencies(
app: PoetryTestApplication,
mocker: MockerFixture,
poetry: Poetry,
repo: TestRepository,
tmp_path: Path,
) -> None:
"""Test that --non-package works with --dependency flag."""
command = app.find("init")
assert isinstance(command, InitCommand)
command._pool = poetry.pool
repo.add_package(get_package("requests", "2.28.0"))
p = mocker.patch("pathlib.Path.cwd")
p.return_value = tmp_path
tester = CommandTester(command)
tester.execute(
"--name my-project --non-package --dependency requests",
interactive=False,
)
toml_content = (tmp_path / "pyproject.toml").read_text(encoding="utf-8")
assert "package-mode = false" in toml_content
assert "requests" in toml_content
assert "[build-system]" not in toml_content
def test_non_package_noninteractive_with_dev_dependencies(
app: PoetryTestApplication,
mocker: MockerFixture,
poetry: Poetry,
repo: TestRepository,
tmp_path: Path,
) -> None:
"""Test that --non-package works with --dev-dependency flag."""
command = app.find("init")
assert isinstance(command, InitCommand)
command._pool = poetry.pool
repo.add_package(get_package("requests", "2.28.0"))
p = mocker.patch("pathlib.Path.cwd")
p.return_value = tmp_path
tester = CommandTester(command)
tester.execute(
"--name my-project --non-package --dev-dependency requests",
interactive=False,
)
toml_content = (tmp_path / "pyproject.toml").read_text(encoding="utf-8")
assert "package-mode = false" in toml_content
# Ensure the dev dependency is recorded under the dev dependencies section
assert "[tool.poetry.group.dev.dependencies]" in toml_content
assert 'requests = "2.28.0"' in toml_content
assert "[build-system]" not in toml_content
```
If your project uses a different TOML layout for dev dependencies (for example `[tool.poetry.dev-dependencies]` instead of `[tool.poetry.group.dev.dependencies]`, or a different version constraint format), update the two assertions in `test_non_package_noninteractive_with_dev_dependencies` accordingly:
- Replace `"[tool.poetry.group.dev.dependencies]"` with the correct section header.
- Adjust `'requests = "2.28.0"'` if the generated constraint differs (e.g. caret range or compatible release).
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| def test_non_package_interactive_skips_package_questions( | ||
| tester: CommandTester, | ||
| ) -> None: | ||
| """Test that --non-package in interactive mode skips name/version/description/ | ||
| author/license/python questions and goes straight to dependency questions.""" | ||
| inputs = [ | ||
| "n", # Interactive packages (would you like to define...) | ||
| "n", # Interactive dev packages | ||
| "\n", # Confirm generation | ||
| ] |
There was a problem hiding this comment.
suggestion (testing): Strengthen the interactive non-package test by explicitly asserting skipped prompts and file contents
This test currently only verifies that package-mode = false and the absence of [build-system] appear in the console output, but it doesn’t assert that the package-specific prompts are actually skipped or that the generated pyproject.toml reflects non-package mode. Please also:
- Assert that strings like
"Package name","Version","Description", and"Author"do not appear inoutput. - Optionally read
pyproject.tomlfrom disk in this interactive scenario and assert it contains[project],package-mode = false, and no[build-system]section, consistent with the non-interactive tests.
This makes the test truly guard the interactive --non-package flow rather than just checking for a single flag in the output.
Suggested implementation:
def test_non_package_interactive_skips_package_questions(
tester: CommandTester,
) -> None:
"""Test that --non-package in interactive mode skips name/version/description/
author/license/python questions and goes straight to dependency questions."""
inputs = [
"n", # Interactive packages (would you like to define...)
"n", # Interactive dev packages
"\n", # Confirm generation
]
tester.execute("--non-package", inputs="\n".join(inputs))
output = tester.io.fetch_output()
# Should have package-mode = false in the generated output
assert "package-mode = false" in output
# Should NOT have build-system in the output
assert "[build-system]" not in output
# Should NOT have asked for package-specific metadata
assert "Package name" not in output
assert "Version" not in output
assert "Description" not in output
assert "Author" not in output
# Optionally protect against other package-related prompts
assert "License" not in output
assert "Python" not in output
# The generated file should reflect non-package mode as well
pyproject_content = Path("pyproject.toml").read_text(encoding="utf-8")
assert "[project]" in pyproject_content
assert "package-mode = false" in pyproject_content
assert "[build-system]" not in pyproject_contentThis change assumes that the current working directory for the tester execution is where pyproject.toml is written (which is consistent with other tests in this file that read pyproject.toml directly or via tmp_path).
If other interactive tests in this file use a different pattern (e.g., tmp_path fixture combined with chdir), you may want to:
- Mirror that pattern here (e.g., add a
tmp_path: Pathfixture andos.chdir(tmp_path)or use the helper that already does this fortester), and - Replace
Path("pyproject.toml")with the appropriate path, such astmp_path / "pyproject.toml".
| assert "package-mode = false" in toml_content | ||
|
|
||
| # Should NOT have build-system section | ||
| assert "[build-system]" not in toml_content |
There was a problem hiding this comment.
suggestion (testing): Add a companion test for --dev-dependency in non-package mode
The new behavior should also be validated for --dev-dependency in non-package mode. Please add a similar test (e.g. test_non_package_noninteractive_with_dev_dependencies) that:
- Adds a package to the repo
- Runs
poetry init --non-package --dev-dependency <pkg> - Asserts the dev dependency is in the correct section,
package-mode = falseis present, and[build-system]is absent
This will fill the remaining coverage gap for non-package dependency handling.
Suggested implementation:
def test_non_package_noninteractive_with_dependencies(
app: PoetryTestApplication,
mocker: MockerFixture,
poetry: Poetry,
repo: TestRepository,
tmp_path: Path,
) -> None:
"""Test that --non-package works with --dependency flag."""
command = app.find("init")
assert isinstance(command, InitCommand)
command._pool = poetry.pool
repo.add_package(get_package("requests", "2.28.0"))
p = mocker.patch("pathlib.Path.cwd")
p.return_value = tmp_path
tester = CommandTester(command)
tester.execute(
"--name my-project --non-package --dependency requests",
interactive=False,
)
toml_content = (tmp_path / "pyproject.toml").read_text(encoding="utf-8")
assert "package-mode = false" in toml_content
assert "requests" in toml_content
assert "[build-system]" not in toml_content
def test_non_package_noninteractive_with_dev_dependencies(
app: PoetryTestApplication,
mocker: MockerFixture,
poetry: Poetry,
repo: TestRepository,
tmp_path: Path,
) -> None:
"""Test that --non-package works with --dev-dependency flag."""
command = app.find("init")
assert isinstance(command, InitCommand)
command._pool = poetry.pool
repo.add_package(get_package("requests", "2.28.0"))
p = mocker.patch("pathlib.Path.cwd")
p.return_value = tmp_path
tester = CommandTester(command)
tester.execute(
"--name my-project --non-package --dev-dependency requests",
interactive=False,
)
toml_content = (tmp_path / "pyproject.toml").read_text(encoding="utf-8")
assert "package-mode = false" in toml_content
# Ensure the dev dependency is recorded under the dev dependencies section
assert "[tool.poetry.group.dev.dependencies]" in toml_content
assert 'requests = "2.28.0"' in toml_content
assert "[build-system]" not in toml_contentIf your project uses a different TOML layout for dev dependencies (for example [tool.poetry.dev-dependencies] instead of [tool.poetry.group.dev.dependencies], or a different version constraint format), update the two assertions in test_non_package_noninteractive_with_dev_dependencies accordingly:
- Replace
"[tool.poetry.group.dev.dependencies]"with the correct section header. - Adjust
'requests = "2.28.0"'if the generated constraint differs (e.g. caret range or compatible release).
Add
--non-packageflag toinitandnewcommandsSummary
This PR adds a
--non-packageflag to thepoetry initandpoetry newcommands, allowing users to initialize a project withpackage-mode = falseset inpyproject.tomldirectly from the CLI.Motivation
Currently, to create a non-package project (e.g., an application, a script collection, or a project that only uses Poetry for dependency management), users must:
poetry initorpoetry newpyproject.tomlto addpackage-mode = falseunder[tool.poetry][build-system]sectionThis is a common workflow — many Python projects are applications rather than distributable packages. The
--non-packageflag streamlines this by handling everything in a single step.Usage
Changes
src/poetry/layouts/layout.pypackage_mode: bool = Trueparameter toLayout.__init__()generate_project_content(): whenpackage_mode=False, setspackage-mode = falsein[tool.poetry]instead of generating thepackageskey, and skips the[build-system]section entirelycreate(): skips package directory (__init__.py) andtests/creation whenpackage_mode=False; README is still createdsrc/poetry/console/commands/init.py--non-packageoption toInitCommand.options_init_pyproject(): when--non-packageis set, interactive prompts for package-specific fields (name, version, description, author, license, python version) are skipped — sensible defaults are used insteadpackage_mode=not non_packageto theLayoutconstructorsrc/poetry/console/commands/new.py"non-package"to the set of options inherited fromInitCommandExample output
Running
poetry init --non-package --name my-appproduces:Note the absence of
[build-system]andpackages— neither is needed for non-package projects.Tests
Added 13 new tests across two test files:
test_init.py(7 tests):package-mode = falseand no[build-system][project]section is still generated with name/version--dependencyflagpackageskey in[tool.poetry]src/ortests/directoriesinit(without--non-package) is unaffectedtest_new.py(6 tests):pyproject.tomlwithout[build-system]src/,tests/, or__init__.py(README is still created)--nameflagpackageskey in outputnewstill creates full project structureSummary by Sourcery
Add support for initializing and creating non-package projects via a new CLI flag and adjust project layout generation accordingly.
New Features:
Enhancements:
Tests: