Skip to content

Commit 8e65cbc

Browse files
committed
feat(cli): default new command to src layout
Resolves: #9390
1 parent 1b54590 commit 8e65cbc

File tree

4 files changed

+81
-38
lines changed

4 files changed

+81
-38
lines changed

docs/cli.md

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -608,8 +608,8 @@ poetry lock
608608

609609
## new
610610

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

614614
```bash
615615
poetry new my-package
@@ -621,8 +621,9 @@ will create a folder as follows:
621621
my-package
622622
├── pyproject.toml
623623
├── README.md
624-
├── my_package
625-
│ └── __init__.py
624+
├── src
625+
│ └── my_package
626+
│ └── __init__.py
626627
└── tests
627628
└── __init__.py
628629
```
@@ -634,10 +635,10 @@ the `--name` option:
634635
poetry new my-folder --name my-package
635636
```
636637

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

639640
```bash
640-
poetry new --src my-package
641+
poetry new --flat my-package
641642
```
642643

643644
That will create a folder structure as follows:
@@ -646,18 +647,22 @@ That will create a folder structure as follows:
646647
my-package
647648
├── pyproject.toml
648649
├── README.md
649-
├── src
650-
│ └── my_package
651-
│ └── __init__.py
650+
├── my_package
651+
│ └── __init__.py
652652
└── tests
653653
└── __init__.py
654654
```
655655

656+
{{% note %}}
657+
For an overview of the differences between `flat` and `src` layouts, please see
658+
[here](https://packaging.python.org/en/latest/discussions/src-layout-vs-flat-layout/).
659+
{{% /note %}}
660+
656661
The `--name` option is smart enough to detect namespace packages and create
657662
the required structure for you.
658663

659664
```bash
660-
poetry new --src --name my.package my-package
665+
poetry new --name my.package my-package
661666
```
662667

663668
will create the following structure:
@@ -678,7 +683,7 @@ my-package
678683

679684
* `--interactive (-i)`: Allow interactive specification of project configuration.
680685
* `--name`: Set the resulting package name.
681-
* `--src`: Use the src layout for the project.
686+
* `--flat`: Use the flat layout for the project.
682687
* `--readme`: Specify the readme file extension. Default is `md`. If you intend to publish to PyPI
683688
keep the [recommendations for a PyPI-friendly README](https://packaging.python.org/en/latest/guides/making-a-pypi-friendly-readme/)
684689
in mind.

src/poetry/console/commands/new.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,13 @@ class NewCommand(InitCommand):
2929
flag=True,
3030
),
3131
option("name", None, "Set the resulting package name.", flag=False),
32-
option("src", None, "Use the src layout for the project."),
32+
option(
33+
"src",
34+
None,
35+
"Use the src layout for the project. "
36+
"<warning>Deprecated</>: This is the default option now.",
37+
),
38+
option("flat", None, "Use the flat layout for the project."),
3339
option(
3440
"readme",
3541
None,
@@ -72,9 +78,14 @@ def handle(self) -> int:
7278
f"Destination <fg=yellow>{path}</> exists and is not empty"
7379
)
7480

81+
if self.option("src"):
82+
self.line_error(
83+
"The <c1>--src</> option is now the default and will be removed in a future version."
84+
)
85+
7586
return self._init_pyproject(
7687
project_path=path,
7788
allow_interactive=self.option("interactive"),
78-
layout_name="src" if self.option("src") else "standard",
89+
layout_name="standard" if self.option("flat") else "src",
7990
readme_format=self.option("readme") or "md",
8091
)

tests/console/commands/conftest.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,24 @@ def init_basic_toml() -> str:
3434
readme = "README.md"
3535
requires-python = ">=3.6"
3636
"""
37+
38+
39+
@pytest.fixture()
40+
def new_basic_toml() -> str:
41+
return """\
42+
[project]
43+
name = "my-package"
44+
version = "1.2.3"
45+
description = "This is a description"
46+
authors = [
47+
{name = "Your Name",email = "[email protected]"}
48+
]
49+
license = {text = "MIT"}
50+
readme = "README.md"
51+
requires-python = ">=3.6"
52+
dependencies = [
53+
]
54+
55+
[tool.poetry]
56+
packages = [{include = "my_package", from = "src"}]
57+
"""

tests/console/commands/test_new.py

Lines changed: 31 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def verify_project_directory(
3232
path: Path,
3333
package_name: str,
3434
package_path: str | Path,
35-
include_from: str | None = None,
35+
is_flat: bool = False,
3636
) -> Poetry:
3737
package_path = Path(package_path)
3838
assert path.is_dir()
@@ -49,13 +49,13 @@ def verify_project_directory(
4949
poetry = Factory().create_poetry(cwd=path)
5050
assert poetry.package.name == package_name
5151

52-
if include_from:
52+
if is_flat:
53+
package_include = {"include": package_path.parts[0]}
54+
else:
5355
package_include = {
54-
"include": package_path.relative_to(include_from).parts[0],
55-
"from": include_from,
56+
"include": package_path.relative_to("src").parts[0],
57+
"from": "src",
5658
}
57-
else:
58-
package_include = {"include": package_path.parts[0]}
5959

6060
name = poetry.package.name
6161
packages = poetry.local_config.get("packages")
@@ -72,81 +72,87 @@ def verify_project_directory(
7272
@pytest.mark.parametrize(
7373
"options,directory,package_name,package_path,include_from",
7474
[
75-
([], "package", "package", "package", None),
76-
(["--src"], "package", "package", "src/package", "src"),
75+
(["--flat"], "package", "package", "package", None),
76+
([], "package", "package", "src/package", "src"),
7777
(
78-
["--name namespace.package"],
78+
["--flat", "--name namespace.package"],
7979
"namespace-package",
8080
"namespace-package",
8181
"namespace/package",
8282
None,
8383
),
8484
(
85-
["--src", "--name namespace.package"],
85+
["--name namespace.package"],
8686
"namespace-package",
8787
"namespace-package",
8888
"src/namespace/package",
8989
"src",
9090
),
9191
(
92-
["--name namespace.package_a"],
92+
["--flat", "--name namespace.package_a"],
9393
"namespace-package_a",
9494
"namespace-package-a",
9595
"namespace/package_a",
9696
None,
9797
),
9898
(
99-
["--src", "--name namespace.package_a"],
99+
["--name namespace.package_a"],
100100
"namespace-package_a",
101101
"namespace-package-a",
102102
"src/namespace/package_a",
103103
"src",
104104
),
105105
(
106-
["--name namespace_package"],
106+
["--flat", "--name namespace_package"],
107107
"namespace-package",
108108
"namespace-package",
109109
"namespace_package",
110110
None,
111111
),
112112
(
113-
["--name namespace_package", "--src"],
113+
["--name namespace_package"],
114114
"namespace-package",
115115
"namespace-package",
116116
"src/namespace_package",
117117
"src",
118118
),
119119
(
120-
["--name namespace.package"],
120+
["--flat", "--name namespace.package"],
121121
"package",
122122
"namespace-package",
123123
"namespace/package",
124124
None,
125125
),
126126
(
127-
["--name namespace.package", "--src"],
127+
["--name namespace.package"],
128128
"package",
129129
"namespace-package",
130130
"src/namespace/package",
131131
"src",
132132
),
133133
(
134-
["--name namespace.package"],
134+
["--name namespace.package", "--flat"],
135135
"package",
136136
"namespace-package",
137137
"namespace/package",
138138
None,
139139
),
140140
(
141-
["--name namespace.package", "--src"],
141+
["--name namespace.package"],
142142
"package",
143143
"namespace-package",
144144
"src/namespace/package",
145145
"src",
146146
),
147-
([], "namespace_package", "namespace-package", "namespace_package", None),
148147
(
149-
["--src", "--name namespace_package"],
148+
["--flat"],
149+
"namespace_package",
150+
"namespace-package",
151+
"namespace_package",
152+
None,
153+
),
154+
(
155+
["--name namespace_package"],
150156
"namespace_package",
151157
"namespace-package",
152158
"src/namespace_package",
@@ -166,7 +172,7 @@ def test_command_new(
166172
path = tmp_path / directory
167173
options.append(str(path))
168174
tester.execute(" ".join(options))
169-
verify_project_directory(path, package_name, package_path, include_from)
175+
verify_project_directory(path, package_name, package_path, "--flat" in options)
170176

171177

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

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

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

223229

224230
def test_basic_interactive_new(
225-
tester: CommandTester, tmp_path: Path, init_basic_inputs: str, init_basic_toml: str
231+
tester: CommandTester, tmp_path: Path, init_basic_inputs: str, new_basic_toml: str
226232
) -> None:
227233
path = tmp_path / "somepackage"
228234
tester.execute(f"--interactive {path.as_posix()}", inputs=init_basic_inputs)
229-
verify_project_directory(path, "my-package", "my_package", None)
230-
assert init_basic_toml in tester.io.fetch_output()
235+
verify_project_directory(path, "my-package", "src/my_package")
236+
assert new_basic_toml in tester.io.fetch_output()

0 commit comments

Comments
 (0)