Repository: python-poetry/poetry

Issue:
default pyproject.toml from poetry init -n improperly handles src layout; reports installation success even though it cannot actually be imported
<!-- All the below information must be provided for others to understand and help with your issue. -->
- **Poetry version**: 1.4.0
- **Python version**:
```
Poetry
Version: 1.4.0
Python:  3.10.9

Virtualenv
Python:         3.9.11
```
- **OS version and name**: 5.13.19-2-MANJARO
- **pyproject.toml** (see below)

<!-- All the below steps should be completed before submitting your issue. Checked checkbox should look like this: [x] -->
- [x] I am on the [latest](https://github.com/python-poetry/poetry/releases/latest) stable Poetry version, installed using a recommended method.
- [x] I have searched the [issues](https://github.com/python-poetry/poetry/issues) of this repo and believe that this is not a duplicate.
- [x] I have consulted the [FAQ](https://python-poetry.org/docs/faq/) and [blog](https://python-poetry.org/blog/) for any relevant entries or release notes.
- [x] If an exception occurs when executing a command, I executed it again in debug mode (`-vvv` option) and have included the output below.

## Issue

Trying to make simple hello-word package in src layout. The default pyproject.toml created with `poetry init -n` does not let me install it properly.

To begin with, my file structure is like this:

```
$ tree
.
├── README.md
└── src
    └── fancy_project
        ├── __init__.py
        └── something.py

3 directories, 3 files
```

The `__init__.py` file is blank. The `something.py` file contains this:

```python
def hello():
    print("this is a fancy project!")
```

When I run `poetry init -n`, it generates the following (sanitized) `pyproject.toml`.

```toml
[tool.poetry]
name = "fancy-project"
version = "0.1.0"
description = ""
authors = ["aaaa <aaaa@bbbb.com>"]
readme = "README.md"
packages = [{include = "fancy_project"}]

[tool.poetry.dependencies]
python = "^3.9"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
```

When I run `poetry install`, I get this error:

```
$ poetry install
Updating dependencies
Resolving dependencies... (0.1s)

Writing lock file

/tmp/fancy-project/fancy_project does not contain any element
```

When I amend `pyproject.toml` to change the `include = "fancy_project"` to `include = "src"`, the `poetry install` command seems to succeed:

```
$ poetry install
Installing dependencies from lock file

Installing the current project: fancy-project (0.1.0)
```

However, the package is not actually importable:

```bash
$ poetry run python -c 'from fancy_project.something import hello; hello()'
Traceback (most recent call last):
  File "<string>", line 1, in <module>
ModuleNotFoundError: No module named 'fancy_project'
```

I tried changing the include line to say `include = "src/fancy_project"`. It similarly reports a successful install, but the import fails just the same.

I tried removing the `packages` line from `pyproject.toml` entirely. This time it works:

```
$ poetry install
Installing dependencies from lock file

Installing the current project: fancy-project (0.1.0)
$ poetry run python -c 'from fancy_project.something import hello; hello()'
this is a fancy project!
```

But I don't understand why. I don't remember ever having to muck around with the `packages` line; everything "just worked" the last time I tried this with a src layout.

I believe there is at least one bug here:

- the default `poetry init -n` should make a pyproject.toml that can work with src layout
- `poetry install` should not say that an installation succeeded when it actually didn't
- the docs for `packages` don't clear up any of this confusion
- the "does not contain any element" error message does not make any sense

(PS: I ran `rm -rf .venv` before every `poetry install` command, to start from a clean slate each time)

EDIT:

bash script to precisely reproduce the problem:

```bash
#! /usr/bin/bash
set -euo pipefail

cd /tmp
rm -rf fancy-project
mkdir fancy-project
cd fancy-project

touch README.md
mkdir -p src/fancy_project
touch src/fancy_project/__init__.py
echo 'def hello():' >> src/fancy_project/something.py
echo '    print("this is a fancy project!")' >> src/fancy_project/something.py

poetry init -n

poetry install || true

sed -i 's/include = "fancy_project"/include = "src"/g' pyproject.toml
rm -rf .venv
poetry install
poetry run python -c 'from fancy_project.something import hello; hello()' || true

sed -i 's/include = "src"/include = "src\/fancy_project"/g' pyproject.toml
rm -rf .venv
poetry install
poetry run python -c 'from fancy_project.something import hello; hello()' || true

sed -i 's/^packages =.*//g' pyproject.toml
rm -rf .venv
poetry install
poetry run python -c 'from fancy_project.something import hello; hello()'
```


Discussion:
```toml
packages = [
    { include = "poetry", from = "src" }
]
```
per poetry's own usage

It's impossible for `poetry init -n` to produce a pyproject.toml that works for both src and not-src layouts: so it has to choose one.  I don't see that as a bug.

I expect docs improvements explaining this would be welcome, please contribute!
Okay, was that changed recently? I must have done this sequence of commands dozens of times and I never ran into this problem before.

Also I think the "does not contain any element" error message would be clearer as "directory does not exist"? since it's looking for a nonexistent `/tmp/fancy_project/fancy_project`.
not so far as I know
Okay I'm not crazy, I tested it and the following works on Poetry version 1.1.15, like how I remember:

```bash
#! /usr/bin/bash
set -euo pipefail

cd /tmp
rm -rf fancy-project
mkdir fancy-project
cd fancy-project

touch README.md
mkdir -p src/fancy_project
touch src/fancy_project/__init__.py
echo 'def hello():' >> src/fancy_project/something.py
echo '    print("this is a fancy project!")' >> src/fancy_project/something.py

poetry init -n

poetry install
poetry run python -c 'from fancy_project.something import hello; hello()'
```

The culprit seems to be that 1.1.15 doesn't populate the `packages` line in `pyproject.toml` when you run `poetry init -n`. It just makes this:

```toml
[tool.poetry]
name = "fancy-project"
version = "0.1.0"
description = ""
authors = ["aaaa <aaaa@bbbb.com>"]

[tool.poetry.dependencies]
python = "^3.10"

[tool.poetry.dev-dependencies]

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
```

I looked in the release notes but I can't figure out why this was changed. The default config used to be able to figure out src layouts but now it can't.
it's because you're using a name with a dash in it.  You can probably make it skip the explicit package by making [this comparison](https://github.com/python-poetry/poetry/blob/f27308face9b09da8b5bbd59175b1b46392eb8d5/src/poetry/layouts/layout.py#L101) be against `canonicalize_name(include)`

edit: or possibly it would be better the other way round, and the comparison should be against `module_name(self._project)`.  

again, contributions welcome
Yes you're right, it works when I change everything to `fancyproject`.

I'll see if I can figure out a PR for it.
Thank you for this issue.

I've stumbled across the same error, and sure it is confusing!

In my case, my `some-project` was initialised from a "misnamed" directory `hacking-some-project` with the latter recorded in `pyproject.toml` and of course I've missed that.
you - and all readers - are invited to submit an MR with a fix as outlined at https://github.com/python-poetry/poetry/issues/7609#issuecomment-1455175021

Resolving patch:
diff --git a/src/poetry/layouts/layout.py b/src/poetry/layouts/layout.py
--- a/src/poetry/layouts/layout.py
+++ b/src/poetry/layouts/layout.py
@@ -96,7 +96,7 @@ def get_package_include(self) -> InlineTable | None:
         if self.basedir != Path():
             package.append("from", self.basedir.as_posix())
         else:
-            if include == self._project:
+            if module_name(self._project) == include:
                 # package include and package name are the same,
                 # packages table is redundant here.
                 return None


Test patch:
diff --git a/tests/console/commands/test_init.py b/tests/console/commands/test_init.py
--- a/tests/console/commands/test_init.py
+++ b/tests/console/commands/test_init.py
@@ -13,6 +13,7 @@
 
 from cleo.testers.command_tester import CommandTester
 from packaging.utils import canonicalize_name
+from poetry.core.utils.helpers import module_name
 
 from poetry.console.application import Application
 from poetry.console.commands.init import InitCommand
@@ -86,7 +87,6 @@ def init_basic_toml() -> str:
 authors = ["Your Name <you@example.com>"]
 license = "MIT"
 readme = "README.md"
-packages = [{include = "my_package"}]
 
 [tool.poetry.dependencies]
 python = "~2.7 || ^3.6"
@@ -169,7 +169,6 @@ def test_interactive_with_dependencies(
 authors = ["Your Name <you@example.com>"]
 license = "MIT"
 readme = "README.md"
-packages = [{include = "my_package"}]
 
 [tool.poetry.dependencies]
 python = "~2.7 || ^3.6"
@@ -218,7 +217,6 @@ def test_interactive_with_dependencies_and_no_selection(
 authors = ["Your Name <you@example.com>"]
 license = "MIT"
 readme = "README.md"
-packages = [{include = "my_package"}]
 
 [tool.poetry.dependencies]
 python = "~2.7 || ^3.6"
@@ -249,7 +247,6 @@ def test_empty_license(tester: CommandTester) -> None:
 description = ""
 authors = ["Your Name <you@example.com>"]
 readme = "README.md"
-packages = [{{include = "my_package"}}]
 
 [tool.poetry.dependencies]
 python = "^{python}"
@@ -290,7 +287,6 @@ def test_interactive_with_git_dependencies(
 authors = ["Your Name <you@example.com>"]
 license = "MIT"
 readme = "README.md"
-packages = [{include = "my_package"}]
 
 [tool.poetry.dependencies]
 python = "~2.7 || ^3.6"
@@ -383,7 +379,6 @@ def test_interactive_with_git_dependencies_with_reference(
 authors = ["Your Name <you@example.com>"]
 license = "MIT"
 readme = "README.md"
-packages = [{include = "my_package"}]
 
 [tool.poetry.dependencies]
 python = "~2.7 || ^3.6"
@@ -429,7 +424,6 @@ def test_interactive_with_git_dependencies_and_other_name(
 authors = ["Your Name <you@example.com>"]
 license = "MIT"
 readme = "README.md"
-packages = [{include = "my_package"}]
 
 [tool.poetry.dependencies]
 python = "~2.7 || ^3.6"
@@ -481,7 +475,6 @@ def test_interactive_with_directory_dependency(
 authors = ["Your Name <you@example.com>"]
 license = "MIT"
 readme = "README.md"
-packages = [{include = "my_package"}]
 
 [tool.poetry.dependencies]
 python = "~2.7 || ^3.6"
@@ -532,7 +525,6 @@ def test_interactive_with_directory_dependency_and_other_name(
 authors = ["Your Name <you@example.com>"]
 license = "MIT"
 readme = "README.md"
-packages = [{include = "my_package"}]
 
 [tool.poetry.dependencies]
 python = "~2.7 || ^3.6"
@@ -584,7 +576,6 @@ def test_interactive_with_file_dependency(
 authors = ["Your Name <you@example.com>"]
 license = "MIT"
 readme = "README.md"
-packages = [{include = "my_package"}]
 
 [tool.poetry.dependencies]
 python = "~2.7 || ^3.6"
@@ -629,7 +620,6 @@ def test_interactive_with_wrong_dependency_inputs(
 authors = ["Your Name <you@example.com>"]
 license = "MIT"
 readme = "README.md"
-packages = [{include = "my_package"}]
 
 [tool.poetry.dependencies]
 python = "^3.8"
@@ -664,7 +654,6 @@ def test_python_option(tester: CommandTester) -> None:
 authors = ["Your Name <you@example.com>"]
 license = "MIT"
 readme = "README.md"
-packages = [{include = "my_package"}]
 
 [tool.poetry.dependencies]
 python = "~2.7 || ^3.6"
@@ -697,7 +686,6 @@ def test_predefined_dependency(tester: CommandTester, repo: TestRepository) -> N
 authors = ["Your Name <you@example.com>"]
 license = "MIT"
 readme = "README.md"
-packages = [{include = "my_package"}]
 
 [tool.poetry.dependencies]
 python = "~2.7 || ^3.6"
@@ -739,7 +727,6 @@ def test_predefined_and_interactive_dependencies(
 authors = ["Your Name <you@example.com>"]
 license = "MIT"
 readme = "README.md"
-packages = [{include = "my_package"}]
 
 [tool.poetry.dependencies]
 python = "~2.7 || ^3.6"
@@ -775,7 +762,6 @@ def test_predefined_dev_dependency(tester: CommandTester, repo: TestRepository)
 authors = ["Your Name <you@example.com>"]
 license = "MIT"
 readme = "README.md"
-packages = [{include = "my_package"}]
 
 [tool.poetry.dependencies]
 python = "~2.7 || ^3.6"
@@ -819,7 +805,6 @@ def test_predefined_and_interactive_dev_dependencies(
 authors = ["Your Name <you@example.com>"]
 license = "MIT"
 readme = "README.md"
-packages = [{include = "my_package"}]
 
 [tool.poetry.dependencies]
 python = "~2.7 || ^3.6"
@@ -866,7 +851,6 @@ def test_predefined_all_options(tester: CommandTester, repo: TestRepository) ->
 authors = ["Foo Bar <foo@example.com>"]
 license = "MIT"
 readme = "README.md"
-packages = [{include = "my_package"}]
 
 [tool.poetry.dependencies]
 python = "^3.8"
@@ -959,7 +943,6 @@ def test_init_non_interactive_existing_pyproject_add_dependency(
 description = ""
 authors = ["Your Name <you@example.com>"]
 readme = "README.md"
-packages = [{include = "my_package"}]
 
 [tool.poetry.dependencies]
 python = "^3.6"
@@ -1043,7 +1026,9 @@ def test_package_include(
         ),
     )
 
-    packages = "" if include is None else f'packages = [{{include = "{include}"}}]\n'
+    packages = ""
+    if include and module_name(package_name) != include:
+        packages = f'packages = [{{include = "{include}"}}]\n'
 
     expected = (
         f"[tool.poetry]\n"
diff --git a/tests/console/commands/test_new.py b/tests/console/commands/test_new.py
--- a/tests/console/commands/test_new.py
+++ b/tests/console/commands/test_new.py
@@ -9,6 +9,8 @@
 
 import pytest
 
+from poetry.core.utils.helpers import module_name
+
 from poetry.factory import Factory
 
 
@@ -55,10 +57,11 @@ def verify_project_directory(
     else:
         package_include = {"include": package_path.parts[0]}
 
+    name = poetry.local_config.get("name", "")
     packages = poetry.local_config.get("packages")
 
     if not packages:
-        assert poetry.local_config.get("name") == package_include.get("include")
+        assert module_name(name) == package_include.get("include")
     else:
         assert len(packages) == 1
         assert packages[0] == package_include