Skip to content

Commit

Permalink
feat(cli): default new command to src layout
Browse files Browse the repository at this point in the history
Resolves: #9390
  • Loading branch information
abn committed Feb 2, 2025
1 parent 1b54590 commit 8e65cbc
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 38 deletions.
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()

0 comments on commit 8e65cbc

Please sign in to comment.