Known issues, limitations, error handling, and edge case behavior for inquirer-textual.
enabled Parameter Not FunctionalIssue: The enabled parameter in prompts.checkbox() and InquirerCheckbox does NOT work.
# This does NOT work in v0.2.0
result = prompts.checkbox(
"Select features:",
["Auth", "Database", "Caching"],
enabled=["Auth", "Database"] # Has no effect
)
# All items will start unchecked regardless of enabled parameterImpact: All checkbox items start unchecked. User must manually check desired items with spacebar.
Workaround: None. Users must manually select items every time.
Status: Known bug in version 0.2.0.
Behavior: When validation fails on Enter press, prompt remains active and displays validation error. User can continue editing or quit.
from textual.validation import Function
result = prompts.text(
"Email:",
validators=Function(lambda s: '@' in s, "Invalid email")
)
# If user enters "test" and presses Enter:
# - Validation fails
# - Error message "Invalid email" displayed
# - Prompt remains active
# - User can continue editing or press Ctrl+D to quitNo Exception: Validation failures do NOT raise exceptions. They just keep the prompt active.
Behavior varies by prompt type:
Text/Secret prompts: Allow empty input by default unless validators prevent it.
result = prompts.text("Name:")
# User can press Enter with empty input
# result.command == 'select', result.value == ''Number prompts: Do NOT allow empty input. Enter on empty input does nothing.
result = prompts.number("Age:")
# User cannot submit empty - Enter does nothing
# Must enter valid integer or quit with Ctrl+DSelect/Checkbox prompts: Always have a value (highlighted item or checked items).
result = prompts.select("Choose:", ["A", "B", "C"])
# Always returns a value (the highlighted item) on Enter
# Cannot be empty unless mandatory=False and user presses Ctrl+CBehavior: Result has command='quit' and value contains the current state.
result = prompts.text("Name:")
if result.command == 'quit':
# User pressed Ctrl+D
# result.value contains partial input if any
print("User quit")In multi-prompt: Returns values completed so far.
result = prompts.multi([
InquirerText("Field 1:"),
InquirerText("Field 2:"),
InquirerText("Field 3:")
])
# If user completes Field 1 and 2, then quits:
# result.command == 'quit'
# result.value == ['value1', 'value2'] # Only completed fieldsBehavior: Only allowed when mandatory=False. Otherwise, Ctrl+C does nothing.
# mandatory=True (default) - Ctrl+C does nothing
result = prompts.confirm("Continue?")
# User cannot exit with Ctrl+C
# mandatory=False - Ctrl+C allowed
result = prompts.confirm("Continue?", mandatory=False)
if result.command == 'ctrl+c':
# User pressed Ctrl+C
# result.value is None
print("User aborted")Availability: Only prompts.confirm() and prompts.select() support mandatory parameter. Text/secret/number prompts do not.
Scenario: Calling app.prompt() after app.stop() has been called.
def flow(app):
r1 = app.prompt(InquirerText("First:"))
app.stop(r1.value)
# This raises RuntimeError:
r2 = app.prompt(InquirerText("Second:")) # Error!
app = InquirerApp()
result = app.run(inline=True, inquiry_func=flow)Error: RuntimeError: Cannot prompt after app has been stopped
Solution: Do not call app.prompt() after app.stop(). Use early returns.
def flow(app):
r1 = app.prompt(InquirerText("First:"))
app.stop(r1.value)
return # Exit flow after stopBehavior: Undefined. Do not pass empty choice list to select/checkbox prompts.
# DO NOT DO THIS
result = prompts.select("Choose:", []) # Empty list - behavior undefinedSolution: Always validate that choices list is not empty before calling select/checkbox.
choices = get_choices()
if not choices:
print("No choices available")
else:
result = prompts.select("Choose:", choices)Valid: Can mix strings and Choice objects in same list.
from inquirer_textual.common.Choice import Choice
choices = [
"Plain string",
Choice("Choice object", data={"id": 1}),
"Another string"
]
result = prompts.select("Choose:", choices)
# result.value can be either str or Choice
if isinstance(result.value, Choice):
print(f"Choice: {result.value.name}, Data: {result.value.data}")
else:
print(f"String: {result.value}")Caution: When mixing, must check type to safely access Choice.data.
Important: prompts.number() returns string, not int.
result = prompts.number("Age:")
if result.command == 'select':
age_str: str = result.value # String, not int
age_int: int = int(result.value) # Must convert to intReason: The value is validated to contain only integer characters but is returned as string for consistency with text prompts.
Behavior: When a Choice with custom command is selected, Result.command is that custom command, not 'select'.
from inquirer_textual.common.Choice import Choice
choices = [
Choice("View", command="view"),
Choice("Edit", command="edit")
]
result = prompts.select("Action:", choices)
# If user selects "View":
# result.command == 'view', NOT 'select'
# result.value is the Choice objectPattern: Check for specific commands, not just 'select'.
if result.command == 'view':
view_item()
elif result.command == 'edit':
edit_item()Behavior: default sets initial highlight position, not pre-selection.
result = prompts.select("Choose:", ["A", "B", "C"], default="B")
# "B" is highlighted initially
# User can navigate away and select a different option
# default does NOT guarantee "B" will be selectedNot a default value: Unlike confirm(default=True), select's default is just the initial cursor position.
Behavior: If multiple shortcuts have the same key, the last one wins.
shortcuts = [
Shortcut('q', 'quit', 'Quit'),
Shortcut('q', 'quick_save', 'Quick Save') # Overrides first 'q'
]
result = prompts.text("Input:", shortcuts=shortcuts)
# Pressing 'q' triggers 'quick_save', not 'quit'Solution: Use unique keys for each shortcut.
Behavior: If a validator raises an exception during validation, it's treated as validation failure.
from textual.validation import Validator, ValidationResult
class BuggyValidator(Validator):
def validate(self, value: str) -> ValidationResult:
# This raises exception if value is empty
first_char = value[0] # IndexError if empty
return self.success()
result = prompts.text("Input:", validators=BuggyValidator())
# If user enters empty string, IndexError occurs
# Treated as validation failureSolution: Handle edge cases in validators.
class SafeValidator(Validator):
def validate(self, value: str) -> ValidationResult:
if not value:
return self.failure("Cannot be empty")
# Now safe to access value[0]
return self.success()Behavior: Text prompts return empty string '', not None, when user submits empty input.
result = prompts.text("Name:")
if result.command == 'select':
# result.value is '', not None
if not result.value: # Checks for empty string
print("Empty input")When is value None?
result.value is Noneresult.value is NoneBehavior: If user quits multi-prompt early, result.value contains only completed values.
result = prompts.multi([
InquirerText("A:"),
InquirerText("B:"),
InquirerText("C:")
])
# If user completes A and B, then quits:
# result.command == 'quit'
# result.value == ['value_a', 'value_b'] # Only 2 values, not 3
# len(result.value) == 2Pattern: Check length before unpacking.
if result.command == 'select':
a, b, c = result.value # Safe - all 3 present
elif result.command == 'quit':
# Partial data
if len(result.value) >= 2:
a, b = result.value[:2]
# Process partial dataMistake: Using result.value without checking result.command.
# WRONG
result = prompts.text("Name:")
process(result.value) # Bug: might process None or partial data if user quitCorrect:
result = prompts.text("Name:")
if result.command == 'select':
process(result.value) # Safe
elif result.command == 'quit':
print("User quit")Mistake: Using prompts.number() result as integer without conversion.
# WRONG
result = prompts.number("Port:")
start_server(result.value) # Bug: result.value is string, not intCorrect:
result = prompts.number("Port:")
if result.command == 'select':
port = int(result.value) # Convert to int
start_server(port)Mistake: Assuming checkbox enabled parameter works.
# WRONG - does not work in v0.2.0
result = prompts.checkbox("Select:", ["A", "B", "C"], enabled=["A", "B"])
# Expects A and B to be checked, but all start uncheckedCorrect: Document to user that they must manually check items, or use a workaround with confirm prompts.
# Workaround: Ask for each item individually
items = ["A", "B", "C"]
selected = []
for item in items:
result = prompts.confirm(f"Enable {item}?", default=True)
if result.command == 'select' and result.value:
selected.append(item)Mistake: Not checking command in inquiry_func, leading to None values.
# WRONG
def flow(app):
r1 = app.prompt(InquirerText("First:"))
r2 = app.prompt(InquirerText("Second:"))
app.stop((r1.value, r2.value)) # Bug: if user quit r1, r2.value might be unexpectedCorrect: Check command and return early.
def flow(app):
r1 = app.prompt(InquirerText("First:"))
if r1.command != 'select':
return # User quit
r2 = app.prompt(InquirerText("Second:"))
if r2.command != 'select':
return
app.stop((r1.value, r2.value))Mistake: Assuming select result is always string when using Choice objects.
from inquirer_textual.common.Choice import Choice
choices = [Choice("A", data={"id": 1}), Choice("B", data={"id": 2})]
result = prompts.select("Choose:", choices)
# WRONG
name = result.value.upper() # Bug: result.value is Choice, not strCorrect: Check type or access Choice.name.
if result.command == 'select':
if isinstance(result.value, Choice):
name = result.value.name
data = result.value.data
else:
name = result.valueImportant: Inquirer-textual does NOT raise exceptions for normal user actions:
RuntimeError: Calling app.prompt() after app.stop().
def flow(app):
r1 = app.prompt(InquirerText("First:"))
app.stop(r1.value)
r2 = app.prompt(InquirerText("Second:")) # Raises RuntimeError
# Solution: Return after stop
def flow(app):
r1 = app.prompt(InquirerText("First:"))
app.stop(r1.value)
returnKeyboardInterrupt: Only if user presses Ctrl+C when NOT allowed (i.e., mandatory=True). But this is handled internally and should not propagate.
Import Errors: If inquirer-textual or textual not installed.
try:
from inquirer_textual import prompts
except ImportError:
print("Please install inquirer-textual: pip install inquirer-textual")
exit(1)result = prompts.text("Input:")
if result.command == 'select':
# Safe to use result.value
process(result.value)
elif result.command == 'quit':
# User quit
exit(0)
else:
# Unknown command (shortcuts, etc.)
handle_custom_command(result.command)result = prompts.number("Port:")
if result.command == 'select':
try:
port = int(result.value)
if 1024 <= port <= 65535:
start_server(port)
else:
print("Port must be 1024-65535")
except ValueError:
print("Invalid port number")result = prompts.multi([widget1, widget2, widget3])
if result.command == 'select':
# All completed
v1, v2, v3 = result.value
elif result.command == 'quit':
# Partial completion
completed = len(result.value)
if completed >= 2:
# Process partial
save_draft(result.value)result = prompts.text("Name (optional):")
if result.command == 'quit':
# User quit - use default
name = "Anonymous"
elif result.command == 'select':
name = result.value.strip() or "Anonymous"
else:
name = "Anonymous"
process(name)def safe_flow(app):
"""Flow with comprehensive error handling."""
try:
r1 = app.prompt(InquirerText("First:"))
if r1.command != 'select':
app.stop(None)
return
r2 = app.prompt(InquirerText("Second:"))
if r2.command != 'select':
app.stop(None)
return
app.stop((r1.value, r2.value))
except Exception as e:
print(f"Error: {e}")
app.stop(None)
app = InquirerApp()
result = app.run(inline=True, inquiry_func=safe_flow)
if result.value is None:
print("Flow did not complete")
else:
process(result.value)