CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-questionary

Python library to build pretty command line user prompts with interactive forms and validation

Overall
score

96%

Overview
Eval results
Files

core-classes.mddocs/

Core Classes and Configuration

Essential classes for building and customizing prompts, including choice configuration, styling, validation, conditional logic, and utility functions for enhanced prompt functionality.

Capabilities

Question Class

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

Choice Configuration

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

Separator Class

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 Classes

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

Utility Functions

Formatted Text Output

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

Style and Validation Utilities

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

Constants and Configuration

UI Constants

# 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

Type Definitions

# 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
        """

Usage Examples

Custom Question Configuration

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}")

Conditional Question Execution

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}")

Custom Validation

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()

Advanced Choice Building

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()

Custom Separators

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()

Form with Manual Field Creation

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")

Styling Integration

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-questionary

docs

autocomplete-paths.md

core-classes.md

execution-control.md

forms.md

index.md

selection-prompts.md

text-input.md

tile.json