Skip to content

feat(cli): default new command to src layout #10135

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 16 additions & 11 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -608,8 +608,8 @@ poetry lock

## new

This command will help you kickstart your new Python project by creating
a directory structure suitable for most projects.
This command will help you kickstart your new Python project by creating a new Poetry project. By default, a `src`
layout is chosen.

```bash
poetry new my-package
Expand All @@ -621,8 +621,9 @@ will create a folder as follows:
my-package
├── pyproject.toml
├── README.md
├── my_package
│ └── __init__.py
├── src
│ └── my_package
│ └── __init__.py
└── tests
└── __init__.py
```
Expand All @@ -634,10 +635,10 @@ the `--name` option:
poetry new my-folder --name my-package
```

If you want to use a src folder, you can use the `--src` option:
If you want to use a `flat` project layout, you can use the `--flat` option:

```bash
poetry new --src my-package
poetry new --flat my-package
```

That will create a folder structure as follows:
Expand All @@ -646,18 +647,22 @@ That will create a folder structure as follows:
my-package
├── pyproject.toml
├── README.md
├── src
│ └── my_package
│ └── __init__.py
├── my_package
│ └── __init__.py
└── tests
└── __init__.py
```

{{% note %}}
For an overview of the differences between `flat` and `src` layouts, please see
[here](https://packaging.python.org/en/latest/discussions/src-layout-vs-flat-layout/).
{{% /note %}}

The `--name` option is smart enough to detect namespace packages and create
the required structure for you.

```bash
poetry new --src --name my.package my-package
poetry new --name my.package my-package
```

will create the following structure:
Expand All @@ -678,7 +683,7 @@ my-package

* `--interactive (-i)`: Allow interactive specification of project configuration.
* `--name`: Set the resulting package name.
* `--src`: Use the src layout for the project.
* `--flat`: Use the flat layout for the project.
* `--readme`: Specify the readme file extension. Default is `md`. If you intend to publish to PyPI
keep the [recommendations for a PyPI-friendly README](https://packaging.python.org/en/latest/guides/making-a-pypi-friendly-readme/)
in mind.
Expand Down
15 changes: 13 additions & 2 deletions src/poetry/console/commands/new.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,13 @@ class NewCommand(InitCommand):
flag=True,
),
option("name", None, "Set the resulting package name.", flag=False),
option("src", None, "Use the src layout for the project."),
option(
"src",
None,
"Use the src layout for the project. "
"<warning>Deprecated</>: This is the default option now.",
),
option("flat", None, "Use the flat layout for the project."),
option(
"readme",
None,
Expand Down Expand Up @@ -72,9 +78,14 @@ def handle(self) -> int:
f"Destination <fg=yellow>{path}</> exists and is not empty"
)

if self.option("src"):
self.line_error(
"The <c1>--src</> option is now the default and will be removed in a future version."
)

return self._init_pyproject(
project_path=path,
allow_interactive=self.option("interactive"),
layout_name="src" if self.option("src") else "standard",
layout_name="standard" if self.option("flat") else "src",
readme_format=self.option("readme") or "md",
)
21 changes: 21 additions & 0 deletions tests/console/commands/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,24 @@ def init_basic_toml() -> str:
readme = "README.md"
requires-python = ">=3.6"
"""


@pytest.fixture()
def new_basic_toml() -> str:
return """\
[project]
name = "my-package"
version = "1.2.3"
description = "This is a description"
authors = [
{name = "Your Name",email = "[email protected]"}
]
license = {text = "MIT"}
readme = "README.md"
requires-python = ">=3.6"
dependencies = [
]

[tool.poetry]
packages = [{include = "my_package", from = "src"}]
"""
56 changes: 31 additions & 25 deletions tests/console/commands/test_new.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def verify_project_directory(
path: Path,
package_name: str,
package_path: str | Path,
include_from: str | None = None,
is_flat: bool = False,
) -> Poetry:
package_path = Path(package_path)
assert path.is_dir()
Expand All @@ -49,13 +49,13 @@ def verify_project_directory(
poetry = Factory().create_poetry(cwd=path)
assert poetry.package.name == package_name

if include_from:
if is_flat:
package_include = {"include": package_path.parts[0]}
else:
package_include = {
"include": package_path.relative_to(include_from).parts[0],
"from": include_from,
"include": package_path.relative_to("src").parts[0],
"from": "src",
}
else:
package_include = {"include": package_path.parts[0]}

name = poetry.package.name
packages = poetry.local_config.get("packages")
Expand All @@ -72,81 +72,87 @@ def verify_project_directory(
@pytest.mark.parametrize(
"options,directory,package_name,package_path,include_from",
[
([], "package", "package", "package", None),
(["--src"], "package", "package", "src/package", "src"),
(["--flat"], "package", "package", "package", None),
([], "package", "package", "src/package", "src"),
(
["--name namespace.package"],
["--flat", "--name namespace.package"],
"namespace-package",
"namespace-package",
"namespace/package",
None,
),
(
["--src", "--name namespace.package"],
["--name namespace.package"],
"namespace-package",
"namespace-package",
"src/namespace/package",
"src",
),
(
["--name namespace.package_a"],
["--flat", "--name namespace.package_a"],
"namespace-package_a",
"namespace-package-a",
"namespace/package_a",
None,
),
(
["--src", "--name namespace.package_a"],
["--name namespace.package_a"],
"namespace-package_a",
"namespace-package-a",
"src/namespace/package_a",
"src",
),
(
["--name namespace_package"],
["--flat", "--name namespace_package"],
"namespace-package",
"namespace-package",
"namespace_package",
None,
),
(
["--name namespace_package", "--src"],
["--name namespace_package"],
"namespace-package",
"namespace-package",
"src/namespace_package",
"src",
),
(
["--name namespace.package"],
["--flat", "--name namespace.package"],
"package",
"namespace-package",
"namespace/package",
None,
),
(
["--name namespace.package", "--src"],
["--name namespace.package"],
"package",
"namespace-package",
"src/namespace/package",
"src",
),
(
["--name namespace.package"],
["--name namespace.package", "--flat"],
"package",
"namespace-package",
"namespace/package",
None,
),
(
["--name namespace.package", "--src"],
["--name namespace.package"],
"package",
"namespace-package",
"src/namespace/package",
"src",
),
([], "namespace_package", "namespace-package", "namespace_package", None),
(
["--src", "--name namespace_package"],
["--flat"],
"namespace_package",
"namespace-package",
"namespace_package",
None,
),
(
["--name namespace_package"],
"namespace_package",
"namespace-package",
"src/namespace_package",
Expand All @@ -166,7 +172,7 @@ def test_command_new(
path = tmp_path / directory
options.append(str(path))
tester.execute(" ".join(options))
verify_project_directory(path, package_name, package_path, include_from)
verify_project_directory(path, package_name, package_path, "--flat" in options)


@pytest.mark.parametrize(("fmt",), [(None,), ("md",), ("rst",), ("adoc",), ("creole",)])
Expand All @@ -182,7 +188,7 @@ def test_command_new_with_readme(

tester.execute(" ".join(options))

poetry = verify_project_directory(path, package, package, None)
poetry = verify_project_directory(path, package, Path("src") / package)
project_section = poetry.pyproject.data["project"]
assert isinstance(project_section, dict)
assert project_section["readme"] == f"README.{fmt or 'md'}"
Expand Down Expand Up @@ -222,9 +228,9 @@ def test_respect_use_poetry_python_on_new(


def test_basic_interactive_new(
tester: CommandTester, tmp_path: Path, init_basic_inputs: str, init_basic_toml: str
tester: CommandTester, tmp_path: Path, init_basic_inputs: str, new_basic_toml: str
) -> None:
path = tmp_path / "somepackage"
tester.execute(f"--interactive {path.as_posix()}", inputs=init_basic_inputs)
verify_project_directory(path, "my-package", "my_package", None)
assert init_basic_toml in tester.io.fetch_output()
verify_project_directory(path, "my-package", "src/my_package")
assert new_basic_toml in tester.io.fetch_output()
Comment on lines +235 to +236
Copy link

Choose a reason for hiding this comment

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

suggestion (testing): Test interactive mode with --flat.

Add a new test case for interactive mode with the --flat option to ensure that the flat layout is correctly created when specified interactively.