[{"data":1,"prerenderedAt":2811},["ShallowReactive",2],{"page-\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Fargcomplete-for-argparse-clis\u002F":3,"content-directory":1967},{"id":4,"title":5,"body":6,"date":1953,"description":1954,"difficulty":1955,"draft":1956,"extension":1957,"meta":1958,"navigation":166,"path":1959,"seo":1960,"stem":1961,"tags":1962,"updated":1953,"__hash__":1966},"content\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Fargcomplete-for-argparse-clis\u002Findex.md","Tab Completion for argparse CLIs with argcomplete",{"type":7,"value":8,"toc":1933},"minimark",[9,34,39,56,60,64,91,109,113,694,697,758,761,766,769,1136,1139,1143,1156,1160,1163,1206,1225,1229,1273,1276,1280,1283,1803,1814,1818,1836,1840,1844,1854,1858,1861,1865,1876,1880,1883,1887,1894,1898,1929],[10,11,12,13,17,18,22,23,27,28,33],"p",{},"Click and Typer ship shell completion built in; argparse does not. For the many CLIs built on the standard library — internal tools, scripts that grew up, projects that avoid dependencies — ",[14,15,16],"strong",{},"argcomplete"," fills the gap. It hooks into your existing ",[19,20,21],"code",{},"ArgumentParser",": when the shell asks for completions, argcomplete runs your program in a special mode, walks the parser to see what can come next — subcommands, options, choices — and asks ",[24,25,26],"em",{},"completers"," attached to arguments for anything dynamic. No second definition of your interface is needed. This guide adds argcomplete to an argparse CLI with subcommands, writes a context-aware completer, wires up file completion with extension filtering, covers activation for bash and zsh, and tests completions without a shell. It belongs to the ",[29,30,32],"a",{"href":31},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002F","shell completion topic",".",[35,36,38],"h2",{"id":37},"prerequisites","Prerequisites",[40,41,42,50],"ul",{},[43,44,45,46,33],"li",{},"An argparse CLI, for example built as in ",[29,47,49],{"href":48},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargparse-subparsers-for-subcommands\u002F","argparse subparsers for subcommands",[43,51,52,55],{},[19,53,54],{},"uv add argcomplete"," (examples checked with argcomplete 3.x); bash or zsh for trying it interactively.",[35,57,59],{"id":58},"how-argcomplete-works","How argcomplete works",[61,62],"inline-diagram",{"name":63},"argc-flow",[10,65,66,67,70,71,74,75,78,79,82,83,86,87,90],{},"When you press Tab, the shell's completion function runs your program with environment variables describing the command line (",[19,68,69],{},"COMP_LINE",", ",[19,72,73],{},"COMP_POINT",") and ",[19,76,77],{},"_ARGCOMPLETE=1",". Your program starts normally; when it reaches ",[19,80,81],{},"argcomplete.autocomplete(parser)",", argcomplete sees the variables, parses the partial command line with your parser, computes candidates, writes them to a file descriptor the shell reads, and ",[14,84,85],{},"exits"," — your program's real work never runs. Without those variables, ",[19,88,89],{},"autocomplete()"," returns immediately and costs almost nothing.",[10,92,93,94,96,97,100,101,104,105,33],{},"Two consequences follow. ",[19,95,89],{}," must be called ",[24,98,99],{},"before"," ",[19,102,103],{},"parse_args()",", and as early as possible — everything executed before it runs on every Tab press. And the program's top-level imports are paid for on every completion, so heavy imports belong inside the commands, as discussed in ",[29,106,108],{"href":107},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Favoiding-import-time-side-effects\u002F","avoiding import-time side effects",[35,110,112],{"id":111},"the-recipe","The recipe",[114,115,120],"pre",{"className":116,"code":117,"language":118,"meta":119,"style":119},"language-python shiki shiki-themes github-light github-dark","#!\u002Fusr\u002Fbin\u002Fenv python\n# PYTHON_ARGCOMPLETE_OK\n# src\u002Fdeploytool\u002Fcli.py\nfrom __future__ import annotations\n\nimport argparse\nfrom pathlib import Path\n\nimport argcomplete\n\nENVIRONMENTS = [\"dev\", \"staging\", \"production\"]\n\n\ndef service_completer(prefix: str, parsed_args: argparse.Namespace, **kwargs) -> list[str]:\n    \"\"\"Complete service names; dev has an extra sandbox service.\"\"\"\n    services = [\"api\", \"web\", \"worker\", \"scheduler\"]\n    if getattr(parsed_args, \"env\", None) == \"dev\":\n        services.append(\"sandbox\")\n    return [s for s in services if s.startswith(prefix)]\n\n\ndef build_parser() -> argparse.ArgumentParser:\n    parser = argparse.ArgumentParser(prog=\"deploytool\")\n    sub = parser.add_subparsers(dest=\"command\", required=True)\n    deploy = sub.add_parser(\"deploy\", help=\"deploy a service\")\n    deploy.add_argument(\"--env\", choices=ENVIRONMENTS, default=\"staging\")\n    deploy.add_argument(\"service\").completer = service_completer\n    deploy.add_argument(\"--manifest\", type=Path).completer = \\\n        argcomplete.completers.FilesCompleter([\"yaml\", \"yml\"])\n    sub.add_parser(\"status\", help=\"show status\")\n    return parser\n\n\ndef main() -> None:\n    parser = build_parser()\n    argcomplete.autocomplete(parser)            # exits here when completing\n    args = parser.parse_args()\n    print(args)\n\n\nif __name__ == \"__main__\":\n    main()\n","python","",[19,121,122,131,137,143,161,168,177,190,195,203,208,237,242,247,277,283,314,346,358,385,390,395,406,428,459,485,514,530,553,570,590,598,603,608,623,633,642,653,662,667,672,688],{"__ignoreMap":119},[123,124,127],"span",{"class":125,"line":126},"line",1,[123,128,130],{"class":129},"sJ8bj","#!\u002Fusr\u002Fbin\u002Fenv python\n",[123,132,134],{"class":125,"line":133},2,[123,135,136],{"class":129},"# PYTHON_ARGCOMPLETE_OK\n",[123,138,140],{"class":125,"line":139},3,[123,141,142],{"class":129},"# src\u002Fdeploytool\u002Fcli.py\n",[123,144,146,150,154,157],{"class":125,"line":145},4,[123,147,149],{"class":148},"szBVR","from",[123,151,153],{"class":152},"sj4cs"," __future__",[123,155,156],{"class":148}," import",[123,158,160],{"class":159},"sVt8B"," annotations\n",[123,162,164],{"class":125,"line":163},5,[123,165,167],{"emptyLinePlaceholder":166},true,"\n",[123,169,171,174],{"class":125,"line":170},6,[123,172,173],{"class":148},"import",[123,175,176],{"class":159}," argparse\n",[123,178,180,182,185,187],{"class":125,"line":179},7,[123,181,149],{"class":148},[123,183,184],{"class":159}," pathlib ",[123,186,173],{"class":148},[123,188,189],{"class":159}," Path\n",[123,191,193],{"class":125,"line":192},8,[123,194,167],{"emptyLinePlaceholder":166},[123,196,198,200],{"class":125,"line":197},9,[123,199,173],{"class":148},[123,201,202],{"class":159}," argcomplete\n",[123,204,206],{"class":125,"line":205},10,[123,207,167],{"emptyLinePlaceholder":166},[123,209,211,214,217,220,224,226,229,231,234],{"class":125,"line":210},11,[123,212,213],{"class":152},"ENVIRONMENTS",[123,215,216],{"class":148}," =",[123,218,219],{"class":159}," [",[123,221,223],{"class":222},"sZZnC","\"dev\"",[123,225,70],{"class":159},[123,227,228],{"class":222},"\"staging\"",[123,230,70],{"class":159},[123,232,233],{"class":222},"\"production\"",[123,235,236],{"class":159},"]\n",[123,238,240],{"class":125,"line":239},12,[123,241,167],{"emptyLinePlaceholder":166},[123,243,245],{"class":125,"line":244},13,[123,246,167],{"emptyLinePlaceholder":166},[123,248,250,253,257,260,263,266,269,272,274],{"class":125,"line":249},14,[123,251,252],{"class":148},"def",[123,254,256],{"class":255},"sScJk"," service_completer",[123,258,259],{"class":159},"(prefix: ",[123,261,262],{"class":152},"str",[123,264,265],{"class":159},", parsed_args: argparse.Namespace, ",[123,267,268],{"class":148},"**",[123,270,271],{"class":159},"kwargs) -> list[",[123,273,262],{"class":152},[123,275,276],{"class":159},"]:\n",[123,278,280],{"class":125,"line":279},15,[123,281,282],{"class":222},"    \"\"\"Complete service names; dev has an extra sandbox service.\"\"\"\n",[123,284,286,289,292,294,297,299,302,304,307,309,312],{"class":125,"line":285},16,[123,287,288],{"class":159},"    services ",[123,290,291],{"class":148},"=",[123,293,219],{"class":159},[123,295,296],{"class":222},"\"api\"",[123,298,70],{"class":159},[123,300,301],{"class":222},"\"web\"",[123,303,70],{"class":159},[123,305,306],{"class":222},"\"worker\"",[123,308,70],{"class":159},[123,310,311],{"class":222},"\"scheduler\"",[123,313,236],{"class":159},[123,315,317,320,323,326,329,331,334,337,340,343],{"class":125,"line":316},17,[123,318,319],{"class":148},"    if",[123,321,322],{"class":152}," getattr",[123,324,325],{"class":159},"(parsed_args, ",[123,327,328],{"class":222},"\"env\"",[123,330,70],{"class":159},[123,332,333],{"class":152},"None",[123,335,336],{"class":159},") ",[123,338,339],{"class":148},"==",[123,341,342],{"class":222}," \"dev\"",[123,344,345],{"class":159},":\n",[123,347,349,352,355],{"class":125,"line":348},18,[123,350,351],{"class":159},"        services.append(",[123,353,354],{"class":222},"\"sandbox\"",[123,356,357],{"class":159},")\n",[123,359,361,364,367,370,373,376,379,382],{"class":125,"line":360},19,[123,362,363],{"class":148},"    return",[123,365,366],{"class":159}," [s ",[123,368,369],{"class":148},"for",[123,371,372],{"class":159}," s ",[123,374,375],{"class":148},"in",[123,377,378],{"class":159}," services ",[123,380,381],{"class":148},"if",[123,383,384],{"class":159}," s.startswith(prefix)]\n",[123,386,388],{"class":125,"line":387},20,[123,389,167],{"emptyLinePlaceholder":166},[123,391,393],{"class":125,"line":392},21,[123,394,167],{"emptyLinePlaceholder":166},[123,396,398,400,403],{"class":125,"line":397},22,[123,399,252],{"class":148},[123,401,402],{"class":255}," build_parser",[123,404,405],{"class":159},"() -> argparse.ArgumentParser:\n",[123,407,409,412,414,417,421,423,426],{"class":125,"line":408},23,[123,410,411],{"class":159},"    parser ",[123,413,291],{"class":148},[123,415,416],{"class":159}," argparse.ArgumentParser(",[123,418,420],{"class":419},"s4XuR","prog",[123,422,291],{"class":148},[123,424,425],{"class":222},"\"deploytool\"",[123,427,357],{"class":159},[123,429,431,434,436,439,442,444,447,449,452,454,457],{"class":125,"line":430},24,[123,432,433],{"class":159},"    sub ",[123,435,291],{"class":148},[123,437,438],{"class":159}," parser.add_subparsers(",[123,440,441],{"class":419},"dest",[123,443,291],{"class":148},[123,445,446],{"class":222},"\"command\"",[123,448,70],{"class":159},[123,450,451],{"class":419},"required",[123,453,291],{"class":148},[123,455,456],{"class":152},"True",[123,458,357],{"class":159},[123,460,462,465,467,470,473,475,478,480,483],{"class":125,"line":461},25,[123,463,464],{"class":159},"    deploy ",[123,466,291],{"class":148},[123,468,469],{"class":159}," sub.add_parser(",[123,471,472],{"class":222},"\"deploy\"",[123,474,70],{"class":159},[123,476,477],{"class":419},"help",[123,479,291],{"class":148},[123,481,482],{"class":222},"\"deploy a service\"",[123,484,357],{"class":159},[123,486,488,491,494,496,499,501,503,505,508,510,512],{"class":125,"line":487},26,[123,489,490],{"class":159},"    deploy.add_argument(",[123,492,493],{"class":222},"\"--env\"",[123,495,70],{"class":159},[123,497,498],{"class":419},"choices",[123,500,291],{"class":148},[123,502,213],{"class":152},[123,504,70],{"class":159},[123,506,507],{"class":419},"default",[123,509,291],{"class":148},[123,511,228],{"class":222},[123,513,357],{"class":159},[123,515,517,519,522,525,527],{"class":125,"line":516},27,[123,518,490],{"class":159},[123,520,521],{"class":222},"\"service\"",[123,523,524],{"class":159},").completer ",[123,526,291],{"class":148},[123,528,529],{"class":159}," service_completer\n",[123,531,533,535,538,540,543,545,548,550],{"class":125,"line":532},28,[123,534,490],{"class":159},[123,536,537],{"class":222},"\"--manifest\"",[123,539,70],{"class":159},[123,541,542],{"class":419},"type",[123,544,291],{"class":148},[123,546,547],{"class":159},"Path).completer ",[123,549,291],{"class":148},[123,551,552],{"class":159}," \\\n",[123,554,556,559,562,564,567],{"class":125,"line":555},29,[123,557,558],{"class":159},"        argcomplete.completers.FilesCompleter([",[123,560,561],{"class":222},"\"yaml\"",[123,563,70],{"class":159},[123,565,566],{"class":222},"\"yml\"",[123,568,569],{"class":159},"])\n",[123,571,573,576,579,581,583,585,588],{"class":125,"line":572},30,[123,574,575],{"class":159},"    sub.add_parser(",[123,577,578],{"class":222},"\"status\"",[123,580,70],{"class":159},[123,582,477],{"class":419},[123,584,291],{"class":148},[123,586,587],{"class":222},"\"show status\"",[123,589,357],{"class":159},[123,591,593,595],{"class":125,"line":592},31,[123,594,363],{"class":148},[123,596,597],{"class":159}," parser\n",[123,599,601],{"class":125,"line":600},32,[123,602,167],{"emptyLinePlaceholder":166},[123,604,606],{"class":125,"line":605},33,[123,607,167],{"emptyLinePlaceholder":166},[123,609,611,613,616,619,621],{"class":125,"line":610},34,[123,612,252],{"class":148},[123,614,615],{"class":255}," main",[123,617,618],{"class":159},"() -> ",[123,620,333],{"class":152},[123,622,345],{"class":159},[123,624,626,628,630],{"class":125,"line":625},35,[123,627,411],{"class":159},[123,629,291],{"class":148},[123,631,632],{"class":159}," build_parser()\n",[123,634,636,639],{"class":125,"line":635},36,[123,637,638],{"class":159},"    argcomplete.autocomplete(parser)            ",[123,640,641],{"class":129},"# exits here when completing\n",[123,643,645,648,650],{"class":125,"line":644},37,[123,646,647],{"class":159},"    args ",[123,649,291],{"class":148},[123,651,652],{"class":159}," parser.parse_args()\n",[123,654,656,659],{"class":125,"line":655},38,[123,657,658],{"class":152},"    print",[123,660,661],{"class":159},"(args)\n",[123,663,665],{"class":125,"line":664},39,[123,666,167],{"emptyLinePlaceholder":166},[123,668,670],{"class":125,"line":669},40,[123,671,167],{"emptyLinePlaceholder":166},[123,673,675,677,680,683,686],{"class":125,"line":674},41,[123,676,381],{"class":148},[123,678,679],{"class":152}," __name__",[123,681,682],{"class":148}," ==",[123,684,685],{"class":222}," \"__main__\"",[123,687,345],{"class":159},[123,689,691],{"class":125,"line":690},42,[123,692,693],{"class":159},"    main()\n",[10,695,696],{},"Three pieces do the work:",[40,698,699,713,732],{},[43,700,701,708,709,712],{},[14,702,703,704,707],{},"The ",[19,705,706],{},"# PYTHON_ARGCOMPLETE_OK"," marker"," in the first lines of the executable script lets argcomplete's ",[24,710,711],{},"global"," activation recognise the program as argcomplete-aware. With per-command registration (below) it is not strictly needed, but it costs nothing.",[43,714,715,720,721,724,725,70,728,731],{},[14,716,717,718],{},"Subcommands, options and ",[19,719,498],{}," complete automatically from the parser: ",[19,722,723],{},"deploytool d\u003CTab>"," gives ",[19,726,727],{},"deploy",[19,729,730],{},"--env \u003CTab>"," offers the three environments.",[43,733,734,737,738,741,742,745,746,749,750,753,754,757],{},[14,735,736],{},"Completers"," handle everything else. A completer is any callable that receives the current ",[19,739,740],{},"prefix"," and the arguments parsed so far, and returns candidates. Because ",[19,743,744],{},"parsed_args"," is available, completion can depend on earlier options — here, ",[19,747,748],{},"--env dev"," adds a ",[19,751,752],{},"sandbox"," service. ",[19,755,756],{},"FilesCompleter"," completes paths, optionally filtered by extension.",[61,759],{"name":760},"argc-terminal",[762,763,765],"h3",{"id":764},"a-completer-backed-by-a-cache","A completer backed by a cache",[10,767,768],{},"Real candidate lists usually come from somewhere slow — an API listing services, a directory of hundreds of manifests. A completer can read a small cache file and refresh it only when it is stale, keeping each Tab press fast:",[114,770,772],{"className":116,"code":771,"language":118,"meta":119,"style":119},"import json\nimport time\nfrom pathlib import Path\n\nCACHE = Path.home() \u002F \".cache\" \u002F \"deploytool\" \u002F \"services.json\"\nTTL = 15 * 60\n\n\ndef cached_services(fetch, *, now: float | None = None) -> list[str]:\n    now = now or time.time()\n    try:\n        data = json.loads(CACHE.read_text(encoding=\"utf-8\"))\n        if now - data[\"fetched_at\"] \u003C TTL:\n            return data[\"services\"]\n    except (OSError, ValueError, KeyError):\n        pass\n    try:\n        services = fetch()                       # e.g. an API call with a 1-second timeout\n    except Exception:\n        return []                                # never break the shell over a completion\n    CACHE.parent.mkdir(parents=True, exist_ok=True)\n    CACHE.write_text(json.dumps({\"fetched_at\": now, \"services\": services}))\n    return services\n\n\ndef service_completer(prefix, parsed_args, **kwargs):\n    return [s for s in cached_services(fetch_services) if s.startswith(prefix)]\n",[19,773,774,781,788,798,802,829,845,849,853,889,905,912,938,965,977,1001,1006,1012,1025,1034,1045,1071,1088,1095,1099,1103,1117],{"__ignoreMap":119},[123,775,776,778],{"class":125,"line":126},[123,777,173],{"class":148},[123,779,780],{"class":159}," json\n",[123,782,783,785],{"class":125,"line":133},[123,784,173],{"class":148},[123,786,787],{"class":159}," time\n",[123,789,790,792,794,796],{"class":125,"line":139},[123,791,149],{"class":148},[123,793,184],{"class":159},[123,795,173],{"class":148},[123,797,189],{"class":159},[123,799,800],{"class":125,"line":145},[123,801,167],{"emptyLinePlaceholder":166},[123,803,804,807,809,812,815,818,821,824,826],{"class":125,"line":163},[123,805,806],{"class":152},"CACHE",[123,808,216],{"class":148},[123,810,811],{"class":159}," Path.home() ",[123,813,814],{"class":148},"\u002F",[123,816,817],{"class":222}," \".cache\"",[123,819,820],{"class":148}," \u002F",[123,822,823],{"class":222}," \"deploytool\"",[123,825,820],{"class":148},[123,827,828],{"class":222}," \"services.json\"\n",[123,830,831,834,836,839,842],{"class":125,"line":170},[123,832,833],{"class":152},"TTL",[123,835,216],{"class":148},[123,837,838],{"class":152}," 15",[123,840,841],{"class":148}," *",[123,843,844],{"class":152}," 60\n",[123,846,847],{"class":125,"line":179},[123,848,167],{"emptyLinePlaceholder":166},[123,850,851],{"class":125,"line":192},[123,852,167],{"emptyLinePlaceholder":166},[123,854,855,857,860,863,866,869,872,875,878,880,882,885,887],{"class":125,"line":197},[123,856,252],{"class":148},[123,858,859],{"class":255}," cached_services",[123,861,862],{"class":159},"(fetch, ",[123,864,865],{"class":148},"*",[123,867,868],{"class":159},", now: ",[123,870,871],{"class":152},"float",[123,873,874],{"class":148}," |",[123,876,877],{"class":152}," None",[123,879,216],{"class":148},[123,881,877],{"class":152},[123,883,884],{"class":159},") -> list[",[123,886,262],{"class":152},[123,888,276],{"class":159},[123,890,891,894,896,899,902],{"class":125,"line":205},[123,892,893],{"class":159},"    now ",[123,895,291],{"class":148},[123,897,898],{"class":159}," now ",[123,900,901],{"class":148},"or",[123,903,904],{"class":159}," time.time()\n",[123,906,907,910],{"class":125,"line":210},[123,908,909],{"class":148},"    try",[123,911,345],{"class":159},[123,913,914,917,919,922,924,927,930,932,935],{"class":125,"line":239},[123,915,916],{"class":159},"        data ",[123,918,291],{"class":148},[123,920,921],{"class":159}," json.loads(",[123,923,806],{"class":152},[123,925,926],{"class":159},".read_text(",[123,928,929],{"class":419},"encoding",[123,931,291],{"class":148},[123,933,934],{"class":222},"\"utf-8\"",[123,936,937],{"class":159},"))\n",[123,939,940,943,945,948,951,954,957,960,963],{"class":125,"line":244},[123,941,942],{"class":148},"        if",[123,944,898],{"class":159},[123,946,947],{"class":148},"-",[123,949,950],{"class":159}," data[",[123,952,953],{"class":222},"\"fetched_at\"",[123,955,956],{"class":159},"] ",[123,958,959],{"class":148},"\u003C",[123,961,962],{"class":152}," TTL",[123,964,345],{"class":159},[123,966,967,970,972,975],{"class":125,"line":249},[123,968,969],{"class":148},"            return",[123,971,950],{"class":159},[123,973,974],{"class":222},"\"services\"",[123,976,236],{"class":159},[123,978,979,982,985,988,990,993,995,998],{"class":125,"line":279},[123,980,981],{"class":148},"    except",[123,983,984],{"class":159}," (",[123,986,987],{"class":152},"OSError",[123,989,70],{"class":159},[123,991,992],{"class":152},"ValueError",[123,994,70],{"class":159},[123,996,997],{"class":152},"KeyError",[123,999,1000],{"class":159},"):\n",[123,1002,1003],{"class":125,"line":285},[123,1004,1005],{"class":148},"        pass\n",[123,1007,1008,1010],{"class":125,"line":316},[123,1009,909],{"class":148},[123,1011,345],{"class":159},[123,1013,1014,1017,1019,1022],{"class":125,"line":348},[123,1015,1016],{"class":159},"        services ",[123,1018,291],{"class":148},[123,1020,1021],{"class":159}," fetch()                       ",[123,1023,1024],{"class":129},"# e.g. an API call with a 1-second timeout\n",[123,1026,1027,1029,1032],{"class":125,"line":360},[123,1028,981],{"class":148},[123,1030,1031],{"class":152}," Exception",[123,1033,345],{"class":159},[123,1035,1036,1039,1042],{"class":125,"line":387},[123,1037,1038],{"class":148},"        return",[123,1040,1041],{"class":159}," []                                ",[123,1043,1044],{"class":129},"# never break the shell over a completion\n",[123,1046,1047,1050,1053,1056,1058,1060,1062,1065,1067,1069],{"class":125,"line":392},[123,1048,1049],{"class":152},"    CACHE",[123,1051,1052],{"class":159},".parent.mkdir(",[123,1054,1055],{"class":419},"parents",[123,1057,291],{"class":148},[123,1059,456],{"class":152},[123,1061,70],{"class":159},[123,1063,1064],{"class":419},"exist_ok",[123,1066,291],{"class":148},[123,1068,456],{"class":152},[123,1070,357],{"class":159},[123,1072,1073,1075,1078,1080,1083,1085],{"class":125,"line":397},[123,1074,1049],{"class":152},[123,1076,1077],{"class":159},".write_text(json.dumps({",[123,1079,953],{"class":222},[123,1081,1082],{"class":159},": now, ",[123,1084,974],{"class":222},[123,1086,1087],{"class":159},": services}))\n",[123,1089,1090,1092],{"class":125,"line":408},[123,1091,363],{"class":148},[123,1093,1094],{"class":159}," services\n",[123,1096,1097],{"class":125,"line":430},[123,1098,167],{"emptyLinePlaceholder":166},[123,1100,1101],{"class":125,"line":461},[123,1102,167],{"emptyLinePlaceholder":166},[123,1104,1105,1107,1109,1112,1114],{"class":125,"line":487},[123,1106,252],{"class":148},[123,1108,256],{"class":255},[123,1110,1111],{"class":159},"(prefix, parsed_args, ",[123,1113,268],{"class":148},[123,1115,1116],{"class":159},"kwargs):\n",[123,1118,1119,1121,1123,1125,1127,1129,1132,1134],{"class":125,"line":516},[123,1120,363],{"class":148},[123,1122,366],{"class":159},[123,1124,369],{"class":148},[123,1126,372],{"class":159},[123,1128,375],{"class":148},[123,1130,1131],{"class":159}," cached_services(fetch_services) ",[123,1133,381],{"class":148},[123,1135,384],{"class":159},[10,1137,1138],{},"The first Tab after the cache expires pays for one fetch; every other press reads a few hundred bytes. If the fetch fails — offline, VPN down — the completer returns nothing rather than an error, and the user can still type the value.",[762,1140,1142],{"id":1141},"debugging-completion","Debugging completion",[10,1144,1145,1146,1149,1150,1152,1153,1155],{},"When completion silently produces nothing, set ",[19,1147,1148],{},"_ARC_DEBUG=1"," in the shell and press Tab again: argcomplete prints what it parsed, which completer it called and any exception, to the terminal. The most common culprits are an exception in a completer, output printed before ",[19,1151,89],{},", and a console script whose entry point never reaches the ",[19,1154,89],{}," call.",[35,1157,1159],{"id":1158},"activation","Activation",[10,1161,1162],{},"argcomplete needs the shell to call your program on Tab. There are two ways:",[114,1164,1168],{"className":1165,"code":1166,"language":1167,"meta":119,"style":119},"language-bash shiki shiki-themes github-light github-dark","# per command — add to ~\u002F.bashrc (or ~\u002F.zshrc after enabling bashcompinit)\neval \"$(register-python-argcomplete deploytool)\"\n\n# global — completes every program carrying the PYTHON_ARGCOMPLETE_OK marker\nactivate-global-python-argcomplete --user\n","bash",[19,1169,1170,1175,1189,1193,1198],{"__ignoreMap":119},[123,1171,1172],{"class":125,"line":126},[123,1173,1174],{"class":129},"# per command — add to ~\u002F.bashrc (or ~\u002F.zshrc after enabling bashcompinit)\n",[123,1176,1177,1180,1183,1186],{"class":125,"line":133},[123,1178,1179],{"class":152},"eval",[123,1181,1182],{"class":222}," \"$(",[123,1184,1185],{"class":255},"register-python-argcomplete",[123,1187,1188],{"class":222}," deploytool)\"\n",[123,1190,1191],{"class":125,"line":139},[123,1192,167],{"emptyLinePlaceholder":166},[123,1194,1195],{"class":125,"line":145},[123,1196,1197],{"class":129},"# global — completes every program carrying the PYTHON_ARGCOMPLETE_OK marker\n",[123,1199,1200,1203],{"class":125,"line":163},[123,1201,1202],{"class":255},"activate-global-python-argcomplete",[123,1204,1205],{"class":152}," --user\n",[10,1207,1208,1209,1212,1213,1215,1216,1219,1220,1224],{},"Per-command registration is explicit and works for any entry point, including console scripts installed by pipx or uv; the global mode needs the marker to be visible in the executable the shell finds. For zsh, run ",[19,1210,1211],{},"autoload -U bashcompinit && bashcompinit"," before the ",[19,1214,1179],{},". Fish is supported through ",[19,1217,1218],{},"register-python-argcomplete --shell fish deploytool | source",". The general installation and packaging advice in ",[29,1221,1223],{"href":1222},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Finstalling-shell-completion-for-bash-zsh-fish\u002F","installing shell completion for bash, zsh and fish"," applies unchanged.",[35,1226,1228],{"id":1227},"ux-considerations","UX considerations",[40,1230,1231,1241,1249,1255,1264],{},[43,1232,1233,1236,1237,33],{},[14,1234,1235],{},"Keep completers fast."," They run on every Tab; anything slower than about 100 ms feels broken. Cache network-backed candidates as described in ",[29,1238,1240],{"href":1239},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Fdynamic-completion-values-from-apis-and-files\u002F","dynamic completion values from APIs and files",[43,1242,1243,1248],{},[14,1244,1245,1246,33],{},"Never print from code that runs before ",[19,1247,89],{}," Output to stdout during completion corrupts the candidate list; argcomplete redirects stdout while completing, but code before the call runs unprotected.",[43,1250,1251,1254],{},[14,1252,1253],{},"Fail quietly."," A completer that raises produces no candidates and, at most, a debug message; catch expected errors and return an empty list.",[43,1256,1257,1263],{},[14,1258,1259,1260,1262],{},"Prefer ",[19,1261,498],{}," for fixed sets."," They complete for free and are validated at parse time.",[43,1265,1266,1269,1270,1272],{},[14,1267,1268],{},"Document activation."," One line in the README with the ",[19,1271,1179],{}," command gets most users set up.",[61,1274],{"name":1275},"argc-compare",[35,1277,1279],{"id":1278},"testing-the-behaviour","Testing the behaviour",[10,1281,1282],{},"Completion can be tested without a shell by setting argcomplete's environment variables and asking it to write candidates to a file:",[114,1284,1286],{"className":116,"code":1285,"language":118,"meta":119,"style":119},"# tests\u002Ftest_completion.py\nimport os\nimport subprocess\nimport sys\nfrom pathlib import Path\n\nimport pytest\n\nSCRIPT = Path(__file__).resolve().parents[1] \u002F \"src\" \u002F \"deploytool\" \u002F \"cli.py\"\n\n\ndef complete(line: str, tmp_path: Path, cwd: Path | None = None) -> list[str]:\n    out = tmp_path \u002F \"candidates.txt\"\n    env = {**os.environ, \"_ARGCOMPLETE\": \"1\", \"_ARGCOMPLETE_IFS\": \"\\n\",\n           \"COMP_LINE\": line, \"COMP_POINT\": str(len(line)),\n           \"_ARGCOMPLETE_STDOUT_FILENAME\": str(out)}\n    subprocess.run([sys.executable, str(SCRIPT)], env=env, cwd=cwd, check=True,\n                   stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, pass_fds=())\n    return out.read_text().split()\n\n\n@pytest.mark.parametrize(\"line,expected\", [\n    (\"deploytool d\", [\"deploy\"]),\n    (\"deploytool deploy --env \", [\"dev\", \"staging\", \"production\"]),\n    (\"deploytool deploy w\", [\"web\", \"worker\"]),\n    (\"deploytool deploy --env dev s\", [\"scheduler\", \"sandbox\"]),\n])\ndef test_completions(tmp_path, line, expected):\n    assert complete(line, tmp_path) == expected\n\n\ndef test_manifest_completes_yaml_files(tmp_path):\n    work = tmp_path \u002F \"work\"\n    work.mkdir()\n    (work \u002F \"app.yaml\").write_text(\"\")\n    (work \u002F \"notes.txt\").write_text(\"\")\n    assert complete(\"deploytool deploy api --manifest \", tmp_path, cwd=work) == [\"app.yaml\"]\n",[19,1287,1288,1293,1300,1307,1314,1324,1328,1335,1339,1374,1378,1382,1412,1427,1469,1493,1505,1544,1578,1585,1589,1593,1606,1622,1643,1660,1677,1681,1691,1704,1708,1712,1722,1736,1741,1759,1774],{"__ignoreMap":119},[123,1289,1290],{"class":125,"line":126},[123,1291,1292],{"class":129},"# tests\u002Ftest_completion.py\n",[123,1294,1295,1297],{"class":125,"line":133},[123,1296,173],{"class":148},[123,1298,1299],{"class":159}," os\n",[123,1301,1302,1304],{"class":125,"line":139},[123,1303,173],{"class":148},[123,1305,1306],{"class":159}," subprocess\n",[123,1308,1309,1311],{"class":125,"line":145},[123,1310,173],{"class":148},[123,1312,1313],{"class":159}," sys\n",[123,1315,1316,1318,1320,1322],{"class":125,"line":163},[123,1317,149],{"class":148},[123,1319,184],{"class":159},[123,1321,173],{"class":148},[123,1323,189],{"class":159},[123,1325,1326],{"class":125,"line":170},[123,1327,167],{"emptyLinePlaceholder":166},[123,1329,1330,1332],{"class":125,"line":179},[123,1331,173],{"class":148},[123,1333,1334],{"class":159}," pytest\n",[123,1336,1337],{"class":125,"line":192},[123,1338,167],{"emptyLinePlaceholder":166},[123,1340,1341,1344,1346,1349,1352,1355,1358,1360,1362,1365,1367,1369,1371],{"class":125,"line":197},[123,1342,1343],{"class":152},"SCRIPT",[123,1345,216],{"class":148},[123,1347,1348],{"class":159}," Path(",[123,1350,1351],{"class":152},"__file__",[123,1353,1354],{"class":159},").resolve().parents[",[123,1356,1357],{"class":152},"1",[123,1359,956],{"class":159},[123,1361,814],{"class":148},[123,1363,1364],{"class":222}," \"src\"",[123,1366,820],{"class":148},[123,1368,823],{"class":222},[123,1370,820],{"class":148},[123,1372,1373],{"class":222}," \"cli.py\"\n",[123,1375,1376],{"class":125,"line":205},[123,1377,167],{"emptyLinePlaceholder":166},[123,1379,1380],{"class":125,"line":210},[123,1381,167],{"emptyLinePlaceholder":166},[123,1383,1384,1386,1389,1392,1394,1397,1400,1402,1404,1406,1408,1410],{"class":125,"line":239},[123,1385,252],{"class":148},[123,1387,1388],{"class":255}," complete",[123,1390,1391],{"class":159},"(line: ",[123,1393,262],{"class":152},[123,1395,1396],{"class":159},", tmp_path: Path, cwd: Path ",[123,1398,1399],{"class":148},"|",[123,1401,877],{"class":152},[123,1403,216],{"class":148},[123,1405,877],{"class":152},[123,1407,884],{"class":159},[123,1409,262],{"class":152},[123,1411,276],{"class":159},[123,1413,1414,1417,1419,1422,1424],{"class":125,"line":244},[123,1415,1416],{"class":159},"    out ",[123,1418,291],{"class":148},[123,1420,1421],{"class":159}," tmp_path ",[123,1423,814],{"class":148},[123,1425,1426],{"class":222}," \"candidates.txt\"\n",[123,1428,1429,1432,1434,1437,1439,1442,1445,1448,1451,1453,1456,1458,1461,1464,1466],{"class":125,"line":249},[123,1430,1431],{"class":159},"    env ",[123,1433,291],{"class":148},[123,1435,1436],{"class":159}," {",[123,1438,268],{"class":148},[123,1440,1441],{"class":159},"os.environ, ",[123,1443,1444],{"class":222},"\"_ARGCOMPLETE\"",[123,1446,1447],{"class":159},": ",[123,1449,1450],{"class":222},"\"1\"",[123,1452,70],{"class":159},[123,1454,1455],{"class":222},"\"_ARGCOMPLETE_IFS\"",[123,1457,1447],{"class":159},[123,1459,1460],{"class":222},"\"",[123,1462,1463],{"class":152},"\\n",[123,1465,1460],{"class":222},[123,1467,1468],{"class":159},",\n",[123,1470,1471,1474,1477,1480,1482,1484,1487,1490],{"class":125,"line":279},[123,1472,1473],{"class":222},"           \"COMP_LINE\"",[123,1475,1476],{"class":159},": line, ",[123,1478,1479],{"class":222},"\"COMP_POINT\"",[123,1481,1447],{"class":159},[123,1483,262],{"class":152},[123,1485,1486],{"class":159},"(",[123,1488,1489],{"class":152},"len",[123,1491,1492],{"class":159},"(line)),\n",[123,1494,1495,1498,1500,1502],{"class":125,"line":285},[123,1496,1497],{"class":222},"           \"_ARGCOMPLETE_STDOUT_FILENAME\"",[123,1499,1447],{"class":159},[123,1501,262],{"class":152},[123,1503,1504],{"class":159},"(out)}\n",[123,1506,1507,1510,1512,1514,1516,1519,1522,1524,1527,1530,1532,1535,1538,1540,1542],{"class":125,"line":316},[123,1508,1509],{"class":159},"    subprocess.run([sys.executable, ",[123,1511,262],{"class":152},[123,1513,1486],{"class":159},[123,1515,1343],{"class":152},[123,1517,1518],{"class":159},")], ",[123,1520,1521],{"class":419},"env",[123,1523,291],{"class":148},[123,1525,1526],{"class":159},"env, ",[123,1528,1529],{"class":419},"cwd",[123,1531,291],{"class":148},[123,1533,1534],{"class":159},"cwd, ",[123,1536,1537],{"class":419},"check",[123,1539,291],{"class":148},[123,1541,456],{"class":152},[123,1543,1468],{"class":159},[123,1545,1546,1549,1551,1554,1557,1559,1562,1564,1566,1568,1570,1573,1575],{"class":125,"line":348},[123,1547,1548],{"class":419},"                   stdout",[123,1550,291],{"class":148},[123,1552,1553],{"class":159},"subprocess.",[123,1555,1556],{"class":152},"DEVNULL",[123,1558,70],{"class":159},[123,1560,1561],{"class":419},"stderr",[123,1563,291],{"class":148},[123,1565,1553],{"class":159},[123,1567,1556],{"class":152},[123,1569,70],{"class":159},[123,1571,1572],{"class":419},"pass_fds",[123,1574,291],{"class":148},[123,1576,1577],{"class":159},"())\n",[123,1579,1580,1582],{"class":125,"line":360},[123,1581,363],{"class":148},[123,1583,1584],{"class":159}," out.read_text().split()\n",[123,1586,1587],{"class":125,"line":387},[123,1588,167],{"emptyLinePlaceholder":166},[123,1590,1591],{"class":125,"line":392},[123,1592,167],{"emptyLinePlaceholder":166},[123,1594,1595,1598,1600,1603],{"class":125,"line":397},[123,1596,1597],{"class":255},"@pytest.mark.parametrize",[123,1599,1486],{"class":159},[123,1601,1602],{"class":222},"\"line,expected\"",[123,1604,1605],{"class":159},", [\n",[123,1607,1608,1611,1614,1617,1619],{"class":125,"line":408},[123,1609,1610],{"class":159},"    (",[123,1612,1613],{"class":222},"\"deploytool d\"",[123,1615,1616],{"class":159},", [",[123,1618,472],{"class":222},[123,1620,1621],{"class":159},"]),\n",[123,1623,1624,1626,1629,1631,1633,1635,1637,1639,1641],{"class":125,"line":430},[123,1625,1610],{"class":159},[123,1627,1628],{"class":222},"\"deploytool deploy --env \"",[123,1630,1616],{"class":159},[123,1632,223],{"class":222},[123,1634,70],{"class":159},[123,1636,228],{"class":222},[123,1638,70],{"class":159},[123,1640,233],{"class":222},[123,1642,1621],{"class":159},[123,1644,1645,1647,1650,1652,1654,1656,1658],{"class":125,"line":461},[123,1646,1610],{"class":159},[123,1648,1649],{"class":222},"\"deploytool deploy w\"",[123,1651,1616],{"class":159},[123,1653,301],{"class":222},[123,1655,70],{"class":159},[123,1657,306],{"class":222},[123,1659,1621],{"class":159},[123,1661,1662,1664,1667,1669,1671,1673,1675],{"class":125,"line":487},[123,1663,1610],{"class":159},[123,1665,1666],{"class":222},"\"deploytool deploy --env dev s\"",[123,1668,1616],{"class":159},[123,1670,311],{"class":222},[123,1672,70],{"class":159},[123,1674,354],{"class":222},[123,1676,1621],{"class":159},[123,1678,1679],{"class":125,"line":516},[123,1680,569],{"class":159},[123,1682,1683,1685,1688],{"class":125,"line":532},[123,1684,252],{"class":148},[123,1686,1687],{"class":255}," test_completions",[123,1689,1690],{"class":159},"(tmp_path, line, expected):\n",[123,1692,1693,1696,1699,1701],{"class":125,"line":555},[123,1694,1695],{"class":148},"    assert",[123,1697,1698],{"class":159}," complete(line, tmp_path) ",[123,1700,339],{"class":148},[123,1702,1703],{"class":159}," expected\n",[123,1705,1706],{"class":125,"line":572},[123,1707,167],{"emptyLinePlaceholder":166},[123,1709,1710],{"class":125,"line":592},[123,1711,167],{"emptyLinePlaceholder":166},[123,1713,1714,1716,1719],{"class":125,"line":600},[123,1715,252],{"class":148},[123,1717,1718],{"class":255}," test_manifest_completes_yaml_files",[123,1720,1721],{"class":159},"(tmp_path):\n",[123,1723,1724,1727,1729,1731,1733],{"class":125,"line":605},[123,1725,1726],{"class":159},"    work ",[123,1728,291],{"class":148},[123,1730,1421],{"class":159},[123,1732,814],{"class":148},[123,1734,1735],{"class":222}," \"work\"\n",[123,1737,1738],{"class":125,"line":610},[123,1739,1740],{"class":159},"    work.mkdir()\n",[123,1742,1743,1746,1748,1751,1754,1757],{"class":125,"line":625},[123,1744,1745],{"class":159},"    (work ",[123,1747,814],{"class":148},[123,1749,1750],{"class":222}," \"app.yaml\"",[123,1752,1753],{"class":159},").write_text(",[123,1755,1756],{"class":222},"\"\"",[123,1758,357],{"class":159},[123,1760,1761,1763,1765,1768,1770,1772],{"class":125,"line":635},[123,1762,1745],{"class":159},[123,1764,814],{"class":148},[123,1766,1767],{"class":222}," \"notes.txt\"",[123,1769,1753],{"class":159},[123,1771,1756],{"class":222},[123,1773,357],{"class":159},[123,1775,1776,1778,1781,1784,1787,1789,1791,1794,1796,1798,1801],{"class":125,"line":644},[123,1777,1695],{"class":148},[123,1779,1780],{"class":159}," complete(",[123,1782,1783],{"class":222},"\"deploytool deploy api --manifest \"",[123,1785,1786],{"class":159},", tmp_path, ",[123,1788,1529],{"class":419},[123,1790,291],{"class":148},[123,1792,1793],{"class":159},"work) ",[123,1795,339],{"class":148},[123,1797,219],{"class":159},[123,1799,1800],{"class":222},"\"app.yaml\"",[123,1802,236],{"class":159},[10,1804,1805,1806,1808,1809,1813],{},"The tests run the real script, exactly as the shell would, so they also catch completion-breaking mistakes outside the parser — a print at import time, a slow import, an exception before ",[19,1807,89],{},". The patterns in ",[29,1810,1812],{"href":1811},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Ftesting-shell-completion-in-python-clis\u002F","testing shell completion in Python CLIs"," cover the Click and Typer equivalents.",[35,1815,1817],{"id":1816},"conclusion","Conclusion",[10,1819,1820,1821,1823,1824,1826,1827,1829,1830,1832,1833,1835],{},"argcomplete gives argparse CLIs completion without duplicating the interface: call ",[19,1822,81],{}," before ",[19,1825,103],{}," and as early as possible, let subcommands, options and ",[19,1828,498],{}," complete automatically, attach completers for dynamic values (they can read earlier arguments), use ",[19,1831,756],{}," for paths, and activate with ",[19,1834,1185],{},". Keep startup and completers fast, and test completions by running the script with argcomplete's environment variables.",[35,1837,1839],{"id":1838},"frequently-asked-questions","Frequently asked questions",[762,1841,1843],{"id":1842},"does-argcomplete-slow-down-normal-runs","Does argcomplete slow down normal runs?",[10,1845,1846,1847,1850,1851,1853],{},"Barely: without ",[19,1848,1849],{},"_ARGCOMPLETE"," in the environment, ",[19,1852,89],{}," returns after a few checks. The cost to watch is your own import time, which is paid on every Tab press.",[762,1855,1857],{"id":1856},"can-completers-call-an-api","Can completers call an API?",[10,1859,1860],{},"They can, with a short timeout and a cache. A completer that waits several seconds for a network call makes the whole shell feel frozen; fall back to cached or no candidates when the call is slow.",[762,1862,1864],{"id":1863},"how-do-i-complete-a-positional-argument-with-a-fixed-list","How do I complete a positional argument with a fixed list?",[10,1866,1867,1868,1871,1872,1875],{},"Use ",[19,1869,1870],{},"choices="," if the value must be one of the list, or attach ",[19,1873,1874],{},"argcomplete.completers.ChoicesCompleter([...])"," when you want suggestions without validation.",[762,1877,1879],{"id":1878},"can-i-generate-a-static-completion-script-instead","Can I generate a static completion script instead?",[10,1881,1882],{},"argcomplete is dynamic by design — it runs your program to complete. If you need static scripts for packaging, tools such as shtab generate bash, zsh and tcsh scripts from an argparse parser; the trade-off is that static scripts cannot offer dynamic values.",[762,1884,1886],{"id":1885},"does-argcomplete-work-on-windows","Does argcomplete work on Windows?",[10,1888,1889,1890,1893],{},"In Git Bash and other bash-compatible shells on Windows, yes, with the same registration. PowerShell and cmd are not supported by argcomplete's activation scripts; for PowerShell users, a small ",[19,1891,1892],{},"Register-ArgumentCompleter"," script that calls your program with argcomplete's environment variables set — and reads the candidates from the output file — is the usual route, and worth shipping if many users are on Windows.",[35,1895,1897],{"id":1896},"related","Related",[40,1899,1900,1906,1912,1918,1923],{},[43,1901,1902,1903],{},"Up: ",[29,1904,1905],{"href":31},"Shell completion for Python CLIs",[43,1907,1908],{},[29,1909,1911],{"href":1910},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Fenabling-tab-completion-in-click-and-typer\u002F","Enabling tab completion in Click and Typer",[43,1913,1914],{},[29,1915,1917],{"href":1916},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Fshipping-completion-scripts-with-packages\u002F","Shipping completion scripts with packages",[43,1919,1920],{},[29,1921,1922],{"href":1811},"Testing shell completion in Python CLIs",[43,1924,1925],{},[29,1926,1928],{"href":1927},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002F","Command-line parsing with argparse",[1930,1931,1932],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":119,"searchDepth":133,"depth":133,"links":1934},[1935,1936,1937,1941,1942,1943,1944,1945,1952],{"id":37,"depth":133,"text":38},{"id":58,"depth":133,"text":59},{"id":111,"depth":133,"text":112,"children":1938},[1939,1940],{"id":764,"depth":139,"text":765},{"id":1141,"depth":139,"text":1142},{"id":1158,"depth":133,"text":1159},{"id":1227,"depth":133,"text":1228},{"id":1278,"depth":133,"text":1279},{"id":1816,"depth":133,"text":1817},{"id":1838,"depth":133,"text":1839,"children":1946},[1947,1948,1949,1950,1951],{"id":1842,"depth":139,"text":1843},{"id":1856,"depth":139,"text":1857},{"id":1863,"depth":139,"text":1864},{"id":1878,"depth":139,"text":1879},{"id":1885,"depth":139,"text":1886},{"id":1896,"depth":133,"text":1897},"2026-10-02","Add shell completion to an argparse CLI with argcomplete: the marker comment, autocomplete() placement, custom and file completers, activation for bash and zsh, and subprocess tests.","intermediate",false,"md",{},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Fargcomplete-for-argparse-clis",{"title":5,"description":1954},"advanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Fargcomplete-for-argparse-clis\u002Findex",[1963,1964,16,1167,1965],"argparse","completion","zsh","ZIYQ5Qu__dOohXlRLNAZifqFZu4J8rJNiC1LFqYZ5tU",[1968,1971,1974,1977,1980,1983,1986,1989,1992,1995,1998,2001,2004,2007,2010,2013,2016,2019,2022,2025,2028,2031,2034,2037,2040,2043,2046,2049,2052,2055,2058,2061,2064,2067,2070,2073,2076,2079,2082,2085,2088,2091,2094,2097,2100,2103,2106,2109,2112,2115,2118,2121,2124,2127,2130,2133,2136,2139,2142,2145,2148,2149,2152,2155,2158,2161,2164,2167,2170,2173,2176,2179,2182,2185,2188,2191,2194,2197,2200,2203,2206,2209,2212,2215,2218,2221,2224,2227,2230,2233,2236,2239,2242,2245,2248,2251,2254,2257,2260,2263,2266,2269,2272,2275,2278,2281,2284,2287,2290,2293,2296,2299,2302,2305,2308,2311,2314,2317,2320,2323,2326,2329,2332,2335,2338,2341,2344,2347,2350,2353,2356,2359,2362,2365,2368,2371,2374,2377,2380,2383,2385,2388,2391,2394,2397,2400,2403,2406,2409,2412,2415,2418,2421,2424,2427,2430,2433,2436,2439,2442,2445,2448,2451,2454,2457,2460,2463,2466,2469,2472,2475,2478,2481,2484,2487,2490,2493,2496,2499,2502,2505,2508,2511,2514,2517,2520,2523,2526,2529,2532,2535,2538,2541,2544,2547,2550,2553,2556,2559,2562,2565,2568,2571,2574,2577,2580,2583,2586,2589,2592,2595,2598,2601,2604,2607,2610,2613,2616,2619,2622,2625,2628,2631,2634,2637,2640,2643,2646,2649,2652,2655,2658,2661,2664,2667,2670,2673,2676,2679,2682,2685,2688,2691,2694,2697,2700,2703,2706,2709,2712,2715,2718,2721,2724,2727,2730,2733,2736,2739,2742,2745,2748,2751,2754,2757,2760,2763,2766,2769,2772,2775,2778,2781,2784,2787,2790,2793,2796,2799,2802,2805,2808],{"path":1969,"title":1970},"\u002Fabout","About Python CLI Toolcraft",{"path":1972,"title":1973},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies","Advanced Argument Validation Strategies",{"path":1975,"title":1976},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fparsing-nested-json-arguments-in-python-clis","Parsing Nested JSON Args in Python CLIs",{"path":1978,"title":1979},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fvalidating-dates-and-durations-in-cli-arguments","Validating Dates and Durations in Python CLI Arguments",{"path":1981,"title":1982},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fvalidating-dependent-and-conflicting-options","Validating Dependent and Conflicting CLI Options",{"path":1984,"title":1985},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fvalidating-file-and-directory-paths-in-clis","Validating File and Directory Paths in CLIs",{"path":1987,"title":1988},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fvalidating-urls-hosts-and-ports","Validating URLs, Hosts and Ports in Python CLI Arguments",{"path":1990,"title":1991},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fwriting-custom-click-parameter-types","Writing Custom Click Parameter Types",{"path":1993,"title":1994},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual\u002Fbrowsing-records-with-a-textual-datatable","Browsing Records with a Textual DataTable Picker",{"path":1996,"title":1997},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual\u002Fbuilding-your-first-textual-app","Building Your First Textual App for a Python CLI",{"path":1999,"title":2000},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual\u002Fchoosing-between-a-cli-a-prompt-flow-and-a-tui","Choosing Between a CLI, a Prompt Flow and a TUI",{"path":2002,"title":2003},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual","Building Terminal UIs with Textual for Python CLIs",{"path":2005,"title":2006},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual\u002Frunning-background-work-in-textual-with-workers","Running Background Work in Textual with Workers",{"path":2008,"title":2009},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual\u002Fstyling-textual-apps-with-tcss","Styling Textual Apps with TCSS",{"path":2011,"title":2012},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual\u002Ftesting-textual-apps-with-pilot","Testing Textual Apps with Pilot",{"path":2014,"title":2015},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fadding-examples-and-epilogs-to-help-output","Adding Examples and Epilogs to Help Output",{"path":2017,"title":2018},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fdocumenting-environment-variables-in-help","Documenting Environment Variables in CLI Help",{"path":2020,"title":2021},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fgenerating-man-pages-and-docs-from-a-cli","Generating Man Pages and Docs from a CLI",{"path":2023,"title":2024},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation","CLI Help Output and Documentation",{"path":2026,"title":2027},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Frich-formatted-help-with-rich-click","Rich-Formatted Help for Click CLIs with rich-click",{"path":2029,"title":2030},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fversioning-and-deprecating-cli-flags","Versioning and Deprecating CLI Flags",{"path":2032,"title":2033},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fwriting-help-text-users-actually-read","Writing Help Text Users Actually Read",{"path":2035,"title":2036},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Fadapting-output-to-terminal-width","Adapting Python CLI Output to Terminal Width",{"path":2038,"title":2039},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Fdetecting-ci-environments-and-non-interactive-shells","Detecting CI Environments and Non-Interactive Shells",{"path":2041,"title":2042},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Ffixing-unicode-and-encoding-errors-on-windows","Fixing Unicode and Encoding Errors on Windows in Python CLIs",{"path":2044,"title":2045},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Fhandling-ansi-escape-codes-on-windows-consoles","Handling ANSI Escape Codes on Windows Consoles",{"path":2047,"title":2048},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility","Cross-Platform Terminal Compatibility for Python CLIs",{"path":2050,"title":2051},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Frespecting-no-color-and-force-color","Respecting NO_COLOR and FORCE_COLOR in Python CLIs",{"path":2053,"title":2054},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Fsupporting-dumb-terminals-and-screen-readers","Supporting Dumb Terminals and Screen Readers in a Python CLI",{"path":2056,"title":2057},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fchoosing-exit-codes-for-cli-tools","Choosing Exit Codes for CLI Tools",{"path":2059,"title":2060},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fdesigning-an-exception-hierarchy-for-a-cli","Designing an Exception Hierarchy for a Python CLI",{"path":2062,"title":2063},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fdid-you-mean-suggestions-for-mistyped-input","Did You Mean…? Suggestions for Mistyped CLI Input",{"path":2065,"title":2066},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Ffriendly-error-messages-and-tracebacks","Friendly Error Messages and Tracebacks",{"path":2068,"title":2069},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fhandling-keyboard-interrupt-cleanly","Handling Keyboard Interrupt Cleanly",{"path":2071,"title":2072},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes","Error Handling and Exit Codes for CLIs",{"path":2074,"title":2075},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Freporting-machine-readable-errors-in-json-mode","Reporting Machine-Readable Errors in JSON Mode",{"path":2077,"title":2078},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fwriting-crash-reports-users-can-send","Writing Crash Reports Users Can Send",{"path":2080,"title":2081},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Fconfig-precedence-flags-env-files-defaults","Config Precedence: Flags, Env, Files, Defaults",{"path":2083,"title":2084},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Fdiscovering-project-config-files-by-walking-up-directories","Discovering Project Config Files by Walking Up Directories",{"path":2086,"title":2087},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars","Handling Config Files and Env Vars in CLIs",{"path":2089,"title":2090},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Floading-yaml-configs-safely-in-cli-apps","Loading YAML configs safely in CLI apps",{"path":2092,"title":2093},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Freading-toml-config-with-tomllib","Reading TOML Config with tomllib in Python CLIs",{"path":2095,"title":2096},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Ftyped-settings-with-pydantic-settings","Typed Settings with pydantic-settings in Python CLIs",{"path":2098,"title":2099},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Fvalidating-config-files-with-json-schema","Validating Config Files with JSON Schema in a Python CLI",{"path":2101,"title":2102},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Fwriting-a-config-init-and-edit-command","Writing a Config Init and Edit Command for a Python CLI",{"path":2104,"title":2105},"\u002Fadvanced-input-parsing-user-experience","Advanced Input Parsing for Python CLIs",{"path":2107,"title":2108},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Fadding-progress-bars-and-spinners-to-python-clis","Progress Bars and Spinners for Python CLIs",{"path":2110,"title":2111},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Fbuilding-interactive-prompts-and-menus","Building Interactive Prompts and Menus in Python CLIs",{"path":2113,"title":2114},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Fbuilding-tree-views-with-rich","Building Tree Views with Rich in a Python CLI",{"path":2116,"title":2117},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich","Interactive Terminal UI with Rich",{"path":2119,"title":2120},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Flive-dashboards-with-rich-live","Live Dashboards with Rich Live in Python CLIs",{"path":2122,"title":2123},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Frendering-markdown-and-syntax-highlighting-with-rich","Rendering Markdown and Syntax Highlighting with Rich",{"path":2125,"title":2126},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Frendering-tables-and-json-with-rich","Rendering Tables and JSON with Rich",{"path":2128,"title":2129},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Ftheming-rich-output-consistently","Theming Rich Output Consistently in Python CLIs",{"path":2131,"title":2132},"\u002Fadvanced-input-parsing-user-experience\u002Foutput-formats-for-data-clis\u002Fadding-a-format-flag-for-table-json-and-csv","Adding a Format Flag for Table, JSON and CSV Output",{"path":2134,"title":2135},"\u002Fadvanced-input-parsing-user-experience\u002Foutput-formats-for-data-clis\u002Fcustom-output-templates-with-a-format-string","Custom Output Templates with a Format String in Python CLIs",{"path":2137,"title":2138},"\u002Fadvanced-input-parsing-user-experience\u002Foutput-formats-for-data-clis\u002Fexporting-cli-results-to-files","Exporting CLI Results to Files from a Python CLI",{"path":2140,"title":2141},"\u002Fadvanced-input-parsing-user-experience\u002Foutput-formats-for-data-clis","Output Formats for Data-Heavy Python CLIs",{"path":2143,"title":2144},"\u002Fadvanced-input-parsing-user-experience\u002Foutput-formats-for-data-clis\u002Fselecting-fields-and-columns-from-cli-output","Selecting Fields and Columns from Python CLI Output",{"path":2146,"title":2147},"\u002Fadvanced-input-parsing-user-experience\u002Foutput-formats-for-data-clis\u002Fwriting-csv-and-tsv-output-correctly","Writing CSV and TSV Output Correctly from a Python CLI",{"path":1959,"title":5},{"path":2150,"title":2151},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Fdynamic-completion-values-from-apis-and-files","Dynamic Completion Values from APIs and Files",{"path":2153,"title":2154},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Fenabling-tab-completion-in-click-and-typer","Enabling Tab Completion in Click and Typer",{"path":2156,"title":2157},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis","Shell Completion for Python CLIs",{"path":2159,"title":2160},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Finstalling-shell-completion-for-bash-zsh-fish","Installing Shell Completion for bash, zsh, fish",{"path":2162,"title":2163},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Fshipping-completion-scripts-with-packages","Shipping Shell Completion Scripts with a Python CLI",{"path":2165,"title":2166},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Ftesting-shell-completion-in-python-clis","Testing Shell Completion in Python CLIs",{"path":2168,"title":2169},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fadding-trace-ids-and-context-to-cli-logs","Adding Trace IDs and Context to Python CLI Logs",{"path":2171,"title":2172},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fadding-verbose-and-quiet-logging-flags","Adding Verbose and Quiet Logging Flags",{"path":2174,"title":2175},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps","Structured Logging for CLI Apps",{"path":2177,"title":2178},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Flogging-with-structlog-in-a-cli","Logging with structlog in a Python CLI",{"path":2180,"title":2181},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fseparating-logs-from-program-output","Separating Logs from Program Output in a Python CLI",{"path":2183,"title":2184},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fstructured-json-logging-in-python-clis","Structured JSON Logging in Python CLIs",{"path":2186,"title":2187},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fwriting-rotating-log-files-from-a-cli","Writing Rotating Log Files from a Python CLI",{"path":2189,"title":2190},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Fdetecting-tty-and-adapting-output","Detecting a TTY and Adapting Output",{"path":2192,"title":2193},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Femitting-json-output-for-scripting","Emitting JSON Output for Scripting",{"path":2195,"title":2196},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Fhandling-broken-pipe-and-sigpipe","Handling Broken Pipe and SIGPIPE",{"path":2198,"title":2199},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes","Working with stdin, stdout and Pipes",{"path":2201,"title":2202},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Fnull-delimited-input-and-xargs-compatibility","Null-Delimited Input and xargs Compatibility in Python CLIs",{"path":2204,"title":2205},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Fprocessing-large-files-and-ndjson-streams","Processing Large Files and NDJSON Streams in Python CLIs",{"path":2207,"title":2208},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Freading-piped-input-in-python-clis","Reading Piped Input in Python CLIs",{"path":2210,"title":2211},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Fbuilding-an-api-client-cli-with-httpx","Building an API Client CLI with httpx",{"path":2213,"title":2214},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Fdownloading-files-with-progress-in-python","Downloading Files with Progress in Python CLIs",{"path":2216,"title":2217},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis","Calling HTTP APIs from Python CLIs",{"path":2219,"title":2220},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Fmocking-http-in-cli-tests-with-respx","Mocking HTTP in Python CLI Tests with respx",{"path":2222,"title":2223},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Foauth-device-flow-login-for-clis","OAuth Device Flow Login for Python CLIs",{"path":2225,"title":2226},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Fpaginating-api-results-in-a-cli","Paginating API Results in a Python CLI",{"path":2228,"title":2229},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Fretries-and-backoff-for-cli-http-calls","Retries and Backoff for CLI HTTP Calls",{"path":2231,"title":2232},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Fuploading-files-with-multipart-and-progress","Uploading Files with Multipart and Progress in a Python CLI",{"path":2234,"title":2235},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Fcancelling-async-tasks-on-ctrl-c","Cancelling Async Tasks on Ctrl+C in Python CLIs",{"path":2237,"title":2238},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis","Concurrency and Async in Python CLIs",{"path":2240,"title":2241},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Fmultiprocessing-for-cpu-bound-cli-tasks","Multiprocessing for CPU-Bound CLI Tasks",{"path":2243,"title":2244},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Fparallelising-cli-work-with-thread-pools","Parallelising CLI Work with Thread Pools",{"path":2246,"title":2247},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Frate-limiting-concurrent-requests-in-clis","Rate-Limiting Concurrent Requests in Python CLIs",{"path":2249,"title":2250},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Frunning-async-code-in-typer-and-click","Running Async Code in Typer and Click",{"path":2252,"title":2253},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Fshowing-progress-for-concurrent-tasks","Showing Progress for Concurrent Tasks in a Python CLI",{"path":2255,"title":2256},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Fstructured-concurrency-with-taskgroups-in-clis","Structured Concurrency with TaskGroups in Python CLIs",{"path":2258,"title":2259},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fcross-platform-paths-with-pathlib","Cross-Platform Paths with pathlib in CLIs",{"path":2261,"title":2262},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Ffile-locking-for-concurrent-cli-runs","File Locking for Concurrent CLI Runs in Python",{"path":2264,"title":2265},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fhandling-file-permissions-and-umask-in-clis","Handling File Permissions and umask in Python CLIs",{"path":2267,"title":2268},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes","Filesystem Paths and Atomic Writes for CLIs",{"path":2270,"title":2271},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fsafe-temporary-files-and-directories","Safe Temporary Files and Directories in CLIs",{"path":2273,"title":2274},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fstoring-app-data-with-platformdirs","Storing CLI App Data with platformdirs",{"path":2276,"title":2277},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fwalking-directory-trees-with-ignore-rules","Walking Directory Trees with Ignore Rules in a Python CLI",{"path":2279,"title":2280},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fwriting-files-atomically-in-python-clis","Writing Files Atomically in Python CLIs",{"path":2282,"title":2283},"\u002Fcli-runtime-systems-integration","CLI Runtime & Systems Integration for Python",{"path":2285,"title":2286},"\u002Fcli-runtime-systems-integration\u002Flocal-state-and-sqlite-in-python-clis\u002Fcaching-http-responses-on-disk-in-a-cli","Caching HTTP Responses on Disk in a Python CLI",{"path":2288,"title":2289},"\u002Fcli-runtime-systems-integration\u002Flocal-state-and-sqlite-in-python-clis","Local State and SQLite in Python CLIs",{"path":2291,"title":2292},"\u002Fcli-runtime-systems-integration\u002Flocal-state-and-sqlite-in-python-clis\u002Fmigrating-a-cli-sqlite-schema","Migrating a CLI’s SQLite Schema Between Releases",{"path":2294,"title":2295},"\u002Fcli-runtime-systems-integration\u002Flocal-state-and-sqlite-in-python-clis\u002Frecording-and-querying-cli-run-history","Recording and Querying CLI Run History in SQLite",{"path":2297,"title":2298},"\u002Fcli-runtime-systems-integration\u002Flocal-state-and-sqlite-in-python-clis\u002Fstoring-cli-state-in-sqlite","Storing CLI State in SQLite with a Small Repository Class",{"path":2300,"title":2301},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis\u002Fbuilding-a-watch-mode-with-watchfiles","Building a Watch Mode with watchfiles in Python",{"path":2303,"title":2304},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis\u002Fhandling-sigterm-and-graceful-shutdown","Handling SIGTERM and Graceful Shutdown in CLIs",{"path":2306,"title":2307},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis\u002Fhealth-checks-and-heartbeats-for-long-running-clis","Health Checks and Heartbeats for Long-Running CLIs",{"path":2309,"title":2310},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis","Long-Running and Watch-Mode Python CLIs",{"path":2312,"title":2313},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis\u002Freloading-config-on-sighup","Reloading Configuration on SIGHUP in a Python CLI",{"path":2315,"title":2316},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis\u002Frunning-a-cli-as-a-systemd-service","Running a Python CLI as a systemd Service",{"path":2318,"title":2319},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis\u002Frunning-a-cli-on-a-schedule-with-cron-and-systemd","Running a Python CLI on a Schedule with cron and systemd",{"path":2321,"title":2322},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis\u002Fsending-cli-logs-to-journald-and-syslog","Sending Python CLI Logs to journald and syslog",{"path":2324,"title":2325},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Favoiding-shell-injection-in-python-clis","Avoiding Shell Injection in Python CLIs",{"path":2327,"title":2328},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fcalling-external-commands-safely-with-subprocess","Calling External Commands Safely with subprocess",{"path":2330,"title":2331},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fhandling-subprocess-timeouts-and-exit-codes","Handling Subprocess Timeouts and Exit Codes",{"path":2333,"title":2334},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis","Running Subprocesses from Python CLIs",{"path":2336,"title":2337},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Flaunching-the-users-editor-from-a-cli","Launching the User’s Editor from a Python CLI",{"path":2339,"title":2340},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fpiping-between-subprocesses-in-python","Piping Between Subprocesses in Python Without a Shell",{"path":2342,"title":2343},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fstreaming-subprocess-output-in-real-time","Streaming Subprocess Output in Real Time",{"path":2345,"title":2346},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fwrapping-git-and-other-tools-from-a-python-cli","Wrapping git and Other Tools from a Python CLI",{"path":2348,"title":2349},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis","Secrets and Credentials in Python CLIs",{"path":2351,"title":2352},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Fprompting-for-passwords-securely","Prompting for Passwords Securely in Python CLIs",{"path":2354,"title":2355},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Freading-secrets-from-env-and-files","Reading Secrets from Env Vars and Files in CLIs",{"path":2357,"title":2358},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Fredacting-secrets-from-cli-output-and-logs","Redacting Secrets from CLI Output and Logs",{"path":2360,"title":2361},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Frefreshing-expired-tokens-automatically","Refreshing Expired Tokens Automatically in a Python CLI",{"path":2363,"title":2364},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Fstoring-tokens-with-keyring","Storing CLI Tokens Securely with keyring",{"path":2366,"title":2367},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Fsupporting-multiple-profiles-and-accounts","Supporting Multiple Profiles and Accounts in CLIs",{"path":2369,"title":2370},"\u002Fcli-runtime-systems-integration\u002Fupdate-checks-and-self-updating-clis\u002Fchecking-pypi-for-a-newer-version","Checking PyPI for a Newer Version of Your Python CLI",{"path":2372,"title":2373},"\u002Fcli-runtime-systems-integration\u002Fupdate-checks-and-self-updating-clis","Update Checks, Upgrade Commands and Telemetry for Python CLIs",{"path":2375,"title":2376},"\u002Fcli-runtime-systems-integration\u002Fupdate-checks-and-self-updating-clis\u002Fopt-in-usage-telemetry-for-python-clis","Opt-In Usage Telemetry for Python CLIs Done Responsibly",{"path":2378,"title":2379},"\u002Fcli-runtime-systems-integration\u002Fupdate-checks-and-self-updating-clis\u002Fself-upgrading-a-cli-installed-with-pipx-or-uv","Self-Upgrading a Python CLI Installed with pipx or uv",{"path":2381,"title":2382},"\u002Fcli-runtime-systems-integration\u002Fupdate-checks-and-self-updating-clis\u002Fshowing-non-blocking-update-notices","Showing Non-Blocking Update Notices in a Python CLI",{"path":814,"title":2384},"Python CLI Toolcraft",{"path":2386,"title":2387},"\u002Fmodern-python-cli-frameworks-architecture\u002Falternative-python-cli-frameworks\u002Fbuilding-a-cli-with-cleo","Building a Python CLI with Cleo Command Classes",{"path":2389,"title":2390},"\u002Fmodern-python-cli-frameworks-architecture\u002Falternative-python-cli-frameworks\u002Fbuilding-a-cli-with-cyclopts","Building a Type-Hinted Python CLI with Cyclopts",{"path":2392,"title":2393},"\u002Fmodern-python-cli-frameworks-architecture\u002Falternative-python-cli-frameworks","Beyond Click and Typer: Alternative Python CLI Frameworks",{"path":2395,"title":2396},"\u002Fmodern-python-cli-frameworks-architecture\u002Falternative-python-cli-frameworks\u002Fquick-clis-from-functions-with-python-fire","Quick CLIs from Functions with Python Fire",{"path":2398,"title":2399},"\u002Fmodern-python-cli-frameworks-architecture\u002Falternative-python-cli-frameworks\u002Fusage-string-driven-clis-with-docopt-ng","Usage-String Driven Python CLIs with docopt-ng",{"path":2401,"title":2402},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Favoiding-import-time-side-effects","Avoiding Import-Time Side Effects in a Python CLI",{"path":2404,"title":2405},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Fcaching-expensive-work-between-cli-runs","Caching Expensive Work Between Python CLI Runs",{"path":2407,"title":2408},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Fguarding-startup-with-import-tests","Guarding CLI Startup with Import Tests",{"path":2410,"title":2411},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading","CLI Startup Performance and Lazy Loading",{"path":2413,"title":2414},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Flazy-loading-subcommands-for-faster-startup","Lazy Loading Subcommands for Faster Startup",{"path":2416,"title":2417},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Fprofiling-python-cli-startup-time","Profiling Python CLI Startup Time",{"path":2419,"title":2420},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Freducing-cli-dependency-weight","Reducing CLI Dependency Weight",{"path":2422,"title":2423},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargparse-subparsers-for-subcommands","argparse Subparsers for Subcommands",{"path":2425,"title":2426},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargparse-vs-click-vs-typer-comparison","argparse vs Click vs Typer Compared",{"path":2428,"title":2429},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargument-groups-and-help-formatting-in-argparse","Argument Groups and Help Formatting in argparse",{"path":2431,"title":2432},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse","Command-Line Parsing with argparse",{"path":2434,"title":2435},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fmigrating-from-argparse-to-typer","Migrating from argparse to Typer",{"path":2437,"title":2438},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fmutually-exclusive-options-in-argparse","Mutually Exclusive Options in argparse",{"path":2440,"title":2441},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Freading-arguments-from-files-with-fromfile-prefix-chars","Reading Arguments from Files with argparse’s fromfile_prefix_chars",{"path":2443,"title":2444},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fwriting-custom-argparse-actions","Writing Custom argparse Actions in Python",{"path":2446,"title":2447},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Fadding-dry-run-and-confirmation-to-destructive-commands","Adding Dry-Run and Confirmation to Destructive Commands",{"path":2449,"title":2450},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Fdesigning-idempotent-commands","Designing Idempotent Commands in a Python CLI",{"path":2452,"title":2453},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Ffollowing-posix-and-gnu-argument-conventions","Following POSIX and GNU Argument Conventions in Python",{"path":2455,"title":2456},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Fglobal-options-vs-per-command-options","Global Options vs Per-Command Options in Python CLIs",{"path":2458,"title":2459},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions","Designing CLI Interfaces and Conventions in Python",{"path":2461,"title":2462},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Fnaming-commands-and-flags-consistently","Naming Commands and Flags Consistently in Python CLIs",{"path":2464,"title":2465},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Fpositional-arguments-vs-options","Positional Arguments vs Options: Designing a CLI Signature",{"path":2467,"title":2468},"\u002Fmodern-python-cli-frameworks-architecture","Python CLI Frameworks and Architecture",{"path":2470,"title":2471},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fdiscovering-plugins-with-entry-points","Discovering Plugins with Entry Points in Python CLIs",{"path":2473,"title":2474},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fhook-based-plugins-with-pluggy","Hook-Based Plugins for Python CLIs with pluggy",{"path":2476,"title":2477},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis","Plugin Architectures for Extensible CLIs",{"path":2479,"title":2480},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fisolating-plugin-failures","Isolating Plugin Failures in an Extensible Python CLI",{"path":2482,"title":2483},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Ftesting-plugins-against-the-host-cli","Testing Plugins Against the Host CLI",{"path":2485,"title":2486},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fversioning-a-plugin-api","Versioning a Plugin API for a Python CLI",{"path":2488,"title":2489},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fwriting-a-plugin-for-an-existing-cli","Writing a Plugin for an Existing CLI",{"path":2491,"title":2492},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fbest-practices-for-python-cli-entry-points","Best practices for Python CLI entry points",{"path":2494,"title":2495},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fdependency-injection-patterns-for-cli-commands","Dependency Injection Patterns for CLI Commands",{"path":2497,"title":2498},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fhow-to-structure-a-large-python-cli-project","Structuring a Large Python CLI Project",{"path":2500,"title":2501},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis","Structuring Multi-Command Python CLIs",{"path":2503,"title":2504},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Foffering-a-python-api-alongside-your-cli","Offering a Python API Alongside Your CLI",{"path":2506,"title":2507},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fregistering-commands-from-modules-automatically","Registering CLI Commands from Modules Automatically",{"path":2509,"title":2510},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fsharing-common-options-across-commands","Sharing Common Options Across Python CLI Commands",{"path":2512,"title":2513},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fsharing-state-with-click-context-objects","Sharing State with Click Context Objects",{"path":2515,"title":2516},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fend-to-end-testing-an-installed-cli","End-to-End Testing an Installed Python CLI",{"path":2518,"title":2519},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications","Testing Python CLI Applications",{"path":2521,"title":2522},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fmeasuring-cli-test-coverage","Measuring CLI Test Coverage",{"path":2524,"title":2525},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fmocking-filesystem-and-network-in-cli-tests","Mocking the Filesystem and Network in CLI Tests",{"path":2527,"title":2528},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fproperty-based-testing-cli-arguments-with-hypothesis","Property-Based Testing CLI Arguments with Hypothesis",{"path":2530,"title":2531},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fsnapshot-testing-cli-output","Snapshot Testing CLI Output",{"path":2533,"title":2534},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftesting-click-commands-with-clirunner","Testing Click Commands with CliRunner",{"path":2536,"title":2537},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftesting-commands-that-call-subprocesses","Testing Python CLI Commands That Call Subprocesses",{"path":2539,"title":2540},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftesting-interactive-prompts-and-stdin","Testing Interactive Prompts and stdin",{"path":2542,"title":2543},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftranscript-tests-for-cli-commands","Transcript Tests for Python CLI Commands",{"path":2545,"title":2546},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Fbuilding-a-cli-with-subcommands-in-click","Building a CLI with subcommands in Click",{"path":2548,"title":2549},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Fbuilding-dynamic-commands-in-click","Building Dynamic Commands in Click",{"path":2551,"title":2552},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Fchoices-and-enums-in-typer-and-click","Choices and Enums in Typer and Click Options",{"path":2554,"title":2555},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Fclick-option-callbacks-and-eager-options","Click Option Callbacks and Eager Options Explained",{"path":2557,"title":2558},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Fconverting-a-click-app-to-typer","Converting a Click App to Typer",{"path":2560,"title":2561},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each","Typer vs Click: When to Use Each",{"path":2563,"title":2564},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Frich-markup-and-help-panels-in-typer","Rich Markup and Help Panels in Typer",{"path":2566,"title":2567},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Ftyper-callback-functions-explained","Typer callback functions explained",{"path":2569,"title":2570},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Fusing-annotated-options-in-typer","Using Annotated Options in Typer",{"path":2572,"title":2573},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis\u002Fautomating-releases-from-git-tags","Automating Python CLI Releases from Git Tags",{"path":2575,"title":2576},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis\u002Fcaching-uv-dependencies-in-ci","Caching uv Dependencies in CI for Python CLIs",{"path":2578,"title":2579},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis","CI\u002FCD Pipelines for Python CLIs",{"path":2581,"title":2582},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis\u002Fpublishing-a-cli-docker-image-from-ci","Publishing a Python CLI as a Docker Image from CI",{"path":2584,"title":2585},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis\u002Fpublishing-to-pypi-with-trusted-publishing","Publishing a CLI to PyPI with Trusted Publishing",{"path":2587,"title":2588},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis\u002Frunning-cli-tests-on-windows-and-macos-runners","Running Python CLI Tests on Windows and macOS Runners",{"path":2590,"title":2591},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis\u002Fsmoke-testing-the-built-wheel-in-ci","Smoke-Testing the Built Wheel of a Python CLI in CI",{"path":2593,"title":2594},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis\u002Ftesting-a-cli-across-python-versions-with-github-actions","Testing a CLI Across Python Versions in GitHub Actions",{"path":2596,"title":2597},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fbuilding-a-cookiecutter-template-for-typer-clis","Building a Cookiecutter Template for Typer CLIs",{"path":2599,"title":2600},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fcopier-vs-cookiecutter-for-cli-templates","Copier vs Cookiecutter for CLI Templates",{"path":2602,"title":2603},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter","CLI Project Scaffolding with Cookiecutter",{"path":2605,"title":2606},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fpost-generation-hooks-in-cli-templates","Post-Generation Hooks in Python CLI Templates",{"path":2608,"title":2609},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Ftemplate-variables-and-conditional-files","Template Variables and Conditional Files in CLI Templates",{"path":2611,"title":2612},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Ftesting-a-project-template-with-pytest","Testing a CLI Project Template with pytest",{"path":2614,"title":2615},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fupdating-generated-projects-with-copier-update","Updating Generated CLI Projects with copier update",{"path":2617,"title":2618},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fbuilding-cross-platform-release-binaries-in-ci","Building Cross-Platform Release Binaries in CI",{"path":2620,"title":2621},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fbundling-a-python-cli-with-pyinstaller","Bundling a Python CLI with PyInstaller",{"path":2623,"title":2624},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fcode-signing-and-notarizing-cli-binaries","Code Signing and Notarizing Python CLI Binaries",{"path":2626,"title":2627},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fhomebrew-and-scoop-packaging-for-python-clis","Homebrew and Scoop Packaging for Python CLIs",{"path":2629,"title":2630},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries","Distributing CLIs as Standalone Binaries",{"path":2632,"title":2633},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fnuitka-vs-pyinstaller-for-python-clis","Nuitka vs PyInstaller for Python CLI Binaries",{"path":2635,"title":2636},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Freducing-pyinstaller-binary-size","Reducing the Size of a PyInstaller CLI Binary",{"path":2638,"title":2639},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fshipping-a-cli-as-a-zipapp-with-shiv","Shipping a CLI as a Zipapp with shiv",{"path":2641,"title":2642},"\u002Fproject-setup-dependency-management","Project Setup & Dependency Management",{"path":2644,"title":2645},"\u002Fproject-setup-dependency-management\u002Flinting-and-type-checking-cli-code\u002Fconfiguring-ruff-for-a-cli-project","Configuring Ruff for a Python CLI Project",{"path":2647,"title":2648},"\u002Fproject-setup-dependency-management\u002Flinting-and-type-checking-cli-code\u002Fenforcing-import-boundaries-in-a-cli-codebase","Enforcing Import Boundaries in a Python CLI Codebase",{"path":2650,"title":2651},"\u002Fproject-setup-dependency-management\u002Flinting-and-type-checking-cli-code\u002Ffinding-unused-code-and-dependencies-with-vulture-and-deptry","Finding Unused Code and Dependencies with vulture and deptry",{"path":2653,"title":2654},"\u002Fproject-setup-dependency-management\u002Flinting-and-type-checking-cli-code","Linting and Type-Checking Python CLI Code",{"path":2656,"title":2657},"\u002Fproject-setup-dependency-management\u002Flinting-and-type-checking-cli-code\u002Frunning-pyright-in-strict-mode-on-a-cli","Running Pyright in Strict Mode on a Python CLI",{"path":2659,"title":2660},"\u002Fproject-setup-dependency-management\u002Flinting-and-type-checking-cli-code\u002Frunning-ruff-and-mypy-in-ci-with-annotations","Running Ruff and mypy in CI with Inline Annotations",{"path":2662,"title":2663},"\u002Fproject-setup-dependency-management\u002Flinting-and-type-checking-cli-code\u002Ftype-checking-click-and-typer-code-with-mypy","Type-Checking Click and Typer Code with mypy",{"path":2665,"title":2666},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fautomating-changelogs-with-conventional-commits","Automating Changelogs with Conventional Commits",{"path":2668,"title":2669},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fbumping-versions-consistently-across-a-cli-project","Bumping Versions Consistently Across a Python CLI Project",{"path":2671,"title":2672},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fderiving-versions-from-git-tags-with-hatch-vcs","Deriving CLI Versions from Git Tags with hatch-vcs",{"path":2674,"title":2675},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fexposing-version-info-and-build-metadata","Exposing Version Info and Build Metadata",{"path":2677,"title":2678},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs","Managing CLI Versioning & Changelogs",{"path":2680,"title":2681},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fsemantic-versioning-policy-for-cli-tools","A Semantic Versioning Policy for CLI Tools",{"path":2683,"title":2684},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fshipping-pre-releases-and-release-candidates","Shipping Pre-Releases and Release Candidates of a Python CLI",{"path":2686,"title":2687},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fbuilding-wheels-and-sdists-for-python-clis","Building Wheels and sdists for Python CLIs",{"path":2689,"title":2690},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fbundling-data-files-with-importlib-resources","Bundling Data Files with importlib.resources in CLIs",{"path":2692,"title":2693},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fchoosing-a-build-backend-for-a-python-cli","Choosing a Build Backend for a Python CLI",{"path":2695,"title":2696},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution","Packaging Python CLIs for Distribution",{"path":2698,"title":2699},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Finstalling-and-distributing-clis-with-pipx","Installing and Distributing CLIs with pipx",{"path":2701,"title":2702},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Foptional-dependencies-and-extras-for-clis","Optional Dependencies and Extras for Python CLIs",{"path":2704,"title":2705},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fpublishing-a-python-cli-to-pypi","Publishing a Python CLI to PyPI",{"path":2707,"title":2708},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fwriting-pyproject-toml-metadata-for-a-cli","Writing pyproject.toml Metadata for a Python CLI",{"path":2710,"title":2711},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fdynamic-versioning-with-poetry-plugins","Dynamic Versioning for a Poetry CLI from Git Tags",{"path":2713,"title":2714},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development","Poetry Workflows for CLI Development",{"path":2716,"title":2717},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fmanaging-poetry-lock-files-for-cli-tools","Managing Poetry Lock Files for CLI Tools",{"path":2719,"title":2720},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fmigrating-a-cli-from-poetry-to-uv","Migrating a Python CLI from Poetry to uv",{"path":2722,"title":2723},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpoetry-dependency-groups-for-cli-tooling","Poetry Dependency Groups for CLI Tooling",{"path":2725,"title":2726},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpoetry-entry-points-and-scripts-for-clis","Poetry Entry Points and Scripts for CLIs",{"path":2728,"title":2729},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpublishing-a-cli-with-poetry","Building and Publishing a Python CLI with Poetry",{"path":2731,"title":2732},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects","Pre-commit Hooks for CLI Projects",{"path":2734,"title":2735},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects\u002Fkeeping-hook-versions-current-with-autoupdate","Keeping pre-commit Hook Versions Current with autoupdate",{"path":2737,"title":2738},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects\u002Frunning-pre-commit-in-ci","Running pre-commit in CI for a Python CLI Repository",{"path":2740,"title":2741},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects\u002Fsetting-up-pre-commit-for-python-cli-repos","Setting up pre-commit for Python CLI repos",{"path":2743,"title":2744},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects\u002Fshipping-your-cli-as-a-pre-commit-hook","Shipping Your Python CLI as a pre-commit Hook",{"path":2746,"title":2747},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects\u002Fspeeding-up-slow-pre-commit-hooks","Speeding Up Slow pre-commit Hooks in a CLI Repository",{"path":2749,"title":2750},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects\u002Fwriting-local-pre-commit-hooks-in-python","Writing Local pre-commit Hooks in Python",{"path":2752,"title":2753},"\u002Fproject-setup-dependency-management\u002Fsecuring-a-python-cli-supply-chain\u002Fauditing-dependencies-with-pip-audit","Auditing a Python CLI’s Dependencies with pip-audit",{"path":2755,"title":2756},"\u002Fproject-setup-dependency-management\u002Fsecuring-a-python-cli-supply-chain\u002Fgenerating-an-sbom-for-a-python-cli","Generating an SBOM for a Python CLI Release",{"path":2758,"title":2759},"\u002Fproject-setup-dependency-management\u002Fsecuring-a-python-cli-supply-chain","Securing the Supply Chain of a Python CLI",{"path":2761,"title":2762},"\u002Fproject-setup-dependency-management\u002Fsecuring-a-python-cli-supply-chain\u002Fpinning-dependencies-with-hashes","Pinning a Python CLI’s Dependencies with Hashes",{"path":2764,"title":2765},"\u002Fproject-setup-dependency-management\u002Fsecuring-a-python-cli-supply-chain\u002Fpublishing-attestations-and-verifying-releases","Publishing Attestations and Verifying Python CLI Releases",{"path":2767,"title":2768},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002Fbuilding-and-publishing-a-cli-with-uv","Building and Publishing a Python CLI with uv",{"path":2770,"title":2771},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management","uv for Python CLI Dependency Management",{"path":2773,"title":2774},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002Flocking-and-syncing-cli-dependencies-with-uv","Locking and Syncing a Python CLI’s Dependencies with uv",{"path":2776,"title":2777},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002Frunning-one-off-cli-scripts-with-uv-run","Running One-Off CLI Scripts with uv run and PEP 723",{"path":2779,"title":2780},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002Fusing-private-package-indexes-with-uv","Using Private Package Indexes with uv for Internal CLIs",{"path":2782,"title":2783},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002Fuv-init-vs-poetry-init-for-cli-tools","uv init vs poetry init for CLI tools",{"path":2785,"title":2786},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002Fuv-tool-install-vs-pipx-for-clis","uv tool install vs pipx for CLIs",{"path":2788,"title":2789},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002Fuv-workspaces-for-multi-package-clis","uv Workspaces for Multi-Package Python CLIs",{"path":2791,"title":2792},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fdebugging-wrong-python-and-wrong-venv-problems","Debugging Wrong-Python and Wrong-Venv Problems in CLIs",{"path":2794,"title":2795},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fexternally-managed-environments-and-pep-668","PEP 668 and Python CLIs: the externally-managed-environment Error",{"path":2797,"title":2798},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices","Python CLI Env Isolation Best Practices",{"path":2800,"title":2801},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fisolating-cli-tools-from-project-dependencies","Isolating CLI Tools from Your Project’s Dependencies",{"path":2803,"title":2804},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fmanaging-virtual-environments-for-cross-platform-clis","Managing Python CLI Virtual Environments",{"path":2806,"title":2807},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fpinning-the-python-version-for-a-cli","Pinning the Python Version for a CLI",{"path":2809,"title":2810},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fsupporting-multiple-python-versions-with-nox","Supporting Multiple Python Versions with nox",1790967537214]