[{"data":1,"prerenderedAt":1880},["ShallowReactive",2],{"page-\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Fconfig-precedence-flags-env-files-defaults\u002F":3,"content-directory":1729},{"id":4,"title":5,"body":6,"date":1715,"description":1716,"difficulty":1717,"draft":1718,"extension":1719,"meta":1720,"navigation":255,"path":1721,"seo":1722,"stem":1723,"tags":1724,"updated":1715,"__hash__":1728},"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":1704},"minimark",[9,22,27,70,77,81,88,182,201,205,212,784,792,821,825,849,978,996,1000,1023,1256,1289,1293,1308,1478,1484,1487,1491,1518,1583,1594,1598,1677,1681,1700],[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.",[10,71,72],{},[73,74],"img",{"alt":75,"src":76},"Configuration precedence from highest to lowest: command-line flags override environment variables, which override the config file, which overrides built-in defaults.","\u002Fillustrations\u002Fconfig-precedence.svg",[23,78,80],{"id":79},"the-canonical-order-and-why","The canonical order, and why",[10,82,83,84,87],{},"Precedence follows one principle: ",[35,85,86],{},"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.",[89,90,91,110],"table",{},[92,93,94],"thead",{},[95,96,97,101,104,107],"tr",{},[98,99,100],"th",{},"Priority",[98,102,103],{},"Source",[98,105,106],{},"Example",[98,108,109],{},"Scope",[111,112,113,129,145,165],"tbody",{},[95,114,115,119,122,126],{},[116,117,118],"td",{},"1 (highest)",[116,120,121],{},"CLI flag",[116,123,124],{},[14,125,16],{},[116,127,128],{},"This invocation",[95,130,131,134,137,142],{},[116,132,133],{},"2",[116,135,136],{},"Environment variable",[116,138,139],{},[14,140,141],{},"MYCLI_PORT=9000",[116,143,144],{},"Session \u002F deployment",[95,146,147,150,153,162],{},[116,148,149],{},"3",[116,151,152],{},"Config file",[116,154,155,158,159],{},[14,156,157],{},"port: 9000"," in ",[14,160,161],{},"config.yaml",[116,163,164],{},"Project \u002F user, persistent",[95,166,167,170,173,179],{},[116,168,169],{},"4 (lowest)",[116,171,172],{},"Built-in default",[116,174,175,178],{},[14,176,177],{},"port = 8000"," in code",[116,180,181],{},"Fallback",[10,183,184,185,65,188,65,191,194,195,200],{},"This is the order users expect because every well-behaved tool they already use — ",[14,186,187],{},"git",[14,189,190],{},"docker",[14,192,193],{},"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 ",[196,197,199],"a",{"href":198},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002F","parent config guide"," walks through that five-level chain.)",[23,202,204],{"id":203},"a-runnable-layered-resolver","A runnable layered resolver",[10,206,207,208,211],{},"The mechanic is a chain of ",[14,209,210],{},"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:",[213,214,219],"pre",{"className":215,"code":216,"language":217,"meta":218,"style":218},"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,220,221,241,250,257,312,317,328,341,353,365,377,382,387,392,421,456,461,466,486,493,527,532,537,570,576,589,603,612,621,630,638,643,648,664,702,732,775],{"__ignoreMap":218},[222,223,226,230,234,237],"span",{"class":224,"line":225},"line",1,[222,227,229],{"class":228},"szBVR","from",[222,231,233],{"class":232},"sj4cs"," __future__",[222,235,236],{"class":228}," import",[222,238,240],{"class":239},"sVt8B"," annotations\n",[222,242,244,247],{"class":224,"line":243},2,[222,245,246],{"class":228},"import",[222,248,249],{"class":239}," os\n",[222,251,253],{"class":224,"line":252},3,[222,254,256],{"emptyLinePlaceholder":255},true,"\n",[222,258,260,263,266,269,273,276,279,281,284,286,289,291,294,296,299,301,304,306,309],{"class":224,"line":259},4,[222,261,262],{"class":232},"DEFAULTS",[222,264,265],{"class":228}," =",[222,267,268],{"class":239}," {",[222,270,272],{"class":271},"sZZnC","\"host\"",[222,274,275],{"class":239},": ",[222,277,278],{"class":271},"\"localhost\"",[222,280,65],{"class":239},[222,282,283],{"class":271},"\"port\"",[222,285,275],{"class":239},[222,287,288],{"class":232},"8000",[222,290,65],{"class":239},[222,292,293],{"class":271},"\"timeout\"",[222,295,275],{"class":239},[222,297,298],{"class":232},"10",[222,300,65],{"class":239},[222,302,303],{"class":271},"\"verbose\"",[222,305,275],{"class":239},[222,307,308],{"class":232},"False",[222,310,311],{"class":239},"}\n",[222,313,315],{"class":224,"line":314},5,[222,316,256],{"emptyLinePlaceholder":255},[222,318,320,323,325],{"class":224,"line":319},6,[222,321,322],{"class":232},"ENV_MAP",[222,324,265],{"class":228},[222,326,327],{"class":239}," {\n",[222,329,331,334,336,338],{"class":224,"line":330},7,[222,332,333],{"class":271},"    \"MYCLI_HOST\"",[222,335,275],{"class":239},[222,337,272],{"class":271},[222,339,340],{"class":239},",\n",[222,342,344,347,349,351],{"class":224,"line":343},8,[222,345,346],{"class":271},"    \"MYCLI_PORT\"",[222,348,275],{"class":239},[222,350,283],{"class":271},[222,352,340],{"class":239},[222,354,356,359,361,363],{"class":224,"line":355},9,[222,357,358],{"class":271},"    \"MYCLI_TIMEOUT\"",[222,360,275],{"class":239},[222,362,293],{"class":271},[222,364,340],{"class":239},[222,366,368,371,373,375],{"class":224,"line":367},10,[222,369,370],{"class":271},"    \"MYCLI_VERBOSE\"",[222,372,275],{"class":239},[222,374,303],{"class":271},[222,376,340],{"class":239},[222,378,380],{"class":224,"line":379},11,[222,381,311],{"class":239},[222,383,385],{"class":224,"line":384},12,[222,386,256],{"emptyLinePlaceholder":255},[222,388,390],{"class":224,"line":389},13,[222,391,256],{"emptyLinePlaceholder":255},[222,393,395,398,402,405,408,410,412,415,418],{"class":224,"line":394},14,[222,396,397],{"class":228},"def",[222,399,401],{"class":400},"sScJk"," from_env",[222,403,404],{"class":239},"(environ: dict[",[222,406,407],{"class":232},"str",[222,409,65],{"class":239},[222,411,407],{"class":232},[222,413,414],{"class":239},"]) -> ",[222,416,417],{"class":232},"dict",[222,419,420],{"class":239},":\n",[222,422,424,427,430,433,436,439,442,445,448,451,453],{"class":224,"line":423},15,[222,425,426],{"class":228},"    return",[222,428,429],{"class":239}," {key: environ[name] ",[222,431,432],{"class":228},"for",[222,434,435],{"class":239}," name, key ",[222,437,438],{"class":228},"in",[222,440,441],{"class":232}," ENV_MAP",[222,443,444],{"class":239},".items() ",[222,446,447],{"class":228},"if",[222,449,450],{"class":239}," name ",[222,452,438],{"class":228},[222,454,455],{"class":239}," environ}\n",[222,457,459],{"class":224,"line":458},16,[222,460,256],{"emptyLinePlaceholder":255},[222,462,464],{"class":224,"line":463},17,[222,465,256],{"emptyLinePlaceholder":255},[222,467,469,471,474,477,479,482,484],{"class":224,"line":468},18,[222,470,397],{"class":228},[222,472,473],{"class":400}," from_flags",[222,475,476],{"class":239},"(flags: ",[222,478,417],{"class":232},[222,480,481],{"class":239},") -> ",[222,483,417],{"class":232},[222,485,420],{"class":239},[222,487,489],{"class":224,"line":488},19,[222,490,492],{"class":491},"sJ8bj","    # Drop unset options so `--port` left off doesn't overwrite lower layers.\n",[222,494,496,498,501,503,506,508,511,513,516,519,522,525],{"class":224,"line":495},20,[222,497,426],{"class":228},[222,499,500],{"class":239}," {key: value ",[222,502,432],{"class":228},[222,504,505],{"class":239}," key, value ",[222,507,438],{"class":228},[222,509,510],{"class":239}," flags.items() ",[222,512,447],{"class":228},[222,514,515],{"class":239}," value ",[222,517,518],{"class":228},"is",[222,520,521],{"class":228}," not",[222,523,524],{"class":232}," None",[222,526,311],{"class":239},[222,528,530],{"class":224,"line":529},21,[222,531,256],{"emptyLinePlaceholder":255},[222,533,535],{"class":224,"line":534},22,[222,536,256],{"emptyLinePlaceholder":255},[222,538,540,542,545,548,550,553,555,557,559,562,564,566,568],{"class":224,"line":539},23,[222,541,397],{"class":228},[222,543,544],{"class":400}," resolve",[222,546,547],{"class":239},"(file_cfg: ",[222,549,417],{"class":232},[222,551,552],{"class":239},", environ: dict[",[222,554,407],{"class":232},[222,556,65],{"class":239},[222,558,407],{"class":232},[222,560,561],{"class":239},"], flags: ",[222,563,417],{"class":232},[222,565,481],{"class":239},[222,567,417],{"class":232},[222,569,420],{"class":239},[222,571,573],{"class":224,"line":572},24,[222,574,575],{"class":271},"    \"\"\"Merge low -> high: defaults \u003C file \u003C env \u003C flags.\"\"\"\n",[222,577,579,582,584,586],{"class":224,"line":578},25,[222,580,581],{"class":239},"    merged: ",[222,583,417],{"class":232},[222,585,265],{"class":228},[222,587,588],{"class":239}," {}\n",[222,590,592,595,597,600],{"class":224,"line":591},26,[222,593,594],{"class":239},"    merged.update(",[222,596,262],{"class":232},[222,598,599],{"class":239},")          ",[222,601,602],{"class":491},"# lowest\n",[222,604,606,609],{"class":224,"line":605},27,[222,607,608],{"class":239},"    merged.update(file_cfg)          ",[222,610,611],{"class":491},"# config file\n",[222,613,615,618],{"class":224,"line":614},28,[222,616,617],{"class":239},"    merged.update(from_env(environ)) ",[222,619,620],{"class":491},"# environment\n",[222,622,624,627],{"class":224,"line":623},29,[222,625,626],{"class":239},"    merged.update(from_flags(flags)) ",[222,628,629],{"class":491},"# flags win\n",[222,631,633,635],{"class":224,"line":632},30,[222,634,426],{"class":228},[222,636,637],{"class":239}," merged\n",[222,639,641],{"class":224,"line":640},31,[222,642,256],{"emptyLinePlaceholder":255},[222,644,646],{"class":224,"line":645},32,[222,647,256],{"emptyLinePlaceholder":255},[222,649,651,653,656,659,662],{"class":224,"line":650},33,[222,652,447],{"class":228},[222,654,655],{"class":232}," __name__",[222,657,658],{"class":228}," ==",[222,660,661],{"class":271}," \"__main__\"",[222,663,420],{"class":239},[222,665,667,670,673,675,677,679,682,684,686,688,691,693,695,697,700],{"class":224,"line":666},34,[222,668,669],{"class":239},"    file_cfg ",[222,671,672],{"class":228},"=",[222,674,268],{"class":239},[222,676,272],{"class":271},[222,678,275],{"class":239},[222,680,681],{"class":271},"\"db.internal\"",[222,683,65],{"class":239},[222,685,283],{"class":271},[222,687,275],{"class":239},[222,689,690],{"class":232},"5432",[222,692,65],{"class":239},[222,694,293],{"class":271},[222,696,275],{"class":239},[222,698,699],{"class":232},"30",[222,701,311],{"class":239},[222,703,705,708,710,712,715,717,720,722,725,727,730],{"class":224,"line":704},35,[222,706,707],{"class":239},"    environ ",[222,709,672],{"class":228},[222,711,268],{"class":239},[222,713,714],{"class":271},"\"MYCLI_PORT\"",[222,716,275],{"class":239},[222,718,719],{"class":271},"\"9000\"",[222,721,65],{"class":239},[222,723,724],{"class":271},"\"MYCLI_VERBOSE\"",[222,726,275],{"class":239},[222,728,729],{"class":271},"\"true\"",[222,731,311],{"class":239},[222,733,735,738,740,742,744,746,749,751,753,755,757,759,761,763,765,767,769,771,773],{"class":224,"line":734},36,[222,736,737],{"class":239},"    flags ",[222,739,672],{"class":228},[222,741,268],{"class":239},[222,743,272],{"class":271},[222,745,275],{"class":239},[222,747,748],{"class":271},"\"cli-host\"",[222,750,65],{"class":239},[222,752,283],{"class":271},[222,754,275],{"class":239},[222,756,47],{"class":232},[222,758,65],{"class":239},[222,760,293],{"class":271},[222,762,275],{"class":239},[222,764,47],{"class":232},[222,766,65],{"class":239},[222,768,303],{"class":271},[222,770,275],{"class":239},[222,772,47],{"class":232},[222,774,311],{"class":239},[222,776,778,781],{"class":224,"line":777},37,[222,779,780],{"class":232},"    print",[222,782,783],{"class":239},"(resolve(file_cfg, environ, flags))\n",[213,785,790],{"className":786,"code":788,"language":789,"meta":218},[787],"language-text","{'host': 'cli-host', 'port': '9000', 'timeout': 30, 'verbose': 'true'}\n","text",[14,791,788],{"__ignoreMap":218},[10,793,794,795,798,799,802,803,805,806,809,810,813,814,817,818,820],{},"Trace each key: ",[14,796,797],{},"host"," came from the flag, ",[14,800,801],{},"port"," from the env var (overriding the file's ",[14,804,690],{},"), ",[14,807,808],{},"timeout"," from the file (nothing higher set it), and ",[14,811,812],{},"verbose"," from the env. That's the precedence table, executed. The ",[14,815,816],{},"from_flags"," filter is the load-bearing detail — without it, every option your parser defaults to ",[14,819,47],{}," would stomp the config file with a blank.",[23,822,824],{"id":823},"coerce-types-after-merging-not-before","Coerce types after merging, not before",[10,826,827,828,830,831,834,835,838,839,845,846,848],{},"Env vars and many file formats hand you strings: ",[14,829,801],{}," above ends up as ",[14,832,833],{},"'9000'",", not ",[14,836,837],{},"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 ",[196,840,844],{"href":841,"rel":842},"https:\u002F\u002Fdocs.pydantic.dev\u002Flatest\u002F",[843],"nofollow","Pydantic v2"," model exactly as the ",[196,847,199],{"href":198}," does:",[213,850,852],{"className":215,"code":851,"language":217,"meta":218,"style":218},"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":218},[222,855,856,858,861,863],{"class":224,"line":225},[222,857,229],{"class":228},[222,859,860],{"class":239}," pydantic ",[222,862,246],{"class":228},[222,864,865],{"class":239}," BaseModel, ConfigDict\n",[222,867,868],{"class":224,"line":243},[222,869,256],{"emptyLinePlaceholder":255},[222,871,872,875,878,881,884],{"class":224,"line":252},[222,873,874],{"class":228},"class",[222,876,877],{"class":400}," AppConfig",[222,879,880],{"class":239},"(",[222,882,883],{"class":400},"BaseModel",[222,885,886],{"class":239},"):\n",[222,888,889,892,894,897,901,903,906],{"class":224,"line":259},[222,890,891],{"class":239},"    model_config ",[222,893,672],{"class":228},[222,895,896],{"class":239}," ConfigDict(",[222,898,900],{"class":899},"s4XuR","extra",[222,902,672],{"class":228},[222,904,905],{"class":271},"\"forbid\"",[222,907,908],{"class":239},")\n",[222,910,911,914,916,918],{"class":224,"line":314},[222,912,913],{"class":239},"    host: ",[222,915,407],{"class":232},[222,917,265],{"class":228},[222,919,920],{"class":271}," \"localhost\"\n",[222,922,923,926,929,931],{"class":224,"line":319},[222,924,925],{"class":239},"    port: ",[222,927,928],{"class":232},"int",[222,930,265],{"class":228},[222,932,933],{"class":232}," 8000\n",[222,935,936,939,941,943],{"class":224,"line":330},[222,937,938],{"class":239},"    timeout: ",[222,940,928],{"class":232},[222,942,265],{"class":228},[222,944,945],{"class":232}," 10\n",[222,947,948,951,954,956],{"class":224,"line":343},[222,949,950],{"class":239},"    verbose: ",[222,952,953],{"class":232},"bool",[222,955,265],{"class":228},[222,957,958],{"class":232}," False\n",[222,960,961],{"class":224,"line":355},[222,962,256],{"emptyLinePlaceholder":255},[222,964,965,968,970],{"class":224,"line":367},[222,966,967],{"class":239},"config ",[222,969,672],{"class":228},[222,971,972],{"class":239}," AppConfig.model_validate(resolve(file_cfg, environ, flags))\n",[222,974,975],{"class":224,"line":379},[222,976,977],{"class":491},"# 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,719],{}," becomes ",[14,985,837],{},[14,987,729],{},[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 ",[196,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",[213,1024,1026],{"className":215,"code":1025,"language":217,"meta":218,"style":218},"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":218},[222,1029,1030,1032],{"class":224,"line":225},[222,1031,246],{"class":228},[222,1033,1034],{"class":239}," click\n",[222,1036,1037],{"class":224,"line":243},[222,1038,256],{"emptyLinePlaceholder":255},[222,1040,1041,1043,1046,1049,1051],{"class":224,"line":252},[222,1042,397],{"class":228},[222,1044,1045],{"class":400}," load_file_config",[222,1047,1048],{"class":239},"() -> ",[222,1050,417],{"class":232},[222,1052,420],{"class":239},[222,1054,1055],{"class":224,"line":259},[222,1056,1057],{"class":491},"    # Read + parse your config.yaml\u002Ftoml here; return {} if absent.\n",[222,1059,1060,1062,1064,1066,1068,1070,1072,1074,1076,1078],{"class":224,"line":314},[222,1061,426],{"class":228},[222,1063,268],{"class":239},[222,1065,283],{"class":271},[222,1067,275],{"class":239},[222,1069,690],{"class":232},[222,1071,65],{"class":239},[222,1073,272],{"class":271},[222,1075,275],{"class":239},[222,1077,681],{"class":271},[222,1079,311],{"class":239},[222,1081,1082],{"class":224,"line":319},[222,1083,256],{"emptyLinePlaceholder":255},[222,1085,1086,1089,1091,1094,1096,1099,1102,1104,1107],{"class":224,"line":330},[222,1087,1088],{"class":400},"@click.group",[222,1090,880],{"class":239},[222,1092,1093],{"class":899},"context_settings",[222,1095,672],{"class":228},[222,1097,1098],{"class":239},"{",[222,1100,1101],{"class":271},"\"auto_envvar_prefix\"",[222,1103,275],{"class":239},[222,1105,1106],{"class":271},"\"MYCLI\"",[222,1108,1109],{"class":239},"})\n",[222,1111,1112],{"class":224,"line":343},[222,1113,1114],{"class":400},"@click.pass_context\n",[222,1116,1117,1119,1122,1125,1127],{"class":224,"line":355},[222,1118,397],{"class":228},[222,1120,1121],{"class":400}," cli",[222,1123,1124],{"class":239},"(ctx: click.Context) -> ",[222,1126,47],{"class":232},[222,1128,420],{"class":239},[222,1130,1131,1134,1136,1139],{"class":224,"line":367},[222,1132,1133],{"class":239},"    ctx.default_map ",[222,1135,672],{"class":228},[222,1137,1138],{"class":239}," load_file_config()   ",[222,1140,1141],{"class":491},"# file layer feeds option defaults\n",[222,1143,1144],{"class":224,"line":379},[222,1145,256],{"emptyLinePlaceholder":255},[222,1147,1148,1151],{"class":224,"line":384},[222,1149,1150],{"class":400},"@cli.command",[222,1152,1153],{"class":239},"()\n",[222,1155,1156,1159,1161,1164,1166,1168,1170,1172],{"class":224,"line":389},[222,1157,1158],{"class":400},"@click.option",[222,1160,880],{"class":239},[222,1162,1163],{"class":271},"\"--host\"",[222,1165,65],{"class":239},[222,1167,1022],{"class":899},[222,1169,672],{"class":228},[222,1171,278],{"class":271},[222,1173,908],{"class":239},[222,1175,1176,1178,1180,1183,1185,1188,1190,1192,1194,1196,1198,1200],{"class":224,"line":394},[222,1177,1158],{"class":400},[222,1179,880],{"class":239},[222,1181,1182],{"class":271},"\"--port\"",[222,1184,65],{"class":239},[222,1186,1187],{"class":899},"type",[222,1189,672],{"class":228},[222,1191,928],{"class":232},[222,1193,65],{"class":239},[222,1195,1022],{"class":899},[222,1197,672],{"class":228},[222,1199,288],{"class":232},[222,1201,908],{"class":239},[222,1203,1204,1206,1209,1212,1214,1217,1219,1221,1223],{"class":224,"line":423},[222,1205,397],{"class":228},[222,1207,1208],{"class":400}," serve",[222,1210,1211],{"class":239},"(host: ",[222,1213,407],{"class":232},[222,1215,1216],{"class":239},", port: ",[222,1218,928],{"class":232},[222,1220,481],{"class":239},[222,1222,47],{"class":232},[222,1224,420],{"class":239},[222,1226,1227,1230,1233,1236,1238,1240,1243,1246,1248,1250,1252,1254],{"class":224,"line":458},[222,1228,1229],{"class":239},"    click.echo(",[222,1231,1232],{"class":228},"f",[222,1234,1235],{"class":271},"\"",[222,1237,1098],{"class":232},[222,1239,797],{"class":239},[222,1241,1242],{"class":232},"}",[222,1244,1245],{"class":271},":",[222,1247,1098],{"class":232},[222,1249,801],{"class":239},[222,1251,1242],{"class":232},[222,1253,1235],{"class":271},[222,1255,908],{"class":239},[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,801],{}," from ",[14,1267,1012],{}," if given, else ",[14,1270,1008],{},", else the file's ",[14,1273,690],{},", else ",[14,1276,288],{}," — 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 ",[196,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:",[213,1309,1311],{"className":215,"code":1310,"language":217,"meta":218,"style":218},"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,1312,1313,1333,1343,1352,1367,1379,1394,1406,1420,1431,1445,1452,1471],{"__ignoreMap":218},[222,1314,1315,1317,1320,1323,1325,1327,1330],{"class":224,"line":225},[222,1316,397],{"class":228},[222,1318,1319],{"class":400}," resolve_with_source",[222,1321,1322],{"class":239},"(file_cfg, environ, flags) -> dict[",[222,1324,407],{"class":232},[222,1326,65],{"class":239},[222,1328,1329],{"class":232},"tuple",[222,1331,1332],{"class":239},"]:\n",[222,1334,1335,1338,1340],{"class":224,"line":243},[222,1336,1337],{"class":239},"    env_cfg, flag_cfg ",[222,1339,672],{"class":228},[222,1341,1342],{"class":239}," from_env(environ), from_flags(flags)\n",[222,1344,1345,1348,1350],{"class":224,"line":252},[222,1346,1347],{"class":239},"    result ",[222,1349,672],{"class":228},[222,1351,588],{"class":239},[222,1353,1354,1357,1360,1362,1365],{"class":224,"line":259},[222,1355,1356],{"class":228},"    for",[222,1358,1359],{"class":239}," key ",[222,1361,438],{"class":228},[222,1363,1364],{"class":232}," DEFAULTS",[222,1366,420],{"class":239},[222,1368,1369,1372,1374,1376],{"class":224,"line":314},[222,1370,1371],{"class":228},"        if",[222,1373,1359],{"class":239},[222,1375,438],{"class":228},[222,1377,1378],{"class":239}," flag_cfg:\n",[222,1380,1381,1384,1386,1389,1392],{"class":224,"line":319},[222,1382,1383],{"class":239},"            result[key] ",[222,1385,672],{"class":228},[222,1387,1388],{"class":239}," (flag_cfg[key], ",[222,1390,1391],{"class":271},"\"flag\"",[222,1393,908],{"class":239},[222,1395,1396,1399,1401,1403],{"class":224,"line":330},[222,1397,1398],{"class":228},"        elif",[222,1400,1359],{"class":239},[222,1402,438],{"class":228},[222,1404,1405],{"class":239}," env_cfg:\n",[222,1407,1408,1410,1412,1415,1418],{"class":224,"line":343},[222,1409,1383],{"class":239},[222,1411,672],{"class":228},[222,1413,1414],{"class":239}," (env_cfg[key], ",[222,1416,1417],{"class":271},"\"env\"",[222,1419,908],{"class":239},[222,1421,1422,1424,1426,1428],{"class":224,"line":355},[222,1423,1398],{"class":228},[222,1425,1359],{"class":239},[222,1427,438],{"class":228},[222,1429,1430],{"class":239}," file_cfg:\n",[222,1432,1433,1435,1437,1440,1443],{"class":224,"line":367},[222,1434,1383],{"class":239},[222,1436,672],{"class":228},[222,1438,1439],{"class":239}," (file_cfg[key], ",[222,1441,1442],{"class":271},"\"file\"",[222,1444,908],{"class":239},[222,1446,1447,1450],{"class":224,"line":379},[222,1448,1449],{"class":228},"        else",[222,1451,420],{"class":239},[222,1453,1454,1456,1458,1461,1463,1466,1469],{"class":224,"line":384},[222,1455,1383],{"class":239},[222,1457,672],{"class":228},[222,1459,1460],{"class":239}," (",[222,1462,262],{"class":232},[222,1464,1465],{"class":239},"[key], ",[222,1467,1468],{"class":271},"\"default\"",[222,1470,908],{"class":239},[222,1472,1473,1475],{"class":224,"line":389},[222,1474,426],{"class":228},[222,1476,1477],{"class":239}," result\n",[213,1479,1482],{"className":1480,"code":1481,"language":789,"meta":218},[787],"$ mycli --show-config\nhost    = cli-host   (flag)\nport    = 9000       (env)\ntimeout = 30         (file)\nverbose = true       (env)\n",[14,1483,1481],{"__ignoreMap":218},[10,1485,1486],{},"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,1488,1490],{"id":1489},"per-key-versus-whole-file-override","Per-key versus whole-file override",[10,1492,1493,1494,1497,1498,1501,1502,1504,1505,1507,1508,1510,1511,1513,1514,1517],{},"Decide explicitly whether a higher layer overrides a config file ",[35,1495,1496],{},"per key"," or ",[35,1499,1500],{},"wholesale",". The ",[14,1503,210],{}," approach above is per-key and shallow: setting ",[14,1506,1008],{}," overrides only ",[14,1509,801],{},", leaving the file's ",[14,1512,797],{}," 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,1515,1516],{},"entire"," sub-table, silently dropping sibling keys:",[213,1519,1521],{"className":215,"code":1520,"language":217,"meta":218,"style":218},"file_cfg = {\"db\": {\"host\": \"a\", \"port\": 1}}\noverride = {\"db\": {\"port\": 2}}\n# shallow: {\"db\": {\"port\": 2}} — \"host\" is GONE\n",[14,1522,1523,1557,1578],{"__ignoreMap":218},[222,1524,1525,1528,1530,1532,1535,1538,1540,1542,1545,1547,1549,1551,1554],{"class":224,"line":225},[222,1526,1527],{"class":239},"file_cfg ",[222,1529,672],{"class":228},[222,1531,268],{"class":239},[222,1533,1534],{"class":271},"\"db\"",[222,1536,1537],{"class":239},": {",[222,1539,272],{"class":271},[222,1541,275],{"class":239},[222,1543,1544],{"class":271},"\"a\"",[222,1546,65],{"class":239},[222,1548,283],{"class":271},[222,1550,275],{"class":239},[222,1552,1553],{"class":232},"1",[222,1555,1556],{"class":239},"}}\n",[222,1558,1559,1562,1564,1566,1568,1570,1572,1574,1576],{"class":224,"line":243},[222,1560,1561],{"class":239},"override ",[222,1563,672],{"class":228},[222,1565,268],{"class":239},[222,1567,1534],{"class":271},[222,1569,1537],{"class":239},[222,1571,283],{"class":271},[222,1573,275],{"class":239},[222,1575,133],{"class":232},[222,1577,1556],{"class":239},[222,1579,1580],{"class":224,"line":252},[222,1581,1582],{"class":491},"# shallow: {\"db\": {\"port\": 2}} — \"host\" is GONE\n",[10,1584,1585,1586,1589,1590,1593],{},"If you support nested config, deep-merge the mappings recursively so ",[14,1587,1588],{},"db.host"," survives while ",[14,1591,1592],{},"db.port"," is overridden. For flat config, the shallow merge is correct and simpler — don't reach for recursion you don't need.",[23,1595,1597],{"id":1596},"production-notes","Production notes",[28,1599,1600,1613,1621,1649,1667],{},[31,1601,1602,1605,1606,1608,1609,1612],{},[35,1603,1604],{},"Flags default to a sentinel, not a value."," Give options a ",[14,1607,47],{}," default (or ",[14,1610,1611],{},"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,1614,1615,1620],{},[35,1616,1617,1618,38],{},"Document the order in ",[14,1619,20],{}," State \"flags > env > config > defaults\" once so users never have to reverse-engineer it.",[31,1622,1623,1626,1627,1630,1631,65,1634,1636,1637,1640,1641,1644,1645,1648],{},[35,1624,1625],{},"Test precedence with a table."," Because ",[14,1628,1629],{},"resolve"," takes ",[14,1632,1633],{},"file_cfg",[14,1635,68],{},", and ",[14,1638,1639],{},"flags"," as arguments, a ",[14,1642,1643],{},"pytest.mark.parametrize"," sweep over combinations pins every rule; don't read ",[14,1646,1647],{},"os.environ"," directly inside the resolver.",[31,1650,1651,1654,1655,1658,1659,1662,1663,38],{},[35,1652,1653],{},"Verbosity flows through here too."," A ",[14,1656,1657],{},"--verbose"," flag should beat ",[14,1660,1661],{},"MYCLI_VERBOSE"," should beat a config value — wire it through this same chain rather than special-casing it, as noted in ",[196,1664,1666],{"href":1665},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fadding-verbose-and-quiet-logging-flags\u002F","adding verbose and quiet flags",[31,1668,1669,1672,1673,1676],{},[35,1670,1671],{},"TOML is identical."," Swap the file parser for ",[14,1674,1675],{},"tomllib"," (stdlib since 3.11); the merge and validation layers are unchanged.",[23,1678,1680],{"id":1679},"related","Related",[28,1682,1683,1689,1695],{},[31,1684,1685,1686],{},"Up: ",[196,1687,1688],{"href":198},"Handling config files and env vars in CLIs",[31,1690,1691,1692],{},"Sideways: ",[196,1693,1694],{"href":994},"Loading YAML configs safely in CLI apps",[31,1696,1691,1697],{},[196,1698,1699],{"href":1665},"Adding verbose and quiet logging flags",[1701,1702,1703],"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":218,"searchDepth":243,"depth":243,"links":1705},[1706,1707,1708,1709,1710,1711,1712,1713,1714],{"id":25,"depth":243,"text":26},{"id":79,"depth":243,"text":80},{"id":203,"depth":243,"text":204},{"id":823,"depth":243,"text":824},{"id":998,"depth":243,"text":999},{"id":1291,"depth":243,"text":1292},{"id":1489,"depth":243,"text":1490},{"id":1596,"depth":243,"text":1597},{"id":1679,"depth":243,"text":1680},"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":1716},"advanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Fconfig-precedence-flags-env-files-defaults\u002Findex",[1725,1726,1727,1611],"config","precedence","cli","JlHXKNrGO8P-LV2hzJIAsVwS5VFnZAeTgjI72y8PUIg",[1730,1733,1736,1739,1742,1745,1748,1749,1752,1754,1757,1760,1763,1766,1769,1772,1775,1778,1781,1784,1787,1790,1793,1796,1799,1802,1805,1808,1811,1814,1817,1820,1823,1826,1829,1832,1835,1838,1841,1844,1847,1850,1853,1856,1859,1862,1865,1868,1871,1874,1877],{"path":1731,"title":1732},"\u002Fabout","About Python CLI Toolcraft",{"path":1734,"title":1735},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies","Advanced Argument Validation Strategies",{"path":1737,"title":1738},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fparsing-nested-json-arguments-in-python-clis","Parsing Nested JSON Args in Python CLIs",{"path":1740,"title":1741},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fchoosing-exit-codes-for-cli-tools","Choosing Exit Codes for CLI Tools",{"path":1743,"title":1744},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Ffriendly-error-messages-and-tracebacks","Friendly Error Messages and Tracebacks",{"path":1746,"title":1747},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes","Error Handling and Exit Codes for CLIs",{"path":1721,"title":5},{"path":1750,"title":1751},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars","Handling Config Files and Env Vars in CLIs",{"path":1753,"title":1694},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Floading-yaml-configs-safely-in-cli-apps",{"path":1755,"title":1756},"\u002Fadvanced-input-parsing-user-experience","Advanced Input Parsing for Python CLIs",{"path":1758,"title":1759},"\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":1761,"title":1762},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich","Interactive Terminal UI with Rich",{"path":1764,"title":1765},"\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":1767,"title":1768},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis","Shell Completion for Python CLIs",{"path":1770,"title":1771},"\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":1773,"title":1774},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fadding-verbose-and-quiet-logging-flags","Adding Verbose and Quiet Logging Flags",{"path":1776,"title":1777},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps","Structured Logging for CLI Apps",{"path":1779,"title":1780},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fstructured-json-logging-in-python-clis","Structured JSON Logging in Python CLIs",{"path":1782,"title":1783},"\u002F","Python CLI Toolcraft",{"path":1785,"title":1786},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading","CLI Startup Performance and Lazy Loading",{"path":1788,"title":1789},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Flazy-loading-subcommands-for-faster-startup","Lazy Loading Subcommands for Faster Startup",{"path":1791,"title":1792},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Fprofiling-python-cli-startup-time","Profiling Python CLI Startup Time",{"path":1794,"title":1795},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargparse-subparsers-for-subcommands","argparse Subparsers for Subcommands",{"path":1797,"title":1798},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse","Command-Line Parsing with argparse",{"path":1800,"title":1801},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fmigrating-from-argparse-to-typer","Migrating from argparse to Typer",{"path":1803,"title":1804},"\u002Fmodern-python-cli-frameworks-architecture","Python CLI Frameworks and Architecture",{"path":1806,"title":1807},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis","Plugin Architectures for Extensible CLIs",{"path":1809,"title":1810},"\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":1812,"title":1813},"\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":1815,"title":1816},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis","Structuring Multi-Command Python CLIs",{"path":1818,"title":1819},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fsharing-state-with-click-context-objects","Sharing State with Click Context Objects",{"path":1821,"title":1822},"\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":1824,"title":1825},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each","Typer vs Click: When to Use Each",{"path":1827,"title":1828},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Ftyper-callback-functions-explained","Typer callback functions explained",{"path":1830,"title":1831},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter","CLI Project Scaffolding with Cookiecutter",{"path":1833,"title":1834},"\u002Fproject-setup-dependency-management","Project Setup & Dependency Management",{"path":1836,"title":1837},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fautomating-changelogs-with-conventional-commits","Automating Changelogs with Conventional Commits",{"path":1839,"title":1840},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs","Managing CLI Versioning & Changelogs",{"path":1842,"title":1843},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fbuilding-wheels-and-sdists-for-python-clis","Building Wheels and sdists for Python CLIs",{"path":1845,"title":1846},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution","Packaging Python CLIs for Distribution",{"path":1848,"title":1849},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Finstalling-and-distributing-clis-with-pipx","Installing and Distributing CLIs with pipx",{"path":1851,"title":1852},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fpublishing-a-python-cli-to-pypi","Publishing a Python CLI to PyPI",{"path":1854,"title":1855},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development","Poetry Workflows for CLI Development",{"path":1857,"title":1858},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpoetry-entry-points-and-scripts-for-clis","Poetry Entry Points and Scripts for CLIs",{"path":1860,"title":1861},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects","Pre-commit Hooks for CLI Projects",{"path":1863,"title":1864},"\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":1866,"title":1867},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management","uv for Python CLI Dependency Management",{"path":1869,"title":1870},"\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":1872,"title":1873},"\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":1875,"title":1876},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices","Python CLI Env Isolation Best Practices",{"path":1878,"title":1879},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fmanaging-virtual-environments-for-cross-platform-clis","Managing Python CLI Virtual Environments",1783281867195]