Skip to content

Commit 7e652a7

Browse files
committed
feat(cli): default new command to src layout
Resolves: #9390
1 parent 9af386a commit 7e652a7

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
@@ -589,8 +589,8 @@ poetry lock
589589

590590
## new
591591

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

595595
```bash
596596
poetry new my-package
@@ -602,8 +602,9 @@ will create a folder as follows:
602602
my-package
603603
├── pyproject.toml
604604
├── README.md
605-
├── my_package
606-
│ └── __init__.py
605+
├── src
606+
│ └── my_package
607+
│ └── __init__.py
607608
└── tests
608609
└── __init__.py
609610
```
@@ -615,10 +616,10 @@ the `--name` option:
615616
poetry new my-folder --name my-package
616617
```
617618

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

620621
```bash
621-
poetry new --src my-package
622+
poetry new --flat my-package
622623
```
623624

624625
That will create a folder structure as follows:
@@ -627,18 +628,22 @@ That will create a folder structure as follows:
627628
my-package
628629
├── pyproject.toml
629630
├── README.md
630-
├── src
631-
│ └── my_package
632-
│ └── __init__.py
631+
├── my_package
632+
│ └── __init__.py
633633
└── tests
634634
└── __init__.py
635635
```
636636

637+
{{% note %}}
638+
For an overview of the differences between `flat` and `src` layouts, please see
639+
[here](https://packaging.python.org/en/latest/discussions/src-layout-vs-flat-layout/).
640+
{{% /note %}}
641+
637642
The `--name` option is smart enough to detect namespace packages and create
638643
the required structure for you.
639644

640645
```bash
641-
poetry new --src --name my.package my-package
646+
poetry new --name my.package my-package
642647
```
643648

644649
will create the following structure:
@@ -659,7 +664,7 @@ my-package
659664

660665
* `--interactive (-i)`: Allow interactive specification of project configuration.
661666
* `--name`: Set the resulting package name.
662-
* `--src`: Use the src layout for the project.
667+
* `--flat`: Use the flat layout for the project.
663668
* `--readme`: Specify the readme file extension. Default is `md`. If you intend to publish to PyPI
664669
keep the [recommendations for a PyPI-friendly README](https://packaging.python.org/en/latest/guides/making-a-pypi-friendly-readme/)
665670
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'}"
@@ -237,9 +243,9 @@ def mock_check_output(cmd: str, *_: Any, **__: Any) -> str:
237243

238244

239245
def test_basic_interactive_new(
240-
tester: CommandTester, tmp_path: Path, init_basic_inputs: str, init_basic_toml: str
246+
tester: CommandTester, tmp_path: Path, init_basic_inputs: str, new_basic_toml: str
241247
) -> None:
242248
path = tmp_path / "somepackage"
243249
tester.execute(f"--interactive {path.as_posix()}", inputs=init_basic_inputs)
244-
verify_project_directory(path, "my-package", "my_package", None)
245-
assert init_basic_toml in tester.io.fetch_output()
250+
verify_project_directory(path, "my-package", "src/my_package")
251+
assert new_basic_toml in tester.io.fetch_output()

0 commit comments

Comments
 (0)