[{"data":1,"prerenderedAt":1934},["ShallowReactive",2],{"page-\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002F":3,"content-directory":1386},{"id":4,"title":5,"body":6,"date":1370,"description":1371,"difficulty":1372,"draft":1373,"extension":1374,"meta":1375,"navigation":219,"path":1376,"seo":1377,"stem":1378,"tags":1379,"updated":1370,"__hash__":1385},"content\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Findex.md","Designing CLI Interfaces and Conventions in Python",{"type":7,"value":8,"toc":1347},"minimark",[9,42,56,60,65,120,124,127,130,153,172,184,192,546,556,560,568,656,659,662,666,669,672,686,718,722,738,916,927,931,944,996,1003,1007,1010,1028,1039,1045,1049,1052,1062,1068,1085,1089,1095,1103,1115,1119,1122,1162,1172,1176,1206,1210,1215,1225,1229,1232,1236,1243,1247,1265,1269,1272,1276,1284,1288,1303,1307,1343],[10,11,12,13,17,18,21,22,25,26,29,30,33,34,37,38,41],"p",{},"A CLI's interface — the commands, flags and arguments people type — is its most durable decision. Code behind it can be rewritten freely; the interface cannot, because it lives in shell history, runbooks, CI configurations, cron entries, colleagues' muscle memory and other people's scripts. Once ",[14,15,16],"code",{},"mytool deploy --dir .\u002Fsite"," has been in a README for a month, renaming ",[14,19,20],{},"--dir"," is a breaking change. Yet most CLIs grow their interface one command at a time, each added by whoever needed it that week, and end up with ",[14,23,24],{},"list"," in one place and ",[14,27,28],{},"ls"," in another, ",[14,31,32],{},"-f"," meaning ",[14,35,36],{},"--force"," here and ",[14,39,40],{},"--file"," there, and a destructive command that deletes production data on a typo.",[10,43,44,45,50,51,55],{},"This topic is about designing the interface deliberately: choosing a shape for the command tree, naming commands and flags consistently, deciding which options are global, making destructive operations safe, and following the conventions — POSIX, GNU and decades of Unix practice — that users already know. It sits in the ",[46,47,49],"a",{"href":48},"\u002Fmodern-python-cli-frameworks-architecture\u002F","Modern Python CLI Frameworks & Architecture"," section alongside ",[46,52,54],{"href":53},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002F","structuring multi-command CLIs",", which covers the code side of the same tree.",[57,58],"inline-diagram",{"name":59},"dsg-topic-map",[61,62,64],"h2",{"id":63},"tldr","TL;DR",[66,67,68,76,82,96,102],"ul",{},[69,70,71,75],"li",{},[72,73,74],"strong",{},"Choose one command-tree shape"," — flat verbs, noun-then-verb or verb-then-noun — and apply it everywhere.",[69,77,78,81],{},[72,79,80],{},"Use one vocabulary",": the same verb for the same action on every resource, the same flag name for the same meaning on every command.",[69,83,84,87,88,91,92,95],{},[72,85,86],{},"Make destructive commands safe by default",": plan, show, confirm, apply — with ",[14,89,90],{},"--dry-run"," and ",[14,93,94],{},"--yes"," for automation.",[69,97,98,101],{},[72,99,100],{},"Keep global options global in meaning",", and decide deliberately where they may appear on the command line.",[69,103,104,107,108,111,112,115,116,119],{},[72,105,106],{},"Follow the conventions users already know",": long and short flags, ",[14,109,110],{},"--opt=value",", ",[14,113,114],{},"--"," to end options, ",[14,117,118],{},"-"," for stdin, exit status 2 for usage errors.",[61,121,123],{"id":122},"choosing-a-shape-for-the-command-tree","Choosing a shape for the command tree",[10,125,126],{},"Before naming individual commands, decide how the tree is organised. Three shapes cover almost every well-known tool:",[57,128],{"name":129},"dsg-shapes",[10,131,132,135,136,111,139,111,142,145,146,111,149,152],{},[72,133,134],{},"Flat verbs"," (",[14,137,138],{},"git commit",[14,140,141],{},"git push",[14,143,144],{},"pip install",") suit tools that operate on essentially one kind of thing. They are the easiest to learn and the hardest to grow: when a second kind of thing arrives, verbs start needing qualifiers (",[14,147,148],{},"git remote add",[14,150,151],{},"git stash list",") and the flat shape bends.",[10,154,155,135,158,111,161,111,164,167,168,171],{},[72,156,157],{},"Noun then verb",[14,159,160],{},"gh pr create",[14,162,163],{},"gh repo clone",[14,165,166],{},"aws s3 cp",") suits tools that manage several resource types. Each noun becomes a command group with a predictable set of verbs, ",[14,169,170],{},"--help"," at each level lists what you can do to that resource, and adding a new resource type does not crowd the top level. This is the best default for most internal tools that wrap an API or platform.",[10,173,174,135,177,111,180,183],{},[72,175,176],{},"Verb then noun",[14,178,179],{},"kubectl get pods",[14,181,182],{},"kubectl delete deployment",") suits tools with a small, fixed set of verbs applied uniformly to many resource types. It shines when every verb really does apply to every noun, and struggles when some resources support operations others do not.",[10,185,186,187,191],{},"In Typer and Click, noun-then-verb maps naturally onto nested groups, which is exactly the structure described in ",[46,188,190],{"href":189},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Fbuilding-a-cli-with-subcommands-in-click\u002F","building a CLI with subcommands in Click",":",[193,194,199],"pre",{"className":195,"code":196,"language":197,"meta":198,"style":198},"language-python shiki shiki-themes github-light github-dark","import typer\n\napp = typer.Typer(no_args_is_help=True)\nsites = typer.Typer(help=\"Manage deployed sites.\", no_args_is_help=True)\nbuilds = typer.Typer(help=\"Inspect and prune builds.\", no_args_is_help=True)\napp.add_typer(sites, name=\"site\")\napp.add_typer(builds, name=\"build\")\n\n\n@sites.command(\"list\")\ndef site_list() -> None:\n    \"\"\"List sites.\"\"\"\n\n\n@sites.command(\"show\")\ndef site_show(name: str) -> None:\n    \"\"\"Show one site in detail.\"\"\"\n\n\n@builds.command(\"list\")\ndef build_list() -> None:\n    \"\"\"List recent builds.\"\"\"\n\n\n@builds.command(\"prune\")\ndef build_prune(older_than: str = \"30d\", dry_run: bool = False, yes: bool = False) -> None:\n    \"\"\"Delete builds older than a threshold.\"\"\"\n","python","",[14,200,201,214,221,246,275,302,318,333,338,343,358,376,382,387,392,404,425,431,436,441,453,467,473,478,483,495,540],{"__ignoreMap":198},[202,203,206,210],"span",{"class":204,"line":205},"line",1,[202,207,209],{"class":208},"szBVR","import",[202,211,213],{"class":212},"sVt8B"," typer\n",[202,215,217],{"class":204,"line":216},2,[202,218,220],{"emptyLinePlaceholder":219},true,"\n",[202,222,224,227,230,233,237,239,243],{"class":204,"line":223},3,[202,225,226],{"class":212},"app ",[202,228,229],{"class":208},"=",[202,231,232],{"class":212}," typer.Typer(",[202,234,236],{"class":235},"s4XuR","no_args_is_help",[202,238,229],{"class":208},[202,240,242],{"class":241},"sj4cs","True",[202,244,245],{"class":212},")\n",[202,247,249,252,254,256,259,261,265,267,269,271,273],{"class":204,"line":248},4,[202,250,251],{"class":212},"sites ",[202,253,229],{"class":208},[202,255,232],{"class":212},[202,257,258],{"class":235},"help",[202,260,229],{"class":208},[202,262,264],{"class":263},"sZZnC","\"Manage deployed sites.\"",[202,266,111],{"class":212},[202,268,236],{"class":235},[202,270,229],{"class":208},[202,272,242],{"class":241},[202,274,245],{"class":212},[202,276,278,281,283,285,287,289,292,294,296,298,300],{"class":204,"line":277},5,[202,279,280],{"class":212},"builds ",[202,282,229],{"class":208},[202,284,232],{"class":212},[202,286,258],{"class":235},[202,288,229],{"class":208},[202,290,291],{"class":263},"\"Inspect and prune builds.\"",[202,293,111],{"class":212},[202,295,236],{"class":235},[202,297,229],{"class":208},[202,299,242],{"class":241},[202,301,245],{"class":212},[202,303,305,308,311,313,316],{"class":204,"line":304},6,[202,306,307],{"class":212},"app.add_typer(sites, ",[202,309,310],{"class":235},"name",[202,312,229],{"class":208},[202,314,315],{"class":263},"\"site\"",[202,317,245],{"class":212},[202,319,321,324,326,328,331],{"class":204,"line":320},7,[202,322,323],{"class":212},"app.add_typer(builds, ",[202,325,310],{"class":235},[202,327,229],{"class":208},[202,329,330],{"class":263},"\"build\"",[202,332,245],{"class":212},[202,334,336],{"class":204,"line":335},8,[202,337,220],{"emptyLinePlaceholder":219},[202,339,341],{"class":204,"line":340},9,[202,342,220],{"emptyLinePlaceholder":219},[202,344,346,350,353,356],{"class":204,"line":345},10,[202,347,349],{"class":348},"sScJk","@sites.command",[202,351,352],{"class":212},"(",[202,354,355],{"class":263},"\"list\"",[202,357,245],{"class":212},[202,359,361,364,367,370,373],{"class":204,"line":360},11,[202,362,363],{"class":208},"def",[202,365,366],{"class":348}," site_list",[202,368,369],{"class":212},"() -> ",[202,371,372],{"class":241},"None",[202,374,375],{"class":212},":\n",[202,377,379],{"class":204,"line":378},12,[202,380,381],{"class":263},"    \"\"\"List sites.\"\"\"\n",[202,383,385],{"class":204,"line":384},13,[202,386,220],{"emptyLinePlaceholder":219},[202,388,390],{"class":204,"line":389},14,[202,391,220],{"emptyLinePlaceholder":219},[202,393,395,397,399,402],{"class":204,"line":394},15,[202,396,349],{"class":348},[202,398,352],{"class":212},[202,400,401],{"class":263},"\"show\"",[202,403,245],{"class":212},[202,405,407,409,412,415,418,421,423],{"class":204,"line":406},16,[202,408,363],{"class":208},[202,410,411],{"class":348}," site_show",[202,413,414],{"class":212},"(name: ",[202,416,417],{"class":241},"str",[202,419,420],{"class":212},") -> ",[202,422,372],{"class":241},[202,424,375],{"class":212},[202,426,428],{"class":204,"line":427},17,[202,429,430],{"class":263},"    \"\"\"Show one site in detail.\"\"\"\n",[202,432,434],{"class":204,"line":433},18,[202,435,220],{"emptyLinePlaceholder":219},[202,437,439],{"class":204,"line":438},19,[202,440,220],{"emptyLinePlaceholder":219},[202,442,444,447,449,451],{"class":204,"line":443},20,[202,445,446],{"class":348},"@builds.command",[202,448,352],{"class":212},[202,450,355],{"class":263},[202,452,245],{"class":212},[202,454,456,458,461,463,465],{"class":204,"line":455},21,[202,457,363],{"class":208},[202,459,460],{"class":348}," build_list",[202,462,369],{"class":212},[202,464,372],{"class":241},[202,466,375],{"class":212},[202,468,470],{"class":204,"line":469},22,[202,471,472],{"class":263},"    \"\"\"List recent builds.\"\"\"\n",[202,474,476],{"class":204,"line":475},23,[202,477,220],{"emptyLinePlaceholder":219},[202,479,481],{"class":204,"line":480},24,[202,482,220],{"emptyLinePlaceholder":219},[202,484,486,488,490,493],{"class":204,"line":485},25,[202,487,446],{"class":348},[202,489,352],{"class":212},[202,491,492],{"class":263},"\"prune\"",[202,494,245],{"class":212},[202,496,498,500,503,506,508,511,514,517,520,522,525,528,530,532,534,536,538],{"class":204,"line":497},26,[202,499,363],{"class":208},[202,501,502],{"class":348}," build_prune",[202,504,505],{"class":212},"(older_than: ",[202,507,417],{"class":241},[202,509,510],{"class":208}," =",[202,512,513],{"class":263}," \"30d\"",[202,515,516],{"class":212},", dry_run: ",[202,518,519],{"class":241},"bool",[202,521,510],{"class":208},[202,523,524],{"class":241}," False",[202,526,527],{"class":212},", yes: ",[202,529,519],{"class":241},[202,531,510],{"class":208},[202,533,524],{"class":241},[202,535,420],{"class":212},[202,537,372],{"class":241},[202,539,375],{"class":212},[202,541,543],{"class":204,"line":542},27,[202,544,545],{"class":263},"    \"\"\"Delete builds older than a threshold.\"\"\"\n",[10,547,548,551,552,555],{},[14,549,550],{},"no_args_is_help=True"," at each level means ",[14,553,554],{},"mytool site"," with nothing else prints the verbs available for sites — the discoverability that makes the noun-then-verb shape pleasant.",[61,557,559],{"id":558},"one-vocabulary-everywhere","One vocabulary, everywhere",[10,561,562,563,567],{},"The single biggest improvement to most grown-organically CLIs is a shared vocabulary: the same verb for the same action on every resource, and the same flag for the same meaning in every command. Users then learn the tool once. ",[46,564,566],{"href":565},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Fnaming-commands-and-flags-consistently\u002F","Naming commands and flags consistently"," develops a full vocabulary; the core rules are short:",[66,569,570,599,628,642],{},[69,571,572,575,576,578,579,582,583,111,586,111,589,592,593,595,596,598],{},[72,573,574],{},"Verbs:"," ",[14,577,24],{}," (many), ",[14,580,581],{},"show"," (one), ",[14,584,585],{},"create",[14,587,588],{},"update",[14,590,591],{},"delete",". Pick one word per action and never introduce a second canonical name for it; aliases for muscle memory (",[14,594,28],{}," for ",[14,597,24],{},") are fine if they are documented as aliases.",[69,600,601,604,605,608,609,612,613,616,617,619,620,623,624,627],{},[72,602,603],{},"Flags:"," lowercase kebab-case long names, always. Short flags only for the few options people type constantly, and only with the meanings Unix tools established: ",[14,606,607],{},"-v"," verbose, ",[14,610,611],{},"-q"," quiet, ",[14,614,615],{},"-o"," output, ",[14,618,32],{}," force, ",[14,621,622],{},"-n"," dry-run or count, ",[14,625,626],{},"-h"," help.",[69,629,630,633,634,637,638,641],{},[72,631,632],{},"Booleans:"," positive names with a generated negation (",[14,635,636],{},"--color\u002F--no-color","), never double negatives like ",[14,639,640],{},"--disable-no-verify",".",[69,643,644,647,648,651,652,655],{},[72,645,646],{},"Units:"," in the name when ambiguous (",[14,649,650],{},"--timeout-seconds","), or accept units in the value (",[14,653,654],{},"--timeout 30s",") with a clear parser.",[10,657,658],{},"The design principles that hold up over years of growth are mostly about restraint:",[57,660],{"name":661},"dsg-principles",[61,663,665],{"id":664},"arguments-options-and-the-anatomy-of-an-invocation","Arguments, options and the anatomy of an invocation",[10,667,668],{},"A command line has more structure than a list of words, and designing well means knowing which part each word belongs to:",[57,670],{"name":671},"dsg-invocation-anatomy",[10,673,674,677,678,681,682,641],{},[72,675,676],{},"Positional arguments"," are for the thing the command acts on — the file, the site, the resource name. Keep to one or two; beyond that, users forget the order. ",[72,679,680],{},"Options"," are for everything that modifies how the command acts, and they should have sensible defaults so the common case needs none. A required option is often a sign that it should be a positional argument, or that the default is missing. Where an option's value can come from configuration or the environment as well as the command line, follow a single precedence order, as in ",[46,683,685],{"href":684},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Fconfig-precedence-flags-env-files-defaults\u002F","config precedence: flags, env, files and defaults",[10,687,688,691,692,111,695,111,698,111,701,704,705,708,709,712,713,717],{},[72,689,690],{},"Global options"," — ",[14,693,694],{},"--verbose",[14,696,697],{},"--profile",[14,699,700],{},"--config",[14,702,703],{},"--no-color"," — affect every command and belong to the top-level group. Click parses group options only before the subcommand name, so ",[14,706,707],{},"mytool deploy -v"," fails where ",[14,710,711],{},"mytool -v deploy"," works — a frequent source of confusion. ",[46,714,716],{"href":715},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Fglobal-options-vs-per-command-options\u002F","Global options vs per-command options"," covers how to decide scope and how to accept common options in either position.",[61,719,721],{"id":720},"safe-destructive-commands","Safe destructive commands",[10,723,724,725,728,729,731,732,734,735,737],{},"Commands that delete, overwrite, deploy or migrate deserve a different design from commands that read. The pattern that works is ",[72,726,727],{},"plan, show, confirm, apply",": compute what would change without changing anything, show it, ask for confirmation, and apply exactly the plan that was shown. ",[14,730,90],{}," stops after showing; ",[14,733,94],{}," skips the prompt for automation; and when no terminal is attached, a missing ",[14,736,94],{}," is an error rather than an implicit yes.",[193,739,741],{"className":195,"code":740,"language":197,"meta":198,"style":198},"import sys\n\nimport typer\n\n\ndef confirm_or_exit(message: str, yes: bool) -> None:\n    if yes:\n        return\n    if not sys.stdin.isatty():\n        typer.echo(f\"error: {message} — refusing without --yes (no terminal to ask)\", err=True)\n        raise typer.Exit(2)\n    if not typer.confirm(message, default=False, err=True):\n        typer.echo(\"aborted\", err=True)\n        raise typer.Exit(1)\n",[14,742,743,750,754,760,764,768,790,798,803,813,847,860,888,905],{"__ignoreMap":198},[202,744,745,747],{"class":204,"line":205},[202,746,209],{"class":208},[202,748,749],{"class":212}," sys\n",[202,751,752],{"class":204,"line":216},[202,753,220],{"emptyLinePlaceholder":219},[202,755,756,758],{"class":204,"line":223},[202,757,209],{"class":208},[202,759,213],{"class":212},[202,761,762],{"class":204,"line":248},[202,763,220],{"emptyLinePlaceholder":219},[202,765,766],{"class":204,"line":277},[202,767,220],{"emptyLinePlaceholder":219},[202,769,770,772,775,778,780,782,784,786,788],{"class":204,"line":304},[202,771,363],{"class":208},[202,773,774],{"class":348}," confirm_or_exit",[202,776,777],{"class":212},"(message: ",[202,779,417],{"class":241},[202,781,527],{"class":212},[202,783,519],{"class":241},[202,785,420],{"class":212},[202,787,372],{"class":241},[202,789,375],{"class":212},[202,791,792,795],{"class":204,"line":320},[202,793,794],{"class":208},"    if",[202,796,797],{"class":212}," yes:\n",[202,799,800],{"class":204,"line":335},[202,801,802],{"class":208},"        return\n",[202,804,805,807,810],{"class":204,"line":340},[202,806,794],{"class":208},[202,808,809],{"class":208}," not",[202,811,812],{"class":212}," sys.stdin.isatty():\n",[202,814,815,818,821,824,827,830,833,836,838,841,843,845],{"class":204,"line":345},[202,816,817],{"class":212},"        typer.echo(",[202,819,820],{"class":208},"f",[202,822,823],{"class":263},"\"error: ",[202,825,826],{"class":241},"{",[202,828,829],{"class":212},"message",[202,831,832],{"class":241},"}",[202,834,835],{"class":263}," — refusing without --yes (no terminal to ask)\"",[202,837,111],{"class":212},[202,839,840],{"class":235},"err",[202,842,229],{"class":208},[202,844,242],{"class":241},[202,846,245],{"class":212},[202,848,849,852,855,858],{"class":204,"line":360},[202,850,851],{"class":208},"        raise",[202,853,854],{"class":212}," typer.Exit(",[202,856,857],{"class":241},"2",[202,859,245],{"class":212},[202,861,862,864,866,869,872,874,877,879,881,883,885],{"class":204,"line":378},[202,863,794],{"class":208},[202,865,809],{"class":208},[202,867,868],{"class":212}," typer.confirm(message, ",[202,870,871],{"class":235},"default",[202,873,229],{"class":208},[202,875,876],{"class":241},"False",[202,878,111],{"class":212},[202,880,840],{"class":235},[202,882,229],{"class":208},[202,884,242],{"class":241},[202,886,887],{"class":212},"):\n",[202,889,890,892,895,897,899,901,903],{"class":204,"line":384},[202,891,817],{"class":212},[202,893,894],{"class":263},"\"aborted\"",[202,896,111],{"class":212},[202,898,840],{"class":235},[202,900,229],{"class":208},[202,902,242],{"class":241},[202,904,245],{"class":212},[202,906,907,909,911,914],{"class":204,"line":389},[202,908,851],{"class":208},[202,910,854],{"class":212},[202,912,913],{"class":241},"1",[202,915,245],{"class":212},[10,917,918,919,923,924,641],{},"Sharing one planning function between the dry run and the real run is what makes the preview trustworthy: they cannot drift apart because they are the same code. ",[46,920,922],{"href":921},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Fadding-dry-run-and-confirmation-to-destructive-commands\u002F","Adding dry-run and confirmation to destructive commands"," builds the full pattern, including production guards that ask for more than a ",[14,925,926],{},"y",[61,928,930],{"id":929},"conventions-users-already-know","Conventions users already know",[10,932,933,934,937,938,940,941,943],{},"Command-line users bring decades of expectations from Unix tools, codified loosely by POSIX and extended by GNU. Meeting them makes a tool feel right; breaking them makes it feel foreign. Click and argparse implement most of them automatically — combined short flags (",[14,935,936],{},"-xvf","), long options, ",[14,939,110],{},", options interleaved with arguments, ",[14,942,114],{}," to end option parsing. A few are your responsibility:",[66,945,946,961,971,977],{},[69,947,948,575,953,956,957,641],{},[72,949,950,952],{},[14,951,118],{}," as stdin or stdout.",[14,954,955],{},"mytool lint -"," should read standard input, the convention described in ",[46,958,960],{"href":959},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Freading-piped-input-in-python-clis\u002F","reading piped input in Python CLIs",[69,962,963,966,967,641],{},[72,964,965],{},"Exit statuses."," 0 for success, 1 for failure, 2 for usage errors (Click and argparse already use 2), and specific codes where scripts need them — see ",[46,968,970],{"href":969},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fchoosing-exit-codes-for-cli-tools\u002F","choosing exit codes for CLI tools",[69,972,973,976],{},[72,974,975],{},"Streams."," Results to stdout, everything else — progress, warnings, prompts — to stderr, so pipes carry only data.",[69,978,979,982,983,111,986,111,989,91,992,995],{},[72,980,981],{},"Environment conventions."," Honour ",[14,984,985],{},"NO_COLOR",[14,987,988],{},"PAGER",[14,990,991],{},"EDITOR",[14,993,994],{},"TMPDIR"," where they apply.",[10,997,998,1002],{},[46,999,1001],{"href":1000},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Ffollowing-posix-and-gnu-argument-conventions\u002F","Following POSIX and GNU argument conventions"," covers the full list and the handful of places where Click's defaults differ from GNU behaviour.",[61,1004,1006],{"id":1005},"output-is-part-of-the-interface-too","Output is part of the interface too",[10,1008,1009],{},"It is easy to think of the interface as only the input side — what users type — but scripts depend just as much on what comes back. Three output decisions deserve the same care as flag names.",[10,1011,1012,1015,1016,1019,1020,1023,1024,641],{},[72,1013,1014],{},"Human and machine output are different products."," Human output can change freely between releases: better wording, colour, alignment, a new column. Machine output cannot. Offer a stable machine format (",[14,1017,1018],{},"--json",", or ",[14,1021,1022],{},"--output json"," if you already have several formats) on every command that returns data, document its schema, and version it. Once people can get JSON, they stop scraping your tables, and you regain the freedom to improve the tables. The details are in ",[46,1025,1027],{"href":1026},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Femitting-json-output-for-scripting\u002F","emitting JSON output for scripting",[10,1029,1030,1033,1034,111,1036,1038],{},[72,1031,1032],{},"Quiet success, loud failure."," A command that succeeds should say little — one line, or nothing if the result is self-evident — and one that fails should say exactly what went wrong and what to do next. Verbosity flags (",[14,1035,607],{},[14,1037,611],{},") then adjust from that baseline rather than users learning to ignore walls of output.",[10,1040,1041,1044],{},[72,1042,1043],{},"Consistent exit statuses per kind of failure."," \"Not found\", \"not allowed\", \"invalid input\" and \"service unavailable\" are different situations for a script. Map them to distinct, documented exit codes across all commands, not ad hoc per command.",[61,1046,1048],{"id":1047},"help-examples-and-completion-as-design-tools","Help, examples and completion as design tools",[10,1050,1051],{},"The interface is only as usable as it is discoverable, and three built-in affordances do most of the work.",[10,1053,1054,1057,1058,641],{},[72,1055,1056],{},"Help text"," should lead with what the command does, in one sentence, then list options with short descriptions and defaults. Examples in an epilog — real, copy-pasteable invocations — are often more useful than the option list itself; see ",[46,1059,1061],{"href":1060},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fadding-examples-and-epilogs-to-help-output\u002F","adding examples and epilogs to help output",[10,1063,1064,1067],{},[72,1065,1066],{},"Error messages"," are help delivered at the moment of need. \"No such option: --enviroment (did you mean --environment?)\" — which Click produces for close matches — teaches the interface as users make mistakes.",[10,1069,1070,1073,1074,1076,1077,1080,1081,641],{},[72,1071,1072],{},"Shell completion"," turns a consistent vocabulary into speed: users press Tab after ",[14,1075,554],{}," and see the verbs, after ",[14,1078,1079],{},"--env"," and see the environments. It rewards the consistency this topic argues for, because predictable names complete predictably. Setting it up is covered in ",[46,1082,1084],{"href":1083},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002F","shell completion for Python CLIs",[61,1086,1088],{"id":1087},"writing-the-interface-down-first","Writing the interface down first",[10,1090,1091,1092,1094],{},"For anything beyond a handful of commands, sketch the interface before implementing it. A plain text file listing every command with its arguments and key options — essentially the ",[14,1093,170],{}," output you intend to have — takes an hour and exposes most inconsistencies before they ship: two verbs for the same action, a flag whose meaning differs between commands, a positional argument list that will not fit the next feature.",[193,1096,1101],{"className":1097,"code":1099,"language":1100,"meta":198},[1098],"language-text","mytool site list    [--output table|json] [--limit N]\nmytool site show    SITE [--output table|json]\nmytool site deploy  SITE DIR [--env dev|staging|prod] [--dry-run] [--yes]\nmytool site delete  SITE [--dry-run] [--yes]\nmytool build list   [--site SITE] [--output table|json] [--limit N]\nmytool build prune  [--older-than DURATION] [--dry-run] [--yes]\n","text",[14,1102,1099],{"__ignoreMap":198},[10,1104,1105,1106,91,1108,1110,1111,641],{},"Read the sketch as a user would. Are the same things called the same names? Would a script author know which flags produce machine-readable output? Does every destructive command have ",[14,1107,90],{},[14,1109,94],{},"? The sketch is also the basis of the contract tests that later keep the interface stable across releases, as described in ",[46,1112,1114],{"href":1113},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fsemantic-versioning-policy-for-cli-tools\u002F","semantic versioning policy for CLI tools",[61,1116,1118],{"id":1117},"evolving-an-interface-that-already-exists","Evolving an interface that already exists",[10,1120,1121],{},"Most teams read a topic like this with a CLI that already has inconsistencies. The fix is gradual:",[1123,1124,1125,1134,1140,1146,1156],"ol",{},[69,1126,1127,1130,1131,1133],{},[72,1128,1129],{},"Write down the current interface"," — generate it from ",[14,1132,170],{}," — and mark each inconsistency.",[69,1135,1136,1139],{},[72,1137,1138],{},"Choose the target vocabulary"," and shape.",[69,1141,1142,1145],{},[72,1143,1144],{},"Add the new names as aliases or new commands"," in a minor release, keeping the old ones working.",[69,1147,1148,1151,1152,641],{},[72,1149,1150],{},"Deprecate the old names"," with warnings on stderr that name the replacement, as described in ",[46,1153,1155],{"href":1154},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fversioning-and-deprecating-cli-flags\u002F","versioning and deprecating CLI flags",[69,1157,1158,1161],{},[72,1159,1160],{},"Remove them in the next major release",", listed in the changelog with the migration for each.",[10,1163,1164,1165,1168,1169,1171],{},"Hidden aliases (",[14,1166,1167],{},"hidden=True"," in Click and Typer) keep old names working without cluttering ",[14,1170,170],{},", so the interface new users see is already the clean one while old scripts keep running.",[61,1173,1175],{"id":1174},"key-takeaways","Key takeaways",[66,1177,1178,1181,1184,1187,1194,1197,1203],{},[69,1179,1180],{},"The interface outlives the code; design it deliberately and write it down before implementing.",[69,1182,1183],{},"Choose one command-tree shape; noun-then-verb suits most tools that manage several resource types.",[69,1185,1186],{},"One verb per action and one meaning per flag, across every command.",[69,1188,1189,1190,91,1192,641],{},"Make destructive commands plan, show, confirm and apply, with ",[14,1191,90],{},[14,1193,94],{},[69,1195,1196],{},"Keep global options global in meaning and decide where they may appear.",[69,1198,1199,1200,1202],{},"Follow POSIX and GNU conventions users already know, and implement ",[14,1201,118],{},", exit statuses and stream separation yourself.",[69,1204,1205],{},"Fix existing inconsistencies with aliases, deprecation warnings and a major release, never by silent renames.",[61,1207,1209],{"id":1208},"frequently-asked-questions","Frequently asked questions",[1211,1212,1214],"h3",{"id":1213},"how-many-commands-is-too-many-at-the-top-level","How many commands is too many at the top level?",[10,1216,1217,1218,1220,1221,1224],{},"When ",[14,1219,170],{}," scrolls off a screen, users stop reading it. Around ten to fifteen top-level entries is a practical limit; beyond that, group related commands under nouns. Typer's ",[14,1222,1223],{},"rich_help_panel"," can also split a long list into labelled sections.",[1211,1226,1228],{"id":1227},"should-commands-prompt-for-missing-required-arguments","Should commands prompt for missing required arguments?",[10,1230,1231],{},"Only as a convenience on top of flags, and only when a terminal is attached. Every value that can be prompted for must also be passable as an argument or option, or the command cannot be scripted.",[1211,1233,1235],{"id":1234},"is-it-acceptable-to-break-conventions-when-my-users-are-not-unix-people","Is it acceptable to break conventions when my users are not Unix people?",[10,1237,1238,1239,1242],{},"Conventions still help Windows users, because most modern CLIs they use (git, docker, kubectl, npm) follow the same patterns. Where Windows users expect different behaviour — ",[14,1240,1241],{},"\u002F?"," for help, for instance — accept it as an alias rather than replacing the convention.",[1211,1244,1246],{"id":1245},"should-flags-or-positional-arguments-carry-the-main-input","Should flags or positional arguments carry the main input?",[10,1248,1249,1250,111,1253,1256,1257,1260,1261,1264],{},"Use a positional argument for the one thing the command obviously acts on — ",[14,1251,1252],{},"mytool deploy SITE",[14,1254,1255],{},"mytool lint FILE..."," — because it reads naturally and composes with shell globbing and ",[14,1258,1259],{},"xargs",". Use flags for everything else. When a command has two equally important inputs, such as a source and a destination, two positionals are acceptable if their order follows an established convention (",[14,1262,1263],{},"cp SRC DEST","); otherwise name them as flags so nobody has to remember the order.",[1211,1266,1268],{"id":1267},"how-do-i-get-feedback-on-an-interface-before-shipping-it","How do I get feedback on an interface before shipping it?",[10,1270,1271],{},"Share the written sketch and ask two or three prospective users to \"use\" it on paper for a real task: which command would they type? Where they hesitate or guess wrong is where the names need work. It costs an hour and saves a deprecation cycle.",[1211,1273,1275],{"id":1274},"who-should-own-the-interface-in-a-team","Who should own the interface in a team?",[10,1277,1278,1279,91,1281,1283],{},"Someone should. Interfaces degrade when every contributor adds commands in their own style, and they stay coherent when one or two people review every new command, flag and output format against the written vocabulary. A short checklist in the pull-request template — \"uses existing verbs? flags match other commands? destructive commands have ",[14,1280,90],{},[14,1282,94],{},"? machine output documented?\" — spreads that review without making it a bottleneck. Contract tests that pin the public interface, run in CI, catch the changes that slip through review.",[1211,1285,1287],{"id":1286},"should-a-single-purpose-tool-still-use-subcommands","Should a single-purpose tool still use subcommands?",[10,1289,1290,1291,1294,1295,1298,1299,1302],{},"No. A tool that does one thing should do it at the top level (",[14,1292,1293],{},"mytool FILE","), like ",[14,1296,1297],{},"grep"," or ",[14,1300,1301],{},"black",". Add a command group only when there is a second, genuinely different operation.",[61,1304,1306],{"id":1305},"related","Related",[66,1308,1309,1314,1319,1323,1327,1331,1337],{},[69,1310,1311,1312],{},"Up: ",[46,1313,49],{"href":48},[69,1315,1316,1317],{},"Down: ",[46,1318,566],{"href":565},[69,1320,1316,1321],{},[46,1322,922],{"href":921},[69,1324,1316,1325],{},[46,1326,716],{"href":715},[69,1328,1316,1329],{},[46,1330,1001],{"href":1000},[69,1332,1333,1334],{},"Sideways: ",[46,1335,1336],{"href":53},"Structuring multi-command Python CLIs",[69,1338,1333,1339],{},[46,1340,1342],{"href":1341},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002F","CLI help output and documentation",[1344,1345,1346],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}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 .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":198,"searchDepth":216,"depth":216,"links":1348},[1349,1350,1351,1352,1353,1354,1355,1356,1357,1358,1359,1360,1369],{"id":63,"depth":216,"text":64},{"id":122,"depth":216,"text":123},{"id":558,"depth":216,"text":559},{"id":664,"depth":216,"text":665},{"id":720,"depth":216,"text":721},{"id":929,"depth":216,"text":930},{"id":1005,"depth":216,"text":1006},{"id":1047,"depth":216,"text":1048},{"id":1087,"depth":216,"text":1088},{"id":1117,"depth":216,"text":1118},{"id":1174,"depth":216,"text":1175},{"id":1208,"depth":216,"text":1209,"children":1361},[1362,1363,1364,1365,1366,1367,1368],{"id":1213,"depth":223,"text":1214},{"id":1227,"depth":223,"text":1228},{"id":1234,"depth":223,"text":1235},{"id":1245,"depth":223,"text":1246},{"id":1267,"depth":223,"text":1268},{"id":1274,"depth":223,"text":1275},{"id":1286,"depth":223,"text":1287},{"id":1305,"depth":216,"text":1306},"2026-09-18","Design a Python CLI’s command-line interface on purpose: command tree shape, naming, option scope, safe destructive commands and POSIX\u002FGNU conventions.","intermediate",false,"md",{},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions",{"title":5,"description":1371},"modern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Findex",[1380,1381,1382,1383,1384],"cli-design","conventions","typer","click","ux","tXLfqW93Jwqn-oD_RpPsh59M-uSaq2Q9jKBpz0vIzUE",[1387,1390,1393,1396,1399,1402,1405,1408,1411,1414,1417,1420,1423,1426,1429,1432,1435,1438,1441,1444,1447,1450,1453,1456,1459,1462,1465,1468,1471,1474,1477,1480,1483,1486,1489,1492,1495,1498,1501,1504,1507,1510,1513,1516,1519,1522,1525,1528,1531,1534,1537,1540,1543,1546,1549,1552,1555,1558,1561,1564,1567,1570,1573,1576,1579,1582,1585,1588,1591,1594,1597,1600,1603,1606,1609,1612,1615,1618,1621,1624,1627,1630,1633,1636,1639,1642,1645,1648,1651,1654,1657,1660,1663,1666,1669,1672,1675,1678,1681,1684,1687,1690,1693,1696,1699,1702,1705,1706,1709,1712,1715,1718,1721,1724,1727,1730,1733,1736,1739,1742,1745,1748,1751,1754,1757,1760,1763,1766,1769,1772,1775,1778,1781,1784,1787,1790,1793,1796,1799,1802,1805,1808,1811,1814,1817,1820,1823,1826,1829,1832,1835,1838,1841,1844,1847,1850,1853,1856,1859,1862,1865,1868,1871,1874,1877,1880,1883,1886,1889,1892,1895,1898,1901,1904,1907,1910,1913,1916,1919,1922,1925,1928,1931],{"path":1388,"title":1389},"\u002Fabout","About Python CLI Toolcraft",{"path":1391,"title":1392},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies","Advanced Argument Validation Strategies",{"path":1394,"title":1395},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fparsing-nested-json-arguments-in-python-clis","Parsing Nested JSON Args in Python CLIs",{"path":1397,"title":1398},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fvalidating-dependent-and-conflicting-options","Validating Dependent and Conflicting CLI Options",{"path":1400,"title":1401},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fvalidating-file-and-directory-paths-in-clis","Validating File and Directory Paths in CLIs",{"path":1403,"title":1404},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fwriting-custom-click-parameter-types","Writing Custom Click Parameter Types",{"path":1406,"title":1407},"\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":1409,"title":1410},"\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":1412,"title":1413},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual","Building Terminal UIs with Textual for Python CLIs",{"path":1415,"title":1416},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual\u002Ftesting-textual-apps-with-pilot","Testing Textual Apps with Pilot",{"path":1418,"title":1419},"\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":1421,"title":1422},"\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":1424,"title":1425},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation","CLI Help Output and Documentation",{"path":1427,"title":1428},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fversioning-and-deprecating-cli-flags","Versioning and Deprecating CLI Flags",{"path":1430,"title":1431},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fwriting-help-text-users-actually-read","Writing Help Text Users Actually Read",{"path":1433,"title":1434},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Fadapting-output-to-terminal-width","Adapting Python CLI Output to Terminal Width",{"path":1436,"title":1437},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Fdetecting-ci-environments-and-non-interactive-shells","Detecting CI Environments and Non-Interactive Shells",{"path":1439,"title":1440},"\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":1442,"title":1443},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility","Cross-Platform Terminal Compatibility for Python CLIs",{"path":1445,"title":1446},"\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":1448,"title":1449},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fchoosing-exit-codes-for-cli-tools","Choosing Exit Codes for CLI Tools",{"path":1451,"title":1452},"\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":1454,"title":1455},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Ffriendly-error-messages-and-tracebacks","Friendly Error Messages and Tracebacks",{"path":1457,"title":1458},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fhandling-keyboard-interrupt-cleanly","Handling Keyboard Interrupt Cleanly",{"path":1460,"title":1461},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes","Error Handling and Exit Codes for CLIs",{"path":1463,"title":1464},"\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":1466,"title":1467},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Fconfig-precedence-flags-env-files-defaults","Config Precedence: Flags, Env, Files, Defaults",{"path":1469,"title":1470},"\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":1472,"title":1473},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars","Handling Config Files and Env Vars in CLIs",{"path":1475,"title":1476},"\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":1478,"title":1479},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Freading-toml-config-with-tomllib","Reading TOML Config with tomllib in Python CLIs",{"path":1481,"title":1482},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Ftyped-settings-with-pydantic-settings","Typed Settings with pydantic-settings in Python CLIs",{"path":1484,"title":1485},"\u002Fadvanced-input-parsing-user-experience","Advanced Input Parsing for Python CLIs",{"path":1487,"title":1488},"\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":1490,"title":1491},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Fbuilding-interactive-prompts-and-menus","Building Interactive Prompts and Menus in Python CLIs",{"path":1493,"title":1494},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich","Interactive Terminal UI with Rich",{"path":1496,"title":1497},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Flive-dashboards-with-rich-live","Live Dashboards with Rich Live in Python CLIs",{"path":1499,"title":1500},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Frendering-tables-and-json-with-rich","Rendering Tables and JSON with Rich",{"path":1502,"title":1503},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Ftheming-rich-output-consistently","Theming Rich Output Consistently in Python CLIs",{"path":1505,"title":1506},"\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":1508,"title":1509},"\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":1511,"title":1512},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis","Shell Completion for Python CLIs",{"path":1514,"title":1515},"\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":1517,"title":1518},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Ftesting-shell-completion-in-python-clis","Testing Shell Completion in Python CLIs",{"path":1520,"title":1521},"\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":1523,"title":1524},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fadding-verbose-and-quiet-logging-flags","Adding Verbose and Quiet Logging Flags",{"path":1526,"title":1527},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps","Structured Logging for CLI Apps",{"path":1529,"title":1530},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fstructured-json-logging-in-python-clis","Structured JSON Logging in Python CLIs",{"path":1532,"title":1533},"\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":1535,"title":1536},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Fdetecting-tty-and-adapting-output","Detecting a TTY and Adapting Output",{"path":1538,"title":1539},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Femitting-json-output-for-scripting","Emitting JSON Output for Scripting",{"path":1541,"title":1542},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Fhandling-broken-pipe-and-sigpipe","Handling Broken Pipe and SIGPIPE",{"path":1544,"title":1545},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes","Working with stdin, stdout and Pipes",{"path":1547,"title":1548},"\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":1550,"title":1551},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Freading-piped-input-in-python-clis","Reading Piped Input in Python CLIs",{"path":1553,"title":1554},"\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":1556,"title":1557},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Fdownloading-files-with-progress-in-python","Downloading Files with Progress in Python CLIs",{"path":1559,"title":1560},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis","Calling HTTP APIs from Python CLIs",{"path":1562,"title":1563},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Foauth-device-flow-login-for-clis","OAuth Device Flow Login for Python CLIs",{"path":1565,"title":1566},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Fpaginating-api-results-in-a-cli","Paginating API Results in a Python CLI",{"path":1568,"title":1569},"\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":1571,"title":1572},"\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":1574,"title":1575},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis","Concurrency and Async in Python CLIs",{"path":1577,"title":1578},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Fmultiprocessing-for-cpu-bound-cli-tasks","Multiprocessing for CPU-Bound CLI Tasks",{"path":1580,"title":1581},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Fparallelising-cli-work-with-thread-pools","Parallelising CLI Work with Thread Pools",{"path":1583,"title":1584},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Frate-limiting-concurrent-requests-in-clis","Rate-Limiting Concurrent Requests in Python CLIs",{"path":1586,"title":1587},"\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":1589,"title":1590},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fcross-platform-paths-with-pathlib","Cross-Platform Paths with pathlib in CLIs",{"path":1592,"title":1593},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Ffile-locking-for-concurrent-cli-runs","File Locking for Concurrent CLI Runs in Python",{"path":1595,"title":1596},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes","Filesystem Paths and Atomic Writes for CLIs",{"path":1598,"title":1599},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fsafe-temporary-files-and-directories","Safe Temporary Files and Directories in CLIs",{"path":1601,"title":1602},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fstoring-app-data-with-platformdirs","Storing CLI App Data with platformdirs",{"path":1604,"title":1605},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fwriting-files-atomically-in-python-clis","Writing Files Atomically in Python CLIs",{"path":1607,"title":1608},"\u002Fcli-runtime-systems-integration","CLI Runtime & Systems Integration for Python",{"path":1610,"title":1611},"\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":1613,"title":1614},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis\u002Fhandling-sigterm-and-graceful-shutdown","Handling SIGTERM and Graceful Shutdown in CLIs",{"path":1616,"title":1617},"\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":1619,"title":1620},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis","Long-Running and Watch-Mode Python CLIs",{"path":1622,"title":1623},"\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":1625,"title":1626},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Favoiding-shell-injection-in-python-clis","Avoiding Shell Injection in Python CLIs",{"path":1628,"title":1629},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fcalling-external-commands-safely-with-subprocess","Calling External Commands Safely with subprocess",{"path":1631,"title":1632},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fhandling-subprocess-timeouts-and-exit-codes","Handling Subprocess Timeouts and Exit Codes",{"path":1634,"title":1635},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis","Running Subprocesses from Python CLIs",{"path":1637,"title":1638},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fstreaming-subprocess-output-in-real-time","Streaming Subprocess Output in Real Time",{"path":1640,"title":1641},"\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":1643,"title":1644},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis","Secrets and Credentials in Python CLIs",{"path":1646,"title":1647},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Fprompting-for-passwords-securely","Prompting for Passwords Securely in Python CLIs",{"path":1649,"title":1650},"\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":1652,"title":1653},"\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":1655,"title":1656},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Fstoring-tokens-with-keyring","Storing CLI Tokens Securely with keyring",{"path":1658,"title":1659},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Fsupporting-multiple-profiles-and-accounts","Supporting Multiple Profiles and Accounts in CLIs",{"path":1661,"title":1662},"\u002F","Python CLI Toolcraft",{"path":1664,"title":1665},"\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":1667,"title":1668},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading","CLI Startup Performance and Lazy Loading",{"path":1670,"title":1671},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Flazy-loading-subcommands-for-faster-startup","Lazy Loading Subcommands for Faster Startup",{"path":1673,"title":1674},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Fprofiling-python-cli-startup-time","Profiling Python CLI Startup Time",{"path":1676,"title":1677},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Freducing-cli-dependency-weight","Reducing CLI Dependency Weight",{"path":1679,"title":1680},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargparse-subparsers-for-subcommands","argparse Subparsers for Subcommands",{"path":1682,"title":1683},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargparse-vs-click-vs-typer-comparison","argparse vs Click vs Typer Compared",{"path":1685,"title":1686},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse","Command-Line Parsing with argparse",{"path":1688,"title":1689},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fmigrating-from-argparse-to-typer","Migrating from argparse to Typer",{"path":1691,"title":1692},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fmutually-exclusive-options-in-argparse","Mutually Exclusive Options in argparse",{"path":1694,"title":1695},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fwriting-custom-argparse-actions","Writing Custom argparse Actions in Python",{"path":1697,"title":1698},"\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":1700,"title":1701},"\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":1703,"title":1704},"\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":1376,"title":5},{"path":1707,"title":1708},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Fnaming-commands-and-flags-consistently","Naming Commands and Flags Consistently in Python CLIs",{"path":1710,"title":1711},"\u002Fmodern-python-cli-frameworks-architecture","Python CLI Frameworks and Architecture",{"path":1713,"title":1714},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fdiscovering-plugins-with-entry-points","Discovering Plugins with Entry Points in Python CLIs",{"path":1716,"title":1717},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fhook-based-plugins-with-pluggy","Hook-Based Plugins for Python CLIs with pluggy",{"path":1719,"title":1720},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis","Plugin Architectures for Extensible CLIs",{"path":1722,"title":1723},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fversioning-a-plugin-api","Versioning a Plugin API for a Python CLI",{"path":1725,"title":1726},"\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":1728,"title":1729},"\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":1731,"title":1732},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fdependency-injection-patterns-for-cli-commands","Dependency Injection Patterns for CLI Commands",{"path":1734,"title":1735},"\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":1737,"title":1738},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis","Structuring Multi-Command Python CLIs",{"path":1740,"title":1741},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fsharing-common-options-across-commands","Sharing Common Options Across Python CLI Commands",{"path":1743,"title":1744},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fsharing-state-with-click-context-objects","Sharing State with Click Context Objects",{"path":1746,"title":1747},"\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":1749,"title":1750},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications","Testing Python CLI Applications",{"path":1752,"title":1753},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fmeasuring-cli-test-coverage","Measuring CLI Test Coverage",{"path":1755,"title":1756},"\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":1758,"title":1759},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fproperty-based-testing-cli-arguments-with-hypothesis","Property-Based Testing CLI Arguments with Hypothesis",{"path":1761,"title":1762},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fsnapshot-testing-cli-output","Snapshot Testing CLI Output",{"path":1764,"title":1765},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftesting-click-commands-with-clirunner","Testing Click Commands with CliRunner",{"path":1767,"title":1768},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftesting-interactive-prompts-and-stdin","Testing Interactive Prompts and stdin",{"path":1770,"title":1771},"\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":1773,"title":1774},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Fbuilding-dynamic-commands-in-click","Building Dynamic Commands in Click",{"path":1776,"title":1777},"\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":1779,"title":1780},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each","Typer vs Click: When to Use Each",{"path":1782,"title":1783},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Ftyper-callback-functions-explained","Typer callback functions explained",{"path":1785,"title":1786},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Fusing-annotated-options-in-typer","Using Annotated Options in Typer",{"path":1788,"title":1789},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis\u002Fautomating-releases-from-git-tags","Automating Python CLI Releases from Git Tags",{"path":1791,"title":1792},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis\u002Fcaching-uv-dependencies-in-ci","Caching uv Dependencies in CI for Python CLIs",{"path":1794,"title":1795},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis","CI\u002FCD Pipelines for Python CLIs",{"path":1797,"title":1798},"\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":1800,"title":1801},"\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":1803,"title":1804},"\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":1806,"title":1807},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fbuilding-a-cookiecutter-template-for-typer-clis","Building a Cookiecutter Template for Typer CLIs",{"path":1809,"title":1810},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fcopier-vs-cookiecutter-for-cli-templates","Copier vs Cookiecutter for CLI Templates",{"path":1812,"title":1813},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter","CLI Project Scaffolding with Cookiecutter",{"path":1815,"title":1816},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fpost-generation-hooks-in-cli-templates","Post-Generation Hooks in Python CLI Templates",{"path":1818,"title":1819},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fbuilding-cross-platform-release-binaries-in-ci","Building Cross-Platform Release Binaries in CI",{"path":1821,"title":1822},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fbundling-a-python-cli-with-pyinstaller","Bundling a Python CLI with PyInstaller",{"path":1824,"title":1825},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fhomebrew-and-scoop-packaging-for-python-clis","Homebrew and Scoop Packaging for Python CLIs",{"path":1827,"title":1828},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries","Distributing CLIs as Standalone Binaries",{"path":1830,"title":1831},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fnuitka-vs-pyinstaller-for-python-clis","Nuitka vs PyInstaller for Python CLI Binaries",{"path":1833,"title":1834},"\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":1836,"title":1837},"\u002Fproject-setup-dependency-management","Project Setup & Dependency Management",{"path":1839,"title":1840},"\u002Fproject-setup-dependency-management\u002Flinting-and-type-checking-cli-code\u002Fconfiguring-ruff-for-a-cli-project","Configuring Ruff for a Python CLI Project",{"path":1842,"title":1843},"\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":1845,"title":1846},"\u002Fproject-setup-dependency-management\u002Flinting-and-type-checking-cli-code","Linting and Type-Checking Python CLI Code",{"path":1848,"title":1849},"\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":1851,"title":1852},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fautomating-changelogs-with-conventional-commits","Automating Changelogs with Conventional Commits",{"path":1854,"title":1855},"\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":1857,"title":1858},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fexposing-version-info-and-build-metadata","Exposing Version Info and Build Metadata",{"path":1860,"title":1861},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs","Managing CLI Versioning & Changelogs",{"path":1863,"title":1864},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fsemantic-versioning-policy-for-cli-tools","A Semantic Versioning Policy for CLI Tools",{"path":1866,"title":1867},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fbuilding-wheels-and-sdists-for-python-clis","Building Wheels and sdists for Python CLIs",{"path":1869,"title":1870},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fbundling-data-files-with-importlib-resources","Bundling Data Files with importlib.resources in CLIs",{"path":1872,"title":1873},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution","Packaging Python CLIs for Distribution",{"path":1875,"title":1876},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Finstalling-and-distributing-clis-with-pipx","Installing and Distributing CLIs with pipx",{"path":1878,"title":1879},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fpublishing-a-python-cli-to-pypi","Publishing a Python CLI to PyPI",{"path":1881,"title":1882},"\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":1884,"title":1885},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development","Poetry Workflows for CLI Development",{"path":1887,"title":1888},"\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":1890,"title":1891},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpoetry-dependency-groups-for-cli-tooling","Poetry Dependency Groups for CLI Tooling",{"path":1893,"title":1894},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpoetry-entry-points-and-scripts-for-clis","Poetry Entry Points and Scripts for CLIs",{"path":1896,"title":1897},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects","Pre-commit Hooks for CLI Projects",{"path":1899,"title":1900},"\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":1902,"title":1903},"\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":1905,"title":1906},"\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":1908,"title":1909},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management","uv for Python CLI Dependency Management",{"path":1911,"title":1912},"\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":1914,"title":1915},"\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":1917,"title":1918},"\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":1920,"title":1921},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002Fuv-workspaces-for-multi-package-clis","uv Workspaces for Multi-Package Python CLIs",{"path":1923,"title":1924},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices","Python CLI Env Isolation Best Practices",{"path":1926,"title":1927},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fmanaging-virtual-environments-for-cross-platform-clis","Managing Python CLI Virtual Environments",{"path":1929,"title":1930},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fpinning-the-python-version-for-a-cli","Pinning the Python Version for a CLI",{"path":1932,"title":1933},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fsupporting-multiple-python-versions-with-nox","Supporting Multiple Python Versions with nox",1789736907243]