Generate fast Bash completion artifacts for import-heavy Python CLIs built with argparse and argcomplete.
Default argcomplete runs the target Python CLI on every completion request. For SDK-backed CLIs that means paying for:
- Python startup
- heavy imports
- parser construction
- completion logic
That can easily turn <TAB> into a 200-600+ ms operation.
argcomplete-snapshot extracts a lightweight completion model once and writes shell-native cache artifacts:
snapshot-v1.jsoncompletion-v1.bashversion-stamp
Steady-state completion stays in Bash. Python only runs when:
- artifacts are missing
- the CLI changed after upgrade
- a maintainer explicitly routes a dynamic resolver through a fallback command
argcomplete: good dynamic behavior, but slow because it starts Python on every completionshtab: fast for static completion scripts, but not built around lazy self-refresh afterpip install -U ...or explicit dynamic fallback contracts
This package is for the middle ground:
- your CLI is too heavy for default
argcomplete - you still need more lifecycle support than a pure static script
- Python
3.13+ - Bash
4+
Bash 4+ is required because the generated runtime uses associative arrays.
Dynamic resolvers require an explicit fallback command. They are not inferred automatically.
Install from PyPI:
pip install argcomplete-snapshotInstall benchmark extras from a checkout:
pip install ".[bench]"import argparse
from argcomplete_snapshot import CompletionKind, set_completion
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(prog="mycli")
parser.add_argument("--output", choices=["json", "yaml", "text"])
profile = parser.add_argument("--profile")
set_completion(profile, resolver="config_profiles")
path = parser.add_argument("--path")
set_completion(path, kind=CompletionKind.FILE)
return parseracs-refresh refresh \
--factory mypackage.cli:build_parser \
--cli-name mycli \
--distribution mycliacs-refresh print-activation \
--factory mypackage.cli:build_parser \
--cli-name mycli \
--distribution mycliacs-refresh print-activation \
--factory mypackage.cli:build_parser \
--cli-name mycli \
--distribution mycli \
--fallback-command "mycli --resolver-fallback"Use fallback only for genuinely dynamic completions. Static options and choices should stay on the Bash hot path.
~/.cache/argcomplete-snapshot/<cli-name>/
snapshot-v1.json
completion-v1.bash
version-stamp
Results:
- benchmarks/results/README.md
- benchmarks/results/benchmark-results.json
- benchmarks/results/hot-path-comparison.png
- benchmarks/results/spawned-comparison.png
Hot path:
Spawned shell:
Current published medians:
| Case | argcomplete |
snapshot |
shtab |
|---|---|---|---|
| Static hot path | 491.10 ms | 1.77 ms | 23.46 ms |
| Static spawned shell | 647.65 ms | 165.43 ms | 95.86 ms |
Read those numbers carefully:
hot pathis the relevant steady-state completion metricspawned shellincludes shell startup and sourcing overhead- dynamic fallback cases are slower because they intentionally call runtime logic
Benchmark environment for the published results:
- macOS
26.3.1 - MacBook Pro
MacBookPro17,1 8CPU cores (4performance +4efficiency)16 GBmemory- Python
3.13.12underuv - benchmark fixtures simulate heavy CLIs by importing
requestsand sleeping for200 ms - benchmark strategy uses
5spawned-shell samples and15hot-path samples per case and implementation
Run locally:
make benchmarkpip install -e ".[dev,bench]"
make check
make benchmarkEarly release.
Implemented:
- typed snapshot model
argparseextraction- Bash artifact emission
- lazy cache refresh
- activation snippet generation
- explicit dynamic fallback contract
- benchmark fixtures and comparisons

