Repository: python-poetry/poetry

Issue:
poetry init - Pressing <enter> at "Enter package # to add ..." causes AttributeError: 'NoneType' object has no attribute 'replace'
<!-- Checked checkbox should look like this: [x] -->
- [x] I am on the [latest](https://github.com/python-poetry/poetry/releases/latest) Poetry version.
- [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] If an exception occurs when executing a command, I executed it again in debug mode (`-vvv` option).  **(it made no difference in the output)**

<!--
  Once those are done, if you're able to fill in the following list with your information,
  it'd be very helpful to whoever handles the issue.
-->

- **OS version and name**:  ubuntu 18.04
- **Poetry version**: 1.0.5
- **Link of a [Gist](https://gist.github.com/) with the contents of your pyproject.toml file**: N/A

## Issue

```console
$ poetry init

This command will guide you through creating your pyproject.toml config.

Package name [y]:  
Version [0.1.0]:  
Description []:  
Author [Anthony Sottile <asottile@umich.edu>, n to skip]:  
License []:  
Compatible Python versions [^3.6]:  

Would you like to define your main dependencies interactively? (yes/no) [yes] 
You can specify a package in the following forms:
  - A single name (requests)
  - A name and a constraint (requests ^2.23.0)
  - A git url (git+https://github.com/python-poetry/poetry.git)
  - A git url with a revision (git+https://github.com/python-poetry/poetry.git#develop)
  - A file path (../my-package/my-package.whl)
  - A directory (../my-package/)
  - An url (https://example.com/packages/my-package-0.1.0.tar.gz)

Search for package to add (or leave blank to continue): 

Would you like to define your development dependencies interactively? (yes/no) [yes] 
Search for package to add (or leave blank to continue): astpretty
Found 3 packages matching astpretty

Enter package # to add, or the complete package name if it is not listed: 
 [0] astpretty
 [1] asteria
 [2] moshmosh-base
 > 
'NoneType' object has no attribute 'replace'

Enter package # to add, or the complete package name if it is not listed: 
 [0] astpretty
 [1] asteria
 [2] moshmosh-base
 > 
```

very minor, but I expect a human-readable error message instead, or to default to the first one (it was an exact name match after all!)



Discussion:
Additional occurrence of the issue, with a stack trace (via `poetry init -vvv`):

```
Would you like to define your development dependencies interactively? (yes/no) [yes]
You can specify a package in the following forms:
  - A single name (requests)
  - A name and a constraint (requests@^2.23.0)
  - A git url (git+https://github.com/python-poetry/poetry.git)
  - A git url with a revision (git+https://github.com/python-poetry/poetry.git#develop)
  - A file path (../my-package/my-package.whl)
  - A directory (../my-package/)
  - A url (https://example.com/packages/my-package-0.1.0.tar.gz)

Search for package to add (or leave blank to continue): IPython
Found 20 packages matching IPython

Enter package # to add, or the complete package name if it is not listed:
 [0] twisted-ipython
 [1] ipython-elasticsearch
 [2] ipython-bg
 [3] mypyc-ipython
 [4] ipython-agnoster
 [5] ipython-futhark
 [6] ipython-sparksql
 [7] progressbar-ipython
 [8] ipython-autotime
 [9] ipython-gremlin
 > IPython
Value "IPython" is invalid

Enter package # to add, or the complete package name if it is not listed:
 [0] twisted-ipython
 [1] ipython-elasticsearch
 [2] ipython-bg
 [3] mypyc-ipython
 [4] ipython-agnoster
 [5] ipython-futhark
 [6] ipython-sparksql
 [7] progressbar-ipython
 [8] ipython-autotime
 [9] ipython-gremlin
 >
'NoneType' object has no attribute 'replace'

Enter package # to add, or the complete package name if it is not listed:
 [0] twisted-ipython
 [1] ipython-elasticsearch
 [2] ipython-bg
 [3] mypyc-ipython
 [4] ipython-agnoster
 [5] ipython-futhark
 [6] ipython-sparksql
 [7] progressbar-ipython
 [8] ipython-autotime
 [9] ipython-gremlin
 >

  Stack trace:

  11  ~/venvs/default/lib/python3.9/site-packages/clikit/console_application.py:131 in run
       129│             parsed_args = resolved_command.args
       130│
     → 131│             status_code = command.handle(parsed_args, io)
       132│         except KeyboardInterrupt:
       133│             status_code = 1

  10  ~/venvs/default/lib/python3.9/site-packages/clikit/api/command/command.py:120 in handle
       118│     def handle(self, args, io):  # type: (Args, IO) -> int
       119│         try:
     → 120│             status_code = self._do_handle(args, io)
       121│         except KeyboardInterrupt:
       122│             if io.is_debug():

   9  ~/venvs/default/lib/python3.9/site-packages/clikit/api/command/command.py:171 in _do_handle
       169│         handler_method = self._config.handler_method
       170│
     → 171│         return getattr(handler, handler_method)(args, io, self)
       172│
       173│     def __repr__(self):  # type: () -> str

   8  ~/venvs/default/lib/python3.9/site-packages/cleo/commands/command.py:92 in wrap_handle
        90│         self._command = command
        91│
     →  92│         return self.handle()
        93│
        94│     def handle(self):  # type: () -> Optional[int]

   7  ~/venvs/default/lib/python3.9/site-packages/poetry/console/commands/init.py:198 in handle
       196│
       197│             dev_requirements.update(
     → 198│                 self._format_requirements(self._determine_requirements([]))
       199│             )
       200│             self.line("")

   6  ~/venvs/default/lib/python3.9/site-packages/poetry/console/commands/init.py:279 in _determine_requirements
       277│                     )
       278│
     → 279│                     package = self.choice(
       280│                         "\nEnter package # to add, or the complete package name if it is not listed",
       281│                         choices,

   5  ~/venvs/default/lib/python3.9/site-packages/cleo/commands/command.py:172 in choice
       170│         question.set_multi_select(multiple)
       171│
     → 172│         return self._io.ask_question(question)
       173│
       174│     def create_question(self, question, type=None, **kwargs):

   4  ~/venvs/default/lib/python3.9/site-packages/cleo/io/io_mixin.py:50 in ask_question
        48│         Asks a question.
        49│         """
     →  50│         answer = question.ask(self)
        51│
        52│         return answer

   3  ~/venvs/default/lib/python3.9/site-packages/clikit/ui/components/question.py:80 in ask
        78│         interviewer = lambda: self._do_ask(io)
        79│
     →  80│         return self._validate_attempts(interviewer, io)
        81│
        82│     def _do_ask(self, io):  # type: (IO) -> str

   2  ~/venvs/default/lib/python3.9/site-packages/clikit/ui/components/question.py:254 in _validate_attempts
       252│                 attempts -= 1
       253│
     → 254│         raise error
       255│
       256│     def _read_from_input(self, io):

   1  ~/venvs/default/lib/python3.9/site-packages/clikit/ui/components/question.py:247 in _validate_attempts
       245│
       246│             try:
     → 247│                 return self._validator(interviewer())
       248│             except Exception as e:
       249│                 error = e

  AttributeError

  'NoneType' object has no attribute 'replace'

  at ~/venvs/default/lib/python3.9/site-packages/clikit/ui/components/choice_question.py:23 in validate
       19│         # Collapse all spaces.
       20│         if isinstance(selected, int):
       21│             selected = str(selected)
       22│
    →  23│         selected_choices = selected.replace(" ", "")
       24│
       25│         if self._question.supports_multiple_choices():
       26│             # Check for a separated comma values
       27│             if not re.match("^[a-zA-Z0-9_-]+(?:,[a-zA-Z0-9_-]+)*$", selected_choices):
```

Python 3.9.0 + Poetry 1.1.8 on macOS.

Pretty sure this just needs a default value of `""` instead of `None` somewhere along that stack trace.

Resolving patch:
diff --git a/src/poetry/console/commands/init.py b/src/poetry/console/commands/init.py
--- a/src/poetry/console/commands/init.py
+++ b/src/poetry/console/commands/init.py
@@ -306,19 +306,26 @@ def _determine_requirements(
 
                     self.line(info_string)
 
+                    # Default to an empty value to signal no package was selected
+                    choices.append("")
+
                     package = self.choice(
                         "\nEnter package # to add, or the complete package name if it"
                         " is not listed",
                         choices,
                         attempts=3,
+                        default=len(choices) - 1,
                     )
 
+                    if not package:
+                        self.line("<warning>No package selected</warning>")
+
                     # package selected by user, set constraint name to package name
-                    if package is not False:
+                    if package:
                         constraint["name"] = package
 
                 # no constraint yet, determine the best version automatically
-                if package is not False and "version" not in constraint:
+                if package and "version" not in constraint:
                     question = self.create_question(
                         "Enter the version constraint to require "
                         "(or leave blank to use the latest version):"
@@ -340,7 +347,7 @@ def _determine_requirements(
 
                     constraint["version"] = package_constraint
 
-                if package is not False:
+                if package:
                     result.append(constraint)
 
                 if self.io.is_interactive():


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
@@ -177,6 +177,50 @@ def test_interactive_with_dependencies(tester: CommandTester, repo: TestReposito
     assert expected in tester.io.fetch_output()
 
 
+# Regression test for https://github.com/python-poetry/poetry/issues/2355
+def test_interactive_with_dependencies_and_no_selection(
+    tester: CommandTester, repo: TestRepository
+):
+    repo.add_package(get_package("django-pendulum", "0.1.6-pre4"))
+    repo.add_package(get_package("pendulum", "2.0.0"))
+    repo.add_package(get_package("pytest", "3.6.0"))
+
+    inputs = [
+        "my-package",  # Package name
+        "1.2.3",  # Version
+        "This is a description",  # Description
+        "n",  # Author
+        "MIT",  # License
+        "~2.7 || ^3.6",  # Python
+        "",  # Interactive packages
+        "pendulu",  # Search for package
+        "",  # Do not select an option
+        "",  # Stop searching for packages
+        "",  # Interactive dev packages
+        "pytest",  # Search for package
+        "",  # Do not select an option
+        "",
+        "",
+        "\n",  # Generate
+    ]
+    tester.execute(inputs="\n".join(inputs))
+    expected = """\
+[tool.poetry]
+name = "my-package"
+version = "1.2.3"
+description = "This is a description"
+authors = ["Your Name <you@example.com>"]
+license = "MIT"
+readme = "README.md"
+packages = [{include = "my_package"}]
+
+[tool.poetry.dependencies]
+python = "~2.7 || ^3.6"
+"""
+
+    assert expected in tester.io.fetch_output()
+
+
 def test_empty_license(tester: CommandTester):
     inputs = [
         "my-package",  # Package name