[{"data":1,"prerenderedAt":2743},["ShallowReactive",2],{"page-\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargument-groups-and-help-formatting-in-argparse\u002F":3,"content-directory":1898},{"id":4,"title":5,"body":6,"date":1883,"description":1884,"difficulty":1885,"draft":1886,"extension":1887,"meta":1888,"navigation":120,"path":1889,"seo":1890,"stem":1891,"tags":1892,"updated":1883,"__hash__":1897},"content\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargument-groups-and-help-formatting-in-argparse\u002Findex.md","Argument Groups and Help Formatting in argparse",{"type":7,"value":8,"toc":1862},"minimark",[9,24,29,43,47,51,75,79,978,981,988,991,996,1018,1038,1049,1088,1092,1115,1121,1125,1154,1241,1263,1267,1316,1319,1323,1326,1714,1728,1732,1744,1748,1752,1758,1765,1780,1784,1794,1798,1813,1817,1824,1828,1858],[10,11,12,13,17,18,23],"p",{},"argparse's default help output is functional and flat: one \"options\" section listing every flag in definition order, metavars derived from destination names (",[14,15,16],"code",{},"--max-size MAX_SIZE","), no examples, and line breaks in your description collapsed into a paragraph. For a script with three flags that is fine. For a real CLI with fifteen options it is a wall that users scroll past. argparse has everything needed to do much better without a third-party framework — argument groups, explicit metavars, epilogs, formatter classes — but the pieces are scattered through the documentation and one of them needs a small subclass. This guide assembles them into a help screen people can scan, and adds a test so it stays that way. It belongs to the ",[19,20,22],"a",{"href":21},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002F","argparse topic",".",[25,26,28],"h2",{"id":27},"prerequisites","Prerequisites",[30,31,32,36],"ul",{},[33,34,35],"li",{},"Python 3.10+; everything here is standard library. The \"did you mean\" suggestion shown later needs Python 3.14.",[33,37,38,39,23],{},"An argparse-based CLI, possibly with subcommands as in ",[19,40,42],{"href":41},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargparse-subparsers-for-subcommands\u002F","argparse subparsers for subcommands",[25,44,46],{"id":45},"anatomy-of-a-help-screen","Anatomy of a help screen",[48,49],"inline-diagram",{"name":50},"aphelp-anatomy",[10,52,53,54,58,59,62,63,66,67,70,71,74],{},"A good help screen has five parts, in this order: a ",[55,56,57],"strong",{},"usage line"," that shows what is required; a one-sentence ",[55,60,61],{},"description","; ",[55,64,65],{},"groups"," of related options, each with a heading and optionally a sentence of explanation; ",[55,68,69],{},"defaults"," where they inform; and ",[55,72,73],{},"examples"," at the end. argparse produces the usage line automatically from your arguments. The rest is configuration.",[25,76,78],{"id":77},"the-recipe","The recipe",[80,81,86],"pre",{"className":82,"code":83,"language":84,"meta":85,"style":85},"language-python shiki shiki-themes github-light github-dark","# src\u002Fbackup\u002Fcli.py\nfrom __future__ import annotations\n\nimport argparse\nimport sys\nimport textwrap\n\n\nclass Formatter(argparse.RawDescriptionHelpFormatter):\n    \"\"\"Keep the epilog's line breaks; show defaults only when they say something.\"\"\"\n\n    def _get_help_string(self, action: argparse.Action) -> str:\n        text = action.help or \"\"\n        uninformative = (None, False, [], 0, argparse.SUPPRESS)\n        if \"%(default)\" in text or not action.option_strings or action.default in uninformative:\n            return text\n        return f\"{text} (default: %(default)s)\"\n\n\ndef build_parser() -> argparse.ArgumentParser:\n    parser = argparse.ArgumentParser(\n        prog=\"backup\",\n        description=\"Back up directories to local disk or S3.\",\n        epilog=textwrap.dedent(\"\"\"\\\n            examples:\n              backup ~\u002Fdocs --to \u002Fmnt\u002Fbackup\n              backup ~\u002Fdocs --to s3:\u002F\u002Fbucket\u002Fdocs --storage-class GLACIER\n            \"\"\"),\n        formatter_class=Formatter,\n    )\n    if sys.version_info >= (3, 14):\n        parser.suggest_on_error = True          # \"did you mean\" for typos (3.14+)\n\n    parser.add_argument(\"sources\", nargs=\"+\", metavar=\"SOURCE\", help=\"directories to back up\")\n\n    dest = parser.add_argument_group(\"destination\")\n    dest.add_argument(\"--to\", required=True, metavar=\"DEST\", help=\"local path or s3:\u002F\u002F URL\")\n    dest.add_argument(\"--storage-class\", choices=[\"STANDARD\", \"GLACIER\"], default=\"STANDARD\",\n                      help=\"S3 storage class\")\n\n    filt = parser.add_argument_group(\"filtering\", \"Choose which files are copied.\")\n    filt.add_argument(\"--exclude\", action=\"append\", default=[], metavar=\"GLOB\",\n                      help=\"skip matching paths (repeatable)\")\n    filt.add_argument(\"--max-size\", type=int, metavar=\"MB\", help=\"skip files larger than this\")\n\n    out = parser.add_argument_group(\"output\")\n    verbosity = out.add_mutually_exclusive_group()\n    verbosity.add_argument(\"-v\", \"--verbose\", action=\"count\", default=0,\n                           help=\"more detail (repeatable)\")\n    verbosity.add_argument(\"-q\", \"--quiet\", action=\"store_true\", help=\"only print errors\")\n    return parser\n\n\ndef main(argv: list[str] | None = None) -> int:\n    args = build_parser().parse_args(argv)\n    print(f\"backing up {len(args.sources)} source(s) to {args.to}\")\n    return 0\n","python","",[14,87,88,97,115,122,131,139,147,152,157,181,188,193,211,229,264,298,307,331,336,341,353,364,379,392,409,415,421,427,436,447,453,477,491,496,537,542,558,597,635,648,653,673,710,722,760,765,780,791,824,837,870,879,884,889,923,934,970],{"__ignoreMap":85},[89,90,93],"span",{"class":91,"line":92},"line",1,[89,94,96],{"class":95},"sJ8bj","# src\u002Fbackup\u002Fcli.py\n",[89,98,100,104,108,111],{"class":91,"line":99},2,[89,101,103],{"class":102},"szBVR","from",[89,105,107],{"class":106},"sj4cs"," __future__",[89,109,110],{"class":102}," import",[89,112,114],{"class":113},"sVt8B"," annotations\n",[89,116,118],{"class":91,"line":117},3,[89,119,121],{"emptyLinePlaceholder":120},true,"\n",[89,123,125,128],{"class":91,"line":124},4,[89,126,127],{"class":102},"import",[89,129,130],{"class":113}," argparse\n",[89,132,134,136],{"class":91,"line":133},5,[89,135,127],{"class":102},[89,137,138],{"class":113}," sys\n",[89,140,142,144],{"class":91,"line":141},6,[89,143,127],{"class":102},[89,145,146],{"class":113}," textwrap\n",[89,148,150],{"class":91,"line":149},7,[89,151,121],{"emptyLinePlaceholder":120},[89,153,155],{"class":91,"line":154},8,[89,156,121],{"emptyLinePlaceholder":120},[89,158,160,163,167,170,173,175,178],{"class":91,"line":159},9,[89,161,162],{"class":102},"class",[89,164,166],{"class":165},"sScJk"," Formatter",[89,168,169],{"class":113},"(",[89,171,172],{"class":165},"argparse",[89,174,23],{"class":113},[89,176,177],{"class":165},"RawDescriptionHelpFormatter",[89,179,180],{"class":113},"):\n",[89,182,184],{"class":91,"line":183},10,[89,185,187],{"class":186},"sZZnC","    \"\"\"Keep the epilog's line breaks; show defaults only when they say something.\"\"\"\n",[89,189,191],{"class":91,"line":190},11,[89,192,121],{"emptyLinePlaceholder":120},[89,194,196,199,202,205,208],{"class":91,"line":195},12,[89,197,198],{"class":102},"    def",[89,200,201],{"class":165}," _get_help_string",[89,203,204],{"class":113},"(self, action: argparse.Action) -> ",[89,206,207],{"class":106},"str",[89,209,210],{"class":113},":\n",[89,212,214,217,220,223,226],{"class":91,"line":213},13,[89,215,216],{"class":113},"        text ",[89,218,219],{"class":102},"=",[89,221,222],{"class":113}," action.help ",[89,224,225],{"class":102},"or",[89,227,228],{"class":186}," \"\"\n",[89,230,232,235,237,240,243,246,249,252,255,258,261],{"class":91,"line":231},14,[89,233,234],{"class":113},"        uninformative ",[89,236,219],{"class":102},[89,238,239],{"class":113}," (",[89,241,242],{"class":106},"None",[89,244,245],{"class":113},", ",[89,247,248],{"class":106},"False",[89,250,251],{"class":113},", [], ",[89,253,254],{"class":106},"0",[89,256,257],{"class":113},", argparse.",[89,259,260],{"class":106},"SUPPRESS",[89,262,263],{"class":113},")\n",[89,265,267,270,273,276,279,281,284,287,289,292,295],{"class":91,"line":266},15,[89,268,269],{"class":102},"        if",[89,271,272],{"class":186}," \"%(default)\"",[89,274,275],{"class":102}," in",[89,277,278],{"class":113}," text ",[89,280,225],{"class":102},[89,282,283],{"class":102}," not",[89,285,286],{"class":113}," action.option_strings ",[89,288,225],{"class":102},[89,290,291],{"class":113}," action.default ",[89,293,294],{"class":102},"in",[89,296,297],{"class":113}," uninformative:\n",[89,299,301,304],{"class":91,"line":300},16,[89,302,303],{"class":102},"            return",[89,305,306],{"class":113}," text\n",[89,308,310,313,316,319,322,325,328],{"class":91,"line":309},17,[89,311,312],{"class":102},"        return",[89,314,315],{"class":102}," f",[89,317,318],{"class":186},"\"",[89,320,321],{"class":106},"{",[89,323,324],{"class":113},"text",[89,326,327],{"class":106},"}",[89,329,330],{"class":186}," (default: %(default)s)\"\n",[89,332,334],{"class":91,"line":333},18,[89,335,121],{"emptyLinePlaceholder":120},[89,337,339],{"class":91,"line":338},19,[89,340,121],{"emptyLinePlaceholder":120},[89,342,344,347,350],{"class":91,"line":343},20,[89,345,346],{"class":102},"def",[89,348,349],{"class":165}," build_parser",[89,351,352],{"class":113},"() -> argparse.ArgumentParser:\n",[89,354,356,359,361],{"class":91,"line":355},21,[89,357,358],{"class":113},"    parser ",[89,360,219],{"class":102},[89,362,363],{"class":113}," argparse.ArgumentParser(\n",[89,365,367,371,373,376],{"class":91,"line":366},22,[89,368,370],{"class":369},"s4XuR","        prog",[89,372,219],{"class":102},[89,374,375],{"class":186},"\"backup\"",[89,377,378],{"class":113},",\n",[89,380,382,385,387,390],{"class":91,"line":381},23,[89,383,384],{"class":369},"        description",[89,386,219],{"class":102},[89,388,389],{"class":186},"\"Back up directories to local disk or S3.\"",[89,391,378],{"class":113},[89,393,395,398,400,403,406],{"class":91,"line":394},24,[89,396,397],{"class":369},"        epilog",[89,399,219],{"class":102},[89,401,402],{"class":113},"textwrap.dedent(",[89,404,405],{"class":186},"\"\"\"",[89,407,408],{"class":106},"\\\n",[89,410,412],{"class":91,"line":411},25,[89,413,414],{"class":186},"            examples:\n",[89,416,418],{"class":91,"line":417},26,[89,419,420],{"class":186},"              backup ~\u002Fdocs --to \u002Fmnt\u002Fbackup\n",[89,422,424],{"class":91,"line":423},27,[89,425,426],{"class":186},"              backup ~\u002Fdocs --to s3:\u002F\u002Fbucket\u002Fdocs --storage-class GLACIER\n",[89,428,430,433],{"class":91,"line":429},28,[89,431,432],{"class":186},"            \"\"\"",[89,434,435],{"class":113},"),\n",[89,437,439,442,444],{"class":91,"line":438},29,[89,440,441],{"class":369},"        formatter_class",[89,443,219],{"class":102},[89,445,446],{"class":113},"Formatter,\n",[89,448,450],{"class":91,"line":449},30,[89,451,452],{"class":113},"    )\n",[89,454,456,459,462,465,467,470,472,475],{"class":91,"line":455},31,[89,457,458],{"class":102},"    if",[89,460,461],{"class":113}," sys.version_info ",[89,463,464],{"class":102},">=",[89,466,239],{"class":113},[89,468,469],{"class":106},"3",[89,471,245],{"class":113},[89,473,474],{"class":106},"14",[89,476,180],{"class":113},[89,478,480,483,485,488],{"class":91,"line":479},32,[89,481,482],{"class":113},"        parser.suggest_on_error ",[89,484,219],{"class":102},[89,486,487],{"class":106}," True",[89,489,490],{"class":95},"          # \"did you mean\" for typos (3.14+)\n",[89,492,494],{"class":91,"line":493},33,[89,495,121],{"emptyLinePlaceholder":120},[89,497,499,502,505,507,510,512,515,517,520,522,525,527,530,532,535],{"class":91,"line":498},34,[89,500,501],{"class":113},"    parser.add_argument(",[89,503,504],{"class":186},"\"sources\"",[89,506,245],{"class":113},[89,508,509],{"class":369},"nargs",[89,511,219],{"class":102},[89,513,514],{"class":186},"\"+\"",[89,516,245],{"class":113},[89,518,519],{"class":369},"metavar",[89,521,219],{"class":102},[89,523,524],{"class":186},"\"SOURCE\"",[89,526,245],{"class":113},[89,528,529],{"class":369},"help",[89,531,219],{"class":102},[89,533,534],{"class":186},"\"directories to back up\"",[89,536,263],{"class":113},[89,538,540],{"class":91,"line":539},35,[89,541,121],{"emptyLinePlaceholder":120},[89,543,545,548,550,553,556],{"class":91,"line":544},36,[89,546,547],{"class":113},"    dest ",[89,549,219],{"class":102},[89,551,552],{"class":113}," parser.add_argument_group(",[89,554,555],{"class":186},"\"destination\"",[89,557,263],{"class":113},[89,559,561,564,567,569,572,574,577,579,581,583,586,588,590,592,595],{"class":91,"line":560},37,[89,562,563],{"class":113},"    dest.add_argument(",[89,565,566],{"class":186},"\"--to\"",[89,568,245],{"class":113},[89,570,571],{"class":369},"required",[89,573,219],{"class":102},[89,575,576],{"class":106},"True",[89,578,245],{"class":113},[89,580,519],{"class":369},[89,582,219],{"class":102},[89,584,585],{"class":186},"\"DEST\"",[89,587,245],{"class":113},[89,589,529],{"class":369},[89,591,219],{"class":102},[89,593,594],{"class":186},"\"local path or s3:\u002F\u002F URL\"",[89,596,263],{"class":113},[89,598,600,602,605,607,610,612,615,618,620,623,626,629,631,633],{"class":91,"line":599},38,[89,601,563],{"class":113},[89,603,604],{"class":186},"\"--storage-class\"",[89,606,245],{"class":113},[89,608,609],{"class":369},"choices",[89,611,219],{"class":102},[89,613,614],{"class":113},"[",[89,616,617],{"class":186},"\"STANDARD\"",[89,619,245],{"class":113},[89,621,622],{"class":186},"\"GLACIER\"",[89,624,625],{"class":113},"], ",[89,627,628],{"class":369},"default",[89,630,219],{"class":102},[89,632,617],{"class":186},[89,634,378],{"class":113},[89,636,638,641,643,646],{"class":91,"line":637},39,[89,639,640],{"class":369},"                      help",[89,642,219],{"class":102},[89,644,645],{"class":186},"\"S3 storage class\"",[89,647,263],{"class":113},[89,649,651],{"class":91,"line":650},40,[89,652,121],{"emptyLinePlaceholder":120},[89,654,656,659,661,663,666,668,671],{"class":91,"line":655},41,[89,657,658],{"class":113},"    filt ",[89,660,219],{"class":102},[89,662,552],{"class":113},[89,664,665],{"class":186},"\"filtering\"",[89,667,245],{"class":113},[89,669,670],{"class":186},"\"Choose which files are copied.\"",[89,672,263],{"class":113},[89,674,676,679,682,684,687,689,692,694,696,698,701,703,705,708],{"class":91,"line":675},42,[89,677,678],{"class":113},"    filt.add_argument(",[89,680,681],{"class":186},"\"--exclude\"",[89,683,245],{"class":113},[89,685,686],{"class":369},"action",[89,688,219],{"class":102},[89,690,691],{"class":186},"\"append\"",[89,693,245],{"class":113},[89,695,628],{"class":369},[89,697,219],{"class":102},[89,699,700],{"class":113},"[], ",[89,702,519],{"class":369},[89,704,219],{"class":102},[89,706,707],{"class":186},"\"GLOB\"",[89,709,378],{"class":113},[89,711,713,715,717,720],{"class":91,"line":712},43,[89,714,640],{"class":369},[89,716,219],{"class":102},[89,718,719],{"class":186},"\"skip matching paths (repeatable)\"",[89,721,263],{"class":113},[89,723,725,727,730,732,735,737,740,742,744,746,749,751,753,755,758],{"class":91,"line":724},44,[89,726,678],{"class":113},[89,728,729],{"class":186},"\"--max-size\"",[89,731,245],{"class":113},[89,733,734],{"class":369},"type",[89,736,219],{"class":102},[89,738,739],{"class":106},"int",[89,741,245],{"class":113},[89,743,519],{"class":369},[89,745,219],{"class":102},[89,747,748],{"class":186},"\"MB\"",[89,750,245],{"class":113},[89,752,529],{"class":369},[89,754,219],{"class":102},[89,756,757],{"class":186},"\"skip files larger than this\"",[89,759,263],{"class":113},[89,761,763],{"class":91,"line":762},45,[89,764,121],{"emptyLinePlaceholder":120},[89,766,768,771,773,775,778],{"class":91,"line":767},46,[89,769,770],{"class":113},"    out ",[89,772,219],{"class":102},[89,774,552],{"class":113},[89,776,777],{"class":186},"\"output\"",[89,779,263],{"class":113},[89,781,783,786,788],{"class":91,"line":782},47,[89,784,785],{"class":113},"    verbosity ",[89,787,219],{"class":102},[89,789,790],{"class":113}," out.add_mutually_exclusive_group()\n",[89,792,794,797,800,802,805,807,809,811,814,816,818,820,822],{"class":91,"line":793},48,[89,795,796],{"class":113},"    verbosity.add_argument(",[89,798,799],{"class":186},"\"-v\"",[89,801,245],{"class":113},[89,803,804],{"class":186},"\"--verbose\"",[89,806,245],{"class":113},[89,808,686],{"class":369},[89,810,219],{"class":102},[89,812,813],{"class":186},"\"count\"",[89,815,245],{"class":113},[89,817,628],{"class":369},[89,819,219],{"class":102},[89,821,254],{"class":106},[89,823,378],{"class":113},[89,825,827,830,832,835],{"class":91,"line":826},49,[89,828,829],{"class":369},"                           help",[89,831,219],{"class":102},[89,833,834],{"class":186},"\"more detail (repeatable)\"",[89,836,263],{"class":113},[89,838,840,842,845,847,850,852,854,856,859,861,863,865,868],{"class":91,"line":839},50,[89,841,796],{"class":113},[89,843,844],{"class":186},"\"-q\"",[89,846,245],{"class":113},[89,848,849],{"class":186},"\"--quiet\"",[89,851,245],{"class":113},[89,853,686],{"class":369},[89,855,219],{"class":102},[89,857,858],{"class":186},"\"store_true\"",[89,860,245],{"class":113},[89,862,529],{"class":369},[89,864,219],{"class":102},[89,866,867],{"class":186},"\"only print errors\"",[89,869,263],{"class":113},[89,871,873,876],{"class":91,"line":872},51,[89,874,875],{"class":102},"    return",[89,877,878],{"class":113}," parser\n",[89,880,882],{"class":91,"line":881},52,[89,883,121],{"emptyLinePlaceholder":120},[89,885,887],{"class":91,"line":886},53,[89,888,121],{"emptyLinePlaceholder":120},[89,890,892,894,897,900,902,905,908,911,914,916,919,921],{"class":91,"line":891},54,[89,893,346],{"class":102},[89,895,896],{"class":165}," main",[89,898,899],{"class":113},"(argv: list[",[89,901,207],{"class":106},[89,903,904],{"class":113},"] ",[89,906,907],{"class":102},"|",[89,909,910],{"class":106}," None",[89,912,913],{"class":102}," =",[89,915,910],{"class":106},[89,917,918],{"class":113},") -> ",[89,920,739],{"class":106},[89,922,210],{"class":113},[89,924,926,929,931],{"class":91,"line":925},55,[89,927,928],{"class":113},"    args ",[89,930,219],{"class":102},[89,932,933],{"class":113}," build_parser().parse_args(argv)\n",[89,935,937,940,942,945,948,951,954,956,959,961,964,966,968],{"class":91,"line":936},56,[89,938,939],{"class":106},"    print",[89,941,169],{"class":113},[89,943,944],{"class":102},"f",[89,946,947],{"class":186},"\"backing up ",[89,949,950],{"class":106},"{len",[89,952,953],{"class":113},"(args.sources)",[89,955,327],{"class":106},[89,957,958],{"class":186}," source(s) to ",[89,960,321],{"class":106},[89,962,963],{"class":113},"args.to",[89,965,327],{"class":106},[89,967,318],{"class":186},[89,969,263],{"class":113},[89,971,973,975],{"class":91,"line":972},57,[89,974,875],{"class":102},[89,976,977],{"class":106}," 0\n",[10,979,980],{},"The result, at 90 columns:",[80,982,986],{"className":983,"code":985,"language":324,"meta":85},[984],"language-text","usage: backup [-h] --to DEST [--storage-class {STANDARD,GLACIER}] [--exclude GLOB]\n              [--max-size MB] [-v | -q]\n              SOURCE [SOURCE ...]\n\nBack up directories to local disk or S3.\n\npositional arguments:\n  SOURCE                directories to back up\n\noptions:\n  -h, --help            show this help message and exit\n\ndestination:\n  --to DEST             local path or s3:\u002F\u002F URL\n  --storage-class {STANDARD,GLACIER}\n                        S3 storage class (default: STANDARD)\n\nfiltering:\n  Choose which files are copied.\n\n  --exclude GLOB        skip matching paths (repeatable)\n  --max-size MB         skip files larger than this\n\noutput:\n  -v, --verbose         more detail (repeatable)\n  -q, --quiet           only print errors\n\nexamples:\n  backup ~\u002Fdocs --to \u002Fmnt\u002Fbackup\n  backup ~\u002Fdocs --to s3:\u002F\u002Fbucket\u002Fdocs --storage-class GLACIER\n",[14,987,985],{"__ignoreMap":85},[48,989],{"name":990},"aphelp-terminal",[992,993,995],"h3",{"id":994},"what-each-piece-does","What each piece does",[10,997,998,239,1001,1004,1005,1008,1009,1012,1013,1017],{},[55,999,1000],{},"Argument groups",[14,1002,1003],{},"add_argument_group",") only affect help; parsing is unchanged. Each group gets a heading and an optional description line. Grouping by purpose — where the data goes, which files, how much output — lets users find the option they need without reading all of them. Mutually exclusive groups can live inside a regular group, which is how ",[14,1006,1007],{},"-v"," and ",[14,1010,1011],{},"-q"," end up under \"output\" while still being enforced as exclusive; ",[19,1014,1016],{"href":1015},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fmutually-exclusive-options-in-argparse\u002F","mutually exclusive options in argparse"," covers that mechanism.",[10,1019,1020,1023,1024,1027,1028,1030,1031,1008,1034,1037],{},[55,1021,1022],{},"Metavars"," replace the auto-generated placeholder. ",[14,1025,1026],{},"--max-size MB"," says the unit; ",[14,1029,16],{}," says nothing. ",[14,1032,1033],{},"--to DEST",[14,1035,1036],{},"--exclude GLOB"," tell the reader what kind of value is expected before they read the description.",[10,1039,1040,1043,1044,1048],{},[55,1041,1042],{},"The epilog"," is the natural home for examples, which users read first. ",[19,1045,1047],{"href":1046},"\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"," covers what makes a good example.",[10,1050,1051,1054,1055,1057,1058,1061,1062,1065,1066,1070,1071,245,1074,1008,1077,1080,1081,1084,1085,1087],{},[55,1052,1053],{},"The formatter."," ",[14,1056,177],{}," keeps the line breaks in the description and epilog, which the default formatter would re-wrap into one paragraph. The standard ",[14,1059,1060],{},"ArgumentDefaultsHelpFormatter"," appends ",[14,1063,1064],{},"(default: …)"," to ",[1067,1068,1069],"em",{},"every"," option — including ",[14,1072,1073],{},"(default: None)",[14,1075,1076],{},"(default: False)",[14,1078,1079],{},"(default: [])",", which are noise. The small subclass shows a default only when it is informative and the option does not already mention it. ",[14,1082,1083],{},"_get_help_string"," has a leading underscore, but overriding it is exactly how the standard library's own ",[14,1086,1060],{}," works, and it has been stable for many releases.",[992,1089,1091],{"id":1090},"width-and-colour","Width and colour",[10,1093,1094,1095,1098,1099,1008,1102,1105,1106,1110,1111,1114],{},"argparse wraps help to the terminal width, read from the ",[14,1096,1097],{},"COLUMNS"," environment variable or the terminal itself, with a margin. Python 3.14 also colours help and error output on terminals that support it, honouring ",[14,1100,1101],{},"NO_COLOR",[14,1103,1104],{},"FORCE_COLOR"," — the same conventions described in ",[19,1107,1109],{"href":1108},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Frespecting-no-color-and-force-color\u002F","respecting NO_COLOR and FORCE_COLOR",". On 3.14, ",[14,1112,1113],{},"suggest_on_error"," adds \"maybe you meant\" hints to invalid choices:",[80,1116,1119],{"className":1117,"code":1118,"language":324,"meta":85},[984],"backup: error: argument --storage-class: invalid choice: 'GLACER', maybe you meant 'GLACIER'? (choose from STANDARD, GLACIER)\n",[14,1120,1118],{"__ignoreMap":85},[992,1122,1124],{"id":1123},"help-for-subcommands","Help for subcommands",[10,1126,1127,1128,1131,1132,1135,1136,1139,1140,1143,1144,1008,1147,1149,1150,1153],{},"Subcommands have two help strings, and they appear in different places. ",[14,1129,1130],{},"help="," is the one-line summary shown in the ",[1067,1133,1134],{},"parent's"," list of commands; ",[14,1137,1138],{},"description="," is the paragraph at the top of the subcommand's own ",[14,1141,1142],{},"--help",". Set both, and tidy the command list itself with ",[14,1145,1146],{},"title",[14,1148,519],{}," on ",[14,1151,1152],{},"add_subparsers",":",[80,1155,1157],{"className":82,"code":1156,"language":84,"meta":85,"style":85},"sub = parser.add_subparsers(title=\"commands\", metavar=\"COMMAND\", required=True)\nrun = sub.add_parser(\"run\", help=\"run a backup now\",\n                     description=\"Copy the sources to the destination immediately.\",\n                     formatter_class=Formatter)\n",[14,1158,1159,1195,1219,1231],{"__ignoreMap":85},[89,1160,1161,1164,1166,1169,1171,1173,1176,1178,1180,1182,1185,1187,1189,1191,1193],{"class":91,"line":92},[89,1162,1163],{"class":113},"sub ",[89,1165,219],{"class":102},[89,1167,1168],{"class":113}," parser.add_subparsers(",[89,1170,1146],{"class":369},[89,1172,219],{"class":102},[89,1174,1175],{"class":186},"\"commands\"",[89,1177,245],{"class":113},[89,1179,519],{"class":369},[89,1181,219],{"class":102},[89,1183,1184],{"class":186},"\"COMMAND\"",[89,1186,245],{"class":113},[89,1188,571],{"class":369},[89,1190,219],{"class":102},[89,1192,576],{"class":106},[89,1194,263],{"class":113},[89,1196,1197,1200,1202,1205,1208,1210,1212,1214,1217],{"class":91,"line":99},[89,1198,1199],{"class":113},"run ",[89,1201,219],{"class":102},[89,1203,1204],{"class":113}," sub.add_parser(",[89,1206,1207],{"class":186},"\"run\"",[89,1209,245],{"class":113},[89,1211,529],{"class":369},[89,1213,219],{"class":102},[89,1215,1216],{"class":186},"\"run a backup now\"",[89,1218,378],{"class":113},[89,1220,1221,1224,1226,1229],{"class":91,"line":117},[89,1222,1223],{"class":369},"                     description",[89,1225,219],{"class":102},[89,1227,1228],{"class":186},"\"Copy the sources to the destination immediately.\"",[89,1230,378],{"class":113},[89,1232,1233,1236,1238],{"class":91,"line":124},[89,1234,1235],{"class":369},"                     formatter_class",[89,1237,219],{"class":102},[89,1239,1240],{"class":113},"Formatter)\n",[10,1242,1243,1244,1246,1247,1250,1251,1254,1255,1258,1259,1262],{},"Without ",[14,1245,519],{},", argparse prints the raw set of choices (",[14,1248,1249],{},"{run,list,restore}",") in the usage line and as the section entry, which gets unreadable after three or four commands. With it, the usage shows ",[14,1252,1253],{},"COMMAND"," and the \"commands\" section lists each subcommand with its summary — the layout users know from ",[14,1256,1257],{},"git help",". ",[14,1260,1261],{},"required=True"," makes a missing subcommand a usage error instead of a silent no-op.",[25,1264,1266],{"id":1265},"ux-considerations","UX considerations",[30,1268,1269,1275,1285,1300,1310],{},[33,1270,1271,1274],{},[55,1272,1273],{},"Order groups by importance."," Required and most-used options first; rarely used tuning options last. Within a group, the same principle applies.",[33,1276,1277,1280,1281,1284],{},[55,1278,1279],{},"Write help in lowercase fragments,"," matching argparse's own ",[14,1282,1283],{},"show this help message and exit",". Consistency matters more than which style you pick.",[33,1286,1287,239,1290,245,1293,245,1296,1299],{},[55,1288,1289],{},"Say units and formats in the metavar",[14,1291,1292],{},"MB",[14,1294,1295],{},"SECONDS",[14,1297,1298],{},"YYYY-MM-DD",") so the description can focus on meaning.",[33,1301,1302,1305,1306,1309],{},[55,1303,1304],{},"Hide internal options"," with ",[14,1307,1308],{},"help=argparse.SUPPRESS"," rather than deleting them, if they must stay for compatibility.",[33,1311,1312,1315],{},[55,1313,1314],{},"Keep examples copy-pasteable"," and correct; they are the part of help most likely to be run verbatim.",[48,1317],{"name":1318},"aphelp-checklist",[25,1320,1322],{"id":1321},"testing-the-behaviour","Testing the behaviour",[10,1324,1325],{},"Help text is user-facing output and deserves a snapshot, rendered at a fixed width so it does not depend on the terminal running the tests:",[80,1327,1329],{"className":82,"code":1328,"language":84,"meta":85,"style":85},"# tests\u002Ftest_help.py\nimport pytest\n\nfrom backup.cli import build_parser, main\n\n\n@pytest.fixture\ndef help_text(monkeypatch) -> str:\n    monkeypatch.setenv(\"COLUMNS\", \"90\")\n    monkeypatch.setenv(\"NO_COLOR\", \"1\")\n    return build_parser().format_help()\n\n\ndef test_groups_appear_in_order(help_text):\n    headings = [line for line in help_text.splitlines() if line.endswith(\":\") and not line[0].isspace()]\n    assert headings == [\"positional arguments:\", \"options:\", \"destination:\", \"filtering:\",\n                        \"output:\", \"examples:\"]\n\n\ndef test_only_informative_defaults_are_shown(help_text):\n    assert \"(default: STANDARD)\" in help_text\n    assert \"(default: None)\" not in help_text and \"(default: [])\" not in help_text\n\n\ndef test_epilog_keeps_line_breaks(help_text):\n    assert \"\\n  backup ~\u002Fdocs --to \u002Fmnt\u002Fbackup\\n\" in help_text\n\n\ndef test_exclusive_verbosity_is_enforced(capsys):\n    with pytest.raises(SystemExit) as exc:\n        main([\"src\", \"--to\", \"dst\", \"-v\", \"-q\"])\n    assert exc.value.code == 2\n    assert \"not allowed with argument\" in capsys.readouterr().err\n",[14,1330,1331,1336,1343,1347,1359,1363,1367,1372,1386,1401,1415,1422,1426,1430,1440,1486,1520,1533,1537,1541,1550,1562,1587,1591,1595,1604,1625,1629,1633,1643,1662,1690,1702],{"__ignoreMap":85},[89,1332,1333],{"class":91,"line":92},[89,1334,1335],{"class":95},"# tests\u002Ftest_help.py\n",[89,1337,1338,1340],{"class":91,"line":99},[89,1339,127],{"class":102},[89,1341,1342],{"class":113}," pytest\n",[89,1344,1345],{"class":91,"line":117},[89,1346,121],{"emptyLinePlaceholder":120},[89,1348,1349,1351,1354,1356],{"class":91,"line":124},[89,1350,103],{"class":102},[89,1352,1353],{"class":113}," backup.cli ",[89,1355,127],{"class":102},[89,1357,1358],{"class":113}," build_parser, main\n",[89,1360,1361],{"class":91,"line":133},[89,1362,121],{"emptyLinePlaceholder":120},[89,1364,1365],{"class":91,"line":141},[89,1366,121],{"emptyLinePlaceholder":120},[89,1368,1369],{"class":91,"line":149},[89,1370,1371],{"class":165},"@pytest.fixture\n",[89,1373,1374,1376,1379,1382,1384],{"class":91,"line":154},[89,1375,346],{"class":102},[89,1377,1378],{"class":165}," help_text",[89,1380,1381],{"class":113},"(monkeypatch) -> ",[89,1383,207],{"class":106},[89,1385,210],{"class":113},[89,1387,1388,1391,1394,1396,1399],{"class":91,"line":159},[89,1389,1390],{"class":113},"    monkeypatch.setenv(",[89,1392,1393],{"class":186},"\"COLUMNS\"",[89,1395,245],{"class":113},[89,1397,1398],{"class":186},"\"90\"",[89,1400,263],{"class":113},[89,1402,1403,1405,1408,1410,1413],{"class":91,"line":183},[89,1404,1390],{"class":113},[89,1406,1407],{"class":186},"\"NO_COLOR\"",[89,1409,245],{"class":113},[89,1411,1412],{"class":186},"\"1\"",[89,1414,263],{"class":113},[89,1416,1417,1419],{"class":91,"line":190},[89,1418,875],{"class":102},[89,1420,1421],{"class":113}," build_parser().format_help()\n",[89,1423,1424],{"class":91,"line":195},[89,1425,121],{"emptyLinePlaceholder":120},[89,1427,1428],{"class":91,"line":213},[89,1429,121],{"emptyLinePlaceholder":120},[89,1431,1432,1434,1437],{"class":91,"line":231},[89,1433,346],{"class":102},[89,1435,1436],{"class":165}," test_groups_appear_in_order",[89,1438,1439],{"class":113},"(help_text):\n",[89,1441,1442,1445,1447,1450,1453,1456,1458,1461,1464,1467,1470,1473,1476,1478,1481,1483],{"class":91,"line":266},[89,1443,1444],{"class":113},"    headings ",[89,1446,219],{"class":102},[89,1448,1449],{"class":113}," [line ",[89,1451,1452],{"class":102},"for",[89,1454,1455],{"class":113}," line ",[89,1457,294],{"class":102},[89,1459,1460],{"class":113}," help_text.splitlines() ",[89,1462,1463],{"class":102},"if",[89,1465,1466],{"class":113}," line.endswith(",[89,1468,1469],{"class":186},"\":\"",[89,1471,1472],{"class":113},") ",[89,1474,1475],{"class":102},"and",[89,1477,283],{"class":102},[89,1479,1480],{"class":113}," line[",[89,1482,254],{"class":106},[89,1484,1485],{"class":113},"].isspace()]\n",[89,1487,1488,1491,1494,1497,1500,1503,1505,1508,1510,1513,1515,1518],{"class":91,"line":300},[89,1489,1490],{"class":102},"    assert",[89,1492,1493],{"class":113}," headings ",[89,1495,1496],{"class":102},"==",[89,1498,1499],{"class":113}," [",[89,1501,1502],{"class":186},"\"positional arguments:\"",[89,1504,245],{"class":113},[89,1506,1507],{"class":186},"\"options:\"",[89,1509,245],{"class":113},[89,1511,1512],{"class":186},"\"destination:\"",[89,1514,245],{"class":113},[89,1516,1517],{"class":186},"\"filtering:\"",[89,1519,378],{"class":113},[89,1521,1522,1525,1527,1530],{"class":91,"line":309},[89,1523,1524],{"class":186},"                        \"output:\"",[89,1526,245],{"class":113},[89,1528,1529],{"class":186},"\"examples:\"",[89,1531,1532],{"class":113},"]\n",[89,1534,1535],{"class":91,"line":333},[89,1536,121],{"emptyLinePlaceholder":120},[89,1538,1539],{"class":91,"line":338},[89,1540,121],{"emptyLinePlaceholder":120},[89,1542,1543,1545,1548],{"class":91,"line":343},[89,1544,346],{"class":102},[89,1546,1547],{"class":165}," test_only_informative_defaults_are_shown",[89,1549,1439],{"class":113},[89,1551,1552,1554,1557,1559],{"class":91,"line":355},[89,1553,1490],{"class":102},[89,1555,1556],{"class":186}," \"(default: STANDARD)\"",[89,1558,275],{"class":102},[89,1560,1561],{"class":113}," help_text\n",[89,1563,1564,1566,1569,1571,1573,1576,1578,1581,1583,1585],{"class":91,"line":366},[89,1565,1490],{"class":102},[89,1567,1568],{"class":186}," \"(default: None)\"",[89,1570,283],{"class":102},[89,1572,275],{"class":102},[89,1574,1575],{"class":113}," help_text ",[89,1577,1475],{"class":102},[89,1579,1580],{"class":186}," \"(default: [])\"",[89,1582,283],{"class":102},[89,1584,275],{"class":102},[89,1586,1561],{"class":113},[89,1588,1589],{"class":91,"line":381},[89,1590,121],{"emptyLinePlaceholder":120},[89,1592,1593],{"class":91,"line":394},[89,1594,121],{"emptyLinePlaceholder":120},[89,1596,1597,1599,1602],{"class":91,"line":411},[89,1598,346],{"class":102},[89,1600,1601],{"class":165}," test_epilog_keeps_line_breaks",[89,1603,1439],{"class":113},[89,1605,1606,1608,1611,1614,1617,1619,1621,1623],{"class":91,"line":417},[89,1607,1490],{"class":102},[89,1609,1610],{"class":186}," \"",[89,1612,1613],{"class":106},"\\n",[89,1615,1616],{"class":186},"  backup ~\u002Fdocs --to \u002Fmnt\u002Fbackup",[89,1618,1613],{"class":106},[89,1620,318],{"class":186},[89,1622,275],{"class":102},[89,1624,1561],{"class":113},[89,1626,1627],{"class":91,"line":423},[89,1628,121],{"emptyLinePlaceholder":120},[89,1630,1631],{"class":91,"line":429},[89,1632,121],{"emptyLinePlaceholder":120},[89,1634,1635,1637,1640],{"class":91,"line":438},[89,1636,346],{"class":102},[89,1638,1639],{"class":165}," test_exclusive_verbosity_is_enforced",[89,1641,1642],{"class":113},"(capsys):\n",[89,1644,1645,1648,1651,1654,1656,1659],{"class":91,"line":449},[89,1646,1647],{"class":102},"    with",[89,1649,1650],{"class":113}," pytest.raises(",[89,1652,1653],{"class":106},"SystemExit",[89,1655,1472],{"class":113},[89,1657,1658],{"class":102},"as",[89,1660,1661],{"class":113}," exc:\n",[89,1663,1664,1667,1670,1672,1674,1676,1679,1681,1683,1685,1687],{"class":91,"line":455},[89,1665,1666],{"class":113},"        main([",[89,1668,1669],{"class":186},"\"src\"",[89,1671,245],{"class":113},[89,1673,566],{"class":186},[89,1675,245],{"class":113},[89,1677,1678],{"class":186},"\"dst\"",[89,1680,245],{"class":113},[89,1682,799],{"class":186},[89,1684,245],{"class":113},[89,1686,844],{"class":186},[89,1688,1689],{"class":113},"])\n",[89,1691,1692,1694,1697,1699],{"class":91,"line":479},[89,1693,1490],{"class":102},[89,1695,1696],{"class":113}," exc.value.code ",[89,1698,1496],{"class":102},[89,1700,1701],{"class":106}," 2\n",[89,1703,1704,1706,1709,1711],{"class":91,"line":493},[89,1705,1490],{"class":102},[89,1707,1708],{"class":186}," \"not allowed with argument\"",[89,1710,275],{"class":102},[89,1712,1713],{"class":113}," capsys.readouterr().err\n",[10,1715,1716,1717,1719,1720,1722,1723,1727],{},"Setting ",[14,1718,1097],{}," makes wrapping deterministic; ",[14,1721,1101],{}," keeps Python 3.14's coloured help out of the comparison. For a full snapshot of the help screen, the tooling in ",[19,1724,1726],{"href":1725},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fsnapshot-testing-cli-output\u002F","snapshot testing CLI output"," works unchanged.",[25,1729,1731],{"id":1730},"conclusion","Conclusion",[10,1733,1734,1735,1737,1738,1740,1741,1743],{},"argparse can produce help that reads as well as any framework's: group options by purpose with ",[14,1736,1003],{},", give every option a meaningful metavar, put examples in an epilog, use ",[14,1739,177],{}," with a small override that shows only informative defaults, and enable ",[14,1742,1113],{}," on Python 3.14. Render help at a fixed width in tests, and it will stay readable as the CLI grows.",[25,1745,1747],{"id":1746},"frequently-asked-questions","Frequently asked questions",[992,1749,1751],{"id":1750},"can-i-change-the-options-heading","Can I change the \"options:\" heading?",[10,1753,1754,1755,23],{},"It is not configurable directly; the heading text changed from \"optional arguments:\" to \"options:\" in Python 3.10. If you need full control of headings, put every option in a named group, which leaves the built-in section with only ",[14,1756,1757],{},"-h, --help",[992,1759,1761,1762,1764],{"id":1760},"how-do-i-put-help-in-a-group-too","How do I put ",[14,1763,1142],{}," in a group too?",[10,1766,1767,1768,1771,1772,1775,1776,1779],{},"Create the parser with ",[14,1769,1770],{},"add_help=False"," and add ",[14,1773,1774],{},"-h\u002F--help"," yourself with ",[14,1777,1778],{},"action=\"help\""," in the group of your choice.",[992,1781,1783],{"id":1782},"why-do-my-newlines-in-help-strings-disappear","Why do my newlines in help strings disappear?",[10,1785,1786,1787,1789,1790,1793],{},"The default formatter re-wraps all text. ",[14,1788,177],{}," preserves the description and epilog; ",[14,1791,1792],{},"RawTextHelpFormatter"," preserves every help string, which means you must wrap them yourself.",[992,1795,1797],{"id":1796},"do-argument-groups-work-with-subparsers","Do argument groups work with subparsers?",[10,1799,1800,1801,1804,1805,1808,1809,1812],{},"Yes — each subparser is an ",[14,1802,1803],{},"ArgumentParser"," and has its own groups, formatter and epilog. Pass ",[14,1806,1807],{},"formatter_class=Formatter"," to each ",[14,1810,1811],{},"add_parser"," call, since subparsers do not inherit it.",[992,1814,1816],{"id":1815},"is-it-worth-switching-to-click-or-typer-just-for-nicer-help","Is it worth switching to Click or Typer just for nicer help?",[10,1818,1819,1820,23],{},"Usually not. The techniques here close most of the gap, and argparse keeps its advantage of zero dependencies. Switch when you also want what the frameworks add beyond help — composable command groups, shared options, testing helpers — as weighed in ",[19,1821,1823],{"href":1822},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargparse-vs-click-vs-typer-comparison\u002F","argparse vs Click vs Typer",[25,1825,1827],{"id":1826},"related","Related",[30,1829,1830,1836,1841,1846,1852],{},[33,1831,1832,1833],{},"Up: ",[19,1834,1835],{"href":21},"Command-line parsing with argparse",[33,1837,1838],{},[19,1839,1840],{"href":41},"Argparse subparsers for subcommands",[33,1842,1843],{},[19,1844,1845],{"href":1015},"Mutually exclusive options in argparse",[33,1847,1848],{},[19,1849,1851],{"href":1850},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Freading-arguments-from-files-with-fromfile-prefix-chars\u002F","Reading arguments from files with fromfile_prefix_chars",[33,1853,1854],{},[19,1855,1857],{"href":1856},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fwriting-help-text-users-actually-read\u002F","Writing help text users actually read",[1859,1860,1861],"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 .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}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":85,"searchDepth":99,"depth":99,"links":1863},[1864,1865,1866,1871,1872,1873,1874,1882],{"id":27,"depth":99,"text":28},{"id":45,"depth":99,"text":46},{"id":77,"depth":99,"text":78,"children":1867},[1868,1869,1870],{"id":994,"depth":117,"text":995},{"id":1090,"depth":117,"text":1091},{"id":1123,"depth":117,"text":1124},{"id":1265,"depth":99,"text":1266},{"id":1321,"depth":99,"text":1322},{"id":1730,"depth":99,"text":1731},{"id":1746,"depth":99,"text":1747,"children":1875},[1876,1877,1879,1880,1881],{"id":1750,"depth":117,"text":1751},{"id":1760,"depth":117,"text":1878},"How do I put --help in a group too?",{"id":1782,"depth":117,"text":1783},{"id":1796,"depth":117,"text":1797},{"id":1815,"depth":117,"text":1816},{"id":1826,"depth":99,"text":1827},"2026-10-02","Make argparse help readable: argument groups with descriptions, metavars, examples in the epilog, a formatter that shows only useful defaults, and help snapshot tests.","beginner",false,"md",{},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargument-groups-and-help-formatting-in-argparse",{"title":5,"description":1884},"modern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargument-groups-and-help-formatting-in-argparse\u002Findex",[172,1893,1894,1895,1896],"help-text","formatting","standard-library","ux","NZGCUzma0u6qLHShtP4xed7p16NJQddU6A2LzEctnpg",[1899,1902,1905,1908,1911,1914,1917,1920,1923,1926,1929,1932,1935,1938,1941,1944,1947,1950,1953,1956,1959,1962,1965,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,2151,2154,2157,2160,2163,2166,2169,2172,2175,2178,2181,2184,2187,2190,2193,2196,2199,2202,2205,2208,2211,2214,2217,2220,2223,2226,2229,2232,2235,2238,2241,2244,2247,2250,2253,2256,2259,2262,2265,2268,2271,2274,2277,2280,2283,2286,2289,2292,2295,2298,2301,2304,2307,2310,2313,2316,2319,2322,2325,2328,2331,2334,2337,2340,2343,2346,2349,2352,2355,2358,2361,2362,2365,2368,2371,2374,2377,2380,2383,2386,2389,2392,2395,2398,2401,2404,2407,2410,2413,2416,2419,2422,2425,2428,2431,2434,2437,2440,2443,2446,2449,2452,2455,2458,2461,2464,2467,2470,2473,2476,2479,2482,2485,2488,2491,2494,2497,2500,2503,2506,2509,2512,2515,2518,2521,2524,2527,2530,2533,2536,2539,2542,2545,2548,2551,2554,2557,2560,2563,2566,2569,2572,2575,2578,2581,2584,2587,2590,2593,2596,2599,2602,2605,2608,2611,2614,2617,2620,2623,2626,2629,2632,2635,2638,2641,2644,2647,2650,2653,2656,2659,2662,2665,2668,2671,2674,2677,2680,2683,2686,2689,2692,2695,2698,2701,2704,2707,2710,2713,2716,2719,2722,2725,2728,2731,2734,2737,2740],{"path":1900,"title":1901},"\u002Fabout","About Python CLI Toolcraft",{"path":1903,"title":1904},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies","Advanced Argument Validation Strategies",{"path":1906,"title":1907},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fparsing-nested-json-arguments-in-python-clis","Parsing Nested JSON Args in Python CLIs",{"path":1909,"title":1910},"\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":1912,"title":1913},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fvalidating-dependent-and-conflicting-options","Validating Dependent and Conflicting CLI Options",{"path":1915,"title":1916},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fvalidating-file-and-directory-paths-in-clis","Validating File and Directory Paths in CLIs",{"path":1918,"title":1919},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fvalidating-urls-hosts-and-ports","Validating URLs, Hosts and Ports in Python CLI Arguments",{"path":1921,"title":1922},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fwriting-custom-click-parameter-types","Writing Custom Click Parameter Types",{"path":1924,"title":1925},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual\u002Fbrowsing-records-with-a-textual-datatable","Browsing Records with a Textual DataTable Picker",{"path":1927,"title":1928},"\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":1930,"title":1931},"\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":1933,"title":1934},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual","Building Terminal UIs with Textual for Python CLIs",{"path":1936,"title":1937},"\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":1939,"title":1940},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual\u002Fstyling-textual-apps-with-tcss","Styling Textual Apps with TCSS",{"path":1942,"title":1943},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual\u002Ftesting-textual-apps-with-pilot","Testing Textual Apps with Pilot",{"path":1945,"title":1946},"\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":1948,"title":1949},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fdocumenting-environment-variables-in-help","Documenting Environment Variables in CLI Help",{"path":1951,"title":1952},"\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":1954,"title":1955},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation","CLI Help Output and Documentation",{"path":1957,"title":1958},"\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":1960,"title":1961},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fversioning-and-deprecating-cli-flags","Versioning and Deprecating CLI Flags",{"path":1963,"title":1964},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fwriting-help-text-users-actually-read","Writing Help Text Users Actually Read",{"path":1966,"title":1967},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Fadapting-output-to-terminal-width","Adapting Python CLI Output to Terminal Width",{"path":1969,"title":1970},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Fdetecting-ci-environments-and-non-interactive-shells","Detecting CI Environments and Non-Interactive Shells",{"path":1972,"title":1973},"\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":1975,"title":1976},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Fhandling-ansi-escape-codes-on-windows-consoles","Handling ANSI Escape Codes on Windows Consoles",{"path":1978,"title":1979},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility","Cross-Platform Terminal Compatibility for Python CLIs",{"path":1981,"title":1982},"\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":1984,"title":1985},"\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":1987,"title":1988},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fchoosing-exit-codes-for-cli-tools","Choosing Exit Codes for CLI Tools",{"path":1990,"title":1991},"\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":1993,"title":1994},"\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":1996,"title":1997},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Ffriendly-error-messages-and-tracebacks","Friendly Error Messages and Tracebacks",{"path":1999,"title":2000},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fhandling-keyboard-interrupt-cleanly","Handling Keyboard Interrupt Cleanly",{"path":2002,"title":2003},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes","Error Handling and Exit Codes for CLIs",{"path":2005,"title":2006},"\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":2008,"title":2009},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fwriting-crash-reports-users-can-send","Writing Crash Reports Users Can Send",{"path":2011,"title":2012},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Fconfig-precedence-flags-env-files-defaults","Config Precedence: Flags, Env, Files, Defaults",{"path":2014,"title":2015},"\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":2017,"title":2018},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars","Handling Config Files and Env Vars in CLIs",{"path":2020,"title":2021},"\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":2023,"title":2024},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Freading-toml-config-with-tomllib","Reading TOML Config with tomllib in Python CLIs",{"path":2026,"title":2027},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Ftyped-settings-with-pydantic-settings","Typed Settings with pydantic-settings in Python CLIs",{"path":2029,"title":2030},"\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":2032,"title":2033},"\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":2035,"title":2036},"\u002Fadvanced-input-parsing-user-experience","Advanced Input Parsing for Python CLIs",{"path":2038,"title":2039},"\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":2041,"title":2042},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Fbuilding-interactive-prompts-and-menus","Building Interactive Prompts and Menus in Python CLIs",{"path":2044,"title":2045},"\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":2047,"title":2048},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich","Interactive Terminal UI with Rich",{"path":2050,"title":2051},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Flive-dashboards-with-rich-live","Live Dashboards with Rich Live in Python CLIs",{"path":2053,"title":2054},"\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":2056,"title":2057},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Frendering-tables-and-json-with-rich","Rendering Tables and JSON with Rich",{"path":2059,"title":2060},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Ftheming-rich-output-consistently","Theming Rich Output Consistently in Python CLIs",{"path":2062,"title":2063},"\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":2065,"title":2066},"\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":2068,"title":2069},"\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":2071,"title":2072},"\u002Fadvanced-input-parsing-user-experience\u002Foutput-formats-for-data-clis","Output Formats for Data-Heavy Python CLIs",{"path":2074,"title":2075},"\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":2077,"title":2078},"\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":2080,"title":2081},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Fargcomplete-for-argparse-clis","Tab Completion for argparse CLIs with argcomplete",{"path":2083,"title":2084},"\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":2086,"title":2087},"\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":2089,"title":2090},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis","Shell Completion for Python CLIs",{"path":2092,"title":2093},"\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":2095,"title":2096},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Fshipping-completion-scripts-with-packages","Shipping Shell Completion Scripts with a Python CLI",{"path":2098,"title":2099},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Ftesting-shell-completion-in-python-clis","Testing Shell Completion in Python CLIs",{"path":2101,"title":2102},"\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":2104,"title":2105},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fadding-verbose-and-quiet-logging-flags","Adding Verbose and Quiet Logging Flags",{"path":2107,"title":2108},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps","Structured Logging for CLI Apps",{"path":2110,"title":2111},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Flogging-with-structlog-in-a-cli","Logging with structlog in a Python CLI",{"path":2113,"title":2114},"\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":2116,"title":2117},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fstructured-json-logging-in-python-clis","Structured JSON Logging in Python CLIs",{"path":2119,"title":2120},"\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":2122,"title":2123},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Fdetecting-tty-and-adapting-output","Detecting a TTY and Adapting Output",{"path":2125,"title":2126},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Femitting-json-output-for-scripting","Emitting JSON Output for Scripting",{"path":2128,"title":2129},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Fhandling-broken-pipe-and-sigpipe","Handling Broken Pipe and SIGPIPE",{"path":2131,"title":2132},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes","Working with stdin, stdout and Pipes",{"path":2134,"title":2135},"\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":2137,"title":2138},"\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":2140,"title":2141},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Freading-piped-input-in-python-clis","Reading Piped Input in Python CLIs",{"path":2143,"title":2144},"\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":2146,"title":2147},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Fdownloading-files-with-progress-in-python","Downloading Files with Progress in Python CLIs",{"path":2149,"title":2150},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis","Calling HTTP APIs from Python CLIs",{"path":2152,"title":2153},"\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":2155,"title":2156},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Foauth-device-flow-login-for-clis","OAuth Device Flow Login for Python CLIs",{"path":2158,"title":2159},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Fpaginating-api-results-in-a-cli","Paginating API Results in a Python CLI",{"path":2161,"title":2162},"\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":2164,"title":2165},"\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":2167,"title":2168},"\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":2170,"title":2171},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis","Concurrency and Async in Python CLIs",{"path":2173,"title":2174},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Fmultiprocessing-for-cpu-bound-cli-tasks","Multiprocessing for CPU-Bound CLI Tasks",{"path":2176,"title":2177},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Fparallelising-cli-work-with-thread-pools","Parallelising CLI Work with Thread Pools",{"path":2179,"title":2180},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Frate-limiting-concurrent-requests-in-clis","Rate-Limiting Concurrent Requests in Python CLIs",{"path":2182,"title":2183},"\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":2185,"title":2186},"\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":2188,"title":2189},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Fstructured-concurrency-with-taskgroups-in-clis","Structured Concurrency with TaskGroups in Python CLIs",{"path":2191,"title":2192},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fcross-platform-paths-with-pathlib","Cross-Platform Paths with pathlib in CLIs",{"path":2194,"title":2195},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Ffile-locking-for-concurrent-cli-runs","File Locking for Concurrent CLI Runs in Python",{"path":2197,"title":2198},"\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":2200,"title":2201},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes","Filesystem Paths and Atomic Writes for CLIs",{"path":2203,"title":2204},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fsafe-temporary-files-and-directories","Safe Temporary Files and Directories in CLIs",{"path":2206,"title":2207},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fstoring-app-data-with-platformdirs","Storing CLI App Data with platformdirs",{"path":2209,"title":2210},"\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":2212,"title":2213},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fwriting-files-atomically-in-python-clis","Writing Files Atomically in Python CLIs",{"path":2215,"title":2216},"\u002Fcli-runtime-systems-integration","CLI Runtime & Systems Integration for Python",{"path":2218,"title":2219},"\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":2221,"title":2222},"\u002Fcli-runtime-systems-integration\u002Flocal-state-and-sqlite-in-python-clis","Local State and SQLite in Python CLIs",{"path":2224,"title":2225},"\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":2227,"title":2228},"\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":2230,"title":2231},"\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":2233,"title":2234},"\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":2236,"title":2237},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis\u002Fhandling-sigterm-and-graceful-shutdown","Handling SIGTERM and Graceful Shutdown in CLIs",{"path":2239,"title":2240},"\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":2242,"title":2243},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis","Long-Running and Watch-Mode Python CLIs",{"path":2245,"title":2246},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis\u002Freloading-config-on-sighup","Reloading Configuration on SIGHUP in a Python CLI",{"path":2248,"title":2249},"\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":2251,"title":2252},"\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":2254,"title":2255},"\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":2257,"title":2258},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Favoiding-shell-injection-in-python-clis","Avoiding Shell Injection in Python CLIs",{"path":2260,"title":2261},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fcalling-external-commands-safely-with-subprocess","Calling External Commands Safely with subprocess",{"path":2263,"title":2264},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fhandling-subprocess-timeouts-and-exit-codes","Handling Subprocess Timeouts and Exit Codes",{"path":2266,"title":2267},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis","Running Subprocesses from Python CLIs",{"path":2269,"title":2270},"\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":2272,"title":2273},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fpiping-between-subprocesses-in-python","Piping Between Subprocesses in Python Without a Shell",{"path":2275,"title":2276},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fstreaming-subprocess-output-in-real-time","Streaming Subprocess Output in Real Time",{"path":2278,"title":2279},"\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":2281,"title":2282},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis","Secrets and Credentials in Python CLIs",{"path":2284,"title":2285},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Fprompting-for-passwords-securely","Prompting for Passwords Securely in Python CLIs",{"path":2287,"title":2288},"\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":2290,"title":2291},"\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":2293,"title":2294},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Frefreshing-expired-tokens-automatically","Refreshing Expired Tokens Automatically in a Python CLI",{"path":2296,"title":2297},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Fstoring-tokens-with-keyring","Storing CLI Tokens Securely with keyring",{"path":2299,"title":2300},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Fsupporting-multiple-profiles-and-accounts","Supporting Multiple Profiles and Accounts in CLIs",{"path":2302,"title":2303},"\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":2305,"title":2306},"\u002Fcli-runtime-systems-integration\u002Fupdate-checks-and-self-updating-clis","Update Checks, Upgrade Commands and Telemetry for Python CLIs",{"path":2308,"title":2309},"\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":2311,"title":2312},"\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":2314,"title":2315},"\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":2317,"title":2318},"\u002F","Python CLI Toolcraft",{"path":2320,"title":2321},"\u002Fmodern-python-cli-frameworks-architecture\u002Falternative-python-cli-frameworks\u002Fbuilding-a-cli-with-cleo","Building a Python CLI with Cleo Command Classes",{"path":2323,"title":2324},"\u002Fmodern-python-cli-frameworks-architecture\u002Falternative-python-cli-frameworks\u002Fbuilding-a-cli-with-cyclopts","Building a Type-Hinted Python CLI with Cyclopts",{"path":2326,"title":2327},"\u002Fmodern-python-cli-frameworks-architecture\u002Falternative-python-cli-frameworks","Beyond Click and Typer: Alternative Python CLI Frameworks",{"path":2329,"title":2330},"\u002Fmodern-python-cli-frameworks-architecture\u002Falternative-python-cli-frameworks\u002Fquick-clis-from-functions-with-python-fire","Quick CLIs from Functions with Python Fire",{"path":2332,"title":2333},"\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":2335,"title":2336},"\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":2338,"title":2339},"\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":2341,"title":2342},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Fguarding-startup-with-import-tests","Guarding CLI Startup with Import Tests",{"path":2344,"title":2345},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading","CLI Startup Performance and Lazy Loading",{"path":2347,"title":2348},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Flazy-loading-subcommands-for-faster-startup","Lazy Loading Subcommands for Faster Startup",{"path":2350,"title":2351},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Fprofiling-python-cli-startup-time","Profiling Python CLI Startup Time",{"path":2353,"title":2354},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Freducing-cli-dependency-weight","Reducing CLI Dependency Weight",{"path":2356,"title":2357},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargparse-subparsers-for-subcommands","argparse Subparsers for Subcommands",{"path":2359,"title":2360},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargparse-vs-click-vs-typer-comparison","argparse vs Click vs Typer Compared",{"path":1889,"title":5},{"path":2363,"title":2364},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse","Command-Line Parsing with argparse",{"path":2366,"title":2367},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fmigrating-from-argparse-to-typer","Migrating from argparse to Typer",{"path":2369,"title":2370},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fmutually-exclusive-options-in-argparse","Mutually Exclusive Options in argparse",{"path":2372,"title":2373},"\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":2375,"title":2376},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fwriting-custom-argparse-actions","Writing Custom argparse Actions in Python",{"path":2378,"title":2379},"\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":2381,"title":2382},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Fdesigning-idempotent-commands","Designing Idempotent Commands in a Python CLI",{"path":2384,"title":2385},"\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":2387,"title":2388},"\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":2390,"title":2391},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions","Designing CLI Interfaces and Conventions in Python",{"path":2393,"title":2394},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Fnaming-commands-and-flags-consistently","Naming Commands and Flags Consistently in Python CLIs",{"path":2396,"title":2397},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Fpositional-arguments-vs-options","Positional Arguments vs Options: Designing a CLI Signature",{"path":2399,"title":2400},"\u002Fmodern-python-cli-frameworks-architecture","Python CLI Frameworks and Architecture",{"path":2402,"title":2403},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fdiscovering-plugins-with-entry-points","Discovering Plugins with Entry Points in Python CLIs",{"path":2405,"title":2406},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fhook-based-plugins-with-pluggy","Hook-Based Plugins for Python CLIs with pluggy",{"path":2408,"title":2409},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis","Plugin Architectures for Extensible CLIs",{"path":2411,"title":2412},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fisolating-plugin-failures","Isolating Plugin Failures in an Extensible Python CLI",{"path":2414,"title":2415},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Ftesting-plugins-against-the-host-cli","Testing Plugins Against the Host CLI",{"path":2417,"title":2418},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fversioning-a-plugin-api","Versioning a Plugin API for a Python CLI",{"path":2420,"title":2421},"\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":2423,"title":2424},"\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":2426,"title":2427},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fdependency-injection-patterns-for-cli-commands","Dependency Injection Patterns for CLI Commands",{"path":2429,"title":2430},"\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":2432,"title":2433},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis","Structuring Multi-Command Python CLIs",{"path":2435,"title":2436},"\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":2438,"title":2439},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fregistering-commands-from-modules-automatically","Registering CLI Commands from Modules Automatically",{"path":2441,"title":2442},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fsharing-common-options-across-commands","Sharing Common Options Across Python CLI Commands",{"path":2444,"title":2445},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fsharing-state-with-click-context-objects","Sharing State with Click Context Objects",{"path":2447,"title":2448},"\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":2450,"title":2451},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications","Testing Python CLI Applications",{"path":2453,"title":2454},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fmeasuring-cli-test-coverage","Measuring CLI Test Coverage",{"path":2456,"title":2457},"\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":2459,"title":2460},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fproperty-based-testing-cli-arguments-with-hypothesis","Property-Based Testing CLI Arguments with Hypothesis",{"path":2462,"title":2463},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fsnapshot-testing-cli-output","Snapshot Testing CLI Output",{"path":2465,"title":2466},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftesting-click-commands-with-clirunner","Testing Click Commands with CliRunner",{"path":2468,"title":2469},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftesting-commands-that-call-subprocesses","Testing Python CLI Commands That Call Subprocesses",{"path":2471,"title":2472},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftesting-interactive-prompts-and-stdin","Testing Interactive Prompts and stdin",{"path":2474,"title":2475},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftranscript-tests-for-cli-commands","Transcript Tests for Python CLI Commands",{"path":2477,"title":2478},"\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":2480,"title":2481},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Fbuilding-dynamic-commands-in-click","Building Dynamic Commands in Click",{"path":2483,"title":2484},"\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":2486,"title":2487},"\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":2489,"title":2490},"\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":2492,"title":2493},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each","Typer vs Click: When to Use Each",{"path":2495,"title":2496},"\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":2498,"title":2499},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Ftyper-callback-functions-explained","Typer callback functions explained",{"path":2501,"title":2502},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Fusing-annotated-options-in-typer","Using Annotated Options in Typer",{"path":2504,"title":2505},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis\u002Fautomating-releases-from-git-tags","Automating Python CLI Releases from Git Tags",{"path":2507,"title":2508},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis\u002Fcaching-uv-dependencies-in-ci","Caching uv Dependencies in CI for Python CLIs",{"path":2510,"title":2511},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis","CI\u002FCD Pipelines for Python CLIs",{"path":2513,"title":2514},"\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":2516,"title":2517},"\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":2519,"title":2520},"\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":2522,"title":2523},"\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":2525,"title":2526},"\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":2528,"title":2529},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fbuilding-a-cookiecutter-template-for-typer-clis","Building a Cookiecutter Template for Typer CLIs",{"path":2531,"title":2532},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fcopier-vs-cookiecutter-for-cli-templates","Copier vs Cookiecutter for CLI Templates",{"path":2534,"title":2535},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter","CLI Project Scaffolding with Cookiecutter",{"path":2537,"title":2538},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fpost-generation-hooks-in-cli-templates","Post-Generation Hooks in Python CLI Templates",{"path":2540,"title":2541},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Ftemplate-variables-and-conditional-files","Template Variables and Conditional Files in CLI Templates",{"path":2543,"title":2544},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Ftesting-a-project-template-with-pytest","Testing a CLI Project Template with pytest",{"path":2546,"title":2547},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fupdating-generated-projects-with-copier-update","Updating Generated CLI Projects with copier update",{"path":2549,"title":2550},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fbuilding-cross-platform-release-binaries-in-ci","Building Cross-Platform Release Binaries in CI",{"path":2552,"title":2553},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fbundling-a-python-cli-with-pyinstaller","Bundling a Python CLI with PyInstaller",{"path":2555,"title":2556},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fcode-signing-and-notarizing-cli-binaries","Code Signing and Notarizing Python CLI Binaries",{"path":2558,"title":2559},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fhomebrew-and-scoop-packaging-for-python-clis","Homebrew and Scoop Packaging for Python CLIs",{"path":2561,"title":2562},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries","Distributing CLIs as Standalone Binaries",{"path":2564,"title":2565},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fnuitka-vs-pyinstaller-for-python-clis","Nuitka vs PyInstaller for Python CLI Binaries",{"path":2567,"title":2568},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Freducing-pyinstaller-binary-size","Reducing the Size of a PyInstaller CLI Binary",{"path":2570,"title":2571},"\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":2573,"title":2574},"\u002Fproject-setup-dependency-management","Project Setup & Dependency Management",{"path":2576,"title":2577},"\u002Fproject-setup-dependency-management\u002Flinting-and-type-checking-cli-code\u002Fconfiguring-ruff-for-a-cli-project","Configuring Ruff for a Python CLI Project",{"path":2579,"title":2580},"\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":2582,"title":2583},"\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":2585,"title":2586},"\u002Fproject-setup-dependency-management\u002Flinting-and-type-checking-cli-code","Linting and Type-Checking Python CLI Code",{"path":2588,"title":2589},"\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":2591,"title":2592},"\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":2594,"title":2595},"\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":2597,"title":2598},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fautomating-changelogs-with-conventional-commits","Automating Changelogs with Conventional Commits",{"path":2600,"title":2601},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fbumping-versions-consistently-across-a-cli-project","Bumping Versions Consistently Across a Python CLI Project",{"path":2603,"title":2604},"\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":2606,"title":2607},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fexposing-version-info-and-build-metadata","Exposing Version Info and Build Metadata",{"path":2609,"title":2610},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs","Managing CLI Versioning & Changelogs",{"path":2612,"title":2613},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fsemantic-versioning-policy-for-cli-tools","A Semantic Versioning Policy for CLI Tools",{"path":2615,"title":2616},"\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":2618,"title":2619},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fbuilding-wheels-and-sdists-for-python-clis","Building Wheels and sdists for Python CLIs",{"path":2621,"title":2622},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fbundling-data-files-with-importlib-resources","Bundling Data Files with importlib.resources in CLIs",{"path":2624,"title":2625},"\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":2627,"title":2628},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution","Packaging Python CLIs for Distribution",{"path":2630,"title":2631},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Finstalling-and-distributing-clis-with-pipx","Installing and Distributing CLIs with pipx",{"path":2633,"title":2634},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Foptional-dependencies-and-extras-for-clis","Optional Dependencies and Extras for Python CLIs",{"path":2636,"title":2637},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fpublishing-a-python-cli-to-pypi","Publishing a Python CLI to PyPI",{"path":2639,"title":2640},"\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":2642,"title":2643},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fdynamic-versioning-with-poetry-plugins","Dynamic Versioning for a Poetry CLI from Git Tags",{"path":2645,"title":2646},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development","Poetry Workflows for CLI Development",{"path":2648,"title":2649},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fmanaging-poetry-lock-files-for-cli-tools","Managing Poetry Lock Files for CLI Tools",{"path":2651,"title":2652},"\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":2654,"title":2655},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpoetry-dependency-groups-for-cli-tooling","Poetry Dependency Groups for CLI Tooling",{"path":2657,"title":2658},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpoetry-entry-points-and-scripts-for-clis","Poetry Entry Points and Scripts for CLIs",{"path":2660,"title":2661},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpublishing-a-cli-with-poetry","Building and Publishing a Python CLI with Poetry",{"path":2663,"title":2664},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects","Pre-commit Hooks for CLI Projects",{"path":2666,"title":2667},"\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":2669,"title":2670},"\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":2672,"title":2673},"\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":2675,"title":2676},"\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":2678,"title":2679},"\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":2681,"title":2682},"\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":2684,"title":2685},"\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":2687,"title":2688},"\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":2690,"title":2691},"\u002Fproject-setup-dependency-management\u002Fsecuring-a-python-cli-supply-chain","Securing the Supply Chain of a Python CLI",{"path":2693,"title":2694},"\u002Fproject-setup-dependency-management\u002Fsecuring-a-python-cli-supply-chain\u002Fpinning-dependencies-with-hashes","Pinning a Python CLI’s Dependencies with Hashes",{"path":2696,"title":2697},"\u002Fproject-setup-dependency-management\u002Fsecuring-a-python-cli-supply-chain\u002Fpublishing-attestations-and-verifying-releases","Publishing Attestations and Verifying Python CLI Releases",{"path":2699,"title":2700},"\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":2702,"title":2703},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management","uv for Python CLI Dependency Management",{"path":2705,"title":2706},"\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":2708,"title":2709},"\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":2711,"title":2712},"\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":2714,"title":2715},"\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":2717,"title":2718},"\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":2720,"title":2721},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002Fuv-workspaces-for-multi-package-clis","uv Workspaces for Multi-Package Python CLIs",{"path":2723,"title":2724},"\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":2726,"title":2727},"\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":2729,"title":2730},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices","Python CLI Env Isolation Best Practices",{"path":2732,"title":2733},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fisolating-cli-tools-from-project-dependencies","Isolating CLI Tools from Your Project’s Dependencies",{"path":2735,"title":2736},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fmanaging-virtual-environments-for-cross-platform-clis","Managing Python CLI Virtual Environments",{"path":2738,"title":2739},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fpinning-the-python-version-for-a-cli","Pinning the Python Version for a CLI",{"path":2741,"title":2742},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fsupporting-multiple-python-versions-with-nox","Supporting Multiple Python Versions with nox",1790967540248]