Last modified: Nov 02, 2025 By Alexander Williams
Python Typer Custom Shell Completion Guide
Shell completion makes CLI tools user-friendly. It suggests options as users type. Typer provides built-in completion support.
This guide teaches custom completion functions. You will learn to create dynamic suggestions. These adapt to your application's context.
Understanding Shell Completion
Shell completion helps users discover commands. It reduces typing errors. It speeds up command execution.
Typer supports bash, zsh, and fish shells. The framework handles completion automatically. But custom functions add powerful features.
Custom completion provides context-aware suggestions. These can come from databases or APIs. They make your CLI intelligent.
Basic Completion Setup
First, install Typer if not already done. Use pip for installation.
pip install typer
Create a simple CLI application. This demonstrates basic completion.
import typer
app = typer.Typer()
@app.command()
def greet(name: str):
typer.echo(f"Hello, {name}!")
if __name__ == "__main__":
app()
Test the basic completion. Install the completion for your shell.
python my_app.py --install-completion
Creating Custom Completion Functions
Custom functions use the typer.Context object. They access command context. They return relevant suggestions.
Define a function that returns completion items. Use the autocompletion parameter. Attach it to your command arguments.
def complete_name(ctx: typer.Context, incomplete: str):
names = ["Alice", "Bob", "Charlie", "David", "Eve"]
return [name for name in names if name.startswith(incomplete)]
@app.command()
def greet(
name: str = typer.Argument(..., help="Name to greet", autocompletion=complete_name)
):
typer.echo(f"Hello, {name}!")
The function filters names based on input. It shows only matching suggestions.
Dynamic Completion from External Sources
Custom completion can fetch data dynamically. This is useful for file systems or APIs.
Create a function that lists files. It completes file paths in the current directory.
import os
def complete_files(ctx: typer.Context, incomplete: str):
current_dir = os.getcwd()
files = []
for item in os.listdir(current_dir):
if item.startswith(incomplete):
files.append(item)
return files
@app.command()
def process_file(
filename: str = typer.Argument(..., autocompletion=complete_files)
):
typer.echo(f"Processing {filename}")
This provides real-time file suggestions. Users can navigate directories easily.
Context-Aware Completion
Use the context object for smarter completion. Access previous arguments. Make suggestions based on command state.
Create a deployment command. Suggest environments based on selected project.
def complete_environment(ctx: typer.Context, incomplete: str):
# Get the project from context
project = ctx.params.get("project")
environments = {
"web-app": ["development", "staging", "production"],
"api": ["dev", "test", "prod"],
"database": ["local", "cloud"]
}
if project in environments:
return [env for env in environments[project] if env.startswith(incomplete)]
return []
@app.command()
def deploy(
project: str = typer.Argument(..., autocompletion=complete_environment),
environment: str = typer.Argument(..., autocompletion=complete_environment)
):
typer.echo(f"Deploying {project} to {environment}")
The completion changes based on project selection. This creates intelligent workflows.
Integration with Other Typer Features
Custom completion works well with other Typer features. Combine it with Pydantic models for validation.
Check our Python Typer Pydantic Integration Guide for details.
Use completion with configuration files. Merge CLI arguments with file settings.
Learn more in our Python Typer Config File CLI Argument Merging guide.
Global options work with completion too. Create consistent CLI experiences.
See Python Typer Global Options with Root Callbacks for implementation.
Testing Completion Functions
Test your completion logic thoroughly. Use Typer's testing utilities.
Create unit tests for completion functions. Verify they return correct suggestions.
def test_complete_name():
# Mock context object
class MockContext:
params = {}
ctx = MockContext()
# Test completion with partial input
result = complete_name(ctx, "A")
assert "Alice" in result
assert "Bob" not in result
# Test completion with empty input
result = complete_name(ctx, "")
assert len(result) == 5
Testing ensures reliable completion behavior. It catches edge cases early.
Advanced Completion Patterns
Create hierarchical completion for complex commands. Suggest subcommands based on context.
Implement completion for nested command structures. This works well for multi-level CLIs.
def complete_subcommand(ctx: typer.Context, incomplete: str):
commands = ["init", "deploy", "status", "logs", "cleanup"]
return [cmd for cmd in commands if cmd.startswith(incomplete)]
@app.command()
def project(
action: str = typer.Argument(..., autocompletion=complete_subcommand)
):
typer.echo(f"Executing project {action}")
This pattern helps users discover available actions. It improves command discovery.
Performance Considerations
Completion functions should be fast. Users expect instant suggestions.
Avoid expensive operations in completion. Use caching for external data sources.
Implement lazy loading for large datasets. Load suggestions only when needed.
For performance tips, see our Boost Python Typer Performance with Lazy Imports guide.
Shell-Specific Completion
Different shells have unique features. Customize completion behavior per shell.
Detect the shell type in your function. Return formatted suggestions accordingly.
def shell_aware_completion(ctx: typer.Context, incomplete: str):
shell = os.environ.get("SHELL", "")
items = ["item1", "item2", "item3"]
filtered = [item for item in items if item.startswith(incomplete)]
# Add shell-specific formatting
if "zsh" in shell:
return [f"{item}:custom_description" for item in filtered]
elif "fish" in shell:
return [(item, f"Description for {item}") for item in filtered]
else:
return filtered
This provides optimal experience for each shell. Users get native feeling completion.
Error Handling in Completion
Handle errors gracefully in completion functions. Don't let exceptions break the shell.
Wrap external calls in try-except blocks. Return empty list on failure.
def safe_completion(ctx: typer.Context, incomplete: str):
try:
# Potentially risky operation
items = fetch_from_database(incomplete)
return items
except Exception:
# Return empty list on error
return []
This ensures completion never crashes the shell. It degrades gracefully.
Conclusion
Custom shell completion enhances CLI usability. It makes applications intuitive and efficient.
Start with simple static completion. Progress to dynamic context-aware functions.
Remember performance and error handling. Test across different shell environments.
Combine completion with other Typer features. Create professional command-line tools.
Your users will appreciate the smart suggestions. They will work faster with your CLI.