From 8e65cbc52906e2010c69f6bfe0506264827686ec Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Sat, 1 Feb 2025 12:08:17 +0100 Subject: [PATCH] feat(cli): default new command to src layout Resolves: #9390 --- docs/cli.md | 27 ++++++++------ src/poetry/console/commands/new.py | 15 ++++++-- tests/console/commands/conftest.py | 21 +++++++++++ tests/console/commands/test_new.py | 56 +++++++++++++++++------------- 4 files changed, 81 insertions(+), 38 deletions(-) diff --git a/docs/cli.md b/docs/cli.md index 93ab69902ca..ced7a3ca490 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -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 @@ -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 ``` @@ -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: @@ -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: @@ -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. diff --git a/src/poetry/console/commands/new.py b/src/poetry/console/commands/new.py index d1083216329..4fdd1a45ced 100644 --- a/src/poetry/console/commands/new.py +++ b/src/poetry/console/commands/new.py @@ -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. " + "Deprecated: This is the default option now.", + ), + option("flat", None, "Use the flat layout for the project."), option( "readme", None, @@ -72,9 +78,14 @@ def handle(self) -> int: f"Destination {path} exists and is not empty" ) + if self.option("src"): + self.line_error( + "The --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", ) diff --git a/tests/console/commands/conftest.py b/tests/console/commands/conftest.py index b96f5a07e8b..cce363ac293 100644 --- a/tests/console/commands/conftest.py +++ b/tests/console/commands/conftest.py @@ -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 = "you@example.com"} +] +license = {text = "MIT"} +readme = "README.md" +requires-python = ">=3.6" +dependencies = [ +] + +[tool.poetry] +packages = [{include = "my_package", from = "src"}] +""" diff --git a/tests/console/commands/test_new.py b/tests/console/commands/test_new.py index 0d813d4da2e..6411d0e0c22 100644 --- a/tests/console/commands/test_new.py +++ b/tests/console/commands/test_new.py @@ -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() @@ -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") @@ -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", @@ -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",)]) @@ -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'}" @@ -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()