Inquirer based on Textual for creating interactive command-line user input prompts
Core data types: Result, Choice, and Shortcut.
from inquirer_textual.common.Result import Result
from inquirer_textual.common.Choice import Choice
from inquirer_textual.common.Shortcut import ShortcutResult wrapper returned by all prompts and widgets.
from typing import Generic, TypeVar
T = TypeVar('T')
class Result(Generic[T]):
"""
Result object containing command and value.
Attributes:
command (str | None): Exit command - 'select', 'quit', or custom
value (T): Typed value from prompt
"""
command: str | None
value: T
def __init__(self, command: str | None, value: T):
"""
Initialize Result.
Args:
command: Exit command ('select', 'quit', or custom)
value: Typed value from prompt
Note: Users typically don't instantiate Result directly - it's returned by prompt functions.
"""
def __str__(self) -> str:
"""Returns string representation of value."""Always check command before using value:
result = prompts.text("Input:")
if result.command == 'select':
# User confirmed - value is valid
name = result.value
process(name)
elif result.command == 'quit':
print("User quit")
exit(0)# Text prompt
text_result: Result[str] = prompts.text("Name:")
name: str = text_result.value
# Number prompt (returns string)
num_result: Result[str] = prompts.number("Age:")
age: int = int(num_result.value)
# Confirm prompt
confirm_result: Result[bool] = prompts.confirm("OK?")
confirmed: bool = confirm_result.value
# Select prompt
select_result: Result[str | Choice] = prompts.select("Pick:", ["A", "B"])
choice: str | Choice = select_result.value
# Checkbox prompt
checkbox_result: Result[list[str | Choice]] = prompts.checkbox("Select:", ["X", "Y"])
selected: list[str | Choice] = checkbox_result.value
# Multi prompt
multi_result: Result[list[Any]] = prompts.multi([widget1, widget2])
values: list[Any] = multi_result.valueChoice object for select/checkbox prompts with attached data and custom commands.
from typing import Any
class Choice:
"""
Represents a selectable item.
Attributes:
name (str): Display name shown to user
data (Any): Attached data (default: None) - can be any Python object
command (str): Command when selected (default: 'select')
"""
name: str
data: Any = None
command: str = 'select'Basic Choice:
from inquirer_textual.common.Choice import Choice
choices = [
Choice("Option A"),
Choice("Option B"),
Choice("Option C")
]
result = prompts.select("Choose:", choices)
if result.command == 'select':
print(f"Selected: {result.value.name}")With data:
servers = [
Choice("Development", data={"host": "localhost", "port": 3000}),
Choice("Production", data={"host": "example.com", "port": 443})
]
result = prompts.select("Select server:", servers)
if result.command == 'select':
config = result.value.data
connect(config['host'], config['port'])With custom commands:
actions = [
Choice("View", command="view"),
Choice("Edit", command="edit"),
Choice("Delete", command="delete")
]
result = prompts.select("Action:", actions)
if result.command == 'view':
view_item()
elif result.command == 'edit':
edit_item()
elif result.command == 'delete':
confirm = prompts.confirm("Really delete?")
if confirm.value:
delete_item()Complex data objects:
class ServerConfig:
def __init__(self, name, url, api_key):
self.name = name
self.url = url
self.api_key = api_key
servers = [
Choice("Dev Server", data=ServerConfig("dev", "http://localhost", "dev-key")),
Choice("Prod Server", data=ServerConfig("prod", "https://api.example.com", "prod-key"))
]
result = prompts.select("Select:", servers)
if result.command == 'select':
config = result.value.data
client = APIClient(config.url, config.api_key)Factory pattern:
def create_environment_choices(envs):
return [
Choice(
env['display_name'],
data={
'env_id': env['id'],
'config': env['config'],
'credentials': env['creds']
}
)
for env in envs
]
choices = create_environment_choices(environments)
result = prompts.select("Environment:", choices)Keyboard shortcut definition for custom key bindings.
class Shortcut:
"""
Keyboard shortcut definition.
Attributes:
key (str): Key or key combination
command (str): Command name executed when key pressed
description (str | None): Description for footer (default: None)
show (bool): Whether to show in footer (default: True)
Notes:
__post_init__ auto-sets description to command if None
"""
key: str
command: str
description: str | None = None
show: bool = TrueIf description is None, __post_init__() automatically sets it to the command value:
# These are equivalent:
Shortcut('escape', 'cancel')
Shortcut('escape', 'cancel', 'cancel') # description auto-set to 'cancel'Basic shortcuts:
shortcuts = [
Shortcut('escape', 'cancel', 'Cancel'),
Shortcut('ctrl+s', 'save', 'Save'),
Shortcut('ctrl+h', 'help', 'Help')
]
result = prompts.text("Input:", shortcuts=shortcuts)
if result.command == 'cancel':
print("Cancelled")
elif result.command == 'save':
save_draft(result.value)
elif result.command == 'help':
show_help()
elif result.command == 'select':
save_final(result.value)Hidden shortcuts (show=False):
shortcuts = [
Shortcut('escape', 'cancel', 'Cancel'),
Shortcut('ctrl+x', 'debug', show=False), # Not shown in footer
Shortcut('ctrl+q', 'force_quit', show=False)
]
# Footer shows only "Cancel", but ctrl+x and ctrl+q still work
result = prompts.text("Input:", shortcuts=shortcuts)Auto-description:
shortcuts = [
Shortcut('q', 'quit'), # Description auto-set to 'quit'
Shortcut('h', 'help'), # Description auto-set to 'help'
Shortcut('s', 'skip') # Description auto-set to 'skip'
]
result = prompts.text("Input:", shortcuts=shortcuts)Reusable shortcuts:
COMMON_SHORTCUTS = [
Shortcut('escape', 'cancel', 'Cancel'),
Shortcut('ctrl+h', 'help', 'Help')
]
def add_save_shortcut(shortcuts):
return shortcuts + [Shortcut('ctrl+s', 'save', 'Save')]
result1 = prompts.text("Field 1:", shortcuts=COMMON_SHORTCUTS)
result2 = prompts.text("Field 2:", shortcuts=add_save_shortcut(COMMON_SHORTCUTS))Complex key combinations:
shortcuts = [
# Single keys
Shortcut('a', 'action_a'),
Shortcut('escape', 'cancel'),
# Ctrl combinations
Shortcut('ctrl+s', 'save'),
Shortcut('ctrl+q', 'quit'),
Shortcut('ctrl+shift+d', 'debug'),
# Function keys
Shortcut('f1', 'help'),
Shortcut('f5', 'refresh')
]
result = prompts.text("Input:", shortcuts=shortcuts)Standard pattern for handling Result commands:
from inquirer_textual import prompts
from inquirer_textual.common.Shortcut import Shortcut
def get_input_with_shortcuts():
shortcuts = [
Shortcut('escape', 'cancel'),
Shortcut('ctrl+s', 'skip')
]
result = prompts.text("Enter value:", shortcuts=shortcuts)
# Handle commands in priority order
if result.command == 'quit':
# Critical exit - quit application
exit(0)
elif result.command == 'cancel':
# User cancelled - return None
return None
elif result.command == 'skip':
# Use default value
return 'DEFAULT'
elif result.command == 'select':
# Normal case - use entered value
return result.value
else:
# Unknown command - treat as cancel
return NoneSafely access Choice data with type checking:
from inquirer_textual.common.Choice import Choice
result = prompts.select("Pick:", [Choice("A", data={"id": 1}), Choice("B", data={"id": 2})])
if result.command == 'select':
if isinstance(result.value, Choice):
# Choice object - access data
item_id = result.value.data["id"]
else:
# String choice (shouldn't happen if all choices are Choice objects)
item_name = result.valuefrom inquirer_textual.widgets.InquirerText import InquirerText
from inquirer_textual.widgets.InquirerNumber import InquirerNumber
from inquirer_textual.widgets.InquirerConfirm import InquirerConfirm
result = prompts.multi([
InquirerText("Name:"),
InquirerNumber("Age:"),
InquirerConfirm("Active:")
])
if result.command == 'select':
name_str, age_str, active_bool = result.value
# Type conversions
name: str = name_str
age: int = int(age_str)
active: bool = active_bool
process(name, age, active)Install with Tessl CLI
npx tessl i tessl/pypi-inquirer-textual