Skip to content

Commit 137e785

Browse files
committed
cli/new: support interactive config
This change allows new command to be used interactively similar to the init command. Additionally, this also allows for configuration of description, author, python and dependencies via command line options.
1 parent d08c75a commit 137e785

File tree

7 files changed

+177
-150
lines changed

7 files changed

+177
-150
lines changed

docs/cli.md

+6
Original file line numberDiff line numberDiff line change
@@ -102,11 +102,17 @@ my-package
102102

103103
### Options
104104

105+
* `--interactive (-i)`: Allow interactive specification of project configuration.
105106
* `--name`: Set the resulting package name.
106107
* `--src`: Use the src layout for the project.
107108
* `--readme`: Specify the readme file extension. Default is `md`. If you intend to publish to PyPI
108109
keep the [recommendations for a PyPI-friendly README](https://packaging.python.org/en/latest/guides/making-a-pypi-friendly-readme/)
109110
in mind.
111+
* `--description`: Description of the package.
112+
* `--author`: Author of the package.
113+
* `--python` Compatible Python versions.
114+
* `--dependency`: Package to require with a version constraint. Should be in format `foo:1.0.0`.
115+
* `--dev-dependency`: Development requirements, see `--dependency`.
110116

111117

112118
## init

src/poetry/console/commands/init.py

+95-59
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
from contextlib import suppress
34
from pathlib import Path
45
from typing import TYPE_CHECKING
56
from typing import Any
@@ -71,13 +72,6 @@ def __init__(self) -> None:
7172
def handle(self) -> int:
7273
from pathlib import Path
7374

74-
from poetry.core.vcs.git import GitConfig
75-
76-
from poetry.config.config import Config
77-
from poetry.layouts import layout
78-
from poetry.pyproject.toml import PyProjectTOML
79-
from poetry.utils.env import EnvManager
80-
8175
project_path = Path.cwd()
8276

8377
if self.io.input.option("directory"):
@@ -88,6 +82,24 @@ def handle(self) -> int:
8882
)
8983
return 1
9084

85+
return self._init_pyproject(project_path=project_path)
86+
87+
def _init_pyproject(
88+
self,
89+
project_path: Path,
90+
allow_interactive: bool = True,
91+
layout_name: str = "standard",
92+
readme_format: str = "md",
93+
) -> int:
94+
from poetry.core.vcs.git import GitConfig
95+
96+
from poetry.config.config import Config
97+
from poetry.layouts import layout
98+
from poetry.pyproject.toml import PyProjectTOML
99+
from poetry.utils.env import EnvManager
100+
101+
is_interactive = self.io.is_interactive() and allow_interactive
102+
91103
pyproject = PyProjectTOML(project_path / "pyproject.toml")
92104

93105
if pyproject.file.exists():
@@ -107,7 +119,7 @@ def handle(self) -> int:
107119

108120
vcs_config = GitConfig()
109121

110-
if self.io.is_interactive():
122+
if is_interactive:
111123
self.line("")
112124
self.line(
113125
"This command will guide you through creating your"
@@ -117,21 +129,24 @@ def handle(self) -> int:
117129

118130
name = self.option("name")
119131
if not name:
120-
name = Path.cwd().name.lower()
132+
name = project_path.name.lower()
121133

122-
question = self.create_question(
123-
f"Package name [<comment>{name}</comment>]: ", default=name
124-
)
125-
name = self.ask(question)
134+
if is_interactive:
135+
question = self.create_question(
136+
f"Package name [<comment>{name}</comment>]: ", default=name
137+
)
138+
name = self.ask(question)
126139

127140
version = "0.1.0"
128-
question = self.create_question(
129-
f"Version [<comment>{version}</comment>]: ", default=version
130-
)
131-
version = self.ask(question)
132141

133-
description = self.option("description")
134-
if not description:
142+
if is_interactive:
143+
question = self.create_question(
144+
f"Version [<comment>{version}</comment>]: ", default=version
145+
)
146+
version = self.ask(question)
147+
148+
description = self.option("description") or ""
149+
if not description and is_interactive:
135150
description = self.ask(self.create_question("Description []: ", default=""))
136151

137152
author = self.option("author")
@@ -141,22 +156,23 @@ def handle(self) -> int:
141156
if author_email:
142157
author += f" <{author_email}>"
143158

144-
question = self.create_question(
145-
f"Author [<comment>{author}</comment>, n to skip]: ", default=author
146-
)
147-
question.set_validator(lambda v: self._validate_author(v, author))
148-
author = self.ask(question)
159+
if is_interactive:
160+
question = self.create_question(
161+
f"Author [<comment>{author}</comment>, n to skip]: ", default=author
162+
)
163+
question.set_validator(lambda v: self._validate_author(v, author))
164+
author = self.ask(question)
149165

150166
authors = [author] if author else []
151167

152-
license = self.option("license")
153-
if not license:
154-
license = self.ask(self.create_question("License []: ", default=""))
168+
license_name = self.option("license")
169+
if not license_name and is_interactive:
170+
license_name = self.ask(self.create_question("License []: ", default=""))
155171

156172
python = self.option("python")
157173
if not python:
158174
config = Config.create()
159-
default_python = (
175+
python = (
160176
"^"
161177
+ EnvManager.get_python_version(
162178
precision=2,
@@ -165,13 +181,14 @@ def handle(self) -> int:
165181
).to_string()
166182
)
167183

168-
question = self.create_question(
169-
f"Compatible Python versions [<comment>{default_python}</comment>]: ",
170-
default=default_python,
171-
)
172-
python = self.ask(question)
184+
if is_interactive:
185+
question = self.create_question(
186+
f"Compatible Python versions [<comment>{python}</comment>]: ",
187+
default=python,
188+
)
189+
python = self.ask(question)
173190

174-
if self.io.is_interactive():
191+
if is_interactive:
175192
self.line("")
176193

177194
requirements: Requirements = {}
@@ -182,27 +199,25 @@ def handle(self) -> int:
182199

183200
question_text = "Would you like to define your main dependencies interactively?"
184201
help_message = """\
185-
You can specify a package in the following forms:
186-
- A single name (<b>requests</b>): this will search for matches on PyPI
187-
- A name and a constraint (<b>requests@^2.23.0</b>)
188-
- A git url (<b>git+https://github.com/python-poetry/poetry.git</b>)
189-
- A git url with a revision\
190-
(<b>git+https://github.com/python-poetry/poetry.git#develop</b>)
191-
- A file path (<b>../my-package/my-package.whl</b>)
192-
- A directory (<b>../my-package/</b>)
193-
- A url (<b>https://example.com/packages/my-package-0.1.0.tar.gz</b>)
194-
"""
202+
You can specify a package in the following forms:
203+
- A single name (<b>requests</b>): this will search for matches on PyPI
204+
- A name and a constraint (<b>requests@^2.23.0</b>)
205+
- A git url (<b>git+https://github.com/python-poetry/poetry.git</b>)
206+
- A git url with a revision\
207+
(<b>git+https://github.com/python-poetry/poetry.git#develop</b>)
208+
- A file path (<b>../my-package/my-package.whl</b>)
209+
- A directory (<b>../my-package/</b>)
210+
- A url (<b>https://example.com/packages/my-package-0.1.0.tar.gz</b>)
211+
"""
195212

196213
help_displayed = False
197-
if self.confirm(question_text, True):
198-
if self.io.is_interactive():
199-
self.line(help_message)
200-
help_displayed = True
214+
if is_interactive and self.confirm(question_text, True):
215+
self.line(help_message)
216+
help_displayed = True
201217
requirements.update(
202218
self._format_requirements(self._determine_requirements([]))
203219
)
204-
if self.io.is_interactive():
205-
self.line("")
220+
self.line("")
206221

207222
dev_requirements: Requirements = {}
208223
if self.option("dev-dependency"):
@@ -213,44 +228,61 @@ def handle(self) -> int:
213228
question_text = (
214229
"Would you like to define your development dependencies interactively?"
215230
)
216-
if self.confirm(question_text, True):
217-
if self.io.is_interactive() and not help_displayed:
231+
if is_interactive and self.confirm(question_text, True):
232+
if not help_displayed:
218233
self.line(help_message)
219234

220235
dev_requirements.update(
221236
self._format_requirements(self._determine_requirements([]))
222237
)
223-
if self.io.is_interactive():
224-
self.line("")
225238

226-
layout_ = layout("standard")(
239+
self.line("")
240+
241+
layout_ = layout(layout_name)(
227242
name,
228243
version,
229244
description=description,
230245
author=authors[0] if authors else None,
231-
license=license,
246+
readme_format=readme_format,
247+
license=license_name,
232248
python=python,
233249
dependencies=requirements,
234250
dev_dependencies=dev_requirements,
235251
)
236252

253+
create_layout = not project_path.exists()
254+
255+
if create_layout:
256+
layout_.create(project_path, with_pyproject=False)
257+
237258
content = layout_.generate_poetry_content()
238259
for section, item in content.items():
239260
pyproject.data.append(section, item)
240261

241-
if self.io.is_interactive():
262+
if is_interactive:
242263
self.line("<info>Generated file</info>")
243264
self.line("")
244265
self.line(pyproject.data.as_string().replace("\r\n", "\n"))
245266
self.line("")
246267

247-
if not self.confirm("Do you confirm generation?", True):
268+
if is_interactive and not self.confirm("Do you confirm generation?", True):
248269
self.line_error("<error>Command aborted</error>")
249270

250271
return 1
251272

252273
pyproject.save()
253274

275+
if create_layout:
276+
path = project_path.resolve()
277+
278+
with suppress(ValueError):
279+
path = path.relative_to(Path.cwd())
280+
281+
self.line(
282+
f"Created package <info>{layout_._package_name}</> in"
283+
f" <fg=blue>{path.as_posix()}</>"
284+
)
285+
254286
return 0
255287

256288
def _generate_choice_list(
@@ -278,7 +310,11 @@ def _determine_requirements(
278310
requires: list[str],
279311
allow_prereleases: bool = False,
280312
source: str | None = None,
313+
is_interactive: bool | None = None,
281314
) -> list[dict[str, Any]]:
315+
if is_interactive is None:
316+
is_interactive = self.io.is_interactive()
317+
282318
if not requires:
283319
result = []
284320

@@ -368,7 +404,7 @@ def _determine_requirements(
368404
if package:
369405
result.append(constraint)
370406

371-
if self.io.is_interactive():
407+
if is_interactive:
372408
package = self.ask(follow_up_question)
373409

374410
return result

0 commit comments

Comments
 (0)