[{"data":1,"prerenderedAt":2882},["ShallowReactive",2],{"page-\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fdocumenting-environment-variables-in-help\u002F":3,"content-directory":2038},{"id":4,"title":5,"body":6,"date":2022,"description":2023,"difficulty":2024,"draft":2025,"extension":2026,"meta":2027,"navigation":146,"path":2028,"seo":2029,"stem":2030,"tags":2031,"updated":2022,"__hash__":2037},"content\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fdocumenting-environment-variables-in-help\u002Findex.md","Documenting Environment Variables in CLI Help",{"type":7,"value":8,"toc":1998},"minimark",[9,36,41,52,56,60,97,101,112,359,367,395,399,501,520,523,527,546,550,553,1058,1065,1141,1151,1154,1158,1177,1353,1359,1378,1382,1415,1419,1430,1844,1851,1855,1864,1868,1872,1878,1882,1910,1914,1921,1929,1939,1943,1958,1962,1994],[10,11,12,13,17,18,21,22,25,26,29,30,35],"p",{},"Environment variables are half of a CLI's interface and usually the undocumented half. Options appear in ",[14,15,16],"code",{},"--help"," automatically; environment variables appear wherever someone remembered to mention them — a README section that is two releases out of date, a comment in the code, a Slack message. Yet they are exactly what CI pipelines, containers and cron jobs use to configure tools, because nobody wants a token on a command line. Users then discover ",[14,19,20],{},"MYTOOL_REGION"," by reading the source, or never discover ",[14,23,24],{},"MYTOOL_DEBUG"," at all. This guide makes environment variables first-class in help: showing the variable next to each option it can set, naming them predictably, documenting the variables that are not tied to options in an \"Environment\" section, adding an ",[14,27,28],{},"env"," command that shows what is currently set, and testing that no variable goes undocumented. It belongs to the ",[31,32,34],"a",{"href":33},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002F","help output and documentation topic",".",[37,38,40],"h2",{"id":39},"prerequisites","Prerequisites",[42,43,44],"ul",{},[45,46,47,48,35],"li",{},"A Click or Typer CLI that reads configuration from the environment, ideally following ",[31,49,51],{"href":50},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Fconfig-precedence-flags-env-files-defaults\u002F","config precedence: flags, env, files, defaults",[37,53,55],{"id":54},"two-kinds-of-environment-variable","Two kinds of environment variable",[57,58],"inline-diagram",{"name":59},"envdoc-kinds",[10,61,62,66,67,69,70,73,74,77,78,81,82,84,85,88,89,92,93,96],{},[63,64,65],"strong",{},"Option-backed variables"," supply a value for a specific option — ",[14,68,20],{}," for ",[14,71,72],{},"--region",". The framework reads them, validates them like command-line values, and can display them in help. ",[63,75,76],{},"Free-standing variables"," affect behaviour without a matching option: ",[14,79,80],{},"MYTOOL_CONFIG"," (where the config file lives), ",[14,83,24],{},", ",[14,86,87],{},"MYTOOL_NO_UPDATE_CHECK",", and the conventions your tool honours, such as ",[14,90,91],{},"NO_COLOR"," and ",[14,94,95],{},"DO_NOT_TRACK",". The framework knows nothing about these, so you must document them yourself.",[37,98,100],{"id":99},"the-recipe-option-backed-variables","The recipe: option-backed variables",[102,103,105,106,92,109],"h3",{"id":104},"click-show_envvar-and-auto_envvar_prefix","Click: ",[14,107,108],{},"show_envvar",[14,110,111],{},"auto_envvar_prefix",[113,114,119],"pre",{"className":115,"code":116,"language":117,"meta":118,"style":118},"language-python shiki shiki-themes github-light github-dark","# src\u002Fmytool\u002Fcli.py\nimport click\n\n\n@click.group(context_settings={\"auto_envvar_prefix\": \"MYTOOL\", \"show_default\": True})\ndef cli() -> None:\n    \"\"\"Deploy services.\"\"\"\n\n\n@cli.command()\n@click.option(\"--region\", default=\"eu-west-1\", show_envvar=True, help=\"Region.\")\n@click.option(\"--token\", envvar=\"MYTOOL_TOKEN\", show_envvar=True, help=\"API token.\")\ndef deploy(region: str, token: str | None) -> None:\n    \"\"\"Deploy the current build.\"\"\"\n","python","",[14,120,121,130,141,148,153,197,215,221,226,231,240,282,321,353],{"__ignoreMap":118},[122,123,126],"span",{"class":124,"line":125},"line",1,[122,127,129],{"class":128},"sJ8bj","# src\u002Fmytool\u002Fcli.py\n",[122,131,133,137],{"class":124,"line":132},2,[122,134,136],{"class":135},"szBVR","import",[122,138,140],{"class":139},"sVt8B"," click\n",[122,142,144],{"class":124,"line":143},3,[122,145,147],{"emptyLinePlaceholder":146},true,"\n",[122,149,151],{"class":124,"line":150},4,[122,152,147],{"emptyLinePlaceholder":146},[122,154,156,160,163,167,170,173,177,180,183,185,188,190,194],{"class":124,"line":155},5,[122,157,159],{"class":158},"sScJk","@click.group",[122,161,162],{"class":139},"(",[122,164,166],{"class":165},"s4XuR","context_settings",[122,168,169],{"class":135},"=",[122,171,172],{"class":139},"{",[122,174,176],{"class":175},"sZZnC","\"auto_envvar_prefix\"",[122,178,179],{"class":139},": ",[122,181,182],{"class":175},"\"MYTOOL\"",[122,184,84],{"class":139},[122,186,187],{"class":175},"\"show_default\"",[122,189,179],{"class":139},[122,191,193],{"class":192},"sj4cs","True",[122,195,196],{"class":139},"})\n",[122,198,200,203,206,209,212],{"class":124,"line":199},6,[122,201,202],{"class":135},"def",[122,204,205],{"class":158}," cli",[122,207,208],{"class":139},"() -> ",[122,210,211],{"class":192},"None",[122,213,214],{"class":139},":\n",[122,216,218],{"class":124,"line":217},7,[122,219,220],{"class":175},"    \"\"\"Deploy services.\"\"\"\n",[122,222,224],{"class":124,"line":223},8,[122,225,147],{"emptyLinePlaceholder":146},[122,227,229],{"class":124,"line":228},9,[122,230,147],{"emptyLinePlaceholder":146},[122,232,234,237],{"class":124,"line":233},10,[122,235,236],{"class":158},"@cli.command",[122,238,239],{"class":139},"()\n",[122,241,243,246,248,251,253,256,258,261,263,265,267,269,271,274,276,279],{"class":124,"line":242},11,[122,244,245],{"class":158},"@click.option",[122,247,162],{"class":139},[122,249,250],{"class":175},"\"--region\"",[122,252,84],{"class":139},[122,254,255],{"class":165},"default",[122,257,169],{"class":135},[122,259,260],{"class":175},"\"eu-west-1\"",[122,262,84],{"class":139},[122,264,108],{"class":165},[122,266,169],{"class":135},[122,268,193],{"class":192},[122,270,84],{"class":139},[122,272,273],{"class":165},"help",[122,275,169],{"class":135},[122,277,278],{"class":175},"\"Region.\"",[122,280,281],{"class":139},")\n",[122,283,285,287,289,292,294,297,299,302,304,306,308,310,312,314,316,319],{"class":124,"line":284},12,[122,286,245],{"class":158},[122,288,162],{"class":139},[122,290,291],{"class":175},"\"--token\"",[122,293,84],{"class":139},[122,295,296],{"class":165},"envvar",[122,298,169],{"class":135},[122,300,301],{"class":175},"\"MYTOOL_TOKEN\"",[122,303,84],{"class":139},[122,305,108],{"class":165},[122,307,169],{"class":135},[122,309,193],{"class":192},[122,311,84],{"class":139},[122,313,273],{"class":165},[122,315,169],{"class":135},[122,317,318],{"class":175},"\"API token.\"",[122,320,281],{"class":139},[122,322,324,326,329,332,335,338,340,343,346,349,351],{"class":124,"line":323},13,[122,325,202],{"class":135},[122,327,328],{"class":158}," deploy",[122,330,331],{"class":139},"(region: ",[122,333,334],{"class":192},"str",[122,336,337],{"class":139},", token: ",[122,339,334],{"class":192},[122,341,342],{"class":135}," |",[122,344,345],{"class":192}," None",[122,347,348],{"class":139},") -> ",[122,350,211],{"class":192},[122,352,214],{"class":139},[122,354,356],{"class":124,"line":355},14,[122,357,358],{"class":175},"    \"\"\"Deploy the current build.\"\"\"\n",[113,360,365],{"className":361,"code":363,"language":364,"meta":118},[362],"language-text","Options:\n  --region TEXT  Region.  [env var: MYTOOL_DEPLOY_REGION; default: eu-west-1]\n  --token TEXT   API token.  [env var: MYTOOL_TOKEN]\n","text",[14,366,363],{"__ignoreMap":118},[10,368,369,371,372,375,376,379,380,383,384,387,388,390,391,35],{},[14,370,111],{}," gives every option an environment variable named ",[14,373,374],{},"PREFIX_COMMAND_OPTION"," automatically — ",[14,377,378],{},"MYTOOL_DEPLOY_REGION"," here — and ",[14,381,382],{},"show_envvar=True"," prints it in help. Explicit ",[14,385,386],{},"envvar="," names override the automatic scheme for variables that should be shared across commands, such as a token. There is no context setting that turns ",[14,389,108],{}," on for every option at once, so set it per option — or define shared option decorators with it already enabled, as in ",[31,392,394],{"href":393},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fsharing-common-options-across-commands\u002F","sharing common options across commands",[102,396,398],{"id":397},"typer-shown-by-default","Typer: shown by default",[113,400,402],{"className":115,"code":401,"language":117,"meta":118,"style":118},"@app.command()\ndef deploy(\n    region: Annotated[str, typer.Option(envvar=\"MYTOOL_REGION\", help=\"Region.\")] = \"eu-west-1\",\n    token: Annotated[str, typer.Option(envvar=\"MYTOOL_TOKEN\", help=\"API token.\")] = \"\",\n) -> None:\n    ...\n",[14,403,404,411,420,456,488,496],{"__ignoreMap":118},[122,405,406,409],{"class":124,"line":125},[122,407,408],{"class":158},"@app.command",[122,410,239],{"class":139},[122,412,413,415,417],{"class":124,"line":132},[122,414,202],{"class":135},[122,416,328],{"class":158},[122,418,419],{"class":139},"(\n",[122,421,422,425,427,430,432,434,437,439,441,443,445,448,450,453],{"class":124,"line":143},[122,423,424],{"class":139},"    region: Annotated[",[122,426,334],{"class":192},[122,428,429],{"class":139},", typer.Option(",[122,431,296],{"class":165},[122,433,169],{"class":135},[122,435,436],{"class":175},"\"MYTOOL_REGION\"",[122,438,84],{"class":139},[122,440,273],{"class":165},[122,442,169],{"class":135},[122,444,278],{"class":175},[122,446,447],{"class":139},")] ",[122,449,169],{"class":135},[122,451,452],{"class":175}," \"eu-west-1\"",[122,454,455],{"class":139},",\n",[122,457,458,461,463,465,467,469,471,473,475,477,479,481,483,486],{"class":124,"line":150},[122,459,460],{"class":139},"    token: Annotated[",[122,462,334],{"class":192},[122,464,429],{"class":139},[122,466,296],{"class":165},[122,468,169],{"class":135},[122,470,301],{"class":175},[122,472,84],{"class":139},[122,474,273],{"class":165},[122,476,169],{"class":135},[122,478,318],{"class":175},[122,480,447],{"class":139},[122,482,169],{"class":135},[122,484,485],{"class":175}," \"\"",[122,487,455],{"class":139},[122,489,490,492,494],{"class":124,"line":155},[122,491,348],{"class":139},[122,493,211],{"class":192},[122,495,214],{"class":139},[122,497,498],{"class":124,"line":199},[122,499,500],{"class":192},"    ...\n",[10,502,503,504,507,508,510,511,514,515,519],{},"Typer displays ",[14,505,506],{},"[env var: MYTOOL_REGION]"," for any option with ",[14,509,386],{}," unless you pass ",[14,512,513],{},"show_envvar=False",". Showing the ",[516,517,518],"em",{},"name"," of a secret's variable is fine and helpful — it is the value that must never appear, and help never shows values from the environment.",[57,521],{"name":522},"envdoc-terminal",[102,524,526],{"id":525},"naming","Naming",[10,528,529,530,69,532,534,535,537,538,541,542,545],{},"Pick one scheme and keep it: an upper-case tool prefix, then a name that matches the option (",[14,531,20],{},[14,533,72],{},"). Click's automatic ",[14,536,374],{}," scheme is predictable but produces long names for nested commands; explicit names are shorter but must be maintained. Whichever you choose, never reuse a generic name like ",[14,539,540],{},"REGION"," or ",[14,543,544],{},"TOKEN"," — other tools in the same environment will collide with it.",[37,547,549],{"id":548},"the-recipe-free-standing-variables","The recipe: free-standing variables",[10,551,552],{},"For variables the framework does not know about, keep a single registry in code and generate documentation from it:",[113,554,556],{"className":115,"code":555,"language":117,"meta":118,"style":118},"# src\u002Fmytool\u002Fenvvars.py\nfrom __future__ import annotations\n\nimport os\nfrom dataclasses import dataclass\n\n\n@dataclass(frozen=True)\nclass EnvVar:\n    name: str\n    description: str\n    secret: bool = False\n\n\nENV_VARS = [\n    EnvVar(\"MYTOOL_CONFIG\", \"Path to the configuration file.\"),\n    EnvVar(\"MYTOOL_TOKEN\", \"API token (prefer `mytool login`).\", secret=True),\n    EnvVar(\"MYTOOL_DEBUG\", \"Set to 1 for full tracebacks and debug logs.\"),\n    EnvVar(\"MYTOOL_NO_UPDATE_CHECK\", \"Set to 1 to disable update notices.\"),\n    EnvVar(\"NO_COLOR\", \"Disable coloured output (see no-color.org).\"),\n]\n\n\ndef environment_epilog() -> str:\n    width = max(len(v.name) for v in ENV_VARS)\n    lines = [\"Environment:\"] + [f\"  {v.name:\u003C{width}}  {v.description}\" for v in ENV_VARS]\n    return \"\\n\".join(lines)\n\n\ndef current_values() -> list[tuple[str, str]]:\n    rows = []\n    for v in ENV_VARS:\n        value = os.environ.get(v.name)\n        if value is None:\n            shown = \"(not set)\"\n        elif v.secret:\n            shown = \"(set, hidden)\"\n        else:\n            shown = value\n        rows.append((v.name, shown))\n    return rows\n",[14,557,558,563,577,581,588,600,604,608,624,634,642,649,663,667,671,682,699,722,737,752,767,773,778,783,797,830,897,914,919,924,944,955,969,980,996,1007,1016,1026,1034,1044,1050],{"__ignoreMap":118},[122,559,560],{"class":124,"line":125},[122,561,562],{"class":128},"# src\u002Fmytool\u002Fenvvars.py\n",[122,564,565,568,571,574],{"class":124,"line":132},[122,566,567],{"class":135},"from",[122,569,570],{"class":192}," __future__",[122,572,573],{"class":135}," import",[122,575,576],{"class":139}," annotations\n",[122,578,579],{"class":124,"line":143},[122,580,147],{"emptyLinePlaceholder":146},[122,582,583,585],{"class":124,"line":150},[122,584,136],{"class":135},[122,586,587],{"class":139}," os\n",[122,589,590,592,595,597],{"class":124,"line":155},[122,591,567],{"class":135},[122,593,594],{"class":139}," dataclasses ",[122,596,136],{"class":135},[122,598,599],{"class":139}," dataclass\n",[122,601,602],{"class":124,"line":199},[122,603,147],{"emptyLinePlaceholder":146},[122,605,606],{"class":124,"line":217},[122,607,147],{"emptyLinePlaceholder":146},[122,609,610,613,615,618,620,622],{"class":124,"line":223},[122,611,612],{"class":158},"@dataclass",[122,614,162],{"class":139},[122,616,617],{"class":165},"frozen",[122,619,169],{"class":135},[122,621,193],{"class":192},[122,623,281],{"class":139},[122,625,626,629,632],{"class":124,"line":228},[122,627,628],{"class":135},"class",[122,630,631],{"class":158}," EnvVar",[122,633,214],{"class":139},[122,635,636,639],{"class":124,"line":233},[122,637,638],{"class":139},"    name: ",[122,640,641],{"class":192},"str\n",[122,643,644,647],{"class":124,"line":242},[122,645,646],{"class":139},"    description: ",[122,648,641],{"class":192},[122,650,651,654,657,660],{"class":124,"line":284},[122,652,653],{"class":139},"    secret: ",[122,655,656],{"class":192},"bool",[122,658,659],{"class":135}," =",[122,661,662],{"class":192}," False\n",[122,664,665],{"class":124,"line":323},[122,666,147],{"emptyLinePlaceholder":146},[122,668,669],{"class":124,"line":355},[122,670,147],{"emptyLinePlaceholder":146},[122,672,674,677,679],{"class":124,"line":673},15,[122,675,676],{"class":192},"ENV_VARS",[122,678,659],{"class":135},[122,680,681],{"class":139}," [\n",[122,683,685,688,691,693,696],{"class":124,"line":684},16,[122,686,687],{"class":139},"    EnvVar(",[122,689,690],{"class":175},"\"MYTOOL_CONFIG\"",[122,692,84],{"class":139},[122,694,695],{"class":175},"\"Path to the configuration file.\"",[122,697,698],{"class":139},"),\n",[122,700,702,704,706,708,711,713,716,718,720],{"class":124,"line":701},17,[122,703,687],{"class":139},[122,705,301],{"class":175},[122,707,84],{"class":139},[122,709,710],{"class":175},"\"API token (prefer `mytool login`).\"",[122,712,84],{"class":139},[122,714,715],{"class":165},"secret",[122,717,169],{"class":135},[122,719,193],{"class":192},[122,721,698],{"class":139},[122,723,725,727,730,732,735],{"class":124,"line":724},18,[122,726,687],{"class":139},[122,728,729],{"class":175},"\"MYTOOL_DEBUG\"",[122,731,84],{"class":139},[122,733,734],{"class":175},"\"Set to 1 for full tracebacks and debug logs.\"",[122,736,698],{"class":139},[122,738,740,742,745,747,750],{"class":124,"line":739},19,[122,741,687],{"class":139},[122,743,744],{"class":175},"\"MYTOOL_NO_UPDATE_CHECK\"",[122,746,84],{"class":139},[122,748,749],{"class":175},"\"Set to 1 to disable update notices.\"",[122,751,698],{"class":139},[122,753,755,757,760,762,765],{"class":124,"line":754},20,[122,756,687],{"class":139},[122,758,759],{"class":175},"\"NO_COLOR\"",[122,761,84],{"class":139},[122,763,764],{"class":175},"\"Disable coloured output (see no-color.org).\"",[122,766,698],{"class":139},[122,768,770],{"class":124,"line":769},21,[122,771,772],{"class":139},"]\n",[122,774,776],{"class":124,"line":775},22,[122,777,147],{"emptyLinePlaceholder":146},[122,779,781],{"class":124,"line":780},23,[122,782,147],{"emptyLinePlaceholder":146},[122,784,786,788,791,793,795],{"class":124,"line":785},24,[122,787,202],{"class":135},[122,789,790],{"class":158}," environment_epilog",[122,792,208],{"class":139},[122,794,334],{"class":192},[122,796,214],{"class":139},[122,798,800,803,805,808,810,813,816,819,822,825,828],{"class":124,"line":799},25,[122,801,802],{"class":139},"    width ",[122,804,169],{"class":135},[122,806,807],{"class":192}," max",[122,809,162],{"class":139},[122,811,812],{"class":192},"len",[122,814,815],{"class":139},"(v.name) ",[122,817,818],{"class":135},"for",[122,820,821],{"class":139}," v ",[122,823,824],{"class":135},"in",[122,826,827],{"class":192}," ENV_VARS",[122,829,281],{"class":139},[122,831,833,836,838,841,844,847,850,852,855,858,860,863,866,868,871,874,877,880,883,886,889,891,893,895],{"class":124,"line":832},26,[122,834,835],{"class":139},"    lines ",[122,837,169],{"class":135},[122,839,840],{"class":139}," [",[122,842,843],{"class":175},"\"Environment:\"",[122,845,846],{"class":139},"] ",[122,848,849],{"class":135},"+",[122,851,840],{"class":139},[122,853,854],{"class":135},"f",[122,856,857],{"class":175},"\"  ",[122,859,172],{"class":192},[122,861,862],{"class":139},"v.name",[122,864,865],{"class":135},":\u003C",[122,867,172],{"class":192},[122,869,870],{"class":139},"width",[122,872,873],{"class":192},"}}",[122,875,876],{"class":192},"  {",[122,878,879],{"class":139},"v.description",[122,881,882],{"class":192},"}",[122,884,885],{"class":175},"\"",[122,887,888],{"class":135}," for",[122,890,821],{"class":139},[122,892,824],{"class":135},[122,894,827],{"class":192},[122,896,772],{"class":139},[122,898,900,903,906,909,911],{"class":124,"line":899},27,[122,901,902],{"class":135},"    return",[122,904,905],{"class":175}," \"",[122,907,908],{"class":192},"\\n",[122,910,885],{"class":175},[122,912,913],{"class":139},".join(lines)\n",[122,915,917],{"class":124,"line":916},28,[122,918,147],{"emptyLinePlaceholder":146},[122,920,922],{"class":124,"line":921},29,[122,923,147],{"emptyLinePlaceholder":146},[122,925,927,929,932,935,937,939,941],{"class":124,"line":926},30,[122,928,202],{"class":135},[122,930,931],{"class":158}," current_values",[122,933,934],{"class":139},"() -> list[tuple[",[122,936,334],{"class":192},[122,938,84],{"class":139},[122,940,334],{"class":192},[122,942,943],{"class":139},"]]:\n",[122,945,947,950,952],{"class":124,"line":946},31,[122,948,949],{"class":139},"    rows ",[122,951,169],{"class":135},[122,953,954],{"class":139}," []\n",[122,956,958,961,963,965,967],{"class":124,"line":957},32,[122,959,960],{"class":135},"    for",[122,962,821],{"class":139},[122,964,824],{"class":135},[122,966,827],{"class":192},[122,968,214],{"class":139},[122,970,972,975,977],{"class":124,"line":971},33,[122,973,974],{"class":139},"        value ",[122,976,169],{"class":135},[122,978,979],{"class":139}," os.environ.get(v.name)\n",[122,981,983,986,989,992,994],{"class":124,"line":982},34,[122,984,985],{"class":135},"        if",[122,987,988],{"class":139}," value ",[122,990,991],{"class":135},"is",[122,993,345],{"class":192},[122,995,214],{"class":139},[122,997,999,1002,1004],{"class":124,"line":998},35,[122,1000,1001],{"class":139},"            shown ",[122,1003,169],{"class":135},[122,1005,1006],{"class":175}," \"(not set)\"\n",[122,1008,1010,1013],{"class":124,"line":1009},36,[122,1011,1012],{"class":135},"        elif",[122,1014,1015],{"class":139}," v.secret:\n",[122,1017,1019,1021,1023],{"class":124,"line":1018},37,[122,1020,1001],{"class":139},[122,1022,169],{"class":135},[122,1024,1025],{"class":175}," \"(set, hidden)\"\n",[122,1027,1029,1032],{"class":124,"line":1028},38,[122,1030,1031],{"class":135},"        else",[122,1033,214],{"class":139},[122,1035,1037,1039,1041],{"class":124,"line":1036},39,[122,1038,1001],{"class":139},[122,1040,169],{"class":135},[122,1042,1043],{"class":139}," value\n",[122,1045,1047],{"class":124,"line":1046},40,[122,1048,1049],{"class":139},"        rows.append((v.name, shown))\n",[122,1051,1053,1055],{"class":124,"line":1052},41,[122,1054,902],{"class":135},[122,1056,1057],{"class":139}," rows\n",[10,1059,1060,1061,1064],{},"Use the epilog on the root command (with a formatter that keeps line breaks — Click's ",[14,1062,1063],{},"\\b"," marker or Typer's Rich epilog), and add a small command that shows the current state:",[113,1066,1068],{"className":115,"code":1067,"language":117,"meta":118,"style":118},"@cli.command(\"env\")\ndef env_cmd() -> None:\n    \"\"\"Show the environment variables mytool reads and their current values.\"\"\"\n    for name, shown in current_values():\n        click.echo(f\"{name:\u003C24} {shown}\")\n",[14,1069,1070,1081,1094,1099,1111],{"__ignoreMap":118},[122,1071,1072,1074,1076,1079],{"class":124,"line":125},[122,1073,236],{"class":158},[122,1075,162],{"class":139},[122,1077,1078],{"class":175},"\"env\"",[122,1080,281],{"class":139},[122,1082,1083,1085,1088,1090,1092],{"class":124,"line":132},[122,1084,202],{"class":135},[122,1086,1087],{"class":158}," env_cmd",[122,1089,208],{"class":139},[122,1091,211],{"class":192},[122,1093,214],{"class":139},[122,1095,1096],{"class":124,"line":143},[122,1097,1098],{"class":175},"    \"\"\"Show the environment variables mytool reads and their current values.\"\"\"\n",[122,1100,1101,1103,1106,1108],{"class":124,"line":150},[122,1102,960],{"class":135},[122,1104,1105],{"class":139}," name, shown ",[122,1107,824],{"class":135},[122,1109,1110],{"class":139}," current_values():\n",[122,1112,1113,1116,1118,1120,1122,1124,1127,1129,1132,1135,1137,1139],{"class":124,"line":155},[122,1114,1115],{"class":139},"        click.echo(",[122,1117,854],{"class":135},[122,1119,885],{"class":175},[122,1121,172],{"class":192},[122,1123,518],{"class":139},[122,1125,1126],{"class":135},":\u003C24",[122,1128,882],{"class":192},[122,1130,1131],{"class":192}," {",[122,1133,1134],{"class":139},"shown",[122,1136,882],{"class":192},[122,1138,885],{"class":175},[122,1140,281],{"class":139},[10,1142,1143,1146,1147,35],{},[14,1144,1145],{},"mytool env"," answers the support question \"what is actually configured on that CI runner?\" in one paste, without ever printing a secret, as recommended in ",[31,1148,1150],{"href":1149},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Fredacting-secrets-from-cli-output-and-logs\u002F","redacting secrets from CLI output and logs",[57,1152],{"name":1153},"envdoc-registry",[37,1155,1157],{"id":1156},"showing-where-each-value-came-from","Showing where each value came from",[10,1159,1160,1161,1164,1165,1168,1169,1172,1173,1176],{},"Documentation tells users which variables ",[516,1162,1163],{},"exist","; the hardest debugging question is which one ",[516,1166,1167],{},"won",". Click records the source of every parameter value, so a command can report it — the most useful addition to a ",[14,1170,1171],{},"--verbose"," mode or a dedicated ",[14,1174,1175],{},"config show"," command:",[113,1178,1180],{"className":115,"code":1179,"language":117,"meta":118,"style":118},"@cli.command(\"show-config\")\n@click.option(\"--region\", default=\"eu-west-1\", show_envvar=True)\n@click.option(\"--retries\", default=3, type=int, show_envvar=True)\n@click.pass_context\ndef show_config(ctx: click.Context, region: str, retries: int) -> None:\n    \"\"\"Print each setting with the place its value came from.\"\"\"\n    for name, value in ctx.params.items():\n        source = ctx.get_parameter_source(name)\n        click.echo(f\"{name}={value!r} ({source.name.lower()})\")\n",[14,1181,1182,1193,1219,1257,1262,1285,1290,1302,1312],{"__ignoreMap":118},[122,1183,1184,1186,1188,1191],{"class":124,"line":125},[122,1185,236],{"class":158},[122,1187,162],{"class":139},[122,1189,1190],{"class":175},"\"show-config\"",[122,1192,281],{"class":139},[122,1194,1195,1197,1199,1201,1203,1205,1207,1209,1211,1213,1215,1217],{"class":124,"line":132},[122,1196,245],{"class":158},[122,1198,162],{"class":139},[122,1200,250],{"class":175},[122,1202,84],{"class":139},[122,1204,255],{"class":165},[122,1206,169],{"class":135},[122,1208,260],{"class":175},[122,1210,84],{"class":139},[122,1212,108],{"class":165},[122,1214,169],{"class":135},[122,1216,193],{"class":192},[122,1218,281],{"class":139},[122,1220,1221,1223,1225,1228,1230,1232,1234,1237,1239,1242,1244,1247,1249,1251,1253,1255],{"class":124,"line":143},[122,1222,245],{"class":158},[122,1224,162],{"class":139},[122,1226,1227],{"class":175},"\"--retries\"",[122,1229,84],{"class":139},[122,1231,255],{"class":165},[122,1233,169],{"class":135},[122,1235,1236],{"class":192},"3",[122,1238,84],{"class":139},[122,1240,1241],{"class":165},"type",[122,1243,169],{"class":135},[122,1245,1246],{"class":192},"int",[122,1248,84],{"class":139},[122,1250,108],{"class":165},[122,1252,169],{"class":135},[122,1254,193],{"class":192},[122,1256,281],{"class":139},[122,1258,1259],{"class":124,"line":150},[122,1260,1261],{"class":158},"@click.pass_context\n",[122,1263,1264,1266,1269,1272,1274,1277,1279,1281,1283],{"class":124,"line":155},[122,1265,202],{"class":135},[122,1267,1268],{"class":158}," show_config",[122,1270,1271],{"class":139},"(ctx: click.Context, region: ",[122,1273,334],{"class":192},[122,1275,1276],{"class":139},", retries: ",[122,1278,1246],{"class":192},[122,1280,348],{"class":139},[122,1282,211],{"class":192},[122,1284,214],{"class":139},[122,1286,1287],{"class":124,"line":199},[122,1288,1289],{"class":175},"    \"\"\"Print each setting with the place its value came from.\"\"\"\n",[122,1291,1292,1294,1297,1299],{"class":124,"line":217},[122,1293,960],{"class":135},[122,1295,1296],{"class":139}," name, value ",[122,1298,824],{"class":135},[122,1300,1301],{"class":139}," ctx.params.items():\n",[122,1303,1304,1307,1309],{"class":124,"line":223},[122,1305,1306],{"class":139},"        source ",[122,1308,169],{"class":135},[122,1310,1311],{"class":139}," ctx.get_parameter_source(name)\n",[122,1313,1314,1316,1318,1320,1322,1324,1326,1328,1330,1333,1336,1338,1341,1343,1346,1348,1351],{"class":124,"line":228},[122,1315,1115],{"class":139},[122,1317,854],{"class":135},[122,1319,885],{"class":175},[122,1321,172],{"class":192},[122,1323,518],{"class":139},[122,1325,882],{"class":192},[122,1327,169],{"class":175},[122,1329,172],{"class":192},[122,1331,1332],{"class":139},"value",[122,1334,1335],{"class":135},"!r",[122,1337,882],{"class":192},[122,1339,1340],{"class":175}," (",[122,1342,172],{"class":192},[122,1344,1345],{"class":139},"source.name.lower()",[122,1347,882],{"class":192},[122,1349,1350],{"class":175},")\"",[122,1352,281],{"class":139},[113,1354,1357],{"className":1355,"code":1356,"language":364,"meta":118},[362],"$ MYTOOL_SHOW_CONFIG_REGION=us-east-1 mytool show-config --retries 5\nregion='us-east-1' (environment)\nretries=5 (commandline)\n",[14,1358,1356],{"__ignoreMap":118},[10,1360,1361,1364,1365,1368,1369,1373,1374,1377],{},[14,1362,1363],{},"get_parameter_source"," distinguishes the command line, environment variables, ",[14,1366,1367],{},"default_map"," (configuration files loaded as in ",[31,1370,1372],{"href":1371},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Fclick-option-callbacks-and-eager-options\u002F","Click option callbacks and eager options",") and declared defaults. With Typer, the same method exists on the context, but compare ",[14,1375,1376],{},"source.name"," rather than importing Click's enum, because Typer uses its own vendored copy of Click.",[37,1379,1381],{"id":1380},"ux-considerations","UX considerations",[42,1383,1384,1390,1396,1406],{},[45,1385,1386,1389],{},[63,1387,1388],{},"Document precedence once, briefly."," \"Command-line options override environment variables, which override the config file\" in the root help saves many questions.",[45,1391,1392,1395],{},[63,1393,1394],{},"Validate environment values like flags."," Option-backed variables are validated by the framework; for free-standing ones, a bad value should produce a clear error naming the variable, not a mysterious failure later.",[45,1397,1398,1401,1402,35],{},[63,1399,1400],{},"Treat names as API."," Renaming a variable breaks every pipeline that sets it; support the old name with a deprecation warning for a release, as with flags in ",[31,1403,1405],{"href":1404},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fversioning-and-deprecating-cli-flags\u002F","versioning and deprecating CLI flags",[45,1407,1408,1411,1412,1414],{},[63,1409,1410],{},"Keep the list short."," Every variable is something users must discover and you must support; prefer options with ",[14,1413,386],{}," over free-standing variables when a value maps to an option anyway.",[37,1416,1418],{"id":1417},"testing-the-behaviour","Testing the behaviour",[10,1420,1421,1422,1425,1426,1429],{},"Two tests keep the documentation honest. One checks that help shows the variables; the other scans the source for ",[14,1423,1424],{},"os.environ"," reads of ",[14,1427,1428],{},"MYTOOL_*"," names and fails if any are missing from the registry:",[113,1431,1433],{"className":115,"code":1432,"language":117,"meta":118,"style":118},"# tests\u002Ftest_envvars.py\nimport re\nfrom pathlib import Path\n\nfrom click.testing import CliRunner\n\nfrom mytool.cli import cli\nfrom mytool.envvars import ENV_VARS, current_values\n\nSRC = Path(__file__).resolve().parents[1] \u002F \"src\" \u002F \"mytool\"\nREAD = re.compile(r\"\"\"os\\.environ(?:\\.get)?[\\[(]\\s*[\"'](MYTOOL_[A-Z0-9_]+)[\"']\"\"\")\n\n\ndef test_option_envvars_are_shown_in_help():\n    output = CliRunner().invoke(cli, [\"deploy\", \"--help\"]).output\n    assert \"env var: MYTOOL_DEPLOY_REGION\" in output\n\n\ndef test_every_free_standing_variable_is_documented():\n    documented = {v.name for v in ENV_VARS}\n    used = {m for f in SRC.rglob(\"*.py\") for m in READ.findall(f.read_text(encoding=\"utf-8\"))}\n    assert used \u003C= documented, f\"undocumented: {sorted(used - documented)}\"\n\n\ndef test_secrets_are_never_shown(monkeypatch):\n    monkeypatch.setenv(\"MYTOOL_TOKEN\", \"s3cr3t\")\n    assert (\"MYTOOL_TOKEN\", \"(set, hidden)\") in current_values()\n",[14,1434,1435,1440,1447,1459,1463,1475,1479,1491,1505,1509,1542,1613,1617,1621,1631,1652,1666,1670,1674,1683,1704,1757,1792,1796,1800,1810,1824],{"__ignoreMap":118},[122,1436,1437],{"class":124,"line":125},[122,1438,1439],{"class":128},"# tests\u002Ftest_envvars.py\n",[122,1441,1442,1444],{"class":124,"line":132},[122,1443,136],{"class":135},[122,1445,1446],{"class":139}," re\n",[122,1448,1449,1451,1454,1456],{"class":124,"line":143},[122,1450,567],{"class":135},[122,1452,1453],{"class":139}," pathlib ",[122,1455,136],{"class":135},[122,1457,1458],{"class":139}," Path\n",[122,1460,1461],{"class":124,"line":150},[122,1462,147],{"emptyLinePlaceholder":146},[122,1464,1465,1467,1470,1472],{"class":124,"line":155},[122,1466,567],{"class":135},[122,1468,1469],{"class":139}," click.testing ",[122,1471,136],{"class":135},[122,1473,1474],{"class":139}," CliRunner\n",[122,1476,1477],{"class":124,"line":199},[122,1478,147],{"emptyLinePlaceholder":146},[122,1480,1481,1483,1486,1488],{"class":124,"line":217},[122,1482,567],{"class":135},[122,1484,1485],{"class":139}," mytool.cli ",[122,1487,136],{"class":135},[122,1489,1490],{"class":139}," cli\n",[122,1492,1493,1495,1498,1500,1502],{"class":124,"line":223},[122,1494,567],{"class":135},[122,1496,1497],{"class":139}," mytool.envvars ",[122,1499,136],{"class":135},[122,1501,827],{"class":192},[122,1503,1504],{"class":139},", current_values\n",[122,1506,1507],{"class":124,"line":228},[122,1508,147],{"emptyLinePlaceholder":146},[122,1510,1511,1514,1516,1519,1522,1525,1528,1530,1533,1536,1539],{"class":124,"line":233},[122,1512,1513],{"class":192},"SRC",[122,1515,659],{"class":135},[122,1517,1518],{"class":139}," Path(",[122,1520,1521],{"class":192},"__file__",[122,1523,1524],{"class":139},").resolve().parents[",[122,1526,1527],{"class":192},"1",[122,1529,846],{"class":139},[122,1531,1532],{"class":135},"\u002F",[122,1534,1535],{"class":175}," \"src\"",[122,1537,1538],{"class":135}," \u002F",[122,1540,1541],{"class":175}," \"mytool\"\n",[122,1543,1544,1547,1549,1552,1555,1558,1562,1566,1569,1572,1574,1577,1580,1583,1586,1589,1592,1595,1598,1601,1604,1606,1609,1611],{"class":124,"line":242},[122,1545,1546],{"class":192},"READ",[122,1548,659],{"class":135},[122,1550,1551],{"class":139}," re.compile(",[122,1553,1554],{"class":135},"r",[122,1556,1557],{"class":175},"\"\"\"",[122,1559,1561],{"class":1560},"sA_wV","os",[122,1563,1565],{"class":1564},"snhLl","\\.",[122,1567,1568],{"class":1560},"environ",[122,1570,1571],{"class":192},"(?:",[122,1573,1565],{"class":1564},[122,1575,1576],{"class":1560},"get",[122,1578,1579],{"class":192},")",[122,1581,1582],{"class":135},"?",[122,1584,1585],{"class":192},"[",[122,1587,1588],{"class":1564},"\\[",[122,1590,1591],{"class":192},"(]\\s",[122,1593,1594],{"class":135},"*",[122,1596,1597],{"class":192},"[\"'](",[122,1599,1600],{"class":1560},"MYTOOL_",[122,1602,1603],{"class":192},"[A-Z0-9_]",[122,1605,849],{"class":135},[122,1607,1608],{"class":192},")[\"']",[122,1610,1557],{"class":175},[122,1612,281],{"class":139},[122,1614,1615],{"class":124,"line":284},[122,1616,147],{"emptyLinePlaceholder":146},[122,1618,1619],{"class":124,"line":323},[122,1620,147],{"emptyLinePlaceholder":146},[122,1622,1623,1625,1628],{"class":124,"line":355},[122,1624,202],{"class":135},[122,1626,1627],{"class":158}," test_option_envvars_are_shown_in_help",[122,1629,1630],{"class":139},"():\n",[122,1632,1633,1636,1638,1641,1644,1646,1649],{"class":124,"line":673},[122,1634,1635],{"class":139},"    output ",[122,1637,169],{"class":135},[122,1639,1640],{"class":139}," CliRunner().invoke(cli, [",[122,1642,1643],{"class":175},"\"deploy\"",[122,1645,84],{"class":139},[122,1647,1648],{"class":175},"\"--help\"",[122,1650,1651],{"class":139},"]).output\n",[122,1653,1654,1657,1660,1663],{"class":124,"line":684},[122,1655,1656],{"class":135},"    assert",[122,1658,1659],{"class":175}," \"env var: MYTOOL_DEPLOY_REGION\"",[122,1661,1662],{"class":135}," in",[122,1664,1665],{"class":139}," output\n",[122,1667,1668],{"class":124,"line":701},[122,1669,147],{"emptyLinePlaceholder":146},[122,1671,1672],{"class":124,"line":724},[122,1673,147],{"emptyLinePlaceholder":146},[122,1675,1676,1678,1681],{"class":124,"line":739},[122,1677,202],{"class":135},[122,1679,1680],{"class":158}," test_every_free_standing_variable_is_documented",[122,1682,1630],{"class":139},[122,1684,1685,1688,1690,1693,1695,1697,1699,1701],{"class":124,"line":754},[122,1686,1687],{"class":139},"    documented ",[122,1689,169],{"class":135},[122,1691,1692],{"class":139}," {v.name ",[122,1694,818],{"class":135},[122,1696,821],{"class":139},[122,1698,824],{"class":135},[122,1700,827],{"class":192},[122,1702,1703],{"class":139},"}\n",[122,1705,1706,1709,1711,1714,1716,1719,1721,1724,1727,1730,1733,1735,1738,1740,1743,1746,1749,1751,1754],{"class":124,"line":769},[122,1707,1708],{"class":139},"    used ",[122,1710,169],{"class":135},[122,1712,1713],{"class":139}," {m ",[122,1715,818],{"class":135},[122,1717,1718],{"class":139}," f ",[122,1720,824],{"class":135},[122,1722,1723],{"class":192}," SRC",[122,1725,1726],{"class":139},".rglob(",[122,1728,1729],{"class":175},"\"*.py\"",[122,1731,1732],{"class":139},") ",[122,1734,818],{"class":135},[122,1736,1737],{"class":139}," m ",[122,1739,824],{"class":135},[122,1741,1742],{"class":192}," READ",[122,1744,1745],{"class":139},".findall(f.read_text(",[122,1747,1748],{"class":165},"encoding",[122,1750,169],{"class":135},[122,1752,1753],{"class":175},"\"utf-8\"",[122,1755,1756],{"class":139},"))}\n",[122,1758,1759,1761,1764,1767,1770,1772,1775,1778,1781,1784,1787,1789],{"class":124,"line":775},[122,1760,1656],{"class":135},[122,1762,1763],{"class":139}," used ",[122,1765,1766],{"class":135},"\u003C=",[122,1768,1769],{"class":139}," documented, ",[122,1771,854],{"class":135},[122,1773,1774],{"class":175},"\"undocumented: ",[122,1776,1777],{"class":192},"{sorted",[122,1779,1780],{"class":139},"(used ",[122,1782,1783],{"class":135},"-",[122,1785,1786],{"class":139}," documented)",[122,1788,882],{"class":192},[122,1790,1791],{"class":175},"\"\n",[122,1793,1794],{"class":124,"line":780},[122,1795,147],{"emptyLinePlaceholder":146},[122,1797,1798],{"class":124,"line":785},[122,1799,147],{"emptyLinePlaceholder":146},[122,1801,1802,1804,1807],{"class":124,"line":799},[122,1803,202],{"class":135},[122,1805,1806],{"class":158}," test_secrets_are_never_shown",[122,1808,1809],{"class":139},"(monkeypatch):\n",[122,1811,1812,1815,1817,1819,1822],{"class":124,"line":832},[122,1813,1814],{"class":139},"    monkeypatch.setenv(",[122,1816,301],{"class":175},[122,1818,84],{"class":139},[122,1820,1821],{"class":175},"\"s3cr3t\"",[122,1823,281],{"class":139},[122,1825,1826,1828,1830,1832,1834,1837,1839,1841],{"class":124,"line":899},[122,1827,1656],{"class":135},[122,1829,1340],{"class":139},[122,1831,301],{"class":175},[122,1833,84],{"class":139},[122,1835,1836],{"class":175},"\"(set, hidden)\"",[122,1838,1732],{"class":139},[122,1840,824],{"class":135},[122,1842,1843],{"class":139}," current_values()\n",[10,1845,1846,1847,1850],{},"The source scan is crude but effective: it catches the common case of someone adding ",[14,1848,1849],{},"os.environ.get(\"MYTOOL_FOO\")"," in a new module and forgetting to tell anyone.",[37,1852,1854],{"id":1853},"conclusion","Conclusion",[10,1856,1857,1858,1860,1861,1863],{},"Environment variables deserve the same visibility as options. Show option-backed variables in help with ",[14,1859,108],{}," (Click) or the default display (Typer), name them predictably under a tool prefix, keep free-standing variables in one registry that generates an \"Environment\" help section and an ",[14,1862,28],{}," command, never display secret values, and test that every variable the code reads is documented. CI pipelines and containers will thank you.",[37,1865,1867],{"id":1866},"frequently-asked-questions","Frequently asked questions",[102,1869,1871],{"id":1870},"should-every-option-have-an-environment-variable","Should every option have an environment variable?",[10,1873,1874,1875,1877],{},"No. Options that configure the environment — credentials, endpoints, regions, output preferences — benefit; options that describe a single invocation, such as which file to process, rarely do. ",[14,1876,111],{}," gives every option one for free, which is convenient but makes the interface larger than you may want to support.",[102,1879,1881],{"id":1880},"how-do-boolean-environment-variables-work","How do boolean environment variables work?",[10,1883,1884,1885,84,1887,84,1890,84,1893,84,1896,84,1899,1902,1903,92,1906,1909],{},"Click and Typer accept common truthy and falsy strings (",[14,1886,1527],{},[14,1888,1889],{},"true",[14,1891,1892],{},"yes",[14,1894,1895],{},"0",[14,1897,1898],{},"false",[14,1900,1901],{},"no",") for boolean options. For free-standing variables, parse them with the same rules in one helper, so ",[14,1904,1905],{},"MYTOOL_DEBUG=yes",[14,1907,1908],{},"MYTOOL_DEBUG=1"," behave the same.",[102,1911,1913],{"id":1912},"can-i-generate-a-man-page-section-from-the-registry","Can I generate a man page section from the registry?",[10,1915,1916,1917,35],{},"Yes — the same list can feed an ENVIRONMENT section in generated man pages or Markdown docs, as described in ",[31,1918,1920],{"href":1919},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fgenerating-man-pages-and-docs-from-a-cli\u002F","generating man pages and docs from a CLI",[102,1922,1924,1925,1928],{"id":1923},"what-about-env-files","What about ",[14,1926,1927],{},".env"," files?",[10,1930,1931,1932,1934,1935,35],{},"Loading a ",[14,1933,1927],{}," file is a separate decision with its own trade-offs; if your CLI supports it, list that in the Environment section too, and make the file's location and precedence explicit, as in ",[31,1936,1938],{"href":1937},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Freading-secrets-from-env-and-files\u002F","reading secrets from env and files",[102,1940,1942],{"id":1941},"do-environment-variables-work-on-windows-the-same-way","Do environment variables work on Windows the same way?",[10,1944,1945,1946,1949,1950,1953,1954,1957],{},"Yes, though users set them differently (",[14,1947,1948],{},"$env:MYTOOL_REGION = \"us-east-1\""," in PowerShell, ",[14,1951,1952],{},"set MYTOOL_REGION=us-east-1"," in cmd), and names are case-insensitive there. Show the PowerShell form next to the POSIX one in your docs, since copying ",[14,1955,1956],{},"export ..."," into PowerShell fails.",[37,1959,1961],{"id":1960},"related","Related",[42,1963,1964,1970,1976,1982,1988],{},[45,1965,1966,1967],{},"Up: ",[31,1968,1969],{"href":33},"CLI help output and documentation",[45,1971,1972],{},[31,1973,1975],{"href":1974},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fwriting-help-text-users-actually-read\u002F","Writing help text users actually read",[45,1977,1978],{},[31,1979,1981],{"href":1980},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Frich-formatted-help-with-rich-click\u002F","Rich-formatted help with rich-click",[45,1983,1984],{},[31,1985,1987],{"href":1986},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Ftyped-settings-with-pydantic-settings\u002F","Typed settings with pydantic-settings",[45,1989,1990],{},[31,1991,1993],{"href":1992},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Fdetecting-ci-environments-and-non-interactive-shells\u002F","Detecting CI environments and non-interactive shells",[1995,1996,1997],"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 .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 pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}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);}html pre.shiki code .sA_wV, html code.shiki .sA_wV{--shiki-default:#032F62;--shiki-dark:#DBEDFF}html pre.shiki code .snhLl, html code.shiki .snhLl{--shiki-default:#22863A;--shiki-default-font-weight:bold;--shiki-dark:#85E89D;--shiki-dark-font-weight:bold}",{"title":118,"searchDepth":132,"depth":132,"links":1999},[2000,2001,2002,2008,2009,2010,2011,2012,2013,2021],{"id":39,"depth":132,"text":40},{"id":54,"depth":132,"text":55},{"id":99,"depth":132,"text":100,"children":2003},[2004,2006,2007],{"id":104,"depth":143,"text":2005},"Click: show_envvar and auto_envvar_prefix",{"id":397,"depth":143,"text":398},{"id":525,"depth":143,"text":526},{"id":548,"depth":132,"text":549},{"id":1156,"depth":132,"text":1157},{"id":1380,"depth":132,"text":1381},{"id":1417,"depth":132,"text":1418},{"id":1853,"depth":132,"text":1854},{"id":1866,"depth":132,"text":1867,"children":2014},[2015,2016,2017,2018,2020],{"id":1870,"depth":143,"text":1871},{"id":1880,"depth":143,"text":1881},{"id":1912,"depth":143,"text":1913},{"id":1923,"depth":143,"text":2019},"What about .env files?",{"id":1941,"depth":143,"text":1942},{"id":1960,"depth":132,"text":1961},"2026-10-02","Make a Python CLI’s environment variables discoverable: show_envvar in Click and Typer, auto_envvar_prefix naming, an Environment section in help, an env command, and a test that none are undocumented.","intermediate",false,"md",{},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fdocumenting-environment-variables-in-help",{"title":5,"description":2023},"advanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fdocumenting-environment-variables-in-help\u002Findex",[2032,2033,2034,2035,2036],"help-text","environment-variables","click","typer","configuration","fTHaaQwNkNlt9jVCLRNdzyEsWtpZlXhDdV__0r5L6lo",[2039,2042,2045,2048,2051,2054,2057,2060,2063,2066,2069,2072,2075,2078,2081,2084,2087,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,2364,2367,2370,2373,2376,2379,2382,2385,2388,2391,2394,2397,2400,2403,2406,2409,2412,2415,2418,2421,2424,2427,2430,2433,2436,2439,2442,2445,2448,2451,2454,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,2537,2540,2543,2546,2549,2552,2555,2558,2561,2564,2567,2570,2573,2576,2579,2582,2585,2588,2591,2594,2597,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,2684,2687,2690,2693,2696,2699,2702,2705,2708,2711,2714,2717,2720,2723,2726,2729,2732,2735,2738,2741,2744,2747,2750,2753,2756,2759,2762,2765,2768,2771,2774,2777,2780,2783,2786,2789,2792,2795,2798,2801,2804,2807,2810,2813,2816,2819,2822,2825,2828,2831,2834,2837,2840,2843,2846,2849,2852,2855,2858,2861,2864,2867,2870,2873,2876,2879],{"path":2040,"title":2041},"\u002Fabout","About Python CLI Toolcraft",{"path":2043,"title":2044},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies","Advanced Argument Validation Strategies",{"path":2046,"title":2047},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fparsing-nested-json-arguments-in-python-clis","Parsing Nested JSON Args in Python CLIs",{"path":2049,"title":2050},"\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":2052,"title":2053},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fvalidating-dependent-and-conflicting-options","Validating Dependent and Conflicting CLI Options",{"path":2055,"title":2056},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fvalidating-file-and-directory-paths-in-clis","Validating File and Directory Paths in CLIs",{"path":2058,"title":2059},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fvalidating-urls-hosts-and-ports","Validating URLs, Hosts and Ports in Python CLI Arguments",{"path":2061,"title":2062},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fwriting-custom-click-parameter-types","Writing Custom Click Parameter Types",{"path":2064,"title":2065},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual\u002Fbrowsing-records-with-a-textual-datatable","Browsing Records with a Textual DataTable Picker",{"path":2067,"title":2068},"\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":2070,"title":2071},"\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":2073,"title":2074},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual","Building Terminal UIs with Textual for Python CLIs",{"path":2076,"title":2077},"\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":2079,"title":2080},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual\u002Fstyling-textual-apps-with-tcss","Styling Textual Apps with TCSS",{"path":2082,"title":2083},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual\u002Ftesting-textual-apps-with-pilot","Testing Textual Apps with Pilot",{"path":2085,"title":2086},"\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":2028,"title":5},{"path":2089,"title":2090},"\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":2092,"title":2093},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation","CLI Help Output and Documentation",{"path":2095,"title":2096},"\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":2098,"title":2099},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fversioning-and-deprecating-cli-flags","Versioning and Deprecating CLI Flags",{"path":2101,"title":2102},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fwriting-help-text-users-actually-read","Writing Help Text Users Actually Read",{"path":2104,"title":2105},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Fadapting-output-to-terminal-width","Adapting Python CLI Output to Terminal Width",{"path":2107,"title":2108},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Fdetecting-ci-environments-and-non-interactive-shells","Detecting CI Environments and Non-Interactive Shells",{"path":2110,"title":2111},"\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":2113,"title":2114},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Fhandling-ansi-escape-codes-on-windows-consoles","Handling ANSI Escape Codes on Windows Consoles",{"path":2116,"title":2117},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility","Cross-Platform Terminal Compatibility for Python CLIs",{"path":2119,"title":2120},"\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":2122,"title":2123},"\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":2125,"title":2126},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fchoosing-exit-codes-for-cli-tools","Choosing Exit Codes for CLI Tools",{"path":2128,"title":2129},"\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":2131,"title":2132},"\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":2134,"title":2135},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Ffriendly-error-messages-and-tracebacks","Friendly Error Messages and Tracebacks",{"path":2137,"title":2138},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fhandling-keyboard-interrupt-cleanly","Handling Keyboard Interrupt Cleanly",{"path":2140,"title":2141},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes","Error Handling and Exit Codes for CLIs",{"path":2143,"title":2144},"\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":2146,"title":2147},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fwriting-crash-reports-users-can-send","Writing Crash Reports Users Can Send",{"path":2149,"title":2150},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Fconfig-precedence-flags-env-files-defaults","Config Precedence: Flags, Env, Files, Defaults",{"path":2152,"title":2153},"\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":2155,"title":2156},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars","Handling Config Files and Env Vars in CLIs",{"path":2158,"title":2159},"\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":2161,"title":2162},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Freading-toml-config-with-tomllib","Reading TOML Config with tomllib in Python CLIs",{"path":2164,"title":2165},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Ftyped-settings-with-pydantic-settings","Typed Settings with pydantic-settings in Python CLIs",{"path":2167,"title":2168},"\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":2170,"title":2171},"\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":2173,"title":2174},"\u002Fadvanced-input-parsing-user-experience","Advanced Input Parsing for Python CLIs",{"path":2176,"title":2177},"\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":2179,"title":2180},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Fbuilding-interactive-prompts-and-menus","Building Interactive Prompts and Menus in Python CLIs",{"path":2182,"title":2183},"\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":2185,"title":2186},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich","Interactive Terminal UI with Rich",{"path":2188,"title":2189},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Flive-dashboards-with-rich-live","Live Dashboards with Rich Live in Python CLIs",{"path":2191,"title":2192},"\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":2194,"title":2195},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Frendering-tables-and-json-with-rich","Rendering Tables and JSON with Rich",{"path":2197,"title":2198},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Ftheming-rich-output-consistently","Theming Rich Output Consistently in Python CLIs",{"path":2200,"title":2201},"\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":2203,"title":2204},"\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":2206,"title":2207},"\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":2209,"title":2210},"\u002Fadvanced-input-parsing-user-experience\u002Foutput-formats-for-data-clis","Output Formats for Data-Heavy Python CLIs",{"path":2212,"title":2213},"\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":2215,"title":2216},"\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":2218,"title":2219},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Fargcomplete-for-argparse-clis","Tab Completion for argparse CLIs with argcomplete",{"path":2221,"title":2222},"\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":2224,"title":2225},"\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":2227,"title":2228},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis","Shell Completion for Python CLIs",{"path":2230,"title":2231},"\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":2233,"title":2234},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Fshipping-completion-scripts-with-packages","Shipping Shell Completion Scripts with a Python CLI",{"path":2236,"title":2237},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Ftesting-shell-completion-in-python-clis","Testing Shell Completion in Python CLIs",{"path":2239,"title":2240},"\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":2242,"title":2243},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fadding-verbose-and-quiet-logging-flags","Adding Verbose and Quiet Logging Flags",{"path":2245,"title":2246},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps","Structured Logging for CLI Apps",{"path":2248,"title":2249},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Flogging-with-structlog-in-a-cli","Logging with structlog in a Python CLI",{"path":2251,"title":2252},"\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":2254,"title":2255},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fstructured-json-logging-in-python-clis","Structured JSON Logging in Python CLIs",{"path":2257,"title":2258},"\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":2260,"title":2261},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Fdetecting-tty-and-adapting-output","Detecting a TTY and Adapting Output",{"path":2263,"title":2264},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Femitting-json-output-for-scripting","Emitting JSON Output for Scripting",{"path":2266,"title":2267},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Fhandling-broken-pipe-and-sigpipe","Handling Broken Pipe and SIGPIPE",{"path":2269,"title":2270},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes","Working with stdin, stdout and Pipes",{"path":2272,"title":2273},"\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":2275,"title":2276},"\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":2278,"title":2279},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Freading-piped-input-in-python-clis","Reading Piped Input in Python CLIs",{"path":2281,"title":2282},"\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":2284,"title":2285},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Fdownloading-files-with-progress-in-python","Downloading Files with Progress in Python CLIs",{"path":2287,"title":2288},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis","Calling HTTP APIs from Python CLIs",{"path":2290,"title":2291},"\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":2293,"title":2294},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Foauth-device-flow-login-for-clis","OAuth Device Flow Login for Python CLIs",{"path":2296,"title":2297},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Fpaginating-api-results-in-a-cli","Paginating API Results in a Python CLI",{"path":2299,"title":2300},"\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":2302,"title":2303},"\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":2305,"title":2306},"\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":2308,"title":2309},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis","Concurrency and Async in Python CLIs",{"path":2311,"title":2312},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Fmultiprocessing-for-cpu-bound-cli-tasks","Multiprocessing for CPU-Bound CLI Tasks",{"path":2314,"title":2315},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Fparallelising-cli-work-with-thread-pools","Parallelising CLI Work with Thread Pools",{"path":2317,"title":2318},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Frate-limiting-concurrent-requests-in-clis","Rate-Limiting Concurrent Requests in Python CLIs",{"path":2320,"title":2321},"\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":2323,"title":2324},"\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":2326,"title":2327},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Fstructured-concurrency-with-taskgroups-in-clis","Structured Concurrency with TaskGroups in Python CLIs",{"path":2329,"title":2330},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fcross-platform-paths-with-pathlib","Cross-Platform Paths with pathlib in CLIs",{"path":2332,"title":2333},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Ffile-locking-for-concurrent-cli-runs","File Locking for Concurrent CLI Runs in Python",{"path":2335,"title":2336},"\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":2338,"title":2339},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes","Filesystem Paths and Atomic Writes for CLIs",{"path":2341,"title":2342},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fsafe-temporary-files-and-directories","Safe Temporary Files and Directories in CLIs",{"path":2344,"title":2345},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fstoring-app-data-with-platformdirs","Storing CLI App Data with platformdirs",{"path":2347,"title":2348},"\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":2350,"title":2351},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fwriting-files-atomically-in-python-clis","Writing Files Atomically in Python CLIs",{"path":2353,"title":2354},"\u002Fcli-runtime-systems-integration","CLI Runtime & Systems Integration for Python",{"path":2356,"title":2357},"\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":2359,"title":2360},"\u002Fcli-runtime-systems-integration\u002Flocal-state-and-sqlite-in-python-clis","Local State and SQLite in Python CLIs",{"path":2362,"title":2363},"\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":2365,"title":2366},"\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":2368,"title":2369},"\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":2371,"title":2372},"\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":2374,"title":2375},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis\u002Fhandling-sigterm-and-graceful-shutdown","Handling SIGTERM and Graceful Shutdown in CLIs",{"path":2377,"title":2378},"\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":2380,"title":2381},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis","Long-Running and Watch-Mode Python CLIs",{"path":2383,"title":2384},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis\u002Freloading-config-on-sighup","Reloading Configuration on SIGHUP in a Python CLI",{"path":2386,"title":2387},"\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":2389,"title":2390},"\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":2392,"title":2393},"\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":2395,"title":2396},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Favoiding-shell-injection-in-python-clis","Avoiding Shell Injection in Python CLIs",{"path":2398,"title":2399},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fcalling-external-commands-safely-with-subprocess","Calling External Commands Safely with subprocess",{"path":2401,"title":2402},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fhandling-subprocess-timeouts-and-exit-codes","Handling Subprocess Timeouts and Exit Codes",{"path":2404,"title":2405},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis","Running Subprocesses from Python CLIs",{"path":2407,"title":2408},"\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":2410,"title":2411},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fpiping-between-subprocesses-in-python","Piping Between Subprocesses in Python Without a Shell",{"path":2413,"title":2414},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fstreaming-subprocess-output-in-real-time","Streaming Subprocess Output in Real Time",{"path":2416,"title":2417},"\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":2419,"title":2420},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis","Secrets and Credentials in Python CLIs",{"path":2422,"title":2423},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Fprompting-for-passwords-securely","Prompting for Passwords Securely in Python CLIs",{"path":2425,"title":2426},"\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":2428,"title":2429},"\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":2431,"title":2432},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Frefreshing-expired-tokens-automatically","Refreshing Expired Tokens Automatically in a Python CLI",{"path":2434,"title":2435},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Fstoring-tokens-with-keyring","Storing CLI Tokens Securely with keyring",{"path":2437,"title":2438},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Fsupporting-multiple-profiles-and-accounts","Supporting Multiple Profiles and Accounts in CLIs",{"path":2440,"title":2441},"\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":2443,"title":2444},"\u002Fcli-runtime-systems-integration\u002Fupdate-checks-and-self-updating-clis","Update Checks, Upgrade Commands and Telemetry for Python CLIs",{"path":2446,"title":2447},"\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":2449,"title":2450},"\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":2452,"title":2453},"\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":1532,"title":2455},"Python CLI Toolcraft",{"path":2457,"title":2458},"\u002Fmodern-python-cli-frameworks-architecture\u002Falternative-python-cli-frameworks\u002Fbuilding-a-cli-with-cleo","Building a Python CLI with Cleo Command Classes",{"path":2460,"title":2461},"\u002Fmodern-python-cli-frameworks-architecture\u002Falternative-python-cli-frameworks\u002Fbuilding-a-cli-with-cyclopts","Building a Type-Hinted Python CLI with Cyclopts",{"path":2463,"title":2464},"\u002Fmodern-python-cli-frameworks-architecture\u002Falternative-python-cli-frameworks","Beyond Click and Typer: Alternative Python CLI Frameworks",{"path":2466,"title":2467},"\u002Fmodern-python-cli-frameworks-architecture\u002Falternative-python-cli-frameworks\u002Fquick-clis-from-functions-with-python-fire","Quick CLIs from Functions with Python Fire",{"path":2469,"title":2470},"\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":2472,"title":2473},"\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":2475,"title":2476},"\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":2478,"title":2479},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Fguarding-startup-with-import-tests","Guarding CLI Startup with Import Tests",{"path":2481,"title":2482},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading","CLI Startup Performance and Lazy Loading",{"path":2484,"title":2485},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Flazy-loading-subcommands-for-faster-startup","Lazy Loading Subcommands for Faster Startup",{"path":2487,"title":2488},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Fprofiling-python-cli-startup-time","Profiling Python CLI Startup Time",{"path":2490,"title":2491},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Freducing-cli-dependency-weight","Reducing CLI Dependency Weight",{"path":2493,"title":2494},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargparse-subparsers-for-subcommands","argparse Subparsers for Subcommands",{"path":2496,"title":2497},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargparse-vs-click-vs-typer-comparison","argparse vs Click vs Typer Compared",{"path":2499,"title":2500},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargument-groups-and-help-formatting-in-argparse","Argument Groups and Help Formatting in argparse",{"path":2502,"title":2503},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse","Command-Line Parsing with argparse",{"path":2505,"title":2506},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fmigrating-from-argparse-to-typer","Migrating from argparse to Typer",{"path":2508,"title":2509},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fmutually-exclusive-options-in-argparse","Mutually Exclusive Options in argparse",{"path":2511,"title":2512},"\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":2514,"title":2515},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fwriting-custom-argparse-actions","Writing Custom argparse Actions in Python",{"path":2517,"title":2518},"\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":2520,"title":2521},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Fdesigning-idempotent-commands","Designing Idempotent Commands in a Python CLI",{"path":2523,"title":2524},"\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":2526,"title":2527},"\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":2529,"title":2530},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions","Designing CLI Interfaces and Conventions in Python",{"path":2532,"title":2533},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Fnaming-commands-and-flags-consistently","Naming Commands and Flags Consistently in Python CLIs",{"path":2535,"title":2536},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Fpositional-arguments-vs-options","Positional Arguments vs Options: Designing a CLI Signature",{"path":2538,"title":2539},"\u002Fmodern-python-cli-frameworks-architecture","Python CLI Frameworks and Architecture",{"path":2541,"title":2542},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fdiscovering-plugins-with-entry-points","Discovering Plugins with Entry Points in Python CLIs",{"path":2544,"title":2545},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fhook-based-plugins-with-pluggy","Hook-Based Plugins for Python CLIs with pluggy",{"path":2547,"title":2548},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis","Plugin Architectures for Extensible CLIs",{"path":2550,"title":2551},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fisolating-plugin-failures","Isolating Plugin Failures in an Extensible Python CLI",{"path":2553,"title":2554},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Ftesting-plugins-against-the-host-cli","Testing Plugins Against the Host CLI",{"path":2556,"title":2557},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fversioning-a-plugin-api","Versioning a Plugin API for a Python CLI",{"path":2559,"title":2560},"\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":2562,"title":2563},"\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":2565,"title":2566},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fdependency-injection-patterns-for-cli-commands","Dependency Injection Patterns for CLI Commands",{"path":2568,"title":2569},"\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":2571,"title":2572},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis","Structuring Multi-Command Python CLIs",{"path":2574,"title":2575},"\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":2577,"title":2578},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fregistering-commands-from-modules-automatically","Registering CLI Commands from Modules Automatically",{"path":2580,"title":2581},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fsharing-common-options-across-commands","Sharing Common Options Across Python CLI Commands",{"path":2583,"title":2584},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fsharing-state-with-click-context-objects","Sharing State with Click Context Objects",{"path":2586,"title":2587},"\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":2589,"title":2590},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications","Testing Python CLI Applications",{"path":2592,"title":2593},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fmeasuring-cli-test-coverage","Measuring CLI Test Coverage",{"path":2595,"title":2596},"\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":2598,"title":2599},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fproperty-based-testing-cli-arguments-with-hypothesis","Property-Based Testing CLI Arguments with Hypothesis",{"path":2601,"title":2602},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fsnapshot-testing-cli-output","Snapshot Testing CLI Output",{"path":2604,"title":2605},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftesting-click-commands-with-clirunner","Testing Click Commands with CliRunner",{"path":2607,"title":2608},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftesting-commands-that-call-subprocesses","Testing Python CLI Commands That Call Subprocesses",{"path":2610,"title":2611},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftesting-interactive-prompts-and-stdin","Testing Interactive Prompts and stdin",{"path":2613,"title":2614},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftranscript-tests-for-cli-commands","Transcript Tests for Python CLI Commands",{"path":2616,"title":2617},"\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":2619,"title":2620},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Fbuilding-dynamic-commands-in-click","Building Dynamic Commands in Click",{"path":2622,"title":2623},"\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":2625,"title":2626},"\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":2628,"title":2629},"\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":2631,"title":2632},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each","Typer vs Click: When to Use Each",{"path":2634,"title":2635},"\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":2637,"title":2638},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Ftyper-callback-functions-explained","Typer callback functions explained",{"path":2640,"title":2641},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Fusing-annotated-options-in-typer","Using Annotated Options in Typer",{"path":2643,"title":2644},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis\u002Fautomating-releases-from-git-tags","Automating Python CLI Releases from Git Tags",{"path":2646,"title":2647},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis\u002Fcaching-uv-dependencies-in-ci","Caching uv Dependencies in CI for Python CLIs",{"path":2649,"title":2650},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis","CI\u002FCD Pipelines for Python CLIs",{"path":2652,"title":2653},"\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":2655,"title":2656},"\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":2658,"title":2659},"\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":2661,"title":2662},"\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":2664,"title":2665},"\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":2667,"title":2668},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fbuilding-a-cookiecutter-template-for-typer-clis","Building a Cookiecutter Template for Typer CLIs",{"path":2670,"title":2671},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fcopier-vs-cookiecutter-for-cli-templates","Copier vs Cookiecutter for CLI Templates",{"path":2673,"title":2674},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter","CLI Project Scaffolding with Cookiecutter",{"path":2676,"title":2677},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fpost-generation-hooks-in-cli-templates","Post-Generation Hooks in Python CLI Templates",{"path":2679,"title":2680},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Ftemplate-variables-and-conditional-files","Template Variables and Conditional Files in CLI Templates",{"path":2682,"title":2683},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Ftesting-a-project-template-with-pytest","Testing a CLI Project Template with pytest",{"path":2685,"title":2686},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fupdating-generated-projects-with-copier-update","Updating Generated CLI Projects with copier update",{"path":2688,"title":2689},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fbuilding-cross-platform-release-binaries-in-ci","Building Cross-Platform Release Binaries in CI",{"path":2691,"title":2692},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fbundling-a-python-cli-with-pyinstaller","Bundling a Python CLI with PyInstaller",{"path":2694,"title":2695},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fcode-signing-and-notarizing-cli-binaries","Code Signing and Notarizing Python CLI Binaries",{"path":2697,"title":2698},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fhomebrew-and-scoop-packaging-for-python-clis","Homebrew and Scoop Packaging for Python CLIs",{"path":2700,"title":2701},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries","Distributing CLIs as Standalone Binaries",{"path":2703,"title":2704},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fnuitka-vs-pyinstaller-for-python-clis","Nuitka vs PyInstaller for Python CLI Binaries",{"path":2706,"title":2707},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Freducing-pyinstaller-binary-size","Reducing the Size of a PyInstaller CLI Binary",{"path":2709,"title":2710},"\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":2712,"title":2713},"\u002Fproject-setup-dependency-management","Project Setup & Dependency Management",{"path":2715,"title":2716},"\u002Fproject-setup-dependency-management\u002Flinting-and-type-checking-cli-code\u002Fconfiguring-ruff-for-a-cli-project","Configuring Ruff for a Python CLI Project",{"path":2718,"title":2719},"\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":2721,"title":2722},"\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":2724,"title":2725},"\u002Fproject-setup-dependency-management\u002Flinting-and-type-checking-cli-code","Linting and Type-Checking Python CLI Code",{"path":2727,"title":2728},"\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":2730,"title":2731},"\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":2733,"title":2734},"\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":2736,"title":2737},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fautomating-changelogs-with-conventional-commits","Automating Changelogs with Conventional Commits",{"path":2739,"title":2740},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fbumping-versions-consistently-across-a-cli-project","Bumping Versions Consistently Across a Python CLI Project",{"path":2742,"title":2743},"\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":2745,"title":2746},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fexposing-version-info-and-build-metadata","Exposing Version Info and Build Metadata",{"path":2748,"title":2749},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs","Managing CLI Versioning & Changelogs",{"path":2751,"title":2752},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fsemantic-versioning-policy-for-cli-tools","A Semantic Versioning Policy for CLI Tools",{"path":2754,"title":2755},"\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":2757,"title":2758},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fbuilding-wheels-and-sdists-for-python-clis","Building Wheels and sdists for Python CLIs",{"path":2760,"title":2761},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fbundling-data-files-with-importlib-resources","Bundling Data Files with importlib.resources in CLIs",{"path":2763,"title":2764},"\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":2766,"title":2767},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution","Packaging Python CLIs for Distribution",{"path":2769,"title":2770},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Finstalling-and-distributing-clis-with-pipx","Installing and Distributing CLIs with pipx",{"path":2772,"title":2773},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Foptional-dependencies-and-extras-for-clis","Optional Dependencies and Extras for Python CLIs",{"path":2775,"title":2776},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fpublishing-a-python-cli-to-pypi","Publishing a Python CLI to PyPI",{"path":2778,"title":2779},"\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":2781,"title":2782},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fdynamic-versioning-with-poetry-plugins","Dynamic Versioning for a Poetry CLI from Git Tags",{"path":2784,"title":2785},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development","Poetry Workflows for CLI Development",{"path":2787,"title":2788},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fmanaging-poetry-lock-files-for-cli-tools","Managing Poetry Lock Files for CLI Tools",{"path":2790,"title":2791},"\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":2793,"title":2794},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpoetry-dependency-groups-for-cli-tooling","Poetry Dependency Groups for CLI Tooling",{"path":2796,"title":2797},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpoetry-entry-points-and-scripts-for-clis","Poetry Entry Points and Scripts for CLIs",{"path":2799,"title":2800},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpublishing-a-cli-with-poetry","Building and Publishing a Python CLI with Poetry",{"path":2802,"title":2803},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects","Pre-commit Hooks for CLI Projects",{"path":2805,"title":2806},"\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":2808,"title":2809},"\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":2811,"title":2812},"\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":2814,"title":2815},"\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":2817,"title":2818},"\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":2820,"title":2821},"\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":2823,"title":2824},"\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":2826,"title":2827},"\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":2829,"title":2830},"\u002Fproject-setup-dependency-management\u002Fsecuring-a-python-cli-supply-chain","Securing the Supply Chain of a Python CLI",{"path":2832,"title":2833},"\u002Fproject-setup-dependency-management\u002Fsecuring-a-python-cli-supply-chain\u002Fpinning-dependencies-with-hashes","Pinning a Python CLI’s Dependencies with Hashes",{"path":2835,"title":2836},"\u002Fproject-setup-dependency-management\u002Fsecuring-a-python-cli-supply-chain\u002Fpublishing-attestations-and-verifying-releases","Publishing Attestations and Verifying Python CLI Releases",{"path":2838,"title":2839},"\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":2841,"title":2842},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management","uv for Python CLI Dependency Management",{"path":2844,"title":2845},"\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":2847,"title":2848},"\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":2850,"title":2851},"\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":2853,"title":2854},"\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":2856,"title":2857},"\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":2859,"title":2860},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002Fuv-workspaces-for-multi-package-clis","uv Workspaces for Multi-Package Python CLIs",{"path":2862,"title":2863},"\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":2865,"title":2866},"\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":2868,"title":2869},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices","Python CLI Env Isolation Best Practices",{"path":2871,"title":2872},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fisolating-cli-tools-from-project-dependencies","Isolating CLI Tools from Your Project’s Dependencies",{"path":2874,"title":2875},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fmanaging-virtual-environments-for-cross-platform-clis","Managing Python CLI Virtual Environments",{"path":2877,"title":2878},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fpinning-the-python-version-for-a-cli","Pinning the Python Version for a CLI",{"path":2880,"title":2881},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fsupporting-multiple-python-versions-with-nox","Supporting Multiple Python Versions with nox",1790967537200]