Python library to build pretty command line user prompts with interactive forms and validation
Overall
score
96%
Essential classes for building and customizing prompts, including choice configuration, styling, validation, conditional logic, and utility functions for enhanced prompt functionality.
Core question class that serves as the foundation for all interactive prompts with execution methods and conditional logic.
class Question:
application: Application[Any]
should_skip_question: bool
default: Any
def __init__(self, application: Application[Any]) -> None:
"""
Initialize question with prompt_toolkit application.
Args:
application: Configured prompt_toolkit Application instance
"""
def ask(self, patch_stdout: bool = False,
kbi_msg: str = DEFAULT_KBI_MESSAGE) -> Any:
"""
Execute question synchronously with error handling.
Args:
patch_stdout: Patch stdout to prevent interference
kbi_msg: Message displayed on keyboard interrupt
Returns:
User response value
Raises:
KeyboardInterrupt: User cancelled with appropriate message
"""
def unsafe_ask(self, patch_stdout: bool = False) -> Any:
"""
Execute question synchronously without error handling.
Args:
patch_stdout: Patch stdout to prevent interference
Returns:
User response value
"""
def ask_async(self, patch_stdout: bool = False,
kbi_msg: str = DEFAULT_KBI_MESSAGE) -> Any:
"""
Execute question asynchronously with error handling.
Args:
patch_stdout: Patch stdout to prevent interference
kbi_msg: Message displayed on keyboard interrupt
Returns:
User response value
Raises:
KeyboardInterrupt: User cancelled with appropriate message
"""
def unsafe_ask_async(self, patch_stdout: bool = False) -> Any:
"""
Execute question asynchronously without error handling.
Args:
patch_stdout: Patch stdout to prevent interference
Returns:
User response value
"""
def skip_if(self, condition: bool, default: Any = None) -> Question:
"""
Conditionally skip question execution.
Args:
condition: If True, skip question and return default
default: Value to return when question is skipped
Returns:
Self for method chaining
"""Advanced choice configuration for selection prompts with custom values, descriptions, shortcuts, and states.
class Choice:
title: FormattedText
value: Optional[Any]
disabled: Optional[str]
checked: Optional[bool]
shortcut_key: Optional[Union[str, bool]]
description: Optional[str]
def __init__(self, title: FormattedText, value: Optional[Any] = None,
disabled: Optional[str] = None, checked: Optional[bool] = False,
shortcut_key: Optional[Union[str, bool]] = True,
description: Optional[str] = None) -> None:
"""
Configure a choice for selection prompts.
Args:
title: Display text for the choice (can be formatted text)
value: Return value when choice is selected (defaults to title)
disabled: Reason text if choice is disabled (None = enabled)
checked: Initially selected state for checkbox prompts
shortcut_key: Keyboard shortcut (True = auto-generate, str = custom, False = none)
description: Additional description text shown with choice
"""
@staticmethod
def build(c: Union[str, Choice, Dict]) -> Choice:
"""
Build Choice from string, existing Choice, or dictionary.
Args:
c: Choice specification as string, Choice instance, or dict
Returns:
Choice instance
"""
def get_shortcut_title(self) -> str:
"""
Get formatted shortcut display text.
Returns:
Formatted string showing shortcut key with title
"""
@property
def shortcut_key(self) -> Optional[str]:
"""Get the shortcut key for this choice."""
@property
def auto_shortcut(self) -> bool:
"""Check if shortcut is auto-generated."""Visual separators for organizing choice lists in selection prompts.
class Separator(Choice):
default_separator: str = "-" * 15
line: str
def __init__(self, line: Optional[str] = None) -> None:
"""
Create a visual separator for choice lists.
Args:
line: Custom separator text (default: 15 dashes)
"""Form container and field definition classes for multi-question workflows.
class FormField(NamedTuple):
key: str
question: Question
"""
Named tuple representing a question within a form.
Attributes:
key: Field identifier for result dictionary
question: Question instance to execute
"""
class Form:
form_fields: Sequence[FormField]
def __init__(self, *form_fields: FormField) -> None:
"""
Initialize form with FormField instances.
Args:
*form_fields: FormField instances defining the form structure
"""
def ask(self, patch_stdout: bool = False,
kbi_msg: str = DEFAULT_KBI_MESSAGE) -> Dict[str, Any]:
"""
Execute form synchronously with error handling.
Args:
patch_stdout: Patch stdout to prevent interference
kbi_msg: Message displayed on keyboard interrupt
Returns:
Dictionary mapping field keys to user responses
"""
def unsafe_ask(self, patch_stdout: bool = False) -> Dict[str, Any]:
"""Execute form synchronously without error handling."""
def ask_async(self, patch_stdout: bool = False,
kbi_msg: str = DEFAULT_KBI_MESSAGE) -> Dict[str, Any]:
"""Execute form asynchronously with error handling."""
def unsafe_ask_async(self, patch_stdout: bool = False) -> Dict[str, Any]:
"""Execute form asynchronously without error handling."""Enhanced text output with styling support.
def print(text: str, style: Optional[str] = None, **kwargs) -> None:
"""
Print formatted text with optional styling.
Args:
text: Text to print
style: Style specification for the text
**kwargs: Additional formatting arguments
"""
def press_any_key_to_continue(message: Optional[str] = None,
style: Optional[Style] = None,
**kwargs) -> Question:
"""
Create a prompt that waits for any key press.
Args:
message: Custom message to display (default: "Press any key to continue...")
style: Custom styling configuration
**kwargs: Additional prompt_toolkit arguments
Returns:
Question instance that waits for key press
"""Helper functions for styling and validation configuration.
def merge_styles_default(styles: List[Optional[Style]]) -> Style:
"""
Merge custom styles with default questionary styling.
Args:
styles: List of Style instances to merge
Returns:
Merged Style instance
"""
def build_validator(validate: Any) -> Optional[Validator]:
"""
Convert validation input to Validator instance.
Args:
validate: Validation function, Validator instance, or None
Returns:
Validator instance or None
"""# Response constants
YES: str = "Yes"
NO: str = "No"
YES_OR_NO: str = "(Y/n)"
NO_OR_YES: str = "(y/N)"
# UI element constants
DEFAULT_SELECTED_POINTER: str = "»"
INDICATOR_SELECTED: str = "●"
INDICATOR_UNSELECTED: str = "○"
DEFAULT_QUESTION_PREFIX: str = "?"
# Message constants
DEFAULT_KBI_MESSAGE: str = "\nCancelled by user\n"
INVALID_INPUT: str = "Invalid input"
INSTRUCTION_MULTILINE: str = "Press ESC then ENTER to submit"
# Style constants
DEFAULT_STYLE: Style # Default prompt styling configuration# Text formatting type
FormattedText = Union[str, List[Tuple[str, str]], List[Tuple[str, str, Callable]], None]
# Re-exported from prompt_toolkit
class Style:
"""Style configuration for prompt appearance."""
class Validator:
"""Base class for input validation."""
def validate(self, document: Document) -> None:
"""
Validate document and raise ValidationError if invalid.
Args:
document: Input document to validate
Raises:
ValidationError: If validation fails
"""
class ValidationError(Exception):
"""Exception raised when input validation fails."""
def __init__(self, message: str = "", cursor_position: int = 0) -> None:
"""
Initialize validation error.
Args:
message: Error message to display
cursor_position: Cursor position for error highlighting
"""import questionary
from questionary import Question, Choice, Separator
# Create choices with custom configuration
choices = [
Choice("High Priority", value="high", shortcut_key="h",
description="Urgent tasks requiring immediate attention"),
Choice("Medium Priority", value="medium", shortcut_key="m",
description="Important tasks with flexible timing"),
Choice("Low Priority", value="low", shortcut_key="l",
description="Nice-to-have tasks for later"),
Separator("────────────"),
Choice("Cancel", value=None, shortcut_key="c")
]
priority = questionary.select(
"Select task priority:",
choices=choices,
show_description=True
).ask()
print(f"Selected priority: {priority}")import questionary
# Create questions with conditional logic
enable_feature = questionary.confirm("Enable advanced features?").ask()
# Skip configuration if feature is disabled
config_question = questionary.text(
"Enter configuration:"
).skip_if(
condition=not enable_feature,
default="default_config"
)
config = config_question.ask()
print(f"Configuration: {config}")import questionary
from questionary import Validator, ValidationError
class PortValidator(Validator):
def validate(self, document):
try:
port = int(document.text)
if not (1 <= port <= 65535):
raise ValidationError(
message="Port must be between 1 and 65535",
cursor_position=len(document.text)
)
except ValueError:
raise ValidationError(
message="Port must be a number",
cursor_position=len(document.text)
)
port = questionary.text(
"Enter port number:",
validate=PortValidator()
).ask()import questionary
from questionary import Choice
# Build choices from different sources
string_choices = ["Option 1", "Option 2"]
dict_choices = [
{"title": "Database Setup", "value": "db", "disabled": "Not available"},
{"title": "Web Server", "value": "web"}
]
# Convert all to Choice objects
processed_choices = [Choice.build(c) for c in string_choices + dict_choices]
selection = questionary.select(
"Choose setup option:",
choices=processed_choices
).ask()import questionary
from questionary import Choice, Separator
# Create themed choice list with separators
menu_choices = [
Choice("New Project", value="new"),
Choice("Open Project", value="open"),
Separator("═══ Recent Projects ═══"),
Choice("Project Alpha", value="alpha"),
Choice("Project Beta", value="beta"),
Separator("═══ Tools ═══"),
Choice("Settings", value="settings"),
Choice("Help", value="help")
]
selection = questionary.select(
"Main Menu:",
choices=menu_choices
).ask()import questionary
from questionary import FormField, Form
# Create form fields manually for precise control
fields = [
FormField("username", questionary.text("Username:")),
FormField("password", questionary.password("Password:")),
FormField("remember", questionary.confirm("Remember login?", default=True))
]
login_form = Form(*fields)
credentials = login_form.ask()
print(f"Logging in {credentials['username']}")
if credentials['remember']:
print("Login will be remembered")import questionary
from prompt_toolkit.styles import Style
# Custom style configuration
custom_style = Style([
('question', 'fg:#ff0066 bold'),
('answer', 'fg:#44ff00 bold'),
('pointer', 'fg:#673ab7 bold'),
('highlighted', 'bg:#673ab7 fg:#ffffff'),
('selected', 'fg:#cc5454'),
('separator', 'fg:#cc5454'),
('instruction', 'fg:#888888'),
('text', '#ffffff'),
('disabled', 'fg:#858585 italic')
])
# Apply styling to questions
styled_input = questionary.text(
"Enter your name:",
style=custom_style
).ask()
styled_selection = questionary.select(
"Choose option:",
choices=["Option A", "Option B", "Option C"],
style=custom_style
).ask()Install with Tessl CLI
npx tessl i tessl/pypi-questionarydocs
evals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10