CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-inquirer-textual

Inquirer based on Textual for creating interactive command-line user input prompts

Overview
Eval results
Files

data-structures.mddocs/

Data Structures

Core data types: Result, Choice, and Shortcut.

Import

from inquirer_textual.common.Result import Result
from inquirer_textual.common.Choice import Choice
from inquirer_textual.common.Shortcut import Shortcut

Result[T]

Result 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."""

Common Commands

  • 'select': User pressed Enter to confirm (most common)
  • 'quit': User pressed Ctrl+D to quit
  • 'ctrl+c': User pressed Ctrl+C (when mandatory=False)
  • Custom: Any keyboard shortcut command
  • Choice.command: Custom command from Choice object

Usage

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)

Type Examples

# 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.value

Choice

Choice 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'

Use Cases

  1. Attach data to selections - store configuration, IDs, objects
  2. Custom commands - trigger different actions per choice
  3. Display vs value separation - show friendly name, use underlying data

Examples

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)

Shortcut

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 = True

Key Formats

  • Single keys: 'a', 'b', 'escape', 'enter', 'tab'
  • Ctrl combinations: 'ctrl+s', 'ctrl+q', 'ctrl+shift+d'
  • Function keys: 'f1', 'f2', ..., 'f12'

Auto-Description

If 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'

Examples

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)

Pattern: Command Handling

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 None

Pattern: Choice Data Access

Safely 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.value

Pattern: Type-Safe Multi-Prompt

from 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

docs

advanced-app.md

data-structures.md

errors-edge-cases.md

index.md

patterns-recipes.md

prompts-api.md

quick-reference.md

widgets-api.md

tile.json