135 lines
3.9 KiB
Python
135 lines
3.9 KiB
Python
|
import sys
|
||
|
from typing import Any
|
||
|
|
||
|
import prompt_toolkit.patch_stdout
|
||
|
from prompt_toolkit import Application
|
||
|
|
||
|
from questionary import utils
|
||
|
from questionary.constants import DEFAULT_KBI_MESSAGE
|
||
|
|
||
|
|
||
|
class Question:
|
||
|
"""A question to be prompted.
|
||
|
|
||
|
This is an internal class. Questions should be created using the
|
||
|
predefined questions (e.g. text or password)."""
|
||
|
|
||
|
application: "Application[Any]"
|
||
|
should_skip_question: bool
|
||
|
default: Any
|
||
|
|
||
|
def __init__(self, application: "Application[Any]") -> None:
|
||
|
self.application = application
|
||
|
self.should_skip_question = False
|
||
|
self.default = None
|
||
|
|
||
|
async def ask_async(
|
||
|
self, patch_stdout: bool = False, kbi_msg: str = DEFAULT_KBI_MESSAGE
|
||
|
) -> Any:
|
||
|
"""Ask the question using asyncio and return user response.
|
||
|
|
||
|
Args:
|
||
|
patch_stdout: Ensure that the prompt renders correctly if other threads
|
||
|
are printing to stdout.
|
||
|
|
||
|
kbi_msg: The message to be printed on a keyboard interrupt.
|
||
|
|
||
|
Returns:
|
||
|
`Any`: The answer from the question.
|
||
|
"""
|
||
|
|
||
|
try:
|
||
|
sys.stdout.flush()
|
||
|
return await self.unsafe_ask_async(patch_stdout)
|
||
|
except KeyboardInterrupt:
|
||
|
print("\n{}\n".format(kbi_msg))
|
||
|
return None
|
||
|
|
||
|
def ask(
|
||
|
self, patch_stdout: bool = False, kbi_msg: str = DEFAULT_KBI_MESSAGE
|
||
|
) -> Any:
|
||
|
"""Ask the question synchronously and return user response.
|
||
|
|
||
|
Args:
|
||
|
patch_stdout: Ensure that the prompt renders correctly if other threads
|
||
|
are printing to stdout.
|
||
|
|
||
|
kbi_msg: The message to be printed on a keyboard interrupt.
|
||
|
|
||
|
Returns:
|
||
|
`Any`: The answer from the question.
|
||
|
"""
|
||
|
|
||
|
try:
|
||
|
return self.unsafe_ask(patch_stdout)
|
||
|
except KeyboardInterrupt:
|
||
|
print("\n{}\n".format(kbi_msg))
|
||
|
return None
|
||
|
|
||
|
def unsafe_ask(self, patch_stdout: bool = False) -> Any:
|
||
|
"""Ask the question synchronously and return user response.
|
||
|
|
||
|
Does not catch keyboard interrupts.
|
||
|
|
||
|
Args:
|
||
|
patch_stdout: Ensure that the prompt renders correctly if other threads
|
||
|
are printing to stdout.
|
||
|
|
||
|
Returns:
|
||
|
`Any`: The answer from the question.
|
||
|
"""
|
||
|
|
||
|
if self.should_skip_question:
|
||
|
return self.default
|
||
|
|
||
|
if patch_stdout:
|
||
|
with prompt_toolkit.patch_stdout.patch_stdout():
|
||
|
return self.application.run()
|
||
|
else:
|
||
|
return self.application.run()
|
||
|
|
||
|
def skip_if(self, condition: bool, default: Any = None) -> "Question":
|
||
|
"""Skip the question if flag is set and return the default instead.
|
||
|
|
||
|
Args:
|
||
|
condition: A conditional boolean value.
|
||
|
default: The default value to return.
|
||
|
|
||
|
Returns:
|
||
|
:class:`Question`: `self`.
|
||
|
"""
|
||
|
|
||
|
self.should_skip_question = condition
|
||
|
self.default = default
|
||
|
return self
|
||
|
|
||
|
async def unsafe_ask_async(self, patch_stdout: bool = False) -> Any:
|
||
|
"""Ask the question using asyncio and return user response.
|
||
|
|
||
|
Does not catch keyboard interrupts.
|
||
|
|
||
|
Args:
|
||
|
patch_stdout: Ensure that the prompt renders correctly if other threads
|
||
|
are printing to stdout.
|
||
|
|
||
|
Returns:
|
||
|
`Any`: The answer from the question.
|
||
|
"""
|
||
|
|
||
|
if self.should_skip_question:
|
||
|
return self.default
|
||
|
|
||
|
if not utils.ACTIVATED_ASYNC_MODE:
|
||
|
await utils.activate_prompt_toolkit_async_mode()
|
||
|
|
||
|
if patch_stdout:
|
||
|
with prompt_toolkit.patch_stdout.patch_stdout():
|
||
|
r = self.application.run_async()
|
||
|
else:
|
||
|
r = self.application.run_async()
|
||
|
|
||
|
if utils.is_prompt_toolkit_3():
|
||
|
return await r
|
||
|
else:
|
||
|
return await r.to_asyncio_future() # type: ignore[attr-defined]
|