Skip to content

Commit 3d27cf8

Browse files
committed
Add blocking param to click.prompt
1 parent e01c59d commit 3d27cf8

3 files changed

Lines changed: 39 additions & 4 deletions

File tree

CHANGES.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ Version 8.3.0
55

66
Released 2025-09-15
77

8+
- **Important: Breaking asyncclick change**: :func:`asyncclick.prompt` is now
9+
async. It accepts a new `blocking` parameter to switch between keeping the
10+
async loop running (`False`) and not going belly-up when the user interrupts
11+
it using Control-C (`True`). `True` thus is the default (for now).
12+
813
- **Improved flag option handling**: Reworked the relationship between ``flag_value``
914
and ``default`` parameters for better consistency:
1015

docs/prompts.md

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,21 @@ local: true
1616
---
1717
```
1818

19+
## AsyncClick changes
20+
21+
AsyncClick now async-izes :func:`asyncclick.prompt`. This may require changes
22+
in your program. We are sorry to have to do this in a minor release, but there
23+
is no way around this change because the validation callback might be async.
24+
25+
On the positive side, :func:`asyncclick.prompt` gained a new parameter ``blocking``.
26+
When it is set to `False`, interaction with the user no longer blocks the async
27+
event loop.
28+
29+
However, this currently interacts badly with interrupting the program, e.g. by
30+
pressing Control-C. As most programs prompt the user first and run async tasks later,
31+
the default for ``blocking`` is `True` until we can solve the interrupt problem.
32+
33+
1934
(option-prompting)=
2035

2136
## Option Prompts
@@ -74,14 +89,14 @@ To manually ask for user input, you can use the {func}`prompt` function. By defa
7489
you can ask for any other type. For instance, you can ask for a valid integer:
7590

7691
```python
77-
value = click.prompt('Please enter a valid integer', type=int)
92+
value = await click.prompt('Please enter a valid integer', type=int)
7893
```
7994

8095
Additionally, the type will be determined automatically if a default value is provided. For instance, the following will
8196
only accept floats:
8297

8398
```python
84-
value = click.prompt('Please enter a number', default=42.0)
99+
value = await click.prompt('Please enter a number', default=42.0)
85100
```
86101

87102
## Optional Prompts

src/asyncclick/termui.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ async def prompt(
9393
show_default: bool = True,
9494
err: bool = False,
9595
show_choices: bool = True,
96+
blocking: bool = True,
9697
) -> t.Any:
9798
"""Prompts a user for input. This is a convenience function that can
9899
be used to prompt a user for input later.
@@ -120,11 +121,18 @@ async def prompt(
120121
For example if type is a Choice of either day or week,
121122
show_choices is true and text is "Group by" then the
122123
prompt will be "Group by (day, week): ".
124+
:param blocking: if `False`, uses a thread to interact with the terminal.
125+
This keeps the event loop running but may cause interesting
126+
effects when interrupted with Control-C. The default
127+
is `True`, but don't depend on it.
123128
124129
Warning: The user interaction is run inside a separate thread, because otherwise
125130
the call would block the async loop. As a result, behavior when interrupted may be
126131
sub-optimal.
127132
133+
.. versionadded:: 8.3
134+
(AsyncClick) ``prompt`` is now async; ``blocking`` parameter added.
135+
128136
.. versionadded:: 8.0
129137
``confirmation_prompt`` can be a custom string.
130138
@@ -156,6 +164,13 @@ def prompt_func(text: str) -> str:
156164
echo(None, err=err)
157165
raise Abort() from None
158166

167+
if blocking:
168+
async def run_prompt_func(text: str) -> str:
169+
return prompt_func(text)
170+
else:
171+
def run_prompt_func(text: str) -> Awaitable[str]:
172+
return anyio.to_thread.run_sync(prompt_func, text)
173+
159174
if value_proc is None:
160175
value_proc = convert_type(type, default)
161176

@@ -171,7 +186,7 @@ def prompt_func(text: str) -> str:
171186

172187
while True:
173188
while True:
174-
value = await anyio.to_thread.run_sync(prompt_func, prompt)
189+
value = await run_prompt_func(prompt)
175190
if value:
176191
break
177192
elif default is not None:
@@ -190,7 +205,7 @@ def prompt_func(text: str) -> str:
190205
if not confirmation_prompt:
191206
return result
192207
while True:
193-
value2 = await anyio.to_thread.run_sync(prompt_func, confirmation_prompt)
208+
value2 = await run_prompt_func(confirmation_prompt)
194209
is_empty = not value and not value2
195210
if value2 or is_empty:
196211
break

0 commit comments

Comments
 (0)