[{"data":1,"prerenderedAt":2026},["ShallowReactive",2],{"page-\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Fconfig-precedence-flags-env-files-defaults\u002F":3,"content-directory":1779},{"id":4,"title":5,"body":6,"date":1765,"description":1766,"difficulty":1767,"draft":1768,"extension":1769,"meta":1770,"navigation":252,"path":1771,"seo":1772,"stem":1773,"tags":1774,"updated":1765,"__hash__":1778},"content\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Fconfig-precedence-flags-env-files-defaults\u002Findex.md","Config Precedence: Flags, Env, Files, Defaults",{"type":7,"value":8,"toc":1747},"minimark",[9,22,27,70,74,81,85,179,198,202,209,781,789,818,822,846,849,978,996,1000,1023,1256,1289,1293,1308,1311,1481,1487,1490,1494,1521,1586,1597,1601,1680,1684,1689,1692,1696,1699,1703,1706,1710,1713,1717,1720,1724,1743],[10,11,12,13,17,18,21],"p",{},"A CLI reads the same setting from four places — a flag you typed, an environment variable in your shell, a config file on disk, and a built-in default — and the interesting question is never \"how do I read one,\" it's \"which one wins.\" Get the precedence wrong and users hit the maddening bug where ",[14,15,16],"code",{},"--port 9000"," is silently ignored because a config file quietly overrode it. This page fixes the order once, gives you a runnable resolver that merges all four layers, and shows how to make ",[14,19,20],{},"--help"," reveal where each value actually came from.",[23,24,26],"h2",{"id":25},"tldr","TL;DR",[28,29,30,39,42,49,59],"ul",{},[31,32,33,34,38],"li",{},"The canonical order, highest to lowest: ",[35,36,37],"strong",{},"flags > environment variables > config file > defaults",".",[31,40,41],{},"Merge layers low-to-high into one dict so higher sources overwrite lower ones key by key.",[31,43,44,45,48],{},"Only override with values a source actually set — an unset flag (",[14,46,47],{},"None",") must not clobber a real config value.",[31,50,51,52,55,56,38],{},"Click can do much of this for you with ",[14,53,54],{},"auto_envvar_prefix"," and ",[14,57,58],{},"default_map",[31,60,61,62,65,66,69],{},"Make precedence testable by passing ",[14,63,64],{},"argv",", ",[14,67,68],{},"environ",", and file contents in as arguments instead of reading globals.",[23,71,73],{"id":72},"the-canonical-order-and-why","The canonical order, and why",[10,75,76,77,80],{},"Precedence follows one principle: ",[35,78,79],{},"the closer a value is to the moment of invocation, the more it should win."," A flag is the most immediate expression of intent — you typed it just now, for this run — so it beats everything. An env var is scoped to your shell session or deployment. A config file is the most persistent and shared, so it yields to both. Defaults are the last resort when nobody said anything.",[82,83],"inline-diagram",{"name":84},"config-source-trace",[86,87,88,107],"table",{},[89,90,91],"thead",{},[92,93,94,98,101,104],"tr",{},[95,96,97],"th",{},"Priority",[95,99,100],{},"Source",[95,102,103],{},"Example",[95,105,106],{},"Scope",[108,109,110,126,142,162],"tbody",{},[92,111,112,116,119,123],{},[113,114,115],"td",{},"1 (highest)",[113,117,118],{},"CLI flag",[113,120,121],{},[14,122,16],{},[113,124,125],{},"This invocation",[92,127,128,131,134,139],{},[113,129,130],{},"2",[113,132,133],{},"Environment variable",[113,135,136],{},[14,137,138],{},"MYCLI_PORT=9000",[113,140,141],{},"Session \u002F deployment",[92,143,144,147,150,159],{},[113,145,146],{},"3",[113,148,149],{},"Config file",[113,151,152,155,156],{},[14,153,154],{},"port: 9000"," in ",[14,157,158],{},"config.yaml",[113,160,161],{},"Project \u002F user, persistent",[92,163,164,167,170,176],{},[113,165,166],{},"4 (lowest)",[113,168,169],{},"Built-in default",[113,171,172,175],{},[14,173,174],{},"port = 8000"," in code",[113,177,178],{},"Fallback",[10,180,181,182,65,185,65,188,191,192,197],{},"This is the order users expect because every well-behaved tool they already use — ",[14,183,184],{},"git",[14,186,187],{},"docker",[14,189,190],{},"kubectl"," — works this way. Violate it and the surprise is expensive: nothing is more confusing than a flag that appears to do nothing. (If your file layer itself splits into a project file and a user file, slot them between env and defaults; the ",[193,194,196],"a",{"href":195},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002F","parent config guide"," walks through that five-level chain.)",[23,199,201],{"id":200},"a-runnable-layered-resolver","A runnable layered resolver",[10,203,204,205,208],{},"The mechanic is a chain of ",[14,206,207],{},"dict.update"," calls from lowest priority to highest, so each layer overwrites the ones before it. The one rule that makes it correct: a layer contributes only the keys it actually defines. This program runs as-is:",[210,211,216],"pre",{"className":212,"code":213,"language":214,"meta":215,"style":215},"language-python shiki shiki-themes github-light github-dark","from __future__ import annotations\nimport os\n\nDEFAULTS = {\"host\": \"localhost\", \"port\": 8000, \"timeout\": 10, \"verbose\": False}\n\nENV_MAP = {\n    \"MYCLI_HOST\": \"host\",\n    \"MYCLI_PORT\": \"port\",\n    \"MYCLI_TIMEOUT\": \"timeout\",\n    \"MYCLI_VERBOSE\": \"verbose\",\n}\n\n\ndef from_env(environ: dict[str, str]) -> dict:\n    return {key: environ[name] for name, key in ENV_MAP.items() if name in environ}\n\n\ndef from_flags(flags: dict) -> dict:\n    # Drop unset options so `--port` left off doesn't overwrite lower layers.\n    return {key: value for key, value in flags.items() if value is not None}\n\n\ndef resolve(file_cfg: dict, environ: dict[str, str], flags: dict) -> dict:\n    \"\"\"Merge low -> high: defaults \u003C file \u003C env \u003C flags.\"\"\"\n    merged: dict = {}\n    merged.update(DEFAULTS)          # lowest\n    merged.update(file_cfg)          # config file\n    merged.update(from_env(environ)) # environment\n    merged.update(from_flags(flags)) # flags win\n    return merged\n\n\nif __name__ == \"__main__\":\n    file_cfg = {\"host\": \"db.internal\", \"port\": 5432, \"timeout\": 30}\n    environ = {\"MYCLI_PORT\": \"9000\", \"MYCLI_VERBOSE\": \"true\"}\n    flags = {\"host\": \"cli-host\", \"port\": None, \"timeout\": None, \"verbose\": None}\n    print(resolve(file_cfg, environ, flags))\n","python","",[14,217,218,238,247,254,309,314,325,338,350,362,374,379,384,389,418,453,458,463,483,490,524,529,534,567,573,586,600,609,618,627,635,640,645,661,699,729,772],{"__ignoreMap":215},[219,220,223,227,231,234],"span",{"class":221,"line":222},"line",1,[219,224,226],{"class":225},"szBVR","from",[219,228,230],{"class":229},"sj4cs"," __future__",[219,232,233],{"class":225}," import",[219,235,237],{"class":236},"sVt8B"," annotations\n",[219,239,241,244],{"class":221,"line":240},2,[219,242,243],{"class":225},"import",[219,245,246],{"class":236}," os\n",[219,248,250],{"class":221,"line":249},3,[219,251,253],{"emptyLinePlaceholder":252},true,"\n",[219,255,257,260,263,266,270,273,276,278,281,283,286,288,291,293,296,298,301,303,306],{"class":221,"line":256},4,[219,258,259],{"class":229},"DEFAULTS",[219,261,262],{"class":225}," =",[219,264,265],{"class":236}," {",[219,267,269],{"class":268},"sZZnC","\"host\"",[219,271,272],{"class":236},": ",[219,274,275],{"class":268},"\"localhost\"",[219,277,65],{"class":236},[219,279,280],{"class":268},"\"port\"",[219,282,272],{"class":236},[219,284,285],{"class":229},"8000",[219,287,65],{"class":236},[219,289,290],{"class":268},"\"timeout\"",[219,292,272],{"class":236},[219,294,295],{"class":229},"10",[219,297,65],{"class":236},[219,299,300],{"class":268},"\"verbose\"",[219,302,272],{"class":236},[219,304,305],{"class":229},"False",[219,307,308],{"class":236},"}\n",[219,310,312],{"class":221,"line":311},5,[219,313,253],{"emptyLinePlaceholder":252},[219,315,317,320,322],{"class":221,"line":316},6,[219,318,319],{"class":229},"ENV_MAP",[219,321,262],{"class":225},[219,323,324],{"class":236}," {\n",[219,326,328,331,333,335],{"class":221,"line":327},7,[219,329,330],{"class":268},"    \"MYCLI_HOST\"",[219,332,272],{"class":236},[219,334,269],{"class":268},[219,336,337],{"class":236},",\n",[219,339,341,344,346,348],{"class":221,"line":340},8,[219,342,343],{"class":268},"    \"MYCLI_PORT\"",[219,345,272],{"class":236},[219,347,280],{"class":268},[219,349,337],{"class":236},[219,351,353,356,358,360],{"class":221,"line":352},9,[219,354,355],{"class":268},"    \"MYCLI_TIMEOUT\"",[219,357,272],{"class":236},[219,359,290],{"class":268},[219,361,337],{"class":236},[219,363,365,368,370,372],{"class":221,"line":364},10,[219,366,367],{"class":268},"    \"MYCLI_VERBOSE\"",[219,369,272],{"class":236},[219,371,300],{"class":268},[219,373,337],{"class":236},[219,375,377],{"class":221,"line":376},11,[219,378,308],{"class":236},[219,380,382],{"class":221,"line":381},12,[219,383,253],{"emptyLinePlaceholder":252},[219,385,387],{"class":221,"line":386},13,[219,388,253],{"emptyLinePlaceholder":252},[219,390,392,395,399,402,405,407,409,412,415],{"class":221,"line":391},14,[219,393,394],{"class":225},"def",[219,396,398],{"class":397},"sScJk"," from_env",[219,400,401],{"class":236},"(environ: dict[",[219,403,404],{"class":229},"str",[219,406,65],{"class":236},[219,408,404],{"class":229},[219,410,411],{"class":236},"]) -> ",[219,413,414],{"class":229},"dict",[219,416,417],{"class":236},":\n",[219,419,421,424,427,430,433,436,439,442,445,448,450],{"class":221,"line":420},15,[219,422,423],{"class":225},"    return",[219,425,426],{"class":236}," {key: environ[name] ",[219,428,429],{"class":225},"for",[219,431,432],{"class":236}," name, key ",[219,434,435],{"class":225},"in",[219,437,438],{"class":229}," ENV_MAP",[219,440,441],{"class":236},".items() ",[219,443,444],{"class":225},"if",[219,446,447],{"class":236}," name ",[219,449,435],{"class":225},[219,451,452],{"class":236}," environ}\n",[219,454,456],{"class":221,"line":455},16,[219,457,253],{"emptyLinePlaceholder":252},[219,459,461],{"class":221,"line":460},17,[219,462,253],{"emptyLinePlaceholder":252},[219,464,466,468,471,474,476,479,481],{"class":221,"line":465},18,[219,467,394],{"class":225},[219,469,470],{"class":397}," from_flags",[219,472,473],{"class":236},"(flags: ",[219,475,414],{"class":229},[219,477,478],{"class":236},") -> ",[219,480,414],{"class":229},[219,482,417],{"class":236},[219,484,486],{"class":221,"line":485},19,[219,487,489],{"class":488},"sJ8bj","    # Drop unset options so `--port` left off doesn't overwrite lower layers.\n",[219,491,493,495,498,500,503,505,508,510,513,516,519,522],{"class":221,"line":492},20,[219,494,423],{"class":225},[219,496,497],{"class":236}," {key: value ",[219,499,429],{"class":225},[219,501,502],{"class":236}," key, value ",[219,504,435],{"class":225},[219,506,507],{"class":236}," flags.items() ",[219,509,444],{"class":225},[219,511,512],{"class":236}," value ",[219,514,515],{"class":225},"is",[219,517,518],{"class":225}," not",[219,520,521],{"class":229}," None",[219,523,308],{"class":236},[219,525,527],{"class":221,"line":526},21,[219,528,253],{"emptyLinePlaceholder":252},[219,530,532],{"class":221,"line":531},22,[219,533,253],{"emptyLinePlaceholder":252},[219,535,537,539,542,545,547,550,552,554,556,559,561,563,565],{"class":221,"line":536},23,[219,538,394],{"class":225},[219,540,541],{"class":397}," resolve",[219,543,544],{"class":236},"(file_cfg: ",[219,546,414],{"class":229},[219,548,549],{"class":236},", environ: dict[",[219,551,404],{"class":229},[219,553,65],{"class":236},[219,555,404],{"class":229},[219,557,558],{"class":236},"], flags: ",[219,560,414],{"class":229},[219,562,478],{"class":236},[219,564,414],{"class":229},[219,566,417],{"class":236},[219,568,570],{"class":221,"line":569},24,[219,571,572],{"class":268},"    \"\"\"Merge low -> high: defaults \u003C file \u003C env \u003C flags.\"\"\"\n",[219,574,576,579,581,583],{"class":221,"line":575},25,[219,577,578],{"class":236},"    merged: ",[219,580,414],{"class":229},[219,582,262],{"class":225},[219,584,585],{"class":236}," {}\n",[219,587,589,592,594,597],{"class":221,"line":588},26,[219,590,591],{"class":236},"    merged.update(",[219,593,259],{"class":229},[219,595,596],{"class":236},")          ",[219,598,599],{"class":488},"# lowest\n",[219,601,603,606],{"class":221,"line":602},27,[219,604,605],{"class":236},"    merged.update(file_cfg)          ",[219,607,608],{"class":488},"# config file\n",[219,610,612,615],{"class":221,"line":611},28,[219,613,614],{"class":236},"    merged.update(from_env(environ)) ",[219,616,617],{"class":488},"# environment\n",[219,619,621,624],{"class":221,"line":620},29,[219,622,623],{"class":236},"    merged.update(from_flags(flags)) ",[219,625,626],{"class":488},"# flags win\n",[219,628,630,632],{"class":221,"line":629},30,[219,631,423],{"class":225},[219,633,634],{"class":236}," merged\n",[219,636,638],{"class":221,"line":637},31,[219,639,253],{"emptyLinePlaceholder":252},[219,641,643],{"class":221,"line":642},32,[219,644,253],{"emptyLinePlaceholder":252},[219,646,648,650,653,656,659],{"class":221,"line":647},33,[219,649,444],{"class":225},[219,651,652],{"class":229}," __name__",[219,654,655],{"class":225}," ==",[219,657,658],{"class":268}," \"__main__\"",[219,660,417],{"class":236},[219,662,664,667,670,672,674,676,679,681,683,685,688,690,692,694,697],{"class":221,"line":663},34,[219,665,666],{"class":236},"    file_cfg ",[219,668,669],{"class":225},"=",[219,671,265],{"class":236},[219,673,269],{"class":268},[219,675,272],{"class":236},[219,677,678],{"class":268},"\"db.internal\"",[219,680,65],{"class":236},[219,682,280],{"class":268},[219,684,272],{"class":236},[219,686,687],{"class":229},"5432",[219,689,65],{"class":236},[219,691,290],{"class":268},[219,693,272],{"class":236},[219,695,696],{"class":229},"30",[219,698,308],{"class":236},[219,700,702,705,707,709,712,714,717,719,722,724,727],{"class":221,"line":701},35,[219,703,704],{"class":236},"    environ ",[219,706,669],{"class":225},[219,708,265],{"class":236},[219,710,711],{"class":268},"\"MYCLI_PORT\"",[219,713,272],{"class":236},[219,715,716],{"class":268},"\"9000\"",[219,718,65],{"class":236},[219,720,721],{"class":268},"\"MYCLI_VERBOSE\"",[219,723,272],{"class":236},[219,725,726],{"class":268},"\"true\"",[219,728,308],{"class":236},[219,730,732,735,737,739,741,743,746,748,750,752,754,756,758,760,762,764,766,768,770],{"class":221,"line":731},36,[219,733,734],{"class":236},"    flags ",[219,736,669],{"class":225},[219,738,265],{"class":236},[219,740,269],{"class":268},[219,742,272],{"class":236},[219,744,745],{"class":268},"\"cli-host\"",[219,747,65],{"class":236},[219,749,280],{"class":268},[219,751,272],{"class":236},[219,753,47],{"class":229},[219,755,65],{"class":236},[219,757,290],{"class":268},[219,759,272],{"class":236},[219,761,47],{"class":229},[219,763,65],{"class":236},[219,765,300],{"class":268},[219,767,272],{"class":236},[219,769,47],{"class":229},[219,771,308],{"class":236},[219,773,775,778],{"class":221,"line":774},37,[219,776,777],{"class":229},"    print",[219,779,780],{"class":236},"(resolve(file_cfg, environ, flags))\n",[210,782,787],{"className":783,"code":785,"language":786,"meta":215},[784],"language-text","{'host': 'cli-host', 'port': '9000', 'timeout': 30, 'verbose': 'true'}\n","text",[14,788,785],{"__ignoreMap":215},[10,790,791,792,795,796,799,800,802,803,806,807,810,811,814,815,817],{},"Trace each key: ",[14,793,794],{},"host"," came from the flag, ",[14,797,798],{},"port"," from the env var (overriding the file's ",[14,801,687],{},"), ",[14,804,805],{},"timeout"," from the file (nothing higher set it), and ",[14,808,809],{},"verbose"," from the env. That's the precedence table, executed. The ",[14,812,813],{},"from_flags"," filter is the load-bearing detail — without it, every option your parser defaults to ",[14,816,47],{}," would stomp the config file with a blank.",[23,819,821],{"id":820},"coerce-types-after-merging-not-before","Coerce types after merging, not before",[10,823,824,825,827,828,831,832,835,836,842,843,845],{},"Env vars and many file formats hand you strings: ",[14,826,798],{}," above ends up as ",[14,829,830],{},"'9000'",", not ",[14,833,834],{},"9000",". Resist coercing inside each layer — do it once, at the end, against a schema. That keeps every source honest against the same types and unknown-key rules. Feed the merged dict into a ",[193,837,841],{"href":838,"rel":839},"https:\u002F\u002Fdocs.pydantic.dev\u002Flatest\u002F",[840],"nofollow","Pydantic v2"," model exactly as the ",[193,844,196],{"href":195}," does:",[82,847],{"name":848},"config-coercion-flow",[210,850,852],{"className":212,"code":851,"language":214,"meta":215,"style":215},"from pydantic import BaseModel, ConfigDict\n\nclass AppConfig(BaseModel):\n    model_config = ConfigDict(extra=\"forbid\")\n    host: str = \"localhost\"\n    port: int = 8000\n    timeout: int = 10\n    verbose: bool = False\n\nconfig = AppConfig.model_validate(resolve(file_cfg, environ, flags))\n# port -> 9000 (int), verbose -> True (bool)\n",[14,853,854,866,870,887,909,921,934,946,959,963,973],{"__ignoreMap":215},[219,855,856,858,861,863],{"class":221,"line":222},[219,857,226],{"class":225},[219,859,860],{"class":236}," pydantic ",[219,862,243],{"class":225},[219,864,865],{"class":236}," BaseModel, ConfigDict\n",[219,867,868],{"class":221,"line":240},[219,869,253],{"emptyLinePlaceholder":252},[219,871,872,875,878,881,884],{"class":221,"line":249},[219,873,874],{"class":225},"class",[219,876,877],{"class":397}," AppConfig",[219,879,880],{"class":236},"(",[219,882,883],{"class":397},"BaseModel",[219,885,886],{"class":236},"):\n",[219,888,889,892,894,897,901,903,906],{"class":221,"line":256},[219,890,891],{"class":236},"    model_config ",[219,893,669],{"class":225},[219,895,896],{"class":236}," ConfigDict(",[219,898,900],{"class":899},"s4XuR","extra",[219,902,669],{"class":225},[219,904,905],{"class":268},"\"forbid\"",[219,907,908],{"class":236},")\n",[219,910,911,914,916,918],{"class":221,"line":311},[219,912,913],{"class":236},"    host: ",[219,915,404],{"class":229},[219,917,262],{"class":225},[219,919,920],{"class":268}," \"localhost\"\n",[219,922,923,926,929,931],{"class":221,"line":316},[219,924,925],{"class":236},"    port: ",[219,927,928],{"class":229},"int",[219,930,262],{"class":225},[219,932,933],{"class":229}," 8000\n",[219,935,936,939,941,943],{"class":221,"line":327},[219,937,938],{"class":236},"    timeout: ",[219,940,928],{"class":229},[219,942,262],{"class":225},[219,944,945],{"class":229}," 10\n",[219,947,948,951,954,956],{"class":221,"line":340},[219,949,950],{"class":236},"    verbose: ",[219,952,953],{"class":229},"bool",[219,955,262],{"class":225},[219,957,958],{"class":229}," False\n",[219,960,961],{"class":221,"line":352},[219,962,253],{"emptyLinePlaceholder":252},[219,964,965,968,970],{"class":221,"line":364},[219,966,967],{"class":236},"config ",[219,969,669],{"class":225},[219,971,972],{"class":236}," AppConfig.model_validate(resolve(file_cfg, environ, flags))\n",[219,974,975],{"class":221,"line":376},[219,976,977],{"class":488},"# port -> 9000 (int), verbose -> True (bool)\n",[10,979,980,981,983,984,55,986,983,988,991,992,38],{},"Validating after the merge means ",[14,982,716],{}," becomes ",[14,985,834],{},[14,987,726],{},[14,989,990],{},"True"," in one place, and a typo'd key fails loudly instead of being silently dropped. The YAML side of that file layer — loading it safely — is covered in ",[193,993,995],{"href":994},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Floading-yaml-configs-safely-in-cli-apps\u002F","loading YAML configs safely",[23,997,999],{"id":998},"letting-click-layer-it-for-you","Letting Click layer it for you",[10,1001,1002,1003,1005,1006,1009,1010,1013,1014,1016,1017,1019,1020,38],{},"If you're on Click, two built-ins cover the env and file layers so you write less merge code. ",[14,1004,54],{}," reads ",[14,1007,1008],{},"MYCLI_PORT"," for a ",[14,1011,1012],{},"--port"," option automatically, and ",[14,1015,58],{}," (set from a loaded config file in the group callback) supplies file-level defaults. Click's own resolution order is exactly the canonical one: an explicit flag beats the env var, which beats ",[14,1018,58],{},", which beats the option's ",[14,1021,1022],{},"default",[210,1024,1026],{"className":212,"code":1025,"language":214,"meta":215,"style":215},"import click\n\ndef load_file_config() -> dict:\n    # Read + parse your config.yaml\u002Ftoml here; return {} if absent.\n    return {\"port\": 5432, \"host\": \"db.internal\"}\n\n@click.group(context_settings={\"auto_envvar_prefix\": \"MYCLI\"})\n@click.pass_context\ndef cli(ctx: click.Context) -> None:\n    ctx.default_map = load_file_config()   # file layer feeds option defaults\n\n@cli.command()\n@click.option(\"--host\", default=\"localhost\")\n@click.option(\"--port\", type=int, default=8000)\ndef serve(host: str, port: int) -> None:\n    click.echo(f\"{host}:{port}\")\n",[14,1027,1028,1035,1039,1053,1058,1080,1084,1110,1115,1129,1142,1146,1154,1174,1202,1225],{"__ignoreMap":215},[219,1029,1030,1032],{"class":221,"line":222},[219,1031,243],{"class":225},[219,1033,1034],{"class":236}," click\n",[219,1036,1037],{"class":221,"line":240},[219,1038,253],{"emptyLinePlaceholder":252},[219,1040,1041,1043,1046,1049,1051],{"class":221,"line":249},[219,1042,394],{"class":225},[219,1044,1045],{"class":397}," load_file_config",[219,1047,1048],{"class":236},"() -> ",[219,1050,414],{"class":229},[219,1052,417],{"class":236},[219,1054,1055],{"class":221,"line":256},[219,1056,1057],{"class":488},"    # Read + parse your config.yaml\u002Ftoml here; return {} if absent.\n",[219,1059,1060,1062,1064,1066,1068,1070,1072,1074,1076,1078],{"class":221,"line":311},[219,1061,423],{"class":225},[219,1063,265],{"class":236},[219,1065,280],{"class":268},[219,1067,272],{"class":236},[219,1069,687],{"class":229},[219,1071,65],{"class":236},[219,1073,269],{"class":268},[219,1075,272],{"class":236},[219,1077,678],{"class":268},[219,1079,308],{"class":236},[219,1081,1082],{"class":221,"line":316},[219,1083,253],{"emptyLinePlaceholder":252},[219,1085,1086,1089,1091,1094,1096,1099,1102,1104,1107],{"class":221,"line":327},[219,1087,1088],{"class":397},"@click.group",[219,1090,880],{"class":236},[219,1092,1093],{"class":899},"context_settings",[219,1095,669],{"class":225},[219,1097,1098],{"class":236},"{",[219,1100,1101],{"class":268},"\"auto_envvar_prefix\"",[219,1103,272],{"class":236},[219,1105,1106],{"class":268},"\"MYCLI\"",[219,1108,1109],{"class":236},"})\n",[219,1111,1112],{"class":221,"line":340},[219,1113,1114],{"class":397},"@click.pass_context\n",[219,1116,1117,1119,1122,1125,1127],{"class":221,"line":352},[219,1118,394],{"class":225},[219,1120,1121],{"class":397}," cli",[219,1123,1124],{"class":236},"(ctx: click.Context) -> ",[219,1126,47],{"class":229},[219,1128,417],{"class":236},[219,1130,1131,1134,1136,1139],{"class":221,"line":364},[219,1132,1133],{"class":236},"    ctx.default_map ",[219,1135,669],{"class":225},[219,1137,1138],{"class":236}," load_file_config()   ",[219,1140,1141],{"class":488},"# file layer feeds option defaults\n",[219,1143,1144],{"class":221,"line":376},[219,1145,253],{"emptyLinePlaceholder":252},[219,1147,1148,1151],{"class":221,"line":381},[219,1149,1150],{"class":397},"@cli.command",[219,1152,1153],{"class":236},"()\n",[219,1155,1156,1159,1161,1164,1166,1168,1170,1172],{"class":221,"line":386},[219,1157,1158],{"class":397},"@click.option",[219,1160,880],{"class":236},[219,1162,1163],{"class":268},"\"--host\"",[219,1165,65],{"class":236},[219,1167,1022],{"class":899},[219,1169,669],{"class":225},[219,1171,275],{"class":268},[219,1173,908],{"class":236},[219,1175,1176,1178,1180,1183,1185,1188,1190,1192,1194,1196,1198,1200],{"class":221,"line":391},[219,1177,1158],{"class":397},[219,1179,880],{"class":236},[219,1181,1182],{"class":268},"\"--port\"",[219,1184,65],{"class":236},[219,1186,1187],{"class":899},"type",[219,1189,669],{"class":225},[219,1191,928],{"class":229},[219,1193,65],{"class":236},[219,1195,1022],{"class":899},[219,1197,669],{"class":225},[219,1199,285],{"class":229},[219,1201,908],{"class":236},[219,1203,1204,1206,1209,1212,1214,1217,1219,1221,1223],{"class":221,"line":420},[219,1205,394],{"class":225},[219,1207,1208],{"class":397}," serve",[219,1210,1211],{"class":236},"(host: ",[219,1213,404],{"class":229},[219,1215,1216],{"class":236},", port: ",[219,1218,928],{"class":229},[219,1220,478],{"class":236},[219,1222,47],{"class":229},[219,1224,417],{"class":236},[219,1226,1227,1230,1233,1236,1238,1240,1243,1246,1248,1250,1252,1254],{"class":221,"line":455},[219,1228,1229],{"class":236},"    click.echo(",[219,1231,1232],{"class":225},"f",[219,1234,1235],{"class":268},"\"",[219,1237,1098],{"class":229},[219,1239,794],{"class":236},[219,1241,1242],{"class":229},"}",[219,1244,1245],{"class":268},":",[219,1247,1098],{"class":229},[219,1249,798],{"class":236},[219,1251,1242],{"class":229},[219,1253,1235],{"class":268},[219,1255,908],{"class":236},[10,1257,1258,1259,1262,1263,1265,1266,1268,1269,1271,1272,1274,1275,1277,1278,1280,1281,1284,1285,38],{},"Now ",[14,1260,1261],{},"serve"," resolves ",[14,1264,798],{}," from ",[14,1267,1012],{}," if given, else ",[14,1270,1008],{},", else the file's ",[14,1273,687],{},", else ",[14,1276,285],{}," — no manual merge. The ",[14,1279,58],{}," is keyed by command name for groups (",[14,1282,1283],{},"{\"serve\": {\"port\": ...}}","), so nest it accordingly. Sharing that loaded config across subcommands is a natural fit for a ",[193,1286,1288],{"href":1287},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fsharing-state-with-click-context-objects\u002F","Click context object",[23,1290,1292],{"id":1291},"show-users-where-a-value-came-from","Show users where a value came from",[10,1294,1295,1296,1300,1301,1304,1305,1307],{},"Precedence is invisible until it bites, so the best CLIs make the effective source discoverable. Instead of only resolving the value, resolve the ",[1297,1298,1299],"em",{},"origin"," alongside it, and expose it behind a ",[14,1302,1303],{},"--show-config"," flag or in ",[14,1306,20],{},"'s epilog:",[82,1309],{"name":1310},"config-origin-terminal",[210,1312,1314],{"className":212,"code":1313,"language":214,"meta":215,"style":215},"def resolve_with_source(file_cfg, environ, flags) -> dict[str, tuple]:\n    env_cfg, flag_cfg = from_env(environ), from_flags(flags)\n    result = {}\n    for key in DEFAULTS:\n        if key in flag_cfg:\n            result[key] = (flag_cfg[key], \"flag\")\n        elif key in env_cfg:\n            result[key] = (env_cfg[key], \"env\")\n        elif key in file_cfg:\n            result[key] = (file_cfg[key], \"file\")\n        else:\n            result[key] = (DEFAULTS[key], \"default\")\n    return result\n",[14,1315,1316,1336,1346,1355,1370,1382,1397,1409,1423,1434,1448,1455,1474],{"__ignoreMap":215},[219,1317,1318,1320,1323,1326,1328,1330,1333],{"class":221,"line":222},[219,1319,394],{"class":225},[219,1321,1322],{"class":397}," resolve_with_source",[219,1324,1325],{"class":236},"(file_cfg, environ, flags) -> dict[",[219,1327,404],{"class":229},[219,1329,65],{"class":236},[219,1331,1332],{"class":229},"tuple",[219,1334,1335],{"class":236},"]:\n",[219,1337,1338,1341,1343],{"class":221,"line":240},[219,1339,1340],{"class":236},"    env_cfg, flag_cfg ",[219,1342,669],{"class":225},[219,1344,1345],{"class":236}," from_env(environ), from_flags(flags)\n",[219,1347,1348,1351,1353],{"class":221,"line":249},[219,1349,1350],{"class":236},"    result ",[219,1352,669],{"class":225},[219,1354,585],{"class":236},[219,1356,1357,1360,1363,1365,1368],{"class":221,"line":256},[219,1358,1359],{"class":225},"    for",[219,1361,1362],{"class":236}," key ",[219,1364,435],{"class":225},[219,1366,1367],{"class":229}," DEFAULTS",[219,1369,417],{"class":236},[219,1371,1372,1375,1377,1379],{"class":221,"line":311},[219,1373,1374],{"class":225},"        if",[219,1376,1362],{"class":236},[219,1378,435],{"class":225},[219,1380,1381],{"class":236}," flag_cfg:\n",[219,1383,1384,1387,1389,1392,1395],{"class":221,"line":316},[219,1385,1386],{"class":236},"            result[key] ",[219,1388,669],{"class":225},[219,1390,1391],{"class":236}," (flag_cfg[key], ",[219,1393,1394],{"class":268},"\"flag\"",[219,1396,908],{"class":236},[219,1398,1399,1402,1404,1406],{"class":221,"line":327},[219,1400,1401],{"class":225},"        elif",[219,1403,1362],{"class":236},[219,1405,435],{"class":225},[219,1407,1408],{"class":236}," env_cfg:\n",[219,1410,1411,1413,1415,1418,1421],{"class":221,"line":340},[219,1412,1386],{"class":236},[219,1414,669],{"class":225},[219,1416,1417],{"class":236}," (env_cfg[key], ",[219,1419,1420],{"class":268},"\"env\"",[219,1422,908],{"class":236},[219,1424,1425,1427,1429,1431],{"class":221,"line":352},[219,1426,1401],{"class":225},[219,1428,1362],{"class":236},[219,1430,435],{"class":225},[219,1432,1433],{"class":236}," file_cfg:\n",[219,1435,1436,1438,1440,1443,1446],{"class":221,"line":364},[219,1437,1386],{"class":236},[219,1439,669],{"class":225},[219,1441,1442],{"class":236}," (file_cfg[key], ",[219,1444,1445],{"class":268},"\"file\"",[219,1447,908],{"class":236},[219,1449,1450,1453],{"class":221,"line":376},[219,1451,1452],{"class":225},"        else",[219,1454,417],{"class":236},[219,1456,1457,1459,1461,1464,1466,1469,1472],{"class":221,"line":381},[219,1458,1386],{"class":236},[219,1460,669],{"class":225},[219,1462,1463],{"class":236}," (",[219,1465,259],{"class":229},[219,1467,1468],{"class":236},"[key], ",[219,1470,1471],{"class":268},"\"default\"",[219,1473,908],{"class":236},[219,1475,1476,1478],{"class":221,"line":386},[219,1477,423],{"class":225},[219,1479,1480],{"class":236}," result\n",[210,1482,1485],{"className":1483,"code":1484,"language":786,"meta":215},[784],"$ mycli --show-config\nhost    = cli-host   (flag)\nport    = 9000       (env)\ntimeout = 30         (file)\nverbose = true       (env)\n",[14,1486,1484],{"__ignoreMap":215},[10,1488,1489],{},"That table turns \"why is my flag ignored?\" support tickets into a five-second self-diagnosis. It's cheap to build because it reuses the same per-layer dicts your resolver already computes.",[23,1491,1493],{"id":1492},"per-key-versus-whole-file-override","Per-key versus whole-file override",[10,1495,1496,1497,1500,1501,1504,1505,1507,1508,1510,1511,1513,1514,1516,1517,1520],{},"Decide explicitly whether a higher layer overrides a config file ",[35,1498,1499],{},"per key"," or ",[35,1502,1503],{},"wholesale",". The ",[14,1506,207],{}," approach above is per-key and shallow: setting ",[14,1509,1008],{}," overrides only ",[14,1512,798],{},", leaving the file's ",[14,1515,794],{}," intact — which is almost always what users want. The trap is nesting. If your config has a nested table, a shallow update replaces the ",[1297,1518,1519],{},"entire"," sub-table, silently dropping sibling keys:",[210,1522,1524],{"className":212,"code":1523,"language":214,"meta":215,"style":215},"file_cfg = {\"db\": {\"host\": \"a\", \"port\": 1}}\noverride = {\"db\": {\"port\": 2}}\n# shallow: {\"db\": {\"port\": 2}} — \"host\" is GONE\n",[14,1525,1526,1560,1581],{"__ignoreMap":215},[219,1527,1528,1531,1533,1535,1538,1541,1543,1545,1548,1550,1552,1554,1557],{"class":221,"line":222},[219,1529,1530],{"class":236},"file_cfg ",[219,1532,669],{"class":225},[219,1534,265],{"class":236},[219,1536,1537],{"class":268},"\"db\"",[219,1539,1540],{"class":236},": {",[219,1542,269],{"class":268},[219,1544,272],{"class":236},[219,1546,1547],{"class":268},"\"a\"",[219,1549,65],{"class":236},[219,1551,280],{"class":268},[219,1553,272],{"class":236},[219,1555,1556],{"class":229},"1",[219,1558,1559],{"class":236},"}}\n",[219,1561,1562,1565,1567,1569,1571,1573,1575,1577,1579],{"class":221,"line":240},[219,1563,1564],{"class":236},"override ",[219,1566,669],{"class":225},[219,1568,265],{"class":236},[219,1570,1537],{"class":268},[219,1572,1540],{"class":236},[219,1574,280],{"class":268},[219,1576,272],{"class":236},[219,1578,130],{"class":229},[219,1580,1559],{"class":236},[219,1582,1583],{"class":221,"line":249},[219,1584,1585],{"class":488},"# shallow: {\"db\": {\"port\": 2}} — \"host\" is GONE\n",[10,1587,1588,1589,1592,1593,1596],{},"If you support nested config, deep-merge the mappings recursively so ",[14,1590,1591],{},"db.host"," survives while ",[14,1594,1595],{},"db.port"," is overridden. For flat config, the shallow merge is correct and simpler — don't reach for recursion you don't need.",[23,1598,1600],{"id":1599},"production-notes","Production notes",[28,1602,1603,1616,1624,1652,1670],{},[31,1604,1605,1608,1609,1611,1612,1615],{},[35,1606,1607],{},"Flags default to a sentinel, not a value."," Give options a ",[14,1610,47],{}," default (or ",[14,1613,1614],{},"click","'s automatic one) so \"unset\" is distinguishable from \"set to the default.\" Otherwise you can't tell whether to override a lower layer.",[31,1617,1618,1623],{},[35,1619,1620,1621,38],{},"Document the order in ",[14,1622,20],{}," State \"flags > env > config > defaults\" once so users never have to reverse-engineer it.",[31,1625,1626,1629,1630,1633,1634,65,1637,1639,1640,1643,1644,1647,1648,1651],{},[35,1627,1628],{},"Test precedence with a table."," Because ",[14,1631,1632],{},"resolve"," takes ",[14,1635,1636],{},"file_cfg",[14,1638,68],{},", and ",[14,1641,1642],{},"flags"," as arguments, a ",[14,1645,1646],{},"pytest.mark.parametrize"," sweep over combinations pins every rule; don't read ",[14,1649,1650],{},"os.environ"," directly inside the resolver.",[31,1653,1654,1657,1658,1661,1662,1665,1666,38],{},[35,1655,1656],{},"Verbosity flows through here too."," A ",[14,1659,1660],{},"--verbose"," flag should beat ",[14,1663,1664],{},"MYCLI_VERBOSE"," should beat a config value — wire it through this same chain rather than special-casing it, as noted in ",[193,1667,1669],{"href":1668},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fadding-verbose-and-quiet-logging-flags\u002F","adding verbose and quiet flags",[31,1671,1672,1675,1676,1679],{},[35,1673,1674],{},"TOML is identical."," Swap the file parser for ",[14,1677,1678],{},"tomllib"," (stdlib since 3.11); the merge and validation layers are unchanged.",[23,1681,1683],{"id":1682},"frequently-asked-questions","Frequently asked questions",[1685,1686,1688],"h3",{"id":1687},"why-do-flags-beat-environment-variables-and-not-the-other-way-round","Why do flags beat environment variables and not the other way round?",[10,1690,1691],{},"Because precedence follows immediacy. A flag was typed for this one invocation, so it is the most specific statement of intent available; an environment variable is scoped to a shell session or a CI job, and a file is shared by everyone who checks out the repository. Inverting the order means a variable someone exported months ago silently overrides what they just typed, which is the single most confusing thing a configuration system can do.",[1685,1693,1695],{"id":1694},"how-do-i-stop-an-unset-flag-from-clobbering-a-configured-value","How do I stop an unset flag from clobbering a configured value?",[10,1697,1698],{},"Only merge keys a source actually set. With Click that means giving options no default (so an untouched option arrives as None) and filtering the None values out before the merge; with argparse it means using default=argparse.SUPPRESS so absent options never appear in the namespace at all. A parser default of 3 is indistinguishable from the user typing 3, which is why defaults belong in the lowest layer rather than on the option.",[1685,1700,1702],{"id":1701},"should-a-missing-config-file-be-an-error","Should a missing config file be an error?",[10,1704,1705],{},"It depends how it was named. A file the user pointed at explicitly with --config must fail loudly if it is missing — they asked for it, and silently continuing with defaults will waste an afternoon. A file found by convention in the project or home directory is allowed to be absent; that is the whole point of a search path.",[1685,1707,1709],{"id":1708},"can-click-do-the-layering-for-me","Can Click do the layering for me?",[10,1711,1712],{},"Partly, and it is worth using. auto_envvar_prefix maps MYTOOL_RETRIES onto --retries with no extra code, and default_map lets a group callback inject values read from a file so they sit below flags but above the built-in defaults. What Click will not do is decide what happens when two sources disagree about a nested structure, so anything beyond flat keys still wants an explicit resolver you can test.",[1685,1714,1716],{"id":1715},"how-do-i-make-precedence-testable","How do I make precedence testable?",[10,1718,1719],{},"Pass the inputs in rather than reading globals. A resolver whose signature takes argv, an environment mapping and the file contents can be tested in a dozen lines with no monkeypatching, no temporary directories and no risk that a developer's own exported variables change the result. Reading os.environ inside the resolver makes every test order-dependent.",[23,1721,1723],{"id":1722},"related","Related",[28,1725,1726,1732,1738],{},[31,1727,1728,1729],{},"Up: ",[193,1730,1731],{"href":195},"Handling config files and env vars in CLIs",[31,1733,1734,1735],{},"Sideways: ",[193,1736,1737],{"href":994},"Loading YAML configs safely in CLI apps",[31,1739,1734,1740],{},[193,1741,1742],{"href":1668},"Adding verbose and quiet logging flags",[1744,1745,1746],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}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 .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":215,"searchDepth":240,"depth":240,"links":1748},[1749,1750,1751,1752,1753,1754,1755,1756,1757,1764],{"id":25,"depth":240,"text":26},{"id":72,"depth":240,"text":73},{"id":200,"depth":240,"text":201},{"id":820,"depth":240,"text":821},{"id":998,"depth":240,"text":999},{"id":1291,"depth":240,"text":1292},{"id":1492,"depth":240,"text":1493},{"id":1599,"depth":240,"text":1600},{"id":1682,"depth":240,"text":1683,"children":1758},[1759,1760,1761,1762,1763],{"id":1687,"depth":249,"text":1688},{"id":1694,"depth":249,"text":1695},{"id":1701,"depth":249,"text":1702},{"id":1708,"depth":249,"text":1709},{"id":1715,"depth":249,"text":1716},{"id":1722,"depth":240,"text":1723},"2026-07-05","Layer Python CLI configuration correctly: merge command-line flags, environment variables, config files, and defaults with predictable precedence.","intermediate",false,"md",{},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Fconfig-precedence-flags-env-files-defaults",{"title":5,"description":1766},"advanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Fconfig-precedence-flags-env-files-defaults\u002Findex",[1775,1776,1777,1614],"config","precedence","cli","QkxC8E8WKNIT6X-Wjf1CzNbQKRH0rCi1XSJv6pvatMk",[1780,1783,1786,1789,1792,1795,1798,1801,1804,1807,1810,1813,1816,1819,1820,1823,1825,1828,1831,1834,1837,1840,1843,1846,1849,1852,1855,1858,1861,1864,1867,1870,1873,1876,1879,1882,1885,1888,1891,1894,1897,1900,1903,1906,1909,1912,1915,1918,1921,1924,1927,1930,1933,1936,1939,1942,1945,1948,1951,1954,1957,1960,1963,1966,1969,1972,1975,1978,1981,1984,1987,1990,1993,1996,1999,2002,2005,2008,2011,2014,2017,2020,2023],{"path":1781,"title":1782},"\u002Fabout","About Python CLI Toolcraft",{"path":1784,"title":1785},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies","Advanced Argument Validation Strategies",{"path":1787,"title":1788},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fparsing-nested-json-arguments-in-python-clis","Parsing Nested JSON Args in Python CLIs",{"path":1790,"title":1791},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fvalidating-file-and-directory-paths-in-clis","Validating File and Directory Paths in CLIs",{"path":1793,"title":1794},"\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":1796,"title":1797},"\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":1799,"title":1800},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation","CLI Help Output and Documentation",{"path":1802,"title":1803},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fversioning-and-deprecating-cli-flags","Versioning and Deprecating CLI Flags",{"path":1805,"title":1806},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fwriting-help-text-users-actually-read","Writing Help Text Users Actually Read",{"path":1808,"title":1809},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fchoosing-exit-codes-for-cli-tools","Choosing Exit Codes for CLI Tools",{"path":1811,"title":1812},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Ffriendly-error-messages-and-tracebacks","Friendly Error Messages and Tracebacks",{"path":1814,"title":1815},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fhandling-keyboard-interrupt-cleanly","Handling Keyboard Interrupt Cleanly",{"path":1817,"title":1818},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes","Error Handling and Exit Codes for CLIs",{"path":1771,"title":5},{"path":1821,"title":1822},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars","Handling Config Files and Env Vars in CLIs",{"path":1824,"title":1737},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Floading-yaml-configs-safely-in-cli-apps",{"path":1826,"title":1827},"\u002Fadvanced-input-parsing-user-experience","Advanced Input Parsing for Python CLIs",{"path":1829,"title":1830},"\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":1832,"title":1833},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich","Interactive Terminal UI with Rich",{"path":1835,"title":1836},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Frendering-tables-and-json-with-rich","Rendering Tables and JSON with Rich",{"path":1838,"title":1839},"\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":1841,"title":1842},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis","Shell Completion for Python CLIs",{"path":1844,"title":1845},"\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":1847,"title":1848},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fadding-verbose-and-quiet-logging-flags","Adding Verbose and Quiet Logging Flags",{"path":1850,"title":1851},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps","Structured Logging for CLI Apps",{"path":1853,"title":1854},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fstructured-json-logging-in-python-clis","Structured JSON Logging in Python CLIs",{"path":1856,"title":1857},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Fdetecting-tty-and-adapting-output","Detecting a TTY and Adapting Output",{"path":1859,"title":1860},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Femitting-json-output-for-scripting","Emitting JSON Output for Scripting",{"path":1862,"title":1863},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Fhandling-broken-pipe-and-sigpipe","Handling Broken Pipe and SIGPIPE",{"path":1865,"title":1866},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes","Working with stdin, stdout and Pipes",{"path":1868,"title":1869},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Freading-piped-input-in-python-clis","Reading Piped Input in Python CLIs",{"path":1871,"title":1872},"\u002F","Python CLI Toolcraft",{"path":1874,"title":1875},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading","CLI Startup Performance and Lazy Loading",{"path":1877,"title":1878},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Flazy-loading-subcommands-for-faster-startup","Lazy Loading Subcommands for Faster Startup",{"path":1880,"title":1881},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Fprofiling-python-cli-startup-time","Profiling Python CLI Startup Time",{"path":1883,"title":1884},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Freducing-cli-dependency-weight","Reducing CLI Dependency Weight",{"path":1886,"title":1887},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargparse-subparsers-for-subcommands","argparse Subparsers for Subcommands",{"path":1889,"title":1890},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargparse-vs-click-vs-typer-comparison","argparse vs Click vs Typer Compared",{"path":1892,"title":1893},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse","Command-Line Parsing with argparse",{"path":1895,"title":1896},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fmigrating-from-argparse-to-typer","Migrating from argparse to Typer",{"path":1898,"title":1899},"\u002Fmodern-python-cli-frameworks-architecture","Python CLI Frameworks and Architecture",{"path":1901,"title":1902},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis","Plugin Architectures for Extensible CLIs",{"path":1904,"title":1905},"\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":1907,"title":1908},"\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":1910,"title":1911},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fdependency-injection-patterns-for-cli-commands","Dependency Injection Patterns for CLI Commands",{"path":1913,"title":1914},"\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":1916,"title":1917},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis","Structuring Multi-Command Python CLIs",{"path":1919,"title":1920},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fsharing-state-with-click-context-objects","Sharing State with Click Context Objects",{"path":1922,"title":1923},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications","Testing Python CLI Applications",{"path":1925,"title":1926},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fmeasuring-cli-test-coverage","Measuring CLI Test Coverage",{"path":1928,"title":1929},"\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":1931,"title":1932},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fsnapshot-testing-cli-output","Snapshot Testing CLI Output",{"path":1934,"title":1935},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftesting-click-commands-with-clirunner","Testing Click Commands with CliRunner",{"path":1937,"title":1938},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftesting-interactive-prompts-and-stdin","Testing Interactive Prompts and stdin",{"path":1940,"title":1941},"\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":1943,"title":1944},"\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":1946,"title":1947},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each","Typer vs Click: When to Use Each",{"path":1949,"title":1950},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Ftyper-callback-functions-explained","Typer callback functions explained",{"path":1952,"title":1953},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fcopier-vs-cookiecutter-for-cli-templates","Copier vs Cookiecutter for CLI Templates",{"path":1955,"title":1956},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter","CLI Project Scaffolding with Cookiecutter",{"path":1958,"title":1959},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fbuilding-cross-platform-release-binaries-in-ci","Building Cross-Platform Release Binaries in CI",{"path":1961,"title":1962},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fbundling-a-python-cli-with-pyinstaller","Bundling a Python CLI with PyInstaller",{"path":1964,"title":1965},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fhomebrew-and-scoop-packaging-for-python-clis","Homebrew and Scoop Packaging for Python CLIs",{"path":1967,"title":1968},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries","Distributing CLIs as Standalone Binaries",{"path":1970,"title":1971},"\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":1973,"title":1974},"\u002Fproject-setup-dependency-management","Project Setup & Dependency Management",{"path":1976,"title":1977},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fautomating-changelogs-with-conventional-commits","Automating Changelogs with Conventional Commits",{"path":1979,"title":1980},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fexposing-version-info-and-build-metadata","Exposing Version Info and Build Metadata",{"path":1982,"title":1983},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs","Managing CLI Versioning & Changelogs",{"path":1985,"title":1986},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fbuilding-wheels-and-sdists-for-python-clis","Building Wheels and sdists for Python CLIs",{"path":1988,"title":1989},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution","Packaging Python CLIs for Distribution",{"path":1991,"title":1992},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Finstalling-and-distributing-clis-with-pipx","Installing and Distributing CLIs with pipx",{"path":1994,"title":1995},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fpublishing-a-python-cli-to-pypi","Publishing a Python CLI to PyPI",{"path":1997,"title":1998},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development","Poetry Workflows for CLI Development",{"path":2000,"title":2001},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpoetry-entry-points-and-scripts-for-clis","Poetry Entry Points and Scripts for CLIs",{"path":2003,"title":2004},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects","Pre-commit Hooks for CLI Projects",{"path":2006,"title":2007},"\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":2009,"title":2010},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management","uv for Python CLI Dependency Management",{"path":2012,"title":2013},"\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":2015,"title":2016},"\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":2018,"title":2019},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices","Python CLI Env Isolation Best Practices",{"path":2021,"title":2022},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fmanaging-virtual-environments-for-cross-platform-clis","Managing Python CLI Virtual Environments",{"path":2024,"title":2025},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fpinning-the-python-version-for-a-cli","Pinning the Python Version for a CLI",1785614690028]