Accelerate Python CLI tab completion with automatic snapshot-based entrypoint generation.
Python CLI applications using argparse and argcomplete often suffer from slow tab completion due to high import times. Many modern Python SDKs, when exposed through CLIs, have import times exceeding 500ms (e.g., import requests alone is 100ms). This leads to a poor user experience, as every tab completion can trigger a full Python process startup and import, resulting in noticeable delays.
FastEntry automatically generates lightweight completion entrypoints for existing CLI applications, providing super fast completion without requiring any code changes from CLI maintainers.
- CLI maintainer runs:
fastentry enable mycli.py - FastEntry automatically generates:
mycli_completion.py(lightweight entrypoint with minimal imports)mycli_snapshot.json(parser structure for fast completion)
- CLI maintainer updates
pyproject.tomlto point to the new entrypoint - Users get fast completion without any code changes
pip install fastentry# Enable fast completion for a CLI file
fastentry enable mycli.py
# This generates:
# - mycli_completion.py (lightweight entrypoint)
# - mycli_snapshot.json (parser structure)from fastentry import enable_fast_completion
# Generate lightweight entrypoint
output_path = enable_fast_completion('mycli.py')
print(f'Generated: {output_path}')# pyproject.toml
[project.scripts]
mycli = "mycli_completion:main" # Point to lightweight entrypointNo changes required! Users get fast completion automatically after CLI maintainers adopt FastEntry.
#!/usr/bin/env python
# PYTHON_ARGCOMPLETE_OK
# Heavy imports at module level (the problem!)
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import requests
import argparse
import argcomplete
def main():
parser = argparse.ArgumentParser()
# ... parser setup
argcomplete.autocomplete(parser)
args = parser.parse_args()
# ... CLI logic
if __name__ == "__main__":
main()#!/usr/bin/env python
# PYTHON_ARGCOMPLETE_OK
"""
Lightweight completion entrypoint for mycli.py
Generated by FastEntry - DO NOT EDIT MANUALLY
"""
import os
import sys
import json
from pathlib import Path
def is_completion_request():
return os.environ.get('_ARGCOMPLETE') == '1'
def handle_completion_fast():
"""Handle completion requests with minimal imports"""
try:
# Try to use snapshot first
snapshot_path = Path(__file__).parent / "mycli_snapshot.json"
if snapshot_path.exists():
from fastentry import FastEntry
fast_entry = FastEntry(str(snapshot_path))
# ... snapshot-based completion
return
# Fallback to regular argcomplete
import argcomplete
# ... regular completion
except Exception as e:
# Final fallback: import the original CLI (slow but works)
import mycli
mycli.main()
def main():
if is_completion_request():
handle_completion_fast()
return
# Import and run the original CLI
import mycli
mycli.main()
if __name__ == "__main__":
main()- ✅ No Code Changes: CLI maintainers don't need to refactor their code
- ✅ Automatic Generation: Library handles all the complexity
- ✅ Fast Completion: Minimal imports during completion requests
- ✅ Robust Fallback: Always works, even if snapshot fails
- ✅ Backward Compatible: Existing CLIs work unchanged
- ✅ Library-Based: Easy to adopt and integrate
- ❌ Dynamic Completions Not Captured: If your CLI uses dynamic completions (e.g., choices that depend on runtime data, environment variables, or external resources), these cannot be fully represented in the static
snapshot.jsongenerated at build time. In such cases, FastEntry will fall back to the original script for those completions, which may be slower. - ❌ Snapshot Must Be Regenerated on CLI Changes: If you update your CLI's arguments or completion logic, you need to re-run
fastentry enableto regenerate the snapshot and entrypoint. This is a one-time cost, but it's a cost nonetheless.
The system has multiple fallback levels:
- Snapshot-based completion (fastest)
- Regular argcomplete (medium speed)
- Import original CLI (slow but works)
This ensures completion always works, even if the snapshot is missing or corrupted.
This project uses Black for code formatting and Flake8 for linting. Both tools are configured via pyproject.toml (no separate config files).
To format code:
black .To lint code:
flake8 .pytest is included as a development dependency, but there is currently no tests/ folder or test suite in this project. You may add your own tests as needed.