Python Inquirer
A comprehensive reference for building interactive command-line interfaces with Python's inquirer libraries. This covers both the original python-inquirer and the modern InquirerPy libraries for creating engaging CLI prompts.
Quick Start
Installation
# Original python-inquirer (stable, simple)
pip install inquirer
# InquirerPy (modern, feature-rich)
pip install inquirerpy
# Both libraries can coexist
pip install inquirer inquirerpy
Basic Usage Comparison
# python-inquirer (classic)
import inquirer
questions = [
inquirer.Text('name', message="What's your name?"),
inquirer.List('color', message="Favorite color?", choices=['Red', 'Blue', 'Green'])
]
answers = inquirer.prompt(questions)
# InquirerPy (modern - classic syntax)
from InquirerPy import prompt
questions = [
{"type": "input", "message": "What's your name?", "name": "name"},
{"type": "list", "message": "Favorite color?", "choices": ["Red", "Blue", "Green"]}
]
answers = prompt(questions)
# InquirerPy (modern - alternate syntax)
from InquirerPy import inquirer
name = inquirer.text(message="What's your name?").execute()
color = inquirer.select(message="Favorite color?", choices=["Red", "Blue", "Green"]).execute()
Core Prompt Types
Text Input
python-inquirer
import inquirer
# Basic text input
questions = [
inquirer.Text('username', message="Enter username"),
inquirer.Text('email', message="Enter email",
validate=lambda _, x: '@' in x),
]
answers = inquirer.prompt(questions)
InquirerPy
# Classic syntax
from InquirerPy import prompt
questions = [
{
"type": "input",
"message": "Username:",
"name": "username",
"validate": lambda result: len(result) > 0,
"invalid_message": "Username cannot be empty"
}
]
result = prompt(questions)
# Alternate syntax
from InquirerPy import inquirer
username = inquirer.text(
message="Username:",
validate=lambda result: len(result) > 0,
invalid_message="Username cannot be empty"
).execute()
Password Input
python-inquirer
import inquirer
questions = [
inquirer.Password('password', message="Enter password")
]
answers = inquirer.prompt(questions)
InquirerPy
# Classic syntax
from InquirerPy import prompt
from InquirerPy.validator import PasswordValidator
questions = [
{
"type": "secret",
"message": "Password:",
"validate": PasswordValidator(
length=8,
cap=True,
special=True,
number=True
)
}
]
result = prompt(questions)
# Alternate syntax
password = inquirer.secret(
message="Password:",
validate=PasswordValidator(length=8, cap=True, special=True, number=True)
).execute()
Single Choice Lists
python-inquirer
import inquirer
questions = [
inquirer.List('size',
message="What size?",
choices=['Small', 'Medium', 'Large'],
carousel=True # Circular navigation
)
]
answers = inquirer.prompt(questions)
InquirerPy
# Classic syntax
questions = [
{
"type": "list",
"message": "What size?",
"choices": ["Small", "Medium", "Large"],
"default": "Medium"
}
]
result = prompt(questions)
# Alternate syntax
size = inquirer.select(
message="What size?",
choices=["Small", "Medium", "Large"],
default="Medium"
).execute()
Multiple Choice (Checkboxes)
python-inquirer
import inquirer
questions = [
inquirer.Checkbox('interests',
message="Select interests",
choices=['Music', 'Sports', 'Reading', 'Gaming'],
)
]
answers = inquirer.prompt(questions)
InquirerPy
# With validation for minimum selections
from InquirerPy import inquirer
interests = inquirer.checkbox(
message="Select interests:",
choices=["Music", "Sports", "Reading", "Gaming"],
validate=lambda selection: len(selection) >= 2,
invalid_message="Select at least 2 interests"
).execute()
Confirmation Prompts
python-inquirer
import inquirer
questions = [
inquirer.Confirm('proceed', message="Continue?", default=True)
]
answers = inquirer.prompt(questions)
InquirerPy
# Custom confirmation letters (localization)
from InquirerPy import inquirer
confirm = inquirer.confirm(
message="Proceed?",
default=True,
confirm_letter="s", # 's' for 'Sim' (Yes in Portuguese)
reject_letter="n", # 'n' for 'NĆ£o' (No in Portuguese)
transformer=lambda result: "Sim" if result else "NĆ£o"
).execute()
File Path Selection
python-inquirer
import inquirer
questions = [
inquirer.Path('config_file',
message="Config file location?",
path_type=inquirer.Path.FILE,
exists=True
)
]
answers = inquirer.prompt(questions)
InquirerPy
from InquirerPy import inquirer
from InquirerPy.validator import PathValidator
filepath = inquirer.filepath(
message="Select file:",
validate=PathValidator("Path must be valid")
).execute()
Advanced Features
Dynamic Questions (Conditional Logic)
python-inquirer
import inquirer
questions = [
inquirer.Confirm('married', message="Are you married?"),
inquirer.Text('spouse_name',
message="Spouse name?",
ignore=lambda x: not x['married'] # Skip if not married
)
]
answers = inquirer.prompt(questions)
InquirerPy
from InquirerPy import prompt
questions = [
{"type": "confirm", "message": "Are you married?", "name": "married"},
{
"type": "input",
"message": "Spouse name?",
"name": "spouse_name",
"when": lambda result: result["married"] # Show only if married
}
]
result = prompt(questions)
Choice Objects and Separators
InquirerPy Advanced Choices
from InquirerPy import inquirer
from InquirerPy.base.control import Choice
from InquirerPy.separator import Separator
# Advanced choice configuration
choices = [
Choice("aws-east-1", name="AWS East (Virginia)", enabled=True),
Choice("aws-west-1", name="AWS West (California)", enabled=False),
Separator(),
"gcp-us-central",
"azure-eastus"
]
region = inquirer.select(
message="Select cloud region:",
choices=choices,
multiselect=True,
transformer=lambda result: f"{len(result)} region(s) selected"
).execute()
Custom Validation
python-inquirer
import inquirer
import re
def phone_validator(answers, current):
if not re.match(r'^\+?\d[\d ]+\d$', current):
raise inquirer.errors.ValidationError('', reason='Invalid phone format')
return True
questions = [
inquirer.Text('phone',
message="Phone number",
validate=phone_validator)
]
answers = inquirer.prompt(questions)
InquirerPy
from InquirerPy import inquirer
from InquirerPy.validator import NumberValidator, EmptyInputValidator
import re
# Built-in validators
age = inquirer.text(
message="Age:",
validate=NumberValidator(float_allowed=False),
filter=lambda result: int(result) # Convert to integer
).execute()
# Custom validator function
def email_validator(email):
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
return re.match(pattern, email) is not None
email = inquirer.text(
message="Email:",
validate=email_validator,
invalid_message="Please enter a valid email address"
).execute()
# Custom validator class
from prompt_toolkit.validation import ValidationError, Validator
class CustomEmailValidator(Validator):
def validate(self, document):
if '@' not in document.text:
raise ValidationError(
message="Email must contain @ symbol",
cursor_position=len(document.text)
)
email = inquirer.text(
message="Email:",
validate=CustomEmailValidator()
).execute()
Styling and Theming
InquirerPy Styling
from InquirerPy import prompt
from InquirerPy.utils import color_print
# Custom style dictionary
custom_style = {
"questionmark": "#ff9d00 bold",
"answer": "#61afef",
"input": "#98c379",
"question": "",
"answered_question": "",
"instruction": "#abb2bf",
"pointer": "#61afef",
"checkbox": "#98c379",
"separator": "",
"skipped": "#5c6370",
"validator": "#e06c75",
"marker": "#e5c07b",
}
result = prompt(
{"type": "input", "message": "Styled prompt:"},
style=custom_style,
vi_mode=True # Enable vim keybindings
)
# Environment variable styling
import os
os.environ["INQUIRERPY_STYLE_QUESTIONMARK"] = "#ff9d00 bold"
os.environ["INQUIRERPY_STYLE_ANSWER"] = "#61afef"
# Color printing utility
color_print([("#e5c07b", "Hello "), ("#61afef", "World!")])
Default InquirerPy Theme
# Based on onedark color palette
default_style = {
"questionmark": "#e5c07b", # Yellow
"answermark": "#e5c07b", # Yellow
"answer": "#61afef", # Blue
"input": "#98c379", # Green
"question": "", # Default
"answered_question": "", # Default
"instruction": "#abb2bf", # Light gray
"long_instruction": "#abb2bf", # Light gray
"pointer": "#61afef", # Blue
"checkbox": "#98c379", # Green
"separator": "", # Default
"skipped": "#5c6370", # Dark gray
"validator": "", # Default
"marker": "#e5c07b", # Yellow
"fuzzy_prompt": "#c678dd", # Purple
"fuzzy_info": "#abb2bf", # Light gray
"fuzzy_border": "#4b5263", # Dark blue
"fuzzy_match": "#c678dd", # Purple
"spinner_pattern": "#e5c07b", # Yellow
"spinner_text": "", # Default
}
Advanced InquirerPy Features
Fuzzy Search
from InquirerPy import inquirer
# Large list with fuzzy search
frameworks = [
"React", "Vue", "Angular", "Svelte", "Next.js",
"Django", "Flask", "FastAPI", "Express", "Koa",
"Spring Boot", "Laravel", "Ruby on Rails"
]
framework = inquirer.fuzzy(
message="Select framework:",
choices=frameworks,
max_height="70%", # 70% of terminal height
match_exact=True, # Enable exact substring matching
exact_symbol=" E" # Indicator for exact matches
).execute()
Expand Choices
from InquirerPy import inquirer
from InquirerPy.prompts.expand import ExpandChoice, ExpandHelp
# Expand prompt for quick selection
choices = [
ExpandChoice("create", key="c", name="Create new project"),
ExpandChoice("open", key="o", name="Open existing project"),
ExpandChoice("delete", key="d", name="Delete project"),
ExpandChoice("quit", key="q", name="Quit application")
]
action = inquirer.expand(
message="What would you like to do?",
choices=choices,
expand_help=ExpandHelp(key="h", message="Show help")
).execute()
Number Input
from InquirerPy import inquirer
from InquirerPy.validator import NumberValidator
# Number input with validation
age = inquirer.number(
message="Enter your age:",
min_allowed=0,
max_allowed=150,
validate=NumberValidator(),
replace_mode=True # Replace entire input on type
).execute()
Custom Keybindings
from InquirerPy import inquirer
# Custom keybindings
keybindings = {
"skip": [{"key": "c-c"}], # Ctrl+C to skip
"interrupt": [{"key": "c-d"}], # Ctrl+D to interrupt
"toggle-all": [{"key": ["c-a", "space"]}] # Ctrl+A then Space
}
result = inquirer.select(
message="Select options:",
choices=["Option 1", "Option 2", "Option 3", "Option 4"],
multiselect=True,
keybindings=keybindings,
vi_mode=True # Enable vim mode
).execute()
Height Control
from InquirerPy import inquirer
# Control prompt height
result = inquirer.select(
message="Select from long list:",
choices=[f"Item {i}" for i in range(100)],
height=10, # Fixed height of 10 lines
max_height="50%", # Max 50% of terminal height
instruction="Use j/k to navigate"
).execute()
Practical Examples
User Registration Form
from InquirerPy import inquirer
from InquirerPy.validator import EmptyInputValidator, PasswordValidator
import re
def email_validator(email):
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
if not re.match(pattern, email):
return False
return True
def register_user():
print("=== User Registration ===")
# Collect user information
username = inquirer.text(
message="Username:",
validate=EmptyInputValidator("Username is required"),
instruction="Letters, numbers, and underscores only"
).execute()
email = inquirer.text(
message="Email address:",
validate=email_validator,
invalid_message="Please enter a valid email address"
).execute()
password = inquirer.secret(
message="Password:",
validate=PasswordValidator(
length=8,
cap=True,
special=True,
number=True,
message="Password must be 8+ chars with uppercase, number, and special character"
)
).execute()
# Confirm password
confirm_password = inquirer.secret(
message="Confirm password:",
validate=lambda pwd: pwd == password,
invalid_message="Passwords do not match"
).execute()
# Additional preferences
newsletter = inquirer.confirm(
message="Subscribe to newsletter?",
default=False
).execute()
interests = inquirer.checkbox(
message="Select interests:",
choices=[
"Technology", "Sports", "Music", "Travel",
"Food", "Books", "Movies", "Gaming"
],
validate=lambda selection: len(selection) > 0,
invalid_message="Please select at least one interest"
).execute()
# Summary
print(f"\nā
Registration successful!")
print(f"Username: {username}")
print(f"Email: {email}")
print(f"Newsletter: {'Yes' if newsletter else 'No'}")
print(f"Interests: {', '.join(interests)}")
return {
'username': username,
'email': email,
'password': password,
'newsletter': newsletter,
'interests': interests
}
# Run registration
user_data = register_user()
Project Setup Wizard
from InquirerPy import inquirer
from InquirerPy.base.control import Choice
from InquirerPy.separator import Separator
import os
def project_setup():
print("š Project Setup Wizard")
# Project type selection
project_type = inquirer.select(
message="What type of project?",
choices=[
Choice("web", name="š Web Application"),
Choice("api", name="š REST API"),
Choice("desktop", name="š„ļø Desktop Application"),
Choice("cli", name="ā” Command Line Tool"),
Choice("library", name="š Library/Package")
]
).execute()
# Programming language
language = inquirer.select(
message="Programming language:",
choices=[
"Python", "JavaScript", "TypeScript", "Java",
"Go", "Rust", "C++", "C#"
]
).execute()
# Framework selection (conditional)
framework = None
if project_type == "web":
if language == "Python":
framework = inquirer.select(
message="Web framework:",
choices=["Django", "Flask", "FastAPI", "Tornado"]
).execute()
elif language in ["JavaScript", "TypeScript"]:
framework = inquirer.select(
message="Web framework:",
choices=["React", "Vue", "Angular", "Next.js", "Express"]
).execute()
# Features
features = inquirer.checkbox(
message="Select features to include:",
choices=[
"Database integration",
"Authentication",
"Testing setup",
"Docker support",
"CI/CD pipeline",
"Documentation",
"Logging",
"Configuration management"
]
).execute()
# Project name and location
project_name = inquirer.text(
message="Project name:",
validate=lambda name: len(name) > 0 and name.replace('-', '').replace('_', '').isalnum(),
invalid_message="Project name must contain only letters, numbers, hyphens, and underscores"
).execute()
default_path = os.path.join(os.path.expanduser("~"), "projects", project_name)
project_path = inquirer.text(
message="Project location:",
default=default_path
).execute()
# Confirmation
print(f"\nš Project Summary:")
print(f"Type: {project_type}")
print(f"Language: {language}")
if framework:
print(f"Framework: {framework}")
print(f"Features: {', '.join(features)}")
print(f"Name: {project_name}")
print(f"Location: {project_path}")
proceed = inquirer.confirm(
message="Create project with these settings?",
default=True
).execute()
if proceed:
print("ā
Project created successfully!")
# Here you would create the actual project structure
return {
'type': project_type,
'language': language,
'framework': framework,
'features': features,
'name': project_name,
'path': project_path
}
else:
print("ā Project creation cancelled.")
return None
# Run project setup
project_config = project_setup()
Configuration Manager
from InquirerPy import inquirer
from InquirerPy.validator import NumberValidator
import json
import os
def manage_config():
config_file = "app_config.json"
# Load existing config
if os.path.exists(config_file):
with open(config_file, 'r') as f:
config = json.load(f)
print("š Loaded existing configuration")
else:
config = {}
print("š Creating new configuration")
# Menu system
while True:
action = inquirer.select(
message="Configuration Manager:",
choices=[
Choice("view", name="š View current config"),
Choice("edit", name="āļø Edit settings"),
Choice("add", name="ā Add new setting"),
Choice("delete", name="šļø Delete setting"),
Choice("save", name="š¾ Save and exit"),
Choice("exit", name="šŖ Exit without saving")
]
).execute()
if action == "view":
if config:
print("\nš Current Configuration:")
for key, value in config.items():
print(f" {key}: {value}")
else:
print("ā ļø Configuration is empty")
elif action == "edit":
if not config:
print("ā ļø No settings to edit")
continue
setting = inquirer.select(
message="Select setting to edit:",
choices=list(config.keys())
).execute()
current_value = config[setting]
data_type = inquirer.select(
message=f"Data type for '{setting}':",
choices=["String", "Number", "Boolean"],
default="String"
).execute()
if data_type == "String":
new_value = inquirer.text(
message=f"New value for '{setting}':",
default=str(current_value)
).execute()
elif data_type == "Number":
new_value = inquirer.number(
message=f"New value for '{setting}':",
default=float(current_value) if isinstance(current_value, (int, float)) else 0
).execute()
elif data_type == "Boolean":
new_value = inquirer.confirm(
message=f"Enable '{setting}'?",
default=bool(current_value)
).execute()
config[setting] = new_value
print(f"ā
Updated {setting} = {new_value}")
elif action == "add":
key = inquirer.text(
message="Setting name:",
validate=lambda k: len(k) > 0 and k not in config,
invalid_message="Setting name must be unique and non-empty"
).execute()
data_type = inquirer.select(
message="Data type:",
choices=["String", "Number", "Boolean"]
).execute()
if data_type == "String":
value = inquirer.text(message="Value:").execute()
elif data_type == "Number":
value = inquirer.number(message="Value:").execute()
elif data_type == "Boolean":
value = inquirer.confirm(message="Enable?").execute()
config[key] = value
print(f"ā
Added {key} = {value}")
elif action == "delete":
if not config:
print("ā ļø No settings to delete")
continue
setting = inquirer.select(
message="Select setting to delete:",
choices=list(config.keys())
).execute()
confirm = inquirer.confirm(
message=f"Delete '{setting}'?",
default=False
).execute()
if confirm:
del config[setting]
print(f"ā
Deleted {setting}")
elif action == "save":
with open(config_file, 'w') as f:
json.dump(config, f, indent=2)
print(f"ā
Configuration saved to {config_file}")
break
elif action == "exit":
save_changes = inquirer.confirm(
message="Save changes before exiting?",
default=True
).execute()
if save_changes:
with open(config_file, 'w') as f:
json.dump(config, f, indent=2)
print(f"ā
Configuration saved to {config_file}")
break
# Run configuration manager
manage_config()
Testing Interactive Prompts
Unit Testing with Mock
import unittest
from unittest.mock import patch
from InquirerPy import prompt
def get_user_info():
questions = [
{"type": "input", "message": "Name:", "name": "name"},
{"type": "confirm", "message": "Subscribe?", "name": "subscribe"}
]
return prompt(questions)
class TestPrompts(unittest.TestCase):
@patch('your_module.prompt')
def test_get_user_info(self, mock_prompt):
# Mock the prompt response
mock_prompt.return_value = {"name": "John", "subscribe": True}
result = get_user_info()
self.assertEqual(result["name"], "John")
self.assertTrue(result["subscribe"])
mock_prompt.assert_called_once()
if __name__ == '__main__':
unittest.main()
Performance and Best Practices
1. Choose the Right Library
- python-inquirer: Simple, stable, fewer dependencies
- InquirerPy: Modern, feature-rich, better styling, async support
2. Validation Best Practices
# Good: Clear, specific error messages
def validate_email(email):
if not email:
return "Email is required"
if '@' not in email:
return "Email must contain @ symbol"
if not email.endswith(('.com', '.org', '.net')):
return "Email must end with .com, .org, or .net"
return True
# Good: Use built-in validators when possible
from InquirerPy.validator import EmptyInputValidator, NumberValidator, PathValidator
# Good: Combine validation with filtering
age = inquirer.text(
message="Age:",
validate=NumberValidator(float_allowed=False),
filter=lambda x: int(x) # Convert to integer
).execute()
3. User Experience Tips
# Use clear, action-oriented messages
message="Select deployment environment:" # Good
message="Environment?" # Poor
# Provide helpful instructions
instruction="Use arrow keys to navigate, Enter to select"
# Set sensible defaults
default="production" if is_prod_deploy else "development"
# Use separators to group related options
choices=[
"Development servers",
Separator(),
"dev-01", "dev-02", "dev-03",
Separator(),
"Production servers",
Separator(),
"prod-01", "prod-02"
]
# Transform output for better UX
transformer=lambda result: f"{len(result)} items selected"
4. Error Handling
from InquirerPy import inquirer
from InquirerPy.exceptions import InvalidArgument
try:
result = inquirer.select(
message="Select option:",
choices=["A", "B", "C"]
).execute()
except KeyboardInterrupt:
print("\nā Operation cancelled by user")
exit(1)
except InvalidArgument as e:
print(f"ā Configuration error: {e}")
exit(1)
except Exception as e:
print(f"ā Unexpected error: {e}")
exit(1)
5. Async Support (InquirerPy)
import asyncio
from InquirerPy import prompt_async
async def async_prompts():
questions = [
{"type": "input", "message": "Name:"},
{"type": "confirm", "message": "Continue?"}
]
result = await prompt_async(questions)
return result
# Run async prompts
result = asyncio.run(async_prompts())
This cheat sheet covers both libraries comprehensively, providing you with the tools to create engaging, interactive command-line interfaces. Start with simple prompts and gradually incorporate advanced features like validation, styling, and dynamic behavior as your applications grow in complexity.