[{"data":1,"prerenderedAt":2684},["ShallowReactive",2],{"page-\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargparse-subparsers-for-subcommands\u002F":3,"content-directory":2440},{"id":4,"title":5,"body":6,"date":2425,"description":2426,"difficulty":2427,"draft":2428,"extension":2429,"meta":2430,"navigation":145,"path":2431,"seo":2432,"stem":2433,"tags":2434,"updated":2425,"__hash__":2439},"content\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargparse-subparsers-for-subcommands\u002Findex.md","argparse Subparsers for Subcommands",{"type":7,"value":8,"toc":2412},"minimark",[9,38,43,98,102,111,396,472,478,509,513,530,534,869,896,912,916,943,946,1182,1206,1210,1220,1223,1493,1522,1543,1547,1554,1610,1678,1687,1813,1826,1830,1849,1887,1900,1999,2051,2070,2074,2090,2135,2141,2145,2156,2305,2316,2320,2377,2381,2408],[10,11,12,13,17,18,17,21,24,25,28,29,33,34,37],"p",{},"Once a stdlib CLI does more than one thing — ",[14,15,16],"code",{},"tool build",", ",[14,19,20],{},"tool deploy",[14,22,23],{},"tool clean"," — you\nwant git-style subcommands, each with its own arguments and help page. ",[14,26,27],{},"argparse"," handles\nthis with ",[30,31,32],"strong",{},"subparsers",": a special group where each entry is a full parser in its own right.\nThis guide builds a clean, dispatchable subcommand tree with no third-party dependencies, and\nshows the ",[14,35,36],{},"set_defaults(func=...)"," pattern that keeps the routing tidy.",[39,40,42],"h2",{"id":41},"tldr","TL;DR",[44,45,46,58,73,83,89],"ul",{},[47,48,49,50,53,54,57],"li",{},"Create the group with ",[14,51,52],{},"parser.add_subparsers(dest=\"command\", required=True)",", then add one\nparser per subcommand with ",[14,55,56],{},"sub.add_parser(\"name\")",".",[47,59,60,61,64,65,68,69,72],{},"Attach a handler to each subparser with ",[14,62,63],{},"set_defaults(func=handler)"," and dispatch with a\nsingle ",[14,66,67],{},"args.func(args)"," call — no ",[14,70,71],{},"if\u002Felif"," ladder.",[47,74,75,76,79,80,57],{},"Share options across subcommands with a ",[30,77,78],{},"parent parser"," passed via ",[14,81,82],{},"parents=[...]",[47,84,85,86,57],{},"Nest subcommands by giving a subparser its own ",[14,87,88],{},"add_subparsers()",[47,90,91,92,97],{},"This is the manual version of what ",[93,94,96],"a",{"href":95},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Fbuilding-a-cli-with-subcommands-in-click\u002F","Click groups","\ndo for free.",[39,99,101],{"id":100},"the-core-mechanism-add_subparsers","The core mechanism: add_subparsers",[10,103,104,106,107,110],{},[14,105,88],{}," turns one positional slot into a switch over named subparsers. Each\nsubparser is a normal ",[14,108,109],{},"ArgumentParser"," you configure independently.",[112,113,118],"pre",{"className":114,"code":115,"language":116,"meta":117,"style":117},"language-python shiki shiki-themes github-light github-dark","# tool.py\nimport argparse\n\ndef main() -> None:\n    parser = argparse.ArgumentParser(prog=\"tool\", description=\"Project tool.\")\n    subparsers = parser.add_subparsers(dest=\"command\", required=True)\n\n    build = subparsers.add_parser(\"build\", help=\"Build the project.\")\n    build.add_argument(\"--release\", action=\"store_true\", help=\"Optimized build.\")\n\n    deploy = subparsers.add_parser(\"deploy\", help=\"Deploy the project.\")\n    deploy.add_argument(\"target\", help=\"Environment to deploy to.\")\n\n    args = parser.parse_args()\n    print(args)\n\nif __name__ == \"__main__\":\n    main()\n","python","",[14,119,120,129,140,147,167,202,233,238,264,294,299,323,343,348,359,368,373,390],{"__ignoreMap":117},[121,122,125],"span",{"class":123,"line":124},"line",1,[121,126,128],{"class":127},"sJ8bj","# tool.py\n",[121,130,132,136],{"class":123,"line":131},2,[121,133,135],{"class":134},"szBVR","import",[121,137,139],{"class":138},"sVt8B"," argparse\n",[121,141,143],{"class":123,"line":142},3,[121,144,146],{"emptyLinePlaceholder":145},true,"\n",[121,148,150,153,157,160,164],{"class":123,"line":149},4,[121,151,152],{"class":134},"def",[121,154,156],{"class":155},"sScJk"," main",[121,158,159],{"class":138},"() -> ",[121,161,163],{"class":162},"sj4cs","None",[121,165,166],{"class":138},":\n",[121,168,170,173,176,179,183,185,189,191,194,196,199],{"class":123,"line":169},5,[121,171,172],{"class":138},"    parser ",[121,174,175],{"class":134},"=",[121,177,178],{"class":138}," argparse.ArgumentParser(",[121,180,182],{"class":181},"s4XuR","prog",[121,184,175],{"class":134},[121,186,188],{"class":187},"sZZnC","\"tool\"",[121,190,17],{"class":138},[121,192,193],{"class":181},"description",[121,195,175],{"class":134},[121,197,198],{"class":187},"\"Project tool.\"",[121,200,201],{"class":138},")\n",[121,203,205,208,210,213,216,218,221,223,226,228,231],{"class":123,"line":204},6,[121,206,207],{"class":138},"    subparsers ",[121,209,175],{"class":134},[121,211,212],{"class":138}," parser.add_subparsers(",[121,214,215],{"class":181},"dest",[121,217,175],{"class":134},[121,219,220],{"class":187},"\"command\"",[121,222,17],{"class":138},[121,224,225],{"class":181},"required",[121,227,175],{"class":134},[121,229,230],{"class":162},"True",[121,232,201],{"class":138},[121,234,236],{"class":123,"line":235},7,[121,237,146],{"emptyLinePlaceholder":145},[121,239,241,244,246,249,252,254,257,259,262],{"class":123,"line":240},8,[121,242,243],{"class":138},"    build ",[121,245,175],{"class":134},[121,247,248],{"class":138}," subparsers.add_parser(",[121,250,251],{"class":187},"\"build\"",[121,253,17],{"class":138},[121,255,256],{"class":181},"help",[121,258,175],{"class":134},[121,260,261],{"class":187},"\"Build the project.\"",[121,263,201],{"class":138},[121,265,267,270,273,275,278,280,283,285,287,289,292],{"class":123,"line":266},9,[121,268,269],{"class":138},"    build.add_argument(",[121,271,272],{"class":187},"\"--release\"",[121,274,17],{"class":138},[121,276,277],{"class":181},"action",[121,279,175],{"class":134},[121,281,282],{"class":187},"\"store_true\"",[121,284,17],{"class":138},[121,286,256],{"class":181},[121,288,175],{"class":134},[121,290,291],{"class":187},"\"Optimized build.\"",[121,293,201],{"class":138},[121,295,297],{"class":123,"line":296},10,[121,298,146],{"emptyLinePlaceholder":145},[121,300,302,305,307,309,312,314,316,318,321],{"class":123,"line":301},11,[121,303,304],{"class":138},"    deploy ",[121,306,175],{"class":134},[121,308,248],{"class":138},[121,310,311],{"class":187},"\"deploy\"",[121,313,17],{"class":138},[121,315,256],{"class":181},[121,317,175],{"class":134},[121,319,320],{"class":187},"\"Deploy the project.\"",[121,322,201],{"class":138},[121,324,326,329,332,334,336,338,341],{"class":123,"line":325},12,[121,327,328],{"class":138},"    deploy.add_argument(",[121,330,331],{"class":187},"\"target\"",[121,333,17],{"class":138},[121,335,256],{"class":181},[121,337,175],{"class":134},[121,339,340],{"class":187},"\"Environment to deploy to.\"",[121,342,201],{"class":138},[121,344,346],{"class":123,"line":345},13,[121,347,146],{"emptyLinePlaceholder":145},[121,349,351,354,356],{"class":123,"line":350},14,[121,352,353],{"class":138},"    args ",[121,355,175],{"class":134},[121,357,358],{"class":138}," parser.parse_args()\n",[121,360,362,365],{"class":123,"line":361},15,[121,363,364],{"class":162},"    print",[121,366,367],{"class":138},"(args)\n",[121,369,371],{"class":123,"line":370},16,[121,372,146],{"emptyLinePlaceholder":145},[121,374,376,379,382,385,388],{"class":123,"line":375},17,[121,377,378],{"class":134},"if",[121,380,381],{"class":162}," __name__",[121,383,384],{"class":134}," ==",[121,386,387],{"class":187}," \"__main__\"",[121,389,166],{"class":138},[121,391,393],{"class":123,"line":392},18,[121,394,395],{"class":138},"    main()\n",[112,397,401],{"className":398,"code":399,"language":400,"meta":117,"style":117},"language-bash shiki shiki-themes github-light github-dark","$ python tool.py build --release\nNamespace(command='build', release=True)\n\n$ python tool.py deploy prod\nNamespace(command='deploy', target='prod')\n","bash",[14,402,403,420,438,442,456],{"__ignoreMap":117},[121,404,405,408,411,414,417],{"class":123,"line":124},[121,406,407],{"class":155},"$",[121,409,410],{"class":187}," python",[121,412,413],{"class":187}," tool.py",[121,415,416],{"class":187}," build",[121,418,419],{"class":162}," --release\n",[121,421,422,425,427,430,433,436],{"class":123,"line":131},[121,423,424],{"class":155},"Namespace(command",[121,426,175],{"class":187},[121,428,429],{"class":155},"'build'",[121,431,432],{"class":155},",",[121,434,435],{"class":187}," release=True",[121,437,201],{"class":138},[121,439,440],{"class":123,"line":142},[121,441,146],{"emptyLinePlaceholder":145},[121,443,444,446,448,450,453],{"class":123,"line":149},[121,445,407],{"class":155},[121,447,410],{"class":187},[121,449,413],{"class":187},[121,451,452],{"class":187}," deploy",[121,454,455],{"class":187}," prod\n",[121,457,458,460,462,465,467,470],{"class":123,"line":169},[121,459,424],{"class":155},[121,461,175],{"class":187},[121,463,464],{"class":155},"'deploy'",[121,466,432],{"class":155},[121,468,469],{"class":187}," target='prod'",[121,471,201],{"class":138},[10,473,474,475,477],{},"Two arguments to ",[14,476,88],{}," matter most:",[44,479,480,492],{},[47,481,482,487,488,491],{},[30,483,484],{},[14,485,486],{},"dest=\"command\""," stores the chosen subcommand name in ",[14,489,490],{},"args.command",", so you know which\none ran.",[47,493,494,499,500,503,504,508],{},[30,495,496],{},[14,497,498],{},"required=True"," makes running ",[14,501,502],{},"tool"," with no subcommand an error instead of silently\ndoing nothing. On Python 3, subparsers are ",[505,506,507],"em",{},"optional"," by default — always set this\nexplicitly, or a bare invocation falls through to code that assumes a command was given.",[39,510,512],{"id":511},"dispatching-with-set_defaultsfunc","Dispatching with set_defaults(func=...)",[10,514,515,516,518,519,522,523,526,527,57],{},"Reading ",[14,517,490],{}," and branching with ",[14,520,521],{},"if command == \"build\": ... elif ..."," works but\nrots as commands multiply. The idiomatic argparse pattern is to attach the handler function\nto each subparser with ",[14,524,525],{},"set_defaults",", then call whatever landed in ",[14,528,529],{},"args.func",[531,532],"inline-diagram",{"name":533},"argparse-subparser-dispatch",[112,535,537],{"className":114,"code":536,"language":116,"meta":117,"style":117},"# tool.py\nimport argparse\n\ndef cmd_build(args: argparse.Namespace) -> int:\n    print(f\"Building (release={args.release})\")\n    return 0\n\ndef cmd_deploy(args: argparse.Namespace) -> int:\n    print(f\"Deploying to {args.target}\")\n    return 0\n\ndef main() -> int:\n    parser = argparse.ArgumentParser(prog=\"tool\", description=\"Project tool.\")\n    subparsers = parser.add_subparsers(dest=\"command\", required=True)\n\n    build = subparsers.add_parser(\"build\", help=\"Build the project.\")\n    build.add_argument(\"--release\", action=\"store_true\")\n    build.set_defaults(func=cmd_build)\n\n    deploy = subparsers.add_parser(\"deploy\", help=\"Deploy the project.\")\n    deploy.add_argument(\"target\")\n    deploy.set_defaults(func=cmd_deploy)\n\n    args = parser.parse_args()\n    return args.func(args)          # dispatch: no if\u002Felif needed\n\nif __name__ == \"__main__\":\n    raise SystemExit(main())\n",[14,538,539,543,549,553,568,595,603,607,620,643,649,653,665,689,713,717,737,753,766,771,792,801,814,819,828,839,844,857],{"__ignoreMap":117},[121,540,541],{"class":123,"line":124},[121,542,128],{"class":127},[121,544,545,547],{"class":123,"line":131},[121,546,135],{"class":134},[121,548,139],{"class":138},[121,550,551],{"class":123,"line":142},[121,552,146],{"emptyLinePlaceholder":145},[121,554,555,557,560,563,566],{"class":123,"line":149},[121,556,152],{"class":134},[121,558,559],{"class":155}," cmd_build",[121,561,562],{"class":138},"(args: argparse.Namespace) -> ",[121,564,565],{"class":162},"int",[121,567,166],{"class":138},[121,569,570,572,575,578,581,584,587,590,593],{"class":123,"line":169},[121,571,364],{"class":162},[121,573,574],{"class":138},"(",[121,576,577],{"class":134},"f",[121,579,580],{"class":187},"\"Building (release=",[121,582,583],{"class":162},"{",[121,585,586],{"class":138},"args.release",[121,588,589],{"class":162},"}",[121,591,592],{"class":187},")\"",[121,594,201],{"class":138},[121,596,597,600],{"class":123,"line":204},[121,598,599],{"class":134},"    return",[121,601,602],{"class":162}," 0\n",[121,604,605],{"class":123,"line":235},[121,606,146],{"emptyLinePlaceholder":145},[121,608,609,611,614,616,618],{"class":123,"line":240},[121,610,152],{"class":134},[121,612,613],{"class":155}," cmd_deploy",[121,615,562],{"class":138},[121,617,565],{"class":162},[121,619,166],{"class":138},[121,621,622,624,626,628,631,633,636,638,641],{"class":123,"line":266},[121,623,364],{"class":162},[121,625,574],{"class":138},[121,627,577],{"class":134},[121,629,630],{"class":187},"\"Deploying to ",[121,632,583],{"class":162},[121,634,635],{"class":138},"args.target",[121,637,589],{"class":162},[121,639,640],{"class":187},"\"",[121,642,201],{"class":138},[121,644,645,647],{"class":123,"line":296},[121,646,599],{"class":134},[121,648,602],{"class":162},[121,650,651],{"class":123,"line":301},[121,652,146],{"emptyLinePlaceholder":145},[121,654,655,657,659,661,663],{"class":123,"line":325},[121,656,152],{"class":134},[121,658,156],{"class":155},[121,660,159],{"class":138},[121,662,565],{"class":162},[121,664,166],{"class":138},[121,666,667,669,671,673,675,677,679,681,683,685,687],{"class":123,"line":345},[121,668,172],{"class":138},[121,670,175],{"class":134},[121,672,178],{"class":138},[121,674,182],{"class":181},[121,676,175],{"class":134},[121,678,188],{"class":187},[121,680,17],{"class":138},[121,682,193],{"class":181},[121,684,175],{"class":134},[121,686,198],{"class":187},[121,688,201],{"class":138},[121,690,691,693,695,697,699,701,703,705,707,709,711],{"class":123,"line":350},[121,692,207],{"class":138},[121,694,175],{"class":134},[121,696,212],{"class":138},[121,698,215],{"class":181},[121,700,175],{"class":134},[121,702,220],{"class":187},[121,704,17],{"class":138},[121,706,225],{"class":181},[121,708,175],{"class":134},[121,710,230],{"class":162},[121,712,201],{"class":138},[121,714,715],{"class":123,"line":361},[121,716,146],{"emptyLinePlaceholder":145},[121,718,719,721,723,725,727,729,731,733,735],{"class":123,"line":370},[121,720,243],{"class":138},[121,722,175],{"class":134},[121,724,248],{"class":138},[121,726,251],{"class":187},[121,728,17],{"class":138},[121,730,256],{"class":181},[121,732,175],{"class":134},[121,734,261],{"class":187},[121,736,201],{"class":138},[121,738,739,741,743,745,747,749,751],{"class":123,"line":375},[121,740,269],{"class":138},[121,742,272],{"class":187},[121,744,17],{"class":138},[121,746,277],{"class":181},[121,748,175],{"class":134},[121,750,282],{"class":187},[121,752,201],{"class":138},[121,754,755,758,761,763],{"class":123,"line":392},[121,756,757],{"class":138},"    build.set_defaults(",[121,759,760],{"class":181},"func",[121,762,175],{"class":134},[121,764,765],{"class":138},"cmd_build)\n",[121,767,769],{"class":123,"line":768},19,[121,770,146],{"emptyLinePlaceholder":145},[121,772,774,776,778,780,782,784,786,788,790],{"class":123,"line":773},20,[121,775,304],{"class":138},[121,777,175],{"class":134},[121,779,248],{"class":138},[121,781,311],{"class":187},[121,783,17],{"class":138},[121,785,256],{"class":181},[121,787,175],{"class":134},[121,789,320],{"class":187},[121,791,201],{"class":138},[121,793,795,797,799],{"class":123,"line":794},21,[121,796,328],{"class":138},[121,798,331],{"class":187},[121,800,201],{"class":138},[121,802,804,807,809,811],{"class":123,"line":803},22,[121,805,806],{"class":138},"    deploy.set_defaults(",[121,808,760],{"class":181},[121,810,175],{"class":134},[121,812,813],{"class":138},"cmd_deploy)\n",[121,815,817],{"class":123,"line":816},23,[121,818,146],{"emptyLinePlaceholder":145},[121,820,822,824,826],{"class":123,"line":821},24,[121,823,353],{"class":138},[121,825,175],{"class":134},[121,827,358],{"class":138},[121,829,831,833,836],{"class":123,"line":830},25,[121,832,599],{"class":134},[121,834,835],{"class":138}," args.func(args)          ",[121,837,838],{"class":127},"# dispatch: no if\u002Felif needed\n",[121,840,842],{"class":123,"line":841},26,[121,843,146],{"emptyLinePlaceholder":145},[121,845,847,849,851,853,855],{"class":123,"line":846},27,[121,848,378],{"class":134},[121,850,381],{"class":162},[121,852,384],{"class":134},[121,854,387],{"class":187},[121,856,166],{"class":138},[121,858,860,863,866],{"class":123,"line":859},28,[121,861,862],{"class":134},"    raise",[121,864,865],{"class":162}," SystemExit",[121,867,868],{"class":138},"(main())\n",[10,870,871,874,875,877,878,880,881,883,884,886,887,890,891,895],{},[14,872,873],{},"set_defaults(func=cmd_build)"," injects ",[14,876,760],{}," into the namespace only when that subparser is\nselected, so ",[14,879,529],{}," is always the right handler. Adding a fifth or fiftieth command\nnever touches the dispatch line — you write a handler and one ",[14,882,525],{},". Returning an\n",[14,885,565],{}," from each handler and passing it to ",[14,888,889],{},"SystemExit"," gives you real\n",[93,892,894],{"href":893},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fchoosing-exit-codes-for-cli-tools\u002F","exit codes","\nfor callers and CI.",[10,897,898,899,901,902,904,905,908,909,57],{},"Because ",[14,900,498],{}," guarantees a subcommand, ",[14,903,529],{}," always exists. If you ever set\n",[14,906,907],{},"required=False"," on purpose, guard with ",[14,910,911],{},"if not hasattr(args, \"func\"): parser.print_help()",[39,913,915],{"id":914},"sharing-options-with-a-parent-parser","Sharing options with a parent parser",[10,917,918,919,17,922,925,926,929,930,933,934,936,937,940,941,57],{},"Several subcommands often need the same flags — ",[14,920,921],{},"--verbose",[14,923,924],{},"--config",". Declaring them on\neach subparser is duplication; declaring them on the ",[505,927,928],{},"top-level"," parser puts them before the\nsubcommand (",[14,931,932],{},"tool --verbose build","), which is not always what you want. The clean fix is a\n",[30,935,78],{},": a parser marked ",[14,938,939],{},"add_help=False"," whose arguments are inherited via\n",[14,942,82],{},[531,944],{"name":945},"argparse-parent-parser-sharing",[112,947,949],{"className":114,"code":948,"language":116,"meta":117,"style":117},"import argparse\n\n# Shared options live once, in a parent that adds no help of its own.\ncommon = argparse.ArgumentParser(add_help=False)\ncommon.add_argument(\"-v\", \"--verbose\", action=\"store_true\", help=\"Verbose output.\")\ncommon.add_argument(\"--config\", default=\"config.toml\", help=\"Config file path.\")\n\nparser = argparse.ArgumentParser(prog=\"tool\")\nsubparsers = parser.add_subparsers(dest=\"command\", required=True)\n\nbuild = subparsers.add_parser(\"build\", parents=[common], help=\"Build the project.\")\nbuild.add_argument(\"--release\", action=\"store_true\")\n\ndeploy = subparsers.add_parser(\"deploy\", parents=[common], help=\"Deploy.\")\ndeploy.add_argument(\"target\")\n",[14,950,951,957,961,966,985,1017,1045,1049,1066,1091,1095,1124,1141,1145,1173],{"__ignoreMap":117},[121,952,953,955],{"class":123,"line":124},[121,954,135],{"class":134},[121,956,139],{"class":138},[121,958,959],{"class":123,"line":131},[121,960,146],{"emptyLinePlaceholder":145},[121,962,963],{"class":123,"line":142},[121,964,965],{"class":127},"# Shared options live once, in a parent that adds no help of its own.\n",[121,967,968,971,973,975,978,980,983],{"class":123,"line":149},[121,969,970],{"class":138},"common ",[121,972,175],{"class":134},[121,974,178],{"class":138},[121,976,977],{"class":181},"add_help",[121,979,175],{"class":134},[121,981,982],{"class":162},"False",[121,984,201],{"class":138},[121,986,987,990,993,995,998,1000,1002,1004,1006,1008,1010,1012,1015],{"class":123,"line":169},[121,988,989],{"class":138},"common.add_argument(",[121,991,992],{"class":187},"\"-v\"",[121,994,17],{"class":138},[121,996,997],{"class":187},"\"--verbose\"",[121,999,17],{"class":138},[121,1001,277],{"class":181},[121,1003,175],{"class":134},[121,1005,282],{"class":187},[121,1007,17],{"class":138},[121,1009,256],{"class":181},[121,1011,175],{"class":134},[121,1013,1014],{"class":187},"\"Verbose output.\"",[121,1016,201],{"class":138},[121,1018,1019,1021,1024,1026,1029,1031,1034,1036,1038,1040,1043],{"class":123,"line":204},[121,1020,989],{"class":138},[121,1022,1023],{"class":187},"\"--config\"",[121,1025,17],{"class":138},[121,1027,1028],{"class":181},"default",[121,1030,175],{"class":134},[121,1032,1033],{"class":187},"\"config.toml\"",[121,1035,17],{"class":138},[121,1037,256],{"class":181},[121,1039,175],{"class":134},[121,1041,1042],{"class":187},"\"Config file path.\"",[121,1044,201],{"class":138},[121,1046,1047],{"class":123,"line":235},[121,1048,146],{"emptyLinePlaceholder":145},[121,1050,1051,1054,1056,1058,1060,1062,1064],{"class":123,"line":240},[121,1052,1053],{"class":138},"parser ",[121,1055,175],{"class":134},[121,1057,178],{"class":138},[121,1059,182],{"class":181},[121,1061,175],{"class":134},[121,1063,188],{"class":187},[121,1065,201],{"class":138},[121,1067,1068,1071,1073,1075,1077,1079,1081,1083,1085,1087,1089],{"class":123,"line":266},[121,1069,1070],{"class":138},"subparsers ",[121,1072,175],{"class":134},[121,1074,212],{"class":138},[121,1076,215],{"class":181},[121,1078,175],{"class":134},[121,1080,220],{"class":187},[121,1082,17],{"class":138},[121,1084,225],{"class":181},[121,1086,175],{"class":134},[121,1088,230],{"class":162},[121,1090,201],{"class":138},[121,1092,1093],{"class":123,"line":296},[121,1094,146],{"emptyLinePlaceholder":145},[121,1096,1097,1100,1102,1104,1106,1108,1111,1113,1116,1118,1120,1122],{"class":123,"line":301},[121,1098,1099],{"class":138},"build ",[121,1101,175],{"class":134},[121,1103,248],{"class":138},[121,1105,251],{"class":187},[121,1107,17],{"class":138},[121,1109,1110],{"class":181},"parents",[121,1112,175],{"class":134},[121,1114,1115],{"class":138},"[common], ",[121,1117,256],{"class":181},[121,1119,175],{"class":134},[121,1121,261],{"class":187},[121,1123,201],{"class":138},[121,1125,1126,1129,1131,1133,1135,1137,1139],{"class":123,"line":325},[121,1127,1128],{"class":138},"build.add_argument(",[121,1130,272],{"class":187},[121,1132,17],{"class":138},[121,1134,277],{"class":181},[121,1136,175],{"class":134},[121,1138,282],{"class":187},[121,1140,201],{"class":138},[121,1142,1143],{"class":123,"line":345},[121,1144,146],{"emptyLinePlaceholder":145},[121,1146,1147,1150,1152,1154,1156,1158,1160,1162,1164,1166,1168,1171],{"class":123,"line":350},[121,1148,1149],{"class":138},"deploy ",[121,1151,175],{"class":134},[121,1153,248],{"class":138},[121,1155,311],{"class":187},[121,1157,17],{"class":138},[121,1159,1110],{"class":181},[121,1161,175],{"class":134},[121,1163,1115],{"class":138},[121,1165,256],{"class":181},[121,1167,175],{"class":134},[121,1169,1170],{"class":187},"\"Deploy.\"",[121,1172,201],{"class":138},[121,1174,1175,1178,1180],{"class":123,"line":361},[121,1176,1177],{"class":138},"deploy.add_argument(",[121,1179,331],{"class":187},[121,1181,201],{"class":138},[10,1183,1184,1185,1188,1189,1192,1193,1195,1196,1198,1199,1201,1202,1205],{},"Now both ",[14,1186,1187],{},"tool build --verbose"," and ",[14,1190,1191],{},"tool deploy prod --verbose --config prod.toml"," work, and\n",[14,1194,921],{},"\u002F",[14,1197,924],{}," are defined in exactly one place. ",[14,1200,939],{}," on the parent is\nessential — without it, both the parent and child try to add ",[14,1203,1204],{},"-h",", and argparse raises an\n\"conflicting option string\" error at startup.",[39,1207,1209],{"id":1208},"nested-subcommands","Nested subcommands",[10,1211,1212,1213,1216,1217,1219],{},"A subparser can host its own subparsers, giving you ",[14,1214,1215],{},"tool remote add ...",". Give the\nintermediate subparser a fresh ",[14,1218,88],{}," and attach children to it:",[531,1221],{"name":1222},"argparse-nested-subcommands-tree",[112,1224,1226],{"className":114,"code":1225,"language":116,"meta":117,"style":117},"import argparse\n\nparser = argparse.ArgumentParser(prog=\"tool\")\ntop = parser.add_subparsers(dest=\"command\", required=True)\n\nremote = top.add_parser(\"remote\", help=\"Manage remotes.\")\nremote_sub = remote.add_subparsers(dest=\"remote_command\", required=True)\n\nadd = remote_sub.add_parser(\"add\", help=\"Add a remote.\")\nadd.add_argument(\"url\")\nadd.set_defaults(func=lambda a: print(f\"Added {a.url}\"))\n\nrm = remote_sub.add_parser(\"remove\", help=\"Remove a remote.\")\nrm.add_argument(\"name\")\nrm.set_defaults(func=lambda a: print(f\"Removed {a.name}\"))\n\nargs = parser.parse_args()\nargs.func(args)\n",[14,1227,1228,1234,1238,1254,1279,1283,1307,1334,1338,1362,1372,1407,1411,1434,1444,1475,1479,1488],{"__ignoreMap":117},[121,1229,1230,1232],{"class":123,"line":124},[121,1231,135],{"class":134},[121,1233,139],{"class":138},[121,1235,1236],{"class":123,"line":131},[121,1237,146],{"emptyLinePlaceholder":145},[121,1239,1240,1242,1244,1246,1248,1250,1252],{"class":123,"line":142},[121,1241,1053],{"class":138},[121,1243,175],{"class":134},[121,1245,178],{"class":138},[121,1247,182],{"class":181},[121,1249,175],{"class":134},[121,1251,188],{"class":187},[121,1253,201],{"class":138},[121,1255,1256,1259,1261,1263,1265,1267,1269,1271,1273,1275,1277],{"class":123,"line":149},[121,1257,1258],{"class":138},"top ",[121,1260,175],{"class":134},[121,1262,212],{"class":138},[121,1264,215],{"class":181},[121,1266,175],{"class":134},[121,1268,220],{"class":187},[121,1270,17],{"class":138},[121,1272,225],{"class":181},[121,1274,175],{"class":134},[121,1276,230],{"class":162},[121,1278,201],{"class":138},[121,1280,1281],{"class":123,"line":169},[121,1282,146],{"emptyLinePlaceholder":145},[121,1284,1285,1288,1290,1293,1296,1298,1300,1302,1305],{"class":123,"line":204},[121,1286,1287],{"class":138},"remote ",[121,1289,175],{"class":134},[121,1291,1292],{"class":138}," top.add_parser(",[121,1294,1295],{"class":187},"\"remote\"",[121,1297,17],{"class":138},[121,1299,256],{"class":181},[121,1301,175],{"class":134},[121,1303,1304],{"class":187},"\"Manage remotes.\"",[121,1306,201],{"class":138},[121,1308,1309,1312,1314,1317,1319,1321,1324,1326,1328,1330,1332],{"class":123,"line":235},[121,1310,1311],{"class":138},"remote_sub ",[121,1313,175],{"class":134},[121,1315,1316],{"class":138}," remote.add_subparsers(",[121,1318,215],{"class":181},[121,1320,175],{"class":134},[121,1322,1323],{"class":187},"\"remote_command\"",[121,1325,17],{"class":138},[121,1327,225],{"class":181},[121,1329,175],{"class":134},[121,1331,230],{"class":162},[121,1333,201],{"class":138},[121,1335,1336],{"class":123,"line":240},[121,1337,146],{"emptyLinePlaceholder":145},[121,1339,1340,1343,1345,1348,1351,1353,1355,1357,1360],{"class":123,"line":266},[121,1341,1342],{"class":138},"add ",[121,1344,175],{"class":134},[121,1346,1347],{"class":138}," remote_sub.add_parser(",[121,1349,1350],{"class":187},"\"add\"",[121,1352,17],{"class":138},[121,1354,256],{"class":181},[121,1356,175],{"class":134},[121,1358,1359],{"class":187},"\"Add a remote.\"",[121,1361,201],{"class":138},[121,1363,1364,1367,1370],{"class":123,"line":296},[121,1365,1366],{"class":138},"add.add_argument(",[121,1368,1369],{"class":187},"\"url\"",[121,1371,201],{"class":138},[121,1373,1374,1377,1379,1382,1385,1388,1390,1392,1395,1397,1400,1402,1404],{"class":123,"line":301},[121,1375,1376],{"class":138},"add.set_defaults(",[121,1378,760],{"class":181},[121,1380,1381],{"class":134},"=lambda",[121,1383,1384],{"class":138}," a: ",[121,1386,1387],{"class":162},"print",[121,1389,574],{"class":138},[121,1391,577],{"class":134},[121,1393,1394],{"class":187},"\"Added ",[121,1396,583],{"class":162},[121,1398,1399],{"class":138},"a.url",[121,1401,589],{"class":162},[121,1403,640],{"class":187},[121,1405,1406],{"class":138},"))\n",[121,1408,1409],{"class":123,"line":325},[121,1410,146],{"emptyLinePlaceholder":145},[121,1412,1413,1416,1418,1420,1423,1425,1427,1429,1432],{"class":123,"line":345},[121,1414,1415],{"class":138},"rm ",[121,1417,175],{"class":134},[121,1419,1347],{"class":138},[121,1421,1422],{"class":187},"\"remove\"",[121,1424,17],{"class":138},[121,1426,256],{"class":181},[121,1428,175],{"class":134},[121,1430,1431],{"class":187},"\"Remove a remote.\"",[121,1433,201],{"class":138},[121,1435,1436,1439,1442],{"class":123,"line":350},[121,1437,1438],{"class":138},"rm.add_argument(",[121,1440,1441],{"class":187},"\"name\"",[121,1443,201],{"class":138},[121,1445,1446,1449,1451,1453,1455,1457,1459,1461,1464,1466,1469,1471,1473],{"class":123,"line":361},[121,1447,1448],{"class":138},"rm.set_defaults(",[121,1450,760],{"class":181},[121,1452,1381],{"class":134},[121,1454,1384],{"class":138},[121,1456,1387],{"class":162},[121,1458,574],{"class":138},[121,1460,577],{"class":134},[121,1462,1463],{"class":187},"\"Removed ",[121,1465,583],{"class":162},[121,1467,1468],{"class":138},"a.name",[121,1470,589],{"class":162},[121,1472,640],{"class":187},[121,1474,1406],{"class":138},[121,1476,1477],{"class":123,"line":370},[121,1478,146],{"emptyLinePlaceholder":145},[121,1480,1481,1484,1486],{"class":123,"line":375},[121,1482,1483],{"class":138},"args ",[121,1485,175],{"class":134},[121,1487,358],{"class":138},[121,1489,1490],{"class":123,"line":392},[121,1491,1492],{"class":138},"args.func(args)\n",[112,1494,1496],{"className":398,"code":1495,"language":400,"meta":117,"style":117},"$ python tool.py remote add https:\u002F\u002Fexample.com\u002Freg\nAdded https:\u002F\u002Fexample.com\u002Freg\n",[14,1497,1498,1515],{"__ignoreMap":117},[121,1499,1500,1502,1504,1506,1509,1512],{"class":123,"line":124},[121,1501,407],{"class":155},[121,1503,410],{"class":187},[121,1505,413],{"class":187},[121,1507,1508],{"class":187}," remote",[121,1510,1511],{"class":187}," add",[121,1513,1514],{"class":187}," https:\u002F\u002Fexample.com\u002Freg\n",[121,1516,1517,1520],{"class":123,"line":131},[121,1518,1519],{"class":155},"Added",[121,1521,1514],{"class":187},[10,1523,1524,1525,1528,1529,1531,1532,1535,1536,1539,1540,1542],{},"Use a ",[505,1526,1527],{},"distinct"," ",[14,1530,215],{}," at each level (",[14,1533,1534],{},"command",", then ",[14,1537,1538],{},"remote_command",") so the two choices\ndon't overwrite each other in the namespace. The ",[14,1541,36],{}," dispatch keeps\nworking no matter how deep you nest, because the innermost selected subparser wins. In\npractice two levels is the usability ceiling — beyond that, help output and muscle memory\nboth suffer.",[39,1544,1546],{"id":1545},"keeping-help-output-clean","Keeping help output clean",[10,1548,1549,1550,1553],{},"Subparsers can make ",[14,1551,1552],{},"--help"," noisy. A few habits keep it readable:",[44,1555,1556,1578,1600],{},[47,1557,1558,1528,1568,1570,1571,1573,1574,1577],{},[30,1559,1560,1561,1564,1565,57],{},"Give every subparser a ",[14,1562,1563],{},"help="," and a ",[14,1566,1567],{},"description=",[14,1569,1563],{}," is the one-liner shown in\nthe parent's command list; ",[14,1572,1567],{}," is the paragraph shown on ",[14,1575,1576],{},"tool build --help",".\nThey are different strings and both matter.",[47,1579,1580,1587,1588,1591,1592,1595,1596,1599],{},[30,1581,1582,1583,1586],{},"Set ",[14,1584,1585],{},"metavar"," on the subparsers object"," to control the placeholder in usage text:\n",[14,1589,1590],{},"parser.add_subparsers(dest=\"command\", metavar=\"COMMAND\")"," prints ",[14,1593,1594],{},"COMMAND"," instead of the\nauto-generated ",[14,1597,1598],{},"{build,deploy,clean}"," brace list, which gets unwieldy past a few commands.",[47,1601,1602,1605,1606,1609],{},[30,1603,1604],{},"Group related flags"," with ",[14,1607,1608],{},"parser.add_argument_group(\"output options\")"," inside a busy\nsubparser so its help splits into labeled sections.",[112,1611,1613],{"className":114,"code":1612,"language":116,"meta":117,"style":117},"subparsers = parser.add_subparsers(\n    dest=\"command\", required=True, metavar=\"COMMAND\",\n    title=\"commands\", help=\"Run 'tool COMMAND --help' for details.\",\n)\n",[14,1614,1615,1624,1653,1674],{"__ignoreMap":117},[121,1616,1617,1619,1621],{"class":123,"line":124},[121,1618,1070],{"class":138},[121,1620,175],{"class":134},[121,1622,1623],{"class":138}," parser.add_subparsers(\n",[121,1625,1626,1629,1631,1633,1635,1637,1639,1641,1643,1645,1647,1650],{"class":123,"line":131},[121,1627,1628],{"class":181},"    dest",[121,1630,175],{"class":134},[121,1632,220],{"class":187},[121,1634,17],{"class":138},[121,1636,225],{"class":181},[121,1638,175],{"class":134},[121,1640,230],{"class":162},[121,1642,17],{"class":138},[121,1644,1585],{"class":181},[121,1646,175],{"class":134},[121,1648,1649],{"class":187},"\"COMMAND\"",[121,1651,1652],{"class":138},",\n",[121,1654,1655,1658,1660,1663,1665,1667,1669,1672],{"class":123,"line":142},[121,1656,1657],{"class":181},"    title",[121,1659,175],{"class":134},[121,1661,1662],{"class":187},"\"commands\"",[121,1664,17],{"class":138},[121,1666,256],{"class":181},[121,1668,175],{"class":134},[121,1670,1671],{"class":187},"\"Run 'tool COMMAND --help' for details.\"",[121,1673,1652],{"class":138},[121,1675,1676],{"class":123,"line":149},[121,1677,201],{"class":138},[10,1679,1680,1681,1188,1684,1686],{},"With ",[14,1682,1683],{},"title",[14,1685,1585],{}," set, the top-level help reads like a real command index instead of\na brace-list dump:",[112,1688,1690],{"className":398,"code":1689,"language":400,"meta":117,"style":117},"$ python tool.py --help\nusage: tool [-h] COMMAND ...\n\nProject tool.\n\noptions:\n  -h, --help  show this help message and exit\n\ncommands:\n  COMMAND     Run 'tool COMMAND --help' for details.\n    build     Build the project.\n    deploy    Deploy the project.\n",[14,1691,1692,1703,1714,1718,1726,1730,1735,1761,1765,1770,1787,1801],{"__ignoreMap":117},[121,1693,1694,1696,1698,1700],{"class":123,"line":124},[121,1695,407],{"class":155},[121,1697,410],{"class":187},[121,1699,413],{"class":187},[121,1701,1702],{"class":162}," --help\n",[121,1704,1705,1708,1711],{"class":123,"line":131},[121,1706,1707],{"class":155},"usage:",[121,1709,1710],{"class":187}," tool",[121,1712,1713],{"class":138}," [-h] COMMAND ...\n",[121,1715,1716],{"class":123,"line":142},[121,1717,146],{"emptyLinePlaceholder":145},[121,1719,1720,1723],{"class":123,"line":149},[121,1721,1722],{"class":155},"Project",[121,1724,1725],{"class":187}," tool.\n",[121,1727,1728],{"class":123,"line":169},[121,1729,146],{"emptyLinePlaceholder":145},[121,1731,1732],{"class":123,"line":204},[121,1733,1734],{"class":155},"options:\n",[121,1736,1737,1740,1743,1746,1749,1752,1755,1758],{"class":123,"line":235},[121,1738,1739],{"class":155},"  -h,",[121,1741,1742],{"class":162}," --help",[121,1744,1745],{"class":187},"  show",[121,1747,1748],{"class":187}," this",[121,1750,1751],{"class":187}," help",[121,1753,1754],{"class":187}," message",[121,1756,1757],{"class":187}," and",[121,1759,1760],{"class":187}," exit\n",[121,1762,1763],{"class":123,"line":240},[121,1764,146],{"emptyLinePlaceholder":145},[121,1766,1767],{"class":123,"line":266},[121,1768,1769],{"class":155},"commands:\n",[121,1771,1772,1775,1778,1781,1784],{"class":123,"line":296},[121,1773,1774],{"class":155},"  COMMAND",[121,1776,1777],{"class":187},"     Run",[121,1779,1780],{"class":187}," 'tool COMMAND --help'",[121,1782,1783],{"class":187}," for",[121,1785,1786],{"class":187}," details.\n",[121,1788,1789,1792,1795,1798],{"class":123,"line":301},[121,1790,1791],{"class":155},"    build",[121,1793,1794],{"class":187},"     Build",[121,1796,1797],{"class":187}," the",[121,1799,1800],{"class":187}," project.\n",[121,1802,1803,1806,1809,1811],{"class":123,"line":325},[121,1804,1805],{"class":155},"    deploy",[121,1807,1808],{"class":187},"    Deploy",[121,1810,1797],{"class":187},[121,1812,1800],{"class":187},[10,1814,1815,1816,1818,1819,1821,1822,1825],{},"Each subcommand's own ",[14,1817,1204],{}," then shows its ",[14,1820,193],{}," and arguments, so users discover the\ntree one level at a time — the same progressive-disclosure shape a\n",[93,1823,1824],{"href":95},"Click group","\ngives you automatically.",[39,1827,1829],{"id":1828},"aliases-and-mutually-exclusive-options","Aliases and mutually exclusive options",[10,1831,1832,1833,1836,1837,1840,1841,1844,1845,1848],{},"Two features round out a realistic subcommand tree. ",[14,1834,1835],{},"add_parser"," accepts ",[14,1838,1839],{},"aliases",", so\n",[14,1842,1843],{},"tool rm"," can be a shorthand for ",[14,1846,1847],{},"tool remove"," without a second handler:",[112,1850,1852],{"className":114,"code":1851,"language":116,"meta":117,"style":117},"rm = remote_sub.add_parser(\"remove\", aliases=[\"rm\"], help=\"Remove a remote.\")\n",[14,1853,1854],{"__ignoreMap":117},[121,1855,1856,1858,1860,1862,1864,1866,1868,1870,1873,1876,1879,1881,1883,1885],{"class":123,"line":124},[121,1857,1415],{"class":138},[121,1859,175],{"class":134},[121,1861,1347],{"class":138},[121,1863,1422],{"class":187},[121,1865,17],{"class":138},[121,1867,1839],{"class":181},[121,1869,175],{"class":134},[121,1871,1872],{"class":138},"[",[121,1874,1875],{"class":187},"\"rm\"",[121,1877,1878],{"class":138},"], ",[121,1880,256],{"class":181},[121,1882,175],{"class":134},[121,1884,1431],{"class":187},[121,1886,201],{"class":138},[10,1888,1889,1890,1188,1893,1896,1897,1899],{},"Both ",[14,1891,1892],{},"tool remote remove foo",[14,1894,1895],{},"tool remote rm foo"," now route to the same subparser and\nthe same ",[14,1898,760],{},". Within a subcommand you often want \"exactly one of these flags\"; a mutually\nexclusive group enforces that at parse time:",[112,1901,1903],{"className":114,"code":1902,"language":116,"meta":117,"style":117},"push = subparsers.add_parser(\"push\", help=\"Push changes.\")\nmode = push.add_mutually_exclusive_group(required=True)\nmode.add_argument(\"--force\", action=\"store_true\", help=\"Overwrite remote history.\")\nmode.add_argument(\"--safe\", action=\"store_true\", help=\"Refuse on conflict.\")\n",[14,1904,1905,1928,1946,1973],{"__ignoreMap":117},[121,1906,1907,1910,1912,1914,1917,1919,1921,1923,1926],{"class":123,"line":124},[121,1908,1909],{"class":138},"push ",[121,1911,175],{"class":134},[121,1913,248],{"class":138},[121,1915,1916],{"class":187},"\"push\"",[121,1918,17],{"class":138},[121,1920,256],{"class":181},[121,1922,175],{"class":134},[121,1924,1925],{"class":187},"\"Push changes.\"",[121,1927,201],{"class":138},[121,1929,1930,1933,1935,1938,1940,1942,1944],{"class":123,"line":131},[121,1931,1932],{"class":138},"mode ",[121,1934,175],{"class":134},[121,1936,1937],{"class":138}," push.add_mutually_exclusive_group(",[121,1939,225],{"class":181},[121,1941,175],{"class":134},[121,1943,230],{"class":162},[121,1945,201],{"class":138},[121,1947,1948,1951,1954,1956,1958,1960,1962,1964,1966,1968,1971],{"class":123,"line":142},[121,1949,1950],{"class":138},"mode.add_argument(",[121,1952,1953],{"class":187},"\"--force\"",[121,1955,17],{"class":138},[121,1957,277],{"class":181},[121,1959,175],{"class":134},[121,1961,282],{"class":187},[121,1963,17],{"class":138},[121,1965,256],{"class":181},[121,1967,175],{"class":134},[121,1969,1970],{"class":187},"\"Overwrite remote history.\"",[121,1972,201],{"class":138},[121,1974,1975,1977,1980,1982,1984,1986,1988,1990,1992,1994,1997],{"class":123,"line":149},[121,1976,1950],{"class":138},[121,1978,1979],{"class":187},"\"--safe\"",[121,1981,17],{"class":138},[121,1983,277],{"class":181},[121,1985,175],{"class":134},[121,1987,282],{"class":187},[121,1989,17],{"class":138},[121,1991,256],{"class":181},[121,1993,175],{"class":134},[121,1995,1996],{"class":187},"\"Refuse on conflict.\"",[121,1998,201],{"class":138},[112,2000,2002],{"className":398,"code":2001,"language":400,"meta":117,"style":117},"$ python tool.py push --force --safe\ntool push: error: argument --safe: not allowed with argument --force\n",[14,2003,2004,2021],{"__ignoreMap":117},[121,2005,2006,2008,2010,2012,2015,2018],{"class":123,"line":124},[121,2007,407],{"class":155},[121,2009,410],{"class":187},[121,2011,413],{"class":187},[121,2013,2014],{"class":187}," push",[121,2016,2017],{"class":162}," --force",[121,2019,2020],{"class":162}," --safe\n",[121,2022,2023,2025,2028,2031,2034,2037,2040,2043,2046,2048],{"class":123,"line":131},[121,2024,502],{"class":155},[121,2026,2027],{"class":187}," push:",[121,2029,2030],{"class":187}," error:",[121,2032,2033],{"class":187}," argument",[121,2035,2036],{"class":162}," --safe:",[121,2038,2039],{"class":187}," not",[121,2041,2042],{"class":187}," allowed",[121,2044,2045],{"class":187}," with",[121,2047,2033],{"class":187},[121,2049,2050],{"class":162}," --force\n",[10,2052,2053,2055,2056,1188,2059,2062,2063,2065,2066,2069],{},[14,2054,27],{}," rejects the illegal combination itself, with a clear message, so your handler\nnever has to validate that ",[14,2057,2058],{},"--force",[14,2060,2061],{},"--safe"," weren't both passed. ",[14,2064,498],{}," on the\ngroup makes supplying ",[505,2067,2068],{},"neither"," an error too.",[39,2071,2073],{"id":2072},"a-default-subcommand","A default subcommand",[10,2075,2076,2077,2079,2080,2083,2084,2086,2087,2089],{},"Sometimes running the bare tool should behave like one of the subcommands — ",[14,2078,502],{}," alone\nacting as ",[14,2081,2082],{},"tool status",". Keep ",[14,2085,907],{},", then fall back when no ",[14,2088,760],{}," was set:",[112,2091,2093],{"className":114,"code":2092,"language":116,"meta":117,"style":117},"args = parser.parse_args()\nhandler = getattr(args, \"func\", cmd_status)   # default to status\nraise SystemExit(handler(args))\n",[14,2094,2095,2103,2125],{"__ignoreMap":117},[121,2096,2097,2099,2101],{"class":123,"line":124},[121,2098,1483],{"class":138},[121,2100,175],{"class":134},[121,2102,358],{"class":138},[121,2104,2105,2108,2110,2113,2116,2119,2122],{"class":123,"line":131},[121,2106,2107],{"class":138},"handler ",[121,2109,175],{"class":134},[121,2111,2112],{"class":162}," getattr",[121,2114,2115],{"class":138},"(args, ",[121,2117,2118],{"class":187},"\"func\"",[121,2120,2121],{"class":138},", cmd_status)   ",[121,2123,2124],{"class":127},"# default to status\n",[121,2126,2127,2130,2132],{"class":123,"line":142},[121,2128,2129],{"class":134},"raise",[121,2131,865],{"class":162},[121,2133,2134],{"class":138},"(handler(args))\n",[10,2136,2137,2138,2140],{},"This is the one case where you deliberately leave the subparsers group optional. Everywhere\nelse, ",[14,2139,498],{}," and an explicit error is the safer default — a silent no-op confuses\nusers who fat-fingered a command name.",[39,2142,2144],{"id":2143},"testing-subcommand-dispatch","Testing subcommand dispatch",[10,2146,2147,2148,2151,2152,2155],{},"Because parsing is separate from the handlers, you can test both cheaply. Feed an explicit\nargument list to ",[14,2149,2150],{},"parse_args"," — it only reads ",[14,2153,2154],{},"sys.argv"," when you pass nothing — and assert\nthat the right handler and values landed in the namespace.",[112,2157,2159],{"className":114,"code":2158,"language":116,"meta":117,"style":117},"# test_tool.py\nfrom tool import build_parser        # factor the parser into a function that returns it\n\ndef test_build_routes_to_handler() -> None:\n    args = build_parser().parse_args([\"build\", \"--release\"])\n    assert args.command == \"build\"\n    assert args.release is True\n    assert args.func.__name__ == \"cmd_build\"\n\ndef test_missing_subcommand_errors() -> None:\n    import pytest\n    with pytest.raises(SystemExit):        # required=True exits on no subcommand\n        build_parser().parse_args([])\n",[14,2160,2161,2166,2182,2186,2199,2217,2231,2244,2259,2263,2276,2284,2300],{"__ignoreMap":117},[121,2162,2163],{"class":123,"line":124},[121,2164,2165],{"class":127},"# test_tool.py\n",[121,2167,2168,2171,2174,2176,2179],{"class":123,"line":131},[121,2169,2170],{"class":134},"from",[121,2172,2173],{"class":138}," tool ",[121,2175,135],{"class":134},[121,2177,2178],{"class":138}," build_parser        ",[121,2180,2181],{"class":127},"# factor the parser into a function that returns it\n",[121,2183,2184],{"class":123,"line":142},[121,2185,146],{"emptyLinePlaceholder":145},[121,2187,2188,2190,2193,2195,2197],{"class":123,"line":149},[121,2189,152],{"class":134},[121,2191,2192],{"class":155}," test_build_routes_to_handler",[121,2194,159],{"class":138},[121,2196,163],{"class":162},[121,2198,166],{"class":138},[121,2200,2201,2203,2205,2208,2210,2212,2214],{"class":123,"line":169},[121,2202,353],{"class":138},[121,2204,175],{"class":134},[121,2206,2207],{"class":138}," build_parser().parse_args([",[121,2209,251],{"class":187},[121,2211,17],{"class":138},[121,2213,272],{"class":187},[121,2215,2216],{"class":138},"])\n",[121,2218,2219,2222,2225,2228],{"class":123,"line":204},[121,2220,2221],{"class":134},"    assert",[121,2223,2224],{"class":138}," args.command ",[121,2226,2227],{"class":134},"==",[121,2229,2230],{"class":187}," \"build\"\n",[121,2232,2233,2235,2238,2241],{"class":123,"line":235},[121,2234,2221],{"class":134},[121,2236,2237],{"class":138}," args.release ",[121,2239,2240],{"class":134},"is",[121,2242,2243],{"class":162}," True\n",[121,2245,2246,2248,2251,2254,2256],{"class":123,"line":240},[121,2247,2221],{"class":134},[121,2249,2250],{"class":138}," args.func.",[121,2252,2253],{"class":162},"__name__",[121,2255,384],{"class":134},[121,2257,2258],{"class":187}," \"cmd_build\"\n",[121,2260,2261],{"class":123,"line":266},[121,2262,146],{"emptyLinePlaceholder":145},[121,2264,2265,2267,2270,2272,2274],{"class":123,"line":296},[121,2266,152],{"class":134},[121,2268,2269],{"class":155}," test_missing_subcommand_errors",[121,2271,159],{"class":138},[121,2273,163],{"class":162},[121,2275,166],{"class":138},[121,2277,2278,2281],{"class":123,"line":301},[121,2279,2280],{"class":134},"    import",[121,2282,2283],{"class":138}," pytest\n",[121,2285,2286,2289,2292,2294,2297],{"class":123,"line":325},[121,2287,2288],{"class":134},"    with",[121,2290,2291],{"class":138}," pytest.raises(",[121,2293,889],{"class":162},[121,2295,2296],{"class":138},"):        ",[121,2298,2299],{"class":127},"# required=True exits on no subcommand\n",[121,2301,2302],{"class":123,"line":345},[121,2303,2304],{"class":138},"        build_parser().parse_args([])\n",[10,2306,2307,2308,2311,2312,2315],{},"Refactoring parser construction into a ",[14,2309,2310],{},"build_parser() -> argparse.ArgumentParser"," function\n(instead of building it inline in ",[14,2313,2314],{},"main()",") is what makes this testable — do it early.",[39,2317,2319],{"id":2318},"production-notes","Production notes",[44,2321,2322,2330,2347,2358],{},[47,2323,2324,2329],{},[30,2325,2326,2328],{},[14,2327,498],{}," is not the default."," The single most common argparse subcommand bug is a\nbare invocation silently doing nothing because the subparsers group was optional. Always set\nit, and test the empty-args case.",[47,2331,2332,2338,2339,2341,2342,17,2344,2346],{},[30,2333,2334,2335,2337],{},"Distinct ",[14,2336,215],{}," per level."," Reusing ",[14,2340,486],{}," for nested subparsers overwrites the\nouter value. Name them ",[14,2343,1534],{},[14,2345,1538],{},", and so on.",[47,2348,2349,2354,2355,2357],{},[30,2350,2351,2353],{},[14,2352,939],{}," on parent parsers",", always — otherwise the inherited ",[14,2356,1204],{}," collides.",[47,2359,2360,2363,2364,2367,2368,2371,2372,2376],{},[30,2361,2362],{},"This scales to a point."," Manual subparsers are fine for a dozen commands. When you find\nyourself wanting shared context objects, lazy loading for startup time, or completion, the\nhand-rolled version stops paying off. The Click equivalent —\n",[93,2365,2366],{"href":95},"building a CLI with subcommands in Click","\n— gives you groups, ",[14,2369,2370],{},"ctx.obj",", and completion out of the box, and\n",[93,2373,2375],{"href":2374},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fmigrating-from-argparse-to-typer\u002F","migrating from argparse to Typer","\nmaps subparsers directly onto Typer commands.",[39,2378,2380],{"id":2379},"related","Related",[44,2382,2383,2390,2396,2401],{},[47,2384,2385,2386],{},"Up: ",[93,2387,2389],{"href":2388},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002F","Command-Line Parsing with argparse",[47,2391,2392,2393],{},"Sideways: ",[93,2394,2395],{"href":95},"Building a CLI with subcommands in Click",[47,2397,2392,2398],{},[93,2399,2400],{"href":2374},"Migrating from argparse to Typer",[47,2402,2403,2404],{},"Related: ",[93,2405,2407],{"href":2406},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002F","Structuring multi-command Python CLIs",[2409,2410,2411],"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 .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 .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}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":117,"searchDepth":131,"depth":131,"links":2413},[2414,2415,2416,2417,2418,2419,2420,2421,2422,2423,2424],{"id":41,"depth":131,"text":42},{"id":100,"depth":131,"text":101},{"id":511,"depth":131,"text":512},{"id":914,"depth":131,"text":915},{"id":1208,"depth":131,"text":1209},{"id":1545,"depth":131,"text":1546},{"id":1828,"depth":131,"text":1829},{"id":2072,"depth":131,"text":2073},{"id":2143,"depth":131,"text":2144},{"id":2318,"depth":131,"text":2319},{"id":2379,"depth":131,"text":2380},"2026-07-05","Add git-style subcommands to an argparse CLI with add_subparsers, dispatch with set_defaults, share common options, and keep help output clean.","intermediate",false,"md",{},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargparse-subparsers-for-subcommands",{"title":5,"description":2426},"modern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargparse-subparsers-for-subcommands\u002Findex",[27,2435,2436,2437,2438],"cli","subcommands","structure","click","LqsKBzw9I27WfL4yQJ1k5Fvq2YX0Qan7UH0FK60Om_8",[2441,2444,2447,2450,2453,2456,2459,2462,2465,2468,2471,2474,2477,2480,2483,2486,2489,2492,2495,2498,2501,2504,2507,2510,2513,2516,2519,2522,2525,2528,2531,2534,2536,2539,2542,2545,2548,2549,2552,2554,2556,2559,2562,2565,2568,2571,2574,2577,2580,2583,2586,2589,2592,2595,2598,2600,2603,2606,2609,2612,2615,2618,2621,2624,2627,2630,2633,2636,2639,2642,2645,2648,2651,2654,2657,2660,2663,2666,2669,2672,2675,2678,2681],{"path":2442,"title":2443},"\u002Fabout","About Python CLI Toolcraft",{"path":2445,"title":2446},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies","Advanced Argument Validation Strategies",{"path":2448,"title":2449},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fparsing-nested-json-arguments-in-python-clis","Parsing Nested JSON Args in Python CLIs",{"path":2451,"title":2452},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fvalidating-file-and-directory-paths-in-clis","Validating File and Directory Paths in CLIs",{"path":2454,"title":2455},"\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":2457,"title":2458},"\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":2460,"title":2461},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation","CLI Help Output and Documentation",{"path":2463,"title":2464},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fversioning-and-deprecating-cli-flags","Versioning and Deprecating CLI Flags",{"path":2466,"title":2467},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fwriting-help-text-users-actually-read","Writing Help Text Users Actually Read",{"path":2469,"title":2470},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fchoosing-exit-codes-for-cli-tools","Choosing Exit Codes for CLI Tools",{"path":2472,"title":2473},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Ffriendly-error-messages-and-tracebacks","Friendly Error Messages and Tracebacks",{"path":2475,"title":2476},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fhandling-keyboard-interrupt-cleanly","Handling Keyboard Interrupt Cleanly",{"path":2478,"title":2479},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes","Error Handling and Exit Codes for CLIs",{"path":2481,"title":2482},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Fconfig-precedence-flags-env-files-defaults","Config Precedence: Flags, Env, Files, Defaults",{"path":2484,"title":2485},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars","Handling Config Files and Env Vars in CLIs",{"path":2487,"title":2488},"\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":2490,"title":2491},"\u002Fadvanced-input-parsing-user-experience","Advanced Input Parsing for Python CLIs",{"path":2493,"title":2494},"\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":2496,"title":2497},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich","Interactive Terminal UI with Rich",{"path":2499,"title":2500},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Frendering-tables-and-json-with-rich","Rendering Tables and JSON with Rich",{"path":2502,"title":2503},"\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":2505,"title":2506},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis","Shell Completion for Python CLIs",{"path":2508,"title":2509},"\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":2511,"title":2512},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fadding-verbose-and-quiet-logging-flags","Adding Verbose and Quiet Logging Flags",{"path":2514,"title":2515},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps","Structured Logging for CLI Apps",{"path":2517,"title":2518},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fstructured-json-logging-in-python-clis","Structured JSON Logging in Python CLIs",{"path":2520,"title":2521},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Fdetecting-tty-and-adapting-output","Detecting a TTY and Adapting Output",{"path":2523,"title":2524},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Femitting-json-output-for-scripting","Emitting JSON Output for Scripting",{"path":2526,"title":2527},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Fhandling-broken-pipe-and-sigpipe","Handling Broken Pipe and SIGPIPE",{"path":2529,"title":2530},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes","Working with stdin, stdout and Pipes",{"path":2532,"title":2533},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Freading-piped-input-in-python-clis","Reading Piped Input in Python CLIs",{"path":1195,"title":2535},"Python CLI Toolcraft",{"path":2537,"title":2538},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading","CLI Startup Performance and Lazy Loading",{"path":2540,"title":2541},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Flazy-loading-subcommands-for-faster-startup","Lazy Loading Subcommands for Faster Startup",{"path":2543,"title":2544},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Fprofiling-python-cli-startup-time","Profiling Python CLI Startup Time",{"path":2546,"title":2547},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Freducing-cli-dependency-weight","Reducing CLI Dependency Weight",{"path":2431,"title":5},{"path":2550,"title":2551},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargparse-vs-click-vs-typer-comparison","argparse vs Click vs Typer Compared",{"path":2553,"title":2389},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse",{"path":2555,"title":2400},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fmigrating-from-argparse-to-typer",{"path":2557,"title":2558},"\u002Fmodern-python-cli-frameworks-architecture","Python CLI Frameworks and Architecture",{"path":2560,"title":2561},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis","Plugin Architectures for Extensible CLIs",{"path":2563,"title":2564},"\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":2566,"title":2567},"\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":2569,"title":2570},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fdependency-injection-patterns-for-cli-commands","Dependency Injection Patterns for CLI Commands",{"path":2572,"title":2573},"\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":2575,"title":2576},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis","Structuring Multi-Command Python CLIs",{"path":2578,"title":2579},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fsharing-state-with-click-context-objects","Sharing State with Click Context Objects",{"path":2581,"title":2582},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications","Testing Python CLI Applications",{"path":2584,"title":2585},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fmeasuring-cli-test-coverage","Measuring CLI Test Coverage",{"path":2587,"title":2588},"\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":2590,"title":2591},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fsnapshot-testing-cli-output","Snapshot Testing CLI Output",{"path":2593,"title":2594},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftesting-click-commands-with-clirunner","Testing Click Commands with CliRunner",{"path":2596,"title":2597},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftesting-interactive-prompts-and-stdin","Testing Interactive Prompts and stdin",{"path":2599,"title":2395},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Fbuilding-a-cli-with-subcommands-in-click",{"path":2601,"title":2602},"\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":2604,"title":2605},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each","Typer vs Click: When to Use Each",{"path":2607,"title":2608},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Ftyper-callback-functions-explained","Typer callback functions explained",{"path":2610,"title":2611},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fcopier-vs-cookiecutter-for-cli-templates","Copier vs Cookiecutter for CLI Templates",{"path":2613,"title":2614},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter","CLI Project Scaffolding with Cookiecutter",{"path":2616,"title":2617},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fbuilding-cross-platform-release-binaries-in-ci","Building Cross-Platform Release Binaries in CI",{"path":2619,"title":2620},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fbundling-a-python-cli-with-pyinstaller","Bundling a Python CLI with PyInstaller",{"path":2622,"title":2623},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fhomebrew-and-scoop-packaging-for-python-clis","Homebrew and Scoop Packaging for Python CLIs",{"path":2625,"title":2626},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries","Distributing CLIs as Standalone Binaries",{"path":2628,"title":2629},"\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":2631,"title":2632},"\u002Fproject-setup-dependency-management","Project Setup & Dependency Management",{"path":2634,"title":2635},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fautomating-changelogs-with-conventional-commits","Automating Changelogs with Conventional Commits",{"path":2637,"title":2638},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fexposing-version-info-and-build-metadata","Exposing Version Info and Build Metadata",{"path":2640,"title":2641},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs","Managing CLI Versioning & Changelogs",{"path":2643,"title":2644},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fbuilding-wheels-and-sdists-for-python-clis","Building Wheels and sdists for Python CLIs",{"path":2646,"title":2647},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution","Packaging Python CLIs for Distribution",{"path":2649,"title":2650},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Finstalling-and-distributing-clis-with-pipx","Installing and Distributing CLIs with pipx",{"path":2652,"title":2653},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fpublishing-a-python-cli-to-pypi","Publishing a Python CLI to PyPI",{"path":2655,"title":2656},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development","Poetry Workflows for CLI Development",{"path":2658,"title":2659},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpoetry-entry-points-and-scripts-for-clis","Poetry Entry Points and Scripts for CLIs",{"path":2661,"title":2662},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects","Pre-commit Hooks for CLI Projects",{"path":2664,"title":2665},"\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":2667,"title":2668},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management","uv for Python CLI Dependency Management",{"path":2670,"title":2671},"\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":2673,"title":2674},"\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":2676,"title":2677},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices","Python CLI Env Isolation Best Practices",{"path":2679,"title":2680},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fmanaging-virtual-environments-for-cross-platform-clis","Managing Python CLI Virtual Environments",{"path":2682,"title":2683},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fpinning-the-python-version-for-a-cli","Pinning the Python Version for a CLI",1785614690031]