[{"data":1,"prerenderedAt":3269},["ShallowReactive",2],{"page-\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Freading-toml-config-with-tomllib\u002F":3,"content-directory":2426},{"id":4,"title":5,"body":6,"date":2413,"description":2414,"difficulty":2415,"draft":2416,"extension":2417,"meta":2418,"navigation":153,"path":2419,"seo":2420,"stem":2421,"tags":2422,"updated":2413,"__hash__":2425},"content\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Freading-toml-config-with-tomllib\u002Findex.md","Reading TOML Config with tomllib in Python CLIs",{"type":7,"value":8,"toc":2393},"minimark",[9,43,48,79,83,87,102,105,108,112,1478,1481,1555,1560,1566,1580,1597,1626,1632,1642,1650,1654,1663,1681,1754,1764,1768,1771,1811,1815,1822,2275,2282,2286,2294,2298,2302,2309,2316,2337,2341,2344,2352,2358,2362,2389],[10,11,12,13,17,18,17,21,24,25,28,29,32,33,36,37,42],"p",{},"TOML has become the default configuration format for Python tooling — ",[14,15,16],"code",{},"pyproject.toml",", ",[14,19,20],{},"ruff.toml",[14,22,23],{},"uv.toml"," — and it is a good choice for your own CLI too: it is readable, it has real types (integers, booleans, dates, arrays, tables), and since Python 3.11 the standard library can parse it with ",[14,26,27],{},"tomllib",", so reading it costs no dependency. But ",[14,30,31],{},"tomllib.load()"," only gets you a dictionary. Between that dictionary and a configuration your code can trust sit several decisions: what happens when the file is missing, how to report a syntax error, how to catch a misspelt key that would otherwise be silently ignored, and how to make sure ",[14,34,35],{},"replicas = \"3\""," is rejected rather than crashing later. This guide builds a small loader that answers each of those, returning a typed dataclass and errors that tell users exactly what to fix. It belongs to the ",[38,39,41],"a",{"href":40},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002F","handling configuration files and environment variables topic",".",[44,45,47],"h2",{"id":46},"prerequisites","Prerequisites",[49,50,51,66],"ul",{},[52,53,54,55,57,58,61,62,65],"li",{},"Python 3.11+ for ",[14,56,27],{},"; on 3.10, the ",[14,59,60],{},"tomli"," backport provides the identical API (",[14,63,64],{},"uv add \"tomli; python_version \u003C '3.11'\"",").",[52,67,68,69,73,74,78],{},"A decision about where the file lives — typically the user config directory from ",[38,70,72],{"href":71},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fstoring-app-data-with-platformdirs\u002F","storing app data with platformdirs",", a project file found by ",[38,75,77],{"href":76},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Fdiscovering-project-config-files-by-walking-up-directories\u002F","walking up directories",", or both.",[44,80,82],{"id":81},"from-file-to-typed-settings","From file to typed settings",[84,85],"inline-diagram",{"name":86},"toml-flow",[10,88,89,90,94,95,97,98,101],{},"The pipeline has four steps: open the file in ",[91,92,93],"strong",{},"binary mode"," (",[14,96,27],{}," requires bytes, which also means it handles the UTF-8 encoding itself — no Windows code-page surprises), parse it into dictionaries and lists, ",[91,99,100],{},"validate"," keys and types, and build a frozen dataclass the rest of the program uses. Nothing downstream ever touches the raw dictionary.",[10,103,104],{},"TOML's types map cleanly onto Python's, with a couple of details worth knowing:",[84,106],{"name":107},"toml-types",[44,109,111],{"id":110},"the-recipe","The recipe",[113,114,119],"pre",{"className":115,"code":116,"language":117,"meta":118,"style":118},"language-python shiki shiki-themes github-light github-dark","# src\u002Fmytool\u002Fconfig.py\nfrom __future__ import annotations\n\nimport difflib\nimport sys\nfrom dataclasses import dataclass, field, fields\nfrom pathlib import Path\nfrom typing import Any\n\nif sys.version_info >= (3, 11):\n    import tomllib\nelse:                                   # Python 3.10: the same API from the tomli backport\n    import tomli as tomllib\n\n\nclass ConfigError(Exception):\n    pass\n\n\n@dataclass(frozen=True)\nclass DeployConfig:\n    region: str = \"eu-west-1\"\n    replicas: int = 2\n    tags: tuple[str, ...] = ()\n\n\n@dataclass(frozen=True)\nclass Config:\n    api_url: str = \"https:\u002F\u002Fapi.example.com\"\n    timeout: float = 30.0\n    deploy: DeployConfig = field(default_factory=DeployConfig)\n\n\ndef _check_keys(section: str, data: dict[str, Any], cls: type) -> None:\n    known = {f.name for f in fields(cls)}\n    for key in data:\n        if key not in known:\n            close = difflib.get_close_matches(key, known, n=1)\n            hint = f\" (did you mean {close[0]!r}?)\" if close else \"\"\n            raise ConfigError(f\"unknown key {key!r} in [{section}]{hint}\")\n\n\ndef _typed(section: str, key: str, value: Any, expected: type | tuple[type, ...]) -> Any:\n    if isinstance(value, bool) and expected is not bool:        # bool is an int subclass\n        raise ConfigError(f\"[{section}] {key} must be {getattr(expected, '__name__', expected)}, got a boolean\")\n    if not isinstance(value, expected):\n        name = expected.__name__ if isinstance(expected, type) else \" or \".join(t.__name__ for t in expected)\n        raise ConfigError(f\"[{section}] {key} must be {name}, got {type(value).__name__} {value!r}\")\n    return value\n\n\ndef parse(data: dict[str, Any]) -> Config:\n    _check_keys(\"top level\", data, Config)\n    deploy_raw = data.get(\"deploy\", {})\n    if not isinstance(deploy_raw, dict):\n        raise ConfigError(\"[deploy] must be a table\")\n    _check_keys(\"deploy\", deploy_raw, DeployConfig)\n    deploy = DeployConfig(\n        region=_typed(\"deploy\", \"region\", deploy_raw.get(\"region\", DeployConfig.region), str),\n        replicas=_typed(\"deploy\", \"replicas\", deploy_raw.get(\"replicas\", DeployConfig.replicas), int),\n        tags=tuple(_typed(\"deploy\", \"tags\", deploy_raw.get(\"tags\", []), list)),\n    )\n    if deploy.replicas \u003C 1:\n        raise ConfigError(\"[deploy] replicas must be at least 1\")\n    return Config(\n        api_url=_typed(\"top level\", \"api_url\", data.get(\"api_url\", Config.api_url), str),\n        timeout=float(_typed(\"top level\", \"timeout\", data.get(\"timeout\", Config.timeout), (int, float))),\n        deploy=deploy,\n    )\n\n\ndef load(path: Path) -> Config:\n    try:\n        with path.open(\"rb\") as fh:                 # tomllib requires binary mode\n            data = tomllib.load(fh)\n    except FileNotFoundError:\n        return Config()                              # no file: all defaults\n    except tomllib.TOMLDecodeError as exc:\n        raise ConfigError(f\"{path}: invalid TOML: {exc}\") from None\n    try:\n        return parse(data)\n    except ConfigError as exc:\n        raise ConfigError(f\"{path}: {exc}\") from None\n","python","",[14,120,121,130,148,155,164,172,185,198,211,216,241,250,262,275,280,285,303,309,314,319,340,351,367,381,402,407,412,427,437,450,464,483,488,493,526,555,569,586,607,653,701,706,711,748,787,836,848,893,953,962,967,972,988,1000,1017,1034,1046,1056,1067,1098,1126,1160,1166,1182,1194,1202,1231,1266,1277,1282,1287,1292,1303,1311,1333,1344,1355,1367,1380,1417,1424,1432,1444],{"__ignoreMap":118},[122,123,126],"span",{"class":124,"line":125},"line",1,[122,127,129],{"class":128},"sJ8bj","# src\u002Fmytool\u002Fconfig.py\n",[122,131,133,137,141,144],{"class":124,"line":132},2,[122,134,136],{"class":135},"szBVR","from",[122,138,140],{"class":139},"sj4cs"," __future__",[122,142,143],{"class":135}," import",[122,145,147],{"class":146},"sVt8B"," annotations\n",[122,149,151],{"class":124,"line":150},3,[122,152,154],{"emptyLinePlaceholder":153},true,"\n",[122,156,158,161],{"class":124,"line":157},4,[122,159,160],{"class":135},"import",[122,162,163],{"class":146}," difflib\n",[122,165,167,169],{"class":124,"line":166},5,[122,168,160],{"class":135},[122,170,171],{"class":146}," sys\n",[122,173,175,177,180,182],{"class":124,"line":174},6,[122,176,136],{"class":135},[122,178,179],{"class":146}," dataclasses ",[122,181,160],{"class":135},[122,183,184],{"class":146}," dataclass, field, fields\n",[122,186,188,190,193,195],{"class":124,"line":187},7,[122,189,136],{"class":135},[122,191,192],{"class":146}," pathlib ",[122,194,160],{"class":135},[122,196,197],{"class":146}," Path\n",[122,199,201,203,206,208],{"class":124,"line":200},8,[122,202,136],{"class":135},[122,204,205],{"class":146}," typing ",[122,207,160],{"class":135},[122,209,210],{"class":146}," Any\n",[122,212,214],{"class":124,"line":213},9,[122,215,154],{"emptyLinePlaceholder":153},[122,217,219,222,225,228,230,233,235,238],{"class":124,"line":218},10,[122,220,221],{"class":135},"if",[122,223,224],{"class":146}," sys.version_info ",[122,226,227],{"class":135},">=",[122,229,94],{"class":146},[122,231,232],{"class":139},"3",[122,234,17],{"class":146},[122,236,237],{"class":139},"11",[122,239,240],{"class":146},"):\n",[122,242,244,247],{"class":124,"line":243},11,[122,245,246],{"class":135},"    import",[122,248,249],{"class":146}," tomllib\n",[122,251,253,256,259],{"class":124,"line":252},12,[122,254,255],{"class":135},"else",[122,257,258],{"class":146},":                                   ",[122,260,261],{"class":128},"# Python 3.10: the same API from the tomli backport\n",[122,263,265,267,270,273],{"class":124,"line":264},13,[122,266,246],{"class":135},[122,268,269],{"class":146}," tomli ",[122,271,272],{"class":135},"as",[122,274,249],{"class":146},[122,276,278],{"class":124,"line":277},14,[122,279,154],{"emptyLinePlaceholder":153},[122,281,283],{"class":124,"line":282},15,[122,284,154],{"emptyLinePlaceholder":153},[122,286,288,291,295,298,301],{"class":124,"line":287},16,[122,289,290],{"class":135},"class",[122,292,294],{"class":293},"sScJk"," ConfigError",[122,296,297],{"class":146},"(",[122,299,300],{"class":139},"Exception",[122,302,240],{"class":146},[122,304,306],{"class":124,"line":305},17,[122,307,308],{"class":135},"    pass\n",[122,310,312],{"class":124,"line":311},18,[122,313,154],{"emptyLinePlaceholder":153},[122,315,317],{"class":124,"line":316},19,[122,318,154],{"emptyLinePlaceholder":153},[122,320,322,325,327,331,334,337],{"class":124,"line":321},20,[122,323,324],{"class":293},"@dataclass",[122,326,297],{"class":146},[122,328,330],{"class":329},"s4XuR","frozen",[122,332,333],{"class":135},"=",[122,335,336],{"class":139},"True",[122,338,339],{"class":146},")\n",[122,341,343,345,348],{"class":124,"line":342},21,[122,344,290],{"class":135},[122,346,347],{"class":293}," DeployConfig",[122,349,350],{"class":146},":\n",[122,352,354,357,360,363],{"class":124,"line":353},22,[122,355,356],{"class":146},"    region: ",[122,358,359],{"class":139},"str",[122,361,362],{"class":135}," =",[122,364,366],{"class":365},"sZZnC"," \"eu-west-1\"\n",[122,368,370,373,376,378],{"class":124,"line":369},23,[122,371,372],{"class":146},"    replicas: ",[122,374,375],{"class":139},"int",[122,377,362],{"class":135},[122,379,380],{"class":139}," 2\n",[122,382,384,387,389,391,394,397,399],{"class":124,"line":383},24,[122,385,386],{"class":146},"    tags: tuple[",[122,388,359],{"class":139},[122,390,17],{"class":146},[122,392,393],{"class":139},"...",[122,395,396],{"class":146},"] ",[122,398,333],{"class":135},[122,400,401],{"class":146}," ()\n",[122,403,405],{"class":124,"line":404},25,[122,406,154],{"emptyLinePlaceholder":153},[122,408,410],{"class":124,"line":409},26,[122,411,154],{"emptyLinePlaceholder":153},[122,413,415,417,419,421,423,425],{"class":124,"line":414},27,[122,416,324],{"class":293},[122,418,297],{"class":146},[122,420,330],{"class":329},[122,422,333],{"class":135},[122,424,336],{"class":139},[122,426,339],{"class":146},[122,428,430,432,435],{"class":124,"line":429},28,[122,431,290],{"class":135},[122,433,434],{"class":293}," Config",[122,436,350],{"class":146},[122,438,440,443,445,447],{"class":124,"line":439},29,[122,441,442],{"class":146},"    api_url: ",[122,444,359],{"class":139},[122,446,362],{"class":135},[122,448,449],{"class":365}," \"https:\u002F\u002Fapi.example.com\"\n",[122,451,453,456,459,461],{"class":124,"line":452},30,[122,454,455],{"class":146},"    timeout: ",[122,457,458],{"class":139},"float",[122,460,362],{"class":135},[122,462,463],{"class":139}," 30.0\n",[122,465,467,470,472,475,478,480],{"class":124,"line":466},31,[122,468,469],{"class":146},"    deploy: DeployConfig ",[122,471,333],{"class":135},[122,473,474],{"class":146}," field(",[122,476,477],{"class":329},"default_factory",[122,479,333],{"class":135},[122,481,482],{"class":146},"DeployConfig)\n",[122,484,486],{"class":124,"line":485},32,[122,487,154],{"emptyLinePlaceholder":153},[122,489,491],{"class":124,"line":490},33,[122,492,154],{"emptyLinePlaceholder":153},[122,494,496,499,502,505,507,510,512,515,518,521,524],{"class":124,"line":495},34,[122,497,498],{"class":135},"def",[122,500,501],{"class":293}," _check_keys",[122,503,504],{"class":146},"(section: ",[122,506,359],{"class":139},[122,508,509],{"class":146},", data: dict[",[122,511,359],{"class":139},[122,513,514],{"class":146},", Any], cls: ",[122,516,517],{"class":139},"type",[122,519,520],{"class":146},") -> ",[122,522,523],{"class":139},"None",[122,525,350],{"class":146},[122,527,529,532,534,537,540,543,546,549,552],{"class":124,"line":528},35,[122,530,531],{"class":146},"    known ",[122,533,333],{"class":135},[122,535,536],{"class":146}," {f.name ",[122,538,539],{"class":135},"for",[122,541,542],{"class":146}," f ",[122,544,545],{"class":135},"in",[122,547,548],{"class":146}," fields(",[122,550,551],{"class":139},"cls",[122,553,554],{"class":146},")}\n",[122,556,558,561,564,566],{"class":124,"line":557},36,[122,559,560],{"class":135},"    for",[122,562,563],{"class":146}," key ",[122,565,545],{"class":135},[122,567,568],{"class":146}," data:\n",[122,570,572,575,577,580,583],{"class":124,"line":571},37,[122,573,574],{"class":135},"        if",[122,576,563],{"class":146},[122,578,579],{"class":135},"not",[122,581,582],{"class":135}," in",[122,584,585],{"class":146}," known:\n",[122,587,589,592,594,597,600,602,605],{"class":124,"line":588},38,[122,590,591],{"class":146},"            close ",[122,593,333],{"class":135},[122,595,596],{"class":146}," difflib.get_close_matches(key, known, ",[122,598,599],{"class":329},"n",[122,601,333],{"class":135},[122,603,604],{"class":139},"1",[122,606,339],{"class":146},[122,608,610,613,615,618,621,624,627,630,633,636,639,642,645,648,650],{"class":124,"line":609},39,[122,611,612],{"class":146},"            hint ",[122,614,333],{"class":135},[122,616,617],{"class":135}," f",[122,619,620],{"class":365},"\" (did you mean ",[122,622,623],{"class":139},"{",[122,625,626],{"class":146},"close[",[122,628,629],{"class":139},"0",[122,631,632],{"class":146},"]",[122,634,635],{"class":135},"!r",[122,637,638],{"class":139},"}",[122,640,641],{"class":365},"?)\"",[122,643,644],{"class":135}," if",[122,646,647],{"class":146}," close ",[122,649,255],{"class":135},[122,651,652],{"class":365}," \"\"\n",[122,654,656,659,662,665,668,670,673,675,677,680,682,685,687,689,691,694,696,699],{"class":124,"line":655},40,[122,657,658],{"class":135},"            raise",[122,660,661],{"class":146}," ConfigError(",[122,663,664],{"class":135},"f",[122,666,667],{"class":365},"\"unknown key ",[122,669,623],{"class":139},[122,671,672],{"class":146},"key",[122,674,635],{"class":135},[122,676,638],{"class":139},[122,678,679],{"class":365}," in [",[122,681,623],{"class":139},[122,683,684],{"class":146},"section",[122,686,638],{"class":139},[122,688,632],{"class":365},[122,690,623],{"class":139},[122,692,693],{"class":146},"hint",[122,695,638],{"class":139},[122,697,698],{"class":365},"\"",[122,700,339],{"class":146},[122,702,704],{"class":124,"line":703},41,[122,705,154],{"emptyLinePlaceholder":153},[122,707,709],{"class":124,"line":708},42,[122,710,154],{"emptyLinePlaceholder":153},[122,712,714,716,719,721,723,726,728,731,733,736,739,741,743,745],{"class":124,"line":713},43,[122,715,498],{"class":135},[122,717,718],{"class":293}," _typed",[122,720,504],{"class":146},[122,722,359],{"class":139},[122,724,725],{"class":146},", key: ",[122,727,359],{"class":139},[122,729,730],{"class":146},", value: Any, expected: ",[122,732,517],{"class":139},[122,734,735],{"class":135}," |",[122,737,738],{"class":146}," tuple[",[122,740,517],{"class":139},[122,742,17],{"class":146},[122,744,393],{"class":139},[122,746,747],{"class":146},"]) -> Any:\n",[122,749,751,754,757,760,763,766,769,772,775,778,781,784],{"class":124,"line":750},44,[122,752,753],{"class":135},"    if",[122,755,756],{"class":139}," isinstance",[122,758,759],{"class":146},"(value, ",[122,761,762],{"class":139},"bool",[122,764,765],{"class":146},") ",[122,767,768],{"class":135},"and",[122,770,771],{"class":146}," expected ",[122,773,774],{"class":135},"is",[122,776,777],{"class":135}," not",[122,779,780],{"class":139}," bool",[122,782,783],{"class":146},":        ",[122,785,786],{"class":128},"# bool is an int subclass\n",[122,788,790,793,795,797,800,802,804,806,808,810,812,814,817,820,823,826,829,831,834],{"class":124,"line":789},45,[122,791,792],{"class":135},"        raise",[122,794,661],{"class":146},[122,796,664],{"class":135},[122,798,799],{"class":365},"\"[",[122,801,623],{"class":139},[122,803,684],{"class":146},[122,805,638],{"class":139},[122,807,396],{"class":365},[122,809,623],{"class":139},[122,811,672],{"class":146},[122,813,638],{"class":139},[122,815,816],{"class":365}," must be ",[122,818,819],{"class":139},"{getattr",[122,821,822],{"class":146},"(expected, ",[122,824,825],{"class":365},"'__name__'",[122,827,828],{"class":146},", expected)",[122,830,638],{"class":139},[122,832,833],{"class":365},", got a boolean\"",[122,835,339],{"class":146},[122,837,839,841,843,845],{"class":124,"line":838},46,[122,840,753],{"class":135},[122,842,777],{"class":135},[122,844,756],{"class":139},[122,846,847],{"class":146},"(value, expected):\n",[122,849,851,854,856,859,862,864,866,868,870,872,874,877,880,882,885,888,890],{"class":124,"line":850},47,[122,852,853],{"class":146},"        name ",[122,855,333],{"class":135},[122,857,858],{"class":146}," expected.",[122,860,861],{"class":139},"__name__",[122,863,644],{"class":135},[122,865,756],{"class":139},[122,867,822],{"class":146},[122,869,517],{"class":139},[122,871,765],{"class":146},[122,873,255],{"class":135},[122,875,876],{"class":365}," \" or \"",[122,878,879],{"class":146},".join(t.",[122,881,861],{"class":139},[122,883,884],{"class":135}," for",[122,886,887],{"class":146}," t ",[122,889,545],{"class":135},[122,891,892],{"class":146}," expected)\n",[122,894,896,898,900,902,904,906,908,910,912,914,916,918,920,922,925,927,930,933,936,939,942,945,947,949,951],{"class":124,"line":895},48,[122,897,792],{"class":135},[122,899,661],{"class":146},[122,901,664],{"class":135},[122,903,799],{"class":365},[122,905,623],{"class":139},[122,907,684],{"class":146},[122,909,638],{"class":139},[122,911,396],{"class":365},[122,913,623],{"class":139},[122,915,672],{"class":146},[122,917,638],{"class":139},[122,919,816],{"class":365},[122,921,623],{"class":139},[122,923,924],{"class":146},"name",[122,926,638],{"class":139},[122,928,929],{"class":365},", got ",[122,931,932],{"class":139},"{type",[122,934,935],{"class":146},"(value).",[122,937,938],{"class":139},"__name__}",[122,940,941],{"class":139}," {",[122,943,944],{"class":146},"value",[122,946,635],{"class":135},[122,948,638],{"class":139},[122,950,698],{"class":365},[122,952,339],{"class":146},[122,954,956,959],{"class":124,"line":955},49,[122,957,958],{"class":135},"    return",[122,960,961],{"class":146}," value\n",[122,963,965],{"class":124,"line":964},50,[122,966,154],{"emptyLinePlaceholder":153},[122,968,970],{"class":124,"line":969},51,[122,971,154],{"emptyLinePlaceholder":153},[122,973,975,977,980,983,985],{"class":124,"line":974},52,[122,976,498],{"class":135},[122,978,979],{"class":293}," parse",[122,981,982],{"class":146},"(data: dict[",[122,984,359],{"class":139},[122,986,987],{"class":146},", Any]) -> Config:\n",[122,989,991,994,997],{"class":124,"line":990},53,[122,992,993],{"class":146},"    _check_keys(",[122,995,996],{"class":365},"\"top level\"",[122,998,999],{"class":146},", data, Config)\n",[122,1001,1003,1006,1008,1011,1014],{"class":124,"line":1002},54,[122,1004,1005],{"class":146},"    deploy_raw ",[122,1007,333],{"class":135},[122,1009,1010],{"class":146}," data.get(",[122,1012,1013],{"class":365},"\"deploy\"",[122,1015,1016],{"class":146},", {})\n",[122,1018,1020,1022,1024,1026,1029,1032],{"class":124,"line":1019},55,[122,1021,753],{"class":135},[122,1023,777],{"class":135},[122,1025,756],{"class":139},[122,1027,1028],{"class":146},"(deploy_raw, ",[122,1030,1031],{"class":139},"dict",[122,1033,240],{"class":146},[122,1035,1037,1039,1041,1044],{"class":124,"line":1036},56,[122,1038,792],{"class":135},[122,1040,661],{"class":146},[122,1042,1043],{"class":365},"\"[deploy] must be a table\"",[122,1045,339],{"class":146},[122,1047,1049,1051,1053],{"class":124,"line":1048},57,[122,1050,993],{"class":146},[122,1052,1013],{"class":365},[122,1054,1055],{"class":146},", deploy_raw, DeployConfig)\n",[122,1057,1059,1062,1064],{"class":124,"line":1058},58,[122,1060,1061],{"class":146},"    deploy ",[122,1063,333],{"class":135},[122,1065,1066],{"class":146}," DeployConfig(\n",[122,1068,1070,1073,1075,1078,1080,1082,1085,1088,1090,1093,1095],{"class":124,"line":1069},59,[122,1071,1072],{"class":329},"        region",[122,1074,333],{"class":135},[122,1076,1077],{"class":146},"_typed(",[122,1079,1013],{"class":365},[122,1081,17],{"class":146},[122,1083,1084],{"class":365},"\"region\"",[122,1086,1087],{"class":146},", deploy_raw.get(",[122,1089,1084],{"class":365},[122,1091,1092],{"class":146},", DeployConfig.region), ",[122,1094,359],{"class":139},[122,1096,1097],{"class":146},"),\n",[122,1099,1101,1104,1106,1108,1110,1112,1115,1117,1119,1122,1124],{"class":124,"line":1100},60,[122,1102,1103],{"class":329},"        replicas",[122,1105,333],{"class":135},[122,1107,1077],{"class":146},[122,1109,1013],{"class":365},[122,1111,17],{"class":146},[122,1113,1114],{"class":365},"\"replicas\"",[122,1116,1087],{"class":146},[122,1118,1114],{"class":365},[122,1120,1121],{"class":146},", DeployConfig.replicas), ",[122,1123,375],{"class":139},[122,1125,1097],{"class":146},[122,1127,1129,1132,1134,1137,1140,1142,1144,1147,1149,1151,1154,1157],{"class":124,"line":1128},61,[122,1130,1131],{"class":329},"        tags",[122,1133,333],{"class":135},[122,1135,1136],{"class":139},"tuple",[122,1138,1139],{"class":146},"(_typed(",[122,1141,1013],{"class":365},[122,1143,17],{"class":146},[122,1145,1146],{"class":365},"\"tags\"",[122,1148,1087],{"class":146},[122,1150,1146],{"class":365},[122,1152,1153],{"class":146},", []), ",[122,1155,1156],{"class":139},"list",[122,1158,1159],{"class":146},")),\n",[122,1161,1163],{"class":124,"line":1162},62,[122,1164,1165],{"class":146},"    )\n",[122,1167,1169,1171,1174,1177,1180],{"class":124,"line":1168},63,[122,1170,753],{"class":135},[122,1172,1173],{"class":146}," deploy.replicas ",[122,1175,1176],{"class":135},"\u003C",[122,1178,1179],{"class":139}," 1",[122,1181,350],{"class":146},[122,1183,1185,1187,1189,1192],{"class":124,"line":1184},64,[122,1186,792],{"class":135},[122,1188,661],{"class":146},[122,1190,1191],{"class":365},"\"[deploy] replicas must be at least 1\"",[122,1193,339],{"class":146},[122,1195,1197,1199],{"class":124,"line":1196},65,[122,1198,958],{"class":135},[122,1200,1201],{"class":146}," Config(\n",[122,1203,1205,1208,1210,1212,1214,1216,1219,1222,1224,1227,1229],{"class":124,"line":1204},66,[122,1206,1207],{"class":329},"        api_url",[122,1209,333],{"class":135},[122,1211,1077],{"class":146},[122,1213,996],{"class":365},[122,1215,17],{"class":146},[122,1217,1218],{"class":365},"\"api_url\"",[122,1220,1221],{"class":146},", data.get(",[122,1223,1218],{"class":365},[122,1225,1226],{"class":146},", Config.api_url), ",[122,1228,359],{"class":139},[122,1230,1097],{"class":146},[122,1232,1234,1237,1239,1241,1243,1245,1247,1250,1252,1254,1257,1259,1261,1263],{"class":124,"line":1233},67,[122,1235,1236],{"class":329},"        timeout",[122,1238,333],{"class":135},[122,1240,458],{"class":139},[122,1242,1139],{"class":146},[122,1244,996],{"class":365},[122,1246,17],{"class":146},[122,1248,1249],{"class":365},"\"timeout\"",[122,1251,1221],{"class":146},[122,1253,1249],{"class":365},[122,1255,1256],{"class":146},", Config.timeout), (",[122,1258,375],{"class":139},[122,1260,17],{"class":146},[122,1262,458],{"class":139},[122,1264,1265],{"class":146},"))),\n",[122,1267,1269,1272,1274],{"class":124,"line":1268},68,[122,1270,1271],{"class":329},"        deploy",[122,1273,333],{"class":135},[122,1275,1276],{"class":146},"deploy,\n",[122,1278,1280],{"class":124,"line":1279},69,[122,1281,1165],{"class":146},[122,1283,1285],{"class":124,"line":1284},70,[122,1286,154],{"emptyLinePlaceholder":153},[122,1288,1290],{"class":124,"line":1289},71,[122,1291,154],{"emptyLinePlaceholder":153},[122,1293,1295,1297,1300],{"class":124,"line":1294},72,[122,1296,498],{"class":135},[122,1298,1299],{"class":293}," load",[122,1301,1302],{"class":146},"(path: Path) -> Config:\n",[122,1304,1306,1309],{"class":124,"line":1305},73,[122,1307,1308],{"class":135},"    try",[122,1310,350],{"class":146},[122,1312,1314,1317,1320,1323,1325,1327,1330],{"class":124,"line":1313},74,[122,1315,1316],{"class":135},"        with",[122,1318,1319],{"class":146}," path.open(",[122,1321,1322],{"class":365},"\"rb\"",[122,1324,765],{"class":146},[122,1326,272],{"class":135},[122,1328,1329],{"class":146}," fh:                 ",[122,1331,1332],{"class":128},"# tomllib requires binary mode\n",[122,1334,1336,1339,1341],{"class":124,"line":1335},75,[122,1337,1338],{"class":146},"            data ",[122,1340,333],{"class":135},[122,1342,1343],{"class":146}," tomllib.load(fh)\n",[122,1345,1347,1350,1353],{"class":124,"line":1346},76,[122,1348,1349],{"class":135},"    except",[122,1351,1352],{"class":139}," FileNotFoundError",[122,1354,350],{"class":146},[122,1356,1358,1361,1364],{"class":124,"line":1357},77,[122,1359,1360],{"class":135},"        return",[122,1362,1363],{"class":146}," Config()                              ",[122,1365,1366],{"class":128},"# no file: all defaults\n",[122,1368,1370,1372,1375,1377],{"class":124,"line":1369},78,[122,1371,1349],{"class":135},[122,1373,1374],{"class":146}," tomllib.TOMLDecodeError ",[122,1376,272],{"class":135},[122,1378,1379],{"class":146}," exc:\n",[122,1381,1383,1385,1387,1389,1391,1393,1396,1398,1401,1403,1406,1408,1410,1412,1414],{"class":124,"line":1382},79,[122,1384,792],{"class":135},[122,1386,661],{"class":146},[122,1388,664],{"class":135},[122,1390,698],{"class":365},[122,1392,623],{"class":139},[122,1394,1395],{"class":146},"path",[122,1397,638],{"class":139},[122,1399,1400],{"class":365},": invalid TOML: ",[122,1402,623],{"class":139},[122,1404,1405],{"class":146},"exc",[122,1407,638],{"class":139},[122,1409,698],{"class":365},[122,1411,765],{"class":146},[122,1413,136],{"class":135},[122,1415,1416],{"class":139}," None\n",[122,1418,1420,1422],{"class":124,"line":1419},80,[122,1421,1308],{"class":135},[122,1423,350],{"class":146},[122,1425,1427,1429],{"class":124,"line":1426},81,[122,1428,1360],{"class":135},[122,1430,1431],{"class":146}," parse(data)\n",[122,1433,1435,1437,1440,1442],{"class":124,"line":1434},82,[122,1436,1349],{"class":135},[122,1438,1439],{"class":146}," ConfigError ",[122,1441,272],{"class":135},[122,1443,1379],{"class":146},[122,1445,1447,1449,1451,1453,1455,1457,1459,1461,1464,1466,1468,1470,1472,1474,1476],{"class":124,"line":1446},83,[122,1448,792],{"class":135},[122,1450,661],{"class":146},[122,1452,664],{"class":135},[122,1454,698],{"class":365},[122,1456,623],{"class":139},[122,1458,1395],{"class":146},[122,1460,638],{"class":139},[122,1462,1463],{"class":365},": ",[122,1465,623],{"class":139},[122,1467,1405],{"class":146},[122,1469,638],{"class":139},[122,1471,698],{"class":365},[122,1473,765],{"class":146},[122,1475,136],{"class":135},[122,1477,1416],{"class":139},[10,1479,1480],{},"A matching config file:",[113,1482,1486],{"className":1483,"code":1484,"language":1485,"meta":118,"style":118},"language-toml shiki shiki-themes github-light github-dark","# ~\u002F.config\u002Fmytool\u002Fconfig.toml\napi_url = \"https:\u002F\u002Fstaging.example.com\"\ntimeout = 60\n\n[deploy]\nregion = \"us-east-1\"\nreplicas = 4\ntags = [\"web\", \"blue\"]\n","toml",[14,1487,1488,1493,1501,1509,1513,1524,1532,1540],{"__ignoreMap":118},[122,1489,1490],{"class":124,"line":125},[122,1491,1492],{"class":128},"# ~\u002F.config\u002Fmytool\u002Fconfig.toml\n",[122,1494,1495,1498],{"class":124,"line":132},[122,1496,1497],{"class":146},"api_url = ",[122,1499,1500],{"class":365},"\"https:\u002F\u002Fstaging.example.com\"\n",[122,1502,1503,1506],{"class":124,"line":150},[122,1504,1505],{"class":146},"timeout = ",[122,1507,1508],{"class":139},"60\n",[122,1510,1511],{"class":124,"line":157},[122,1512,154],{"emptyLinePlaceholder":153},[122,1514,1515,1518,1521],{"class":124,"line":166},[122,1516,1517],{"class":146},"[",[122,1519,1520],{"class":293},"deploy",[122,1522,1523],{"class":146},"]\n",[122,1525,1526,1529],{"class":124,"line":174},[122,1527,1528],{"class":146},"region = ",[122,1530,1531],{"class":365},"\"us-east-1\"\n",[122,1533,1534,1537],{"class":124,"line":187},[122,1535,1536],{"class":146},"replicas = ",[122,1538,1539],{"class":139},"4\n",[122,1541,1542,1545,1548,1550,1553],{"class":124,"line":200},[122,1543,1544],{"class":146},"tags = [",[122,1546,1547],{"class":365},"\"web\"",[122,1549,17],{"class":146},[122,1551,1552],{"class":365},"\"blue\"",[122,1554,1523],{"class":146},[1556,1557,1559],"h3",{"id":1558},"why-the-loader-looks-like-this","Why the loader looks like this",[10,1561,1562,1565],{},[91,1563,1564],{},"A missing file means defaults, not an error."," Most users never create a config file; the tool must work without one. Only a file that exists and is wrong is an error.",[10,1567,1568,1571,1572,1575,1576,1579],{},[91,1569,1570],{},"Syntax errors keep their position."," ",[14,1573,1574],{},"tomllib.TOMLDecodeError"," includes the line and column (",[14,1577,1578],{},"Invalid value (at line 1, column 11)","); prefixing the file path gives the user everything needed to open the file at the right place.",[10,1581,1582,1585,1586,1589,1590,1593,1594,1596],{},[91,1583,1584],{},"Unknown keys are errors, with suggestions."," A typo like ",[14,1587,1588],{},"replcas = 3"," is otherwise silently ignored — the most frustrating kind of config bug, because nothing fails and the setting simply does not apply. ",[14,1591,1592],{},"difflib.get_close_matches"," turns it into \"unknown key 'replcas' in ",[122,1595,1520],{}," (did you mean 'replicas'?)\".",[10,1598,1599,1602,1603,1605,1606,1608,1609,1611,1612,1614,1615,1618,1619,1621,1622,1625],{},[91,1600,1601],{},"Types are checked explicitly."," TOML is typed, so ",[14,1604,35],{}," is a string and should be reported, not coerced. The ",[14,1607,762],{}," check matters because ",[14,1610,336],{}," is an instance of ",[14,1613,375],{}," in Python; without it, ",[14,1616,1617],{},"replicas = true"," would pass as ",[14,1620,604],{},". Integers are accepted where floats are expected, since ",[14,1623,1624],{},"timeout = 60"," is a natural thing to write.",[10,1627,1628,1631],{},[91,1629,1630],{},"Constraints live next to types."," \"replicas must be at least 1\" is checked while building the dataclass, so the error names the section and key.",[10,1633,1634,1637,1638,1641],{},[91,1635,1636],{},"The result is frozen."," A ",[14,1639,1640],{},"frozen=True"," dataclass cannot be mutated by code halfway through a command, which keeps the configuration a single, predictable source of truth.",[10,1643,1644,1645,1649],{},"For larger configurations, a validation library does the key and type checking declaratively — pydantic, attrs with cattrs, or msgspec. ",[38,1646,1648],{"href":1647},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Ftyped-settings-with-pydantic-settings\u002F","Typed settings with pydantic-settings"," shows that approach, including merging environment variables. The hand-written loader above has the advantage of no dependencies and no import cost, which matters for a CLI's startup time.",[44,1651,1653],{"id":1652},"writing-toml-back","Writing TOML back",[10,1655,1656,1658,1659,1662],{},[14,1657,27],{}," only reads. If your CLI has a ",[14,1660,1661],{},"config set"," command, you need a writer:",[49,1664,1665,1673],{},[52,1666,1667,1672],{},[91,1668,1669],{},[14,1670,1671],{},"tomli-w"," writes dictionaries as TOML. It is small and fast, but it does not preserve comments or formatting — rewriting a user's hand-edited file with it discards their comments.",[52,1674,1675,1680],{},[91,1676,1677],{},[14,1678,1679],{},"tomlkit"," parses into a document object that preserves comments, whitespace and ordering, and writes it back almost unchanged. It is the right choice for editing files people also edit by hand.",[113,1682,1684],{"className":115,"code":1683,"language":117,"meta":118,"style":118},"import tomlkit\n\ndoc = tomlkit.parse(path.read_text(encoding=\"utf-8\"))\ndoc.setdefault(\"deploy\", tomlkit.table())[\"replicas\"] = 5\npath.write_text(tomlkit.dumps(doc), encoding=\"utf-8\")   # comments survive\n",[14,1685,1686,1693,1697,1718,1737],{"__ignoreMap":118},[122,1687,1688,1690],{"class":124,"line":125},[122,1689,160],{"class":135},[122,1691,1692],{"class":146}," tomlkit\n",[122,1694,1695],{"class":124,"line":132},[122,1696,154],{"emptyLinePlaceholder":153},[122,1698,1699,1702,1704,1707,1710,1712,1715],{"class":124,"line":150},[122,1700,1701],{"class":146},"doc ",[122,1703,333],{"class":135},[122,1705,1706],{"class":146}," tomlkit.parse(path.read_text(",[122,1708,1709],{"class":329},"encoding",[122,1711,333],{"class":135},[122,1713,1714],{"class":365},"\"utf-8\"",[122,1716,1717],{"class":146},"))\n",[122,1719,1720,1723,1725,1728,1730,1732,1734],{"class":124,"line":157},[122,1721,1722],{"class":146},"doc.setdefault(",[122,1724,1013],{"class":365},[122,1726,1727],{"class":146},", tomlkit.table())[",[122,1729,1114],{"class":365},[122,1731,396],{"class":146},[122,1733,333],{"class":135},[122,1735,1736],{"class":139}," 5\n",[122,1738,1739,1742,1744,1746,1748,1751],{"class":124,"line":166},[122,1740,1741],{"class":146},"path.write_text(tomlkit.dumps(doc), ",[122,1743,1709],{"class":329},[122,1745,333],{"class":135},[122,1747,1714],{"class":365},[122,1749,1750],{"class":146},")   ",[122,1752,1753],{"class":128},"# comments survive\n",[10,1755,1756,1757,1759,1760,42],{},"Write the result atomically so an interrupted ",[14,1758,1661],{}," cannot leave an empty file, as described in ",[38,1761,1763],{"href":1762},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fwriting-files-atomically-in-python-clis\u002F","writing files atomically in Python CLIs",[44,1765,1767],{"id":1766},"ux-considerations","UX considerations",[84,1769],{"name":1770},"toml-terminal",[49,1772,1773,1779,1785,1802],{},[52,1774,1775,1778],{},[91,1776,1777],{},"Always name the file."," Users may have a user config, a project config and a system config; \"invalid TOML\" without a path sends them searching.",[52,1780,1781,1784],{},[91,1782,1783],{},"Report the first problem precisely rather than all problems vaguely."," For hand-edited files, one precise message with a line number or key is more useful than a list of generic complaints.",[52,1786,1787,1797,1798,42],{},[91,1788,1789,1790,1793,1794,42],{},"Offer ",[14,1791,1792],{},"config show"," and ",[14,1795,1796],{},"config path"," Showing the effective configuration, and where it came from, answers most \"why is it doing that?\" questions. The precedence rules that combine files with flags and environment variables are in ",[38,1799,1801],{"href":1800},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Fconfig-precedence-flags-env-files-defaults\u002F","config precedence: flags, env, files and defaults",[52,1803,1804,1637,1807,1810],{},[91,1805,1806],{},"Ship a commented example.",[14,1808,1809],{},"config init"," command that writes a fully commented default file teaches every option in place.",[44,1812,1814],{"id":1813},"testing-the-behaviour","Testing the behaviour",[10,1816,1817,1818,1821],{},"Test the loader with small files in ",[14,1819,1820],{},"tmp_path",", and give every error its own parametrised case — the messages are part of the interface:",[113,1823,1825],{"className":115,"code":1824,"language":117,"meta":118,"style":118},"# tests\u002Ftest_config.py\nfrom pathlib import Path\n\nimport pytest\n\nfrom mytool.config import Config, ConfigError, load\n\n\ndef write(tmp_path: Path, text: str) -> Path:\n    p = tmp_path \u002F \"mytool.toml\"\n    p.write_text(text, encoding=\"utf-8\")\n    return p\n\n\ndef test_missing_file_gives_defaults(tmp_path):\n    assert load(tmp_path \u002F \"nope.toml\") == Config()\n\n\ndef test_full_file(tmp_path):\n    cfg = load(write(tmp_path, 'api_url = \"https:\u002F\u002Fstaging.example.com\"\\ntimeout = 60\\n\\n'\n                               '[deploy]\\nregion = \"us-east-1\"\\nreplicas = 4\\ntags = [\"web\", \"blue\"]\\n'))\n    assert cfg.timeout == 60.0 and cfg.deploy.replicas == 4 and cfg.deploy.tags == (\"web\", \"blue\")\n\n\n@pytest.mark.parametrize(\"text, fragment\", [\n    ('timeout = \\n', \"invalid TOML\"),\n    ('[deploy]\\nreplcas = 3\\n', \"did you mean 'replicas'\"),\n    ('[deploy]\\nreplicas = \"3\"\\n', \"replicas must be int, got str\"),\n    ('[deploy]\\nreplicas = true\\n', \"got a boolean\"),\n    ('[deploy]\\nreplicas = 0\\n', \"at least 1\"),\n    ('deploy = \"eu\"\\n', \"must be a table\"),\n])\ndef test_errors_name_the_problem(tmp_path, text, fragment):\n    with pytest.raises(ConfigError, match=fragment) as info:\n        load(write(tmp_path, text))\n    assert \"mytool.toml\" in str(info.value)\n",[14,1826,1827,1832,1842,1846,1853,1857,1869,1873,1877,1892,1908,1921,1928,1932,1936,1946,1967,1971,1975,1984,2008,2035,2075,2079,2083,2096,2115,2137,2158,2179,2201,2219,2224,2234,2255,2260],{"__ignoreMap":118},[122,1828,1829],{"class":124,"line":125},[122,1830,1831],{"class":128},"# tests\u002Ftest_config.py\n",[122,1833,1834,1836,1838,1840],{"class":124,"line":132},[122,1835,136],{"class":135},[122,1837,192],{"class":146},[122,1839,160],{"class":135},[122,1841,197],{"class":146},[122,1843,1844],{"class":124,"line":150},[122,1845,154],{"emptyLinePlaceholder":153},[122,1847,1848,1850],{"class":124,"line":157},[122,1849,160],{"class":135},[122,1851,1852],{"class":146}," pytest\n",[122,1854,1855],{"class":124,"line":166},[122,1856,154],{"emptyLinePlaceholder":153},[122,1858,1859,1861,1864,1866],{"class":124,"line":174},[122,1860,136],{"class":135},[122,1862,1863],{"class":146}," mytool.config ",[122,1865,160],{"class":135},[122,1867,1868],{"class":146}," Config, ConfigError, load\n",[122,1870,1871],{"class":124,"line":187},[122,1872,154],{"emptyLinePlaceholder":153},[122,1874,1875],{"class":124,"line":200},[122,1876,154],{"emptyLinePlaceholder":153},[122,1878,1879,1881,1884,1887,1889],{"class":124,"line":213},[122,1880,498],{"class":135},[122,1882,1883],{"class":293}," write",[122,1885,1886],{"class":146},"(tmp_path: Path, text: ",[122,1888,359],{"class":139},[122,1890,1891],{"class":146},") -> Path:\n",[122,1893,1894,1897,1899,1902,1905],{"class":124,"line":218},[122,1895,1896],{"class":146},"    p ",[122,1898,333],{"class":135},[122,1900,1901],{"class":146}," tmp_path ",[122,1903,1904],{"class":135},"\u002F",[122,1906,1907],{"class":365}," \"mytool.toml\"\n",[122,1909,1910,1913,1915,1917,1919],{"class":124,"line":243},[122,1911,1912],{"class":146},"    p.write_text(text, ",[122,1914,1709],{"class":329},[122,1916,333],{"class":135},[122,1918,1714],{"class":365},[122,1920,339],{"class":146},[122,1922,1923,1925],{"class":124,"line":252},[122,1924,958],{"class":135},[122,1926,1927],{"class":146}," p\n",[122,1929,1930],{"class":124,"line":264},[122,1931,154],{"emptyLinePlaceholder":153},[122,1933,1934],{"class":124,"line":277},[122,1935,154],{"emptyLinePlaceholder":153},[122,1937,1938,1940,1943],{"class":124,"line":282},[122,1939,498],{"class":135},[122,1941,1942],{"class":293}," test_missing_file_gives_defaults",[122,1944,1945],{"class":146},"(tmp_path):\n",[122,1947,1948,1951,1954,1956,1959,1961,1964],{"class":124,"line":287},[122,1949,1950],{"class":135},"    assert",[122,1952,1953],{"class":146}," load(tmp_path ",[122,1955,1904],{"class":135},[122,1957,1958],{"class":365}," \"nope.toml\"",[122,1960,765],{"class":146},[122,1962,1963],{"class":135},"==",[122,1965,1966],{"class":146}," Config()\n",[122,1968,1969],{"class":124,"line":305},[122,1970,154],{"emptyLinePlaceholder":153},[122,1972,1973],{"class":124,"line":311},[122,1974,154],{"emptyLinePlaceholder":153},[122,1976,1977,1979,1982],{"class":124,"line":316},[122,1978,498],{"class":135},[122,1980,1981],{"class":293}," test_full_file",[122,1983,1945],{"class":146},[122,1985,1986,1989,1991,1994,1997,2000,2002,2005],{"class":124,"line":321},[122,1987,1988],{"class":146},"    cfg ",[122,1990,333],{"class":135},[122,1992,1993],{"class":146}," load(write(tmp_path, ",[122,1995,1996],{"class":365},"'api_url = \"https:\u002F\u002Fstaging.example.com\"",[122,1998,1999],{"class":139},"\\n",[122,2001,1624],{"class":365},[122,2003,2004],{"class":139},"\\n\\n",[122,2006,2007],{"class":365},"'\n",[122,2009,2010,2013,2015,2018,2020,2023,2025,2028,2030,2033],{"class":124,"line":342},[122,2011,2012],{"class":365},"                               '[deploy]",[122,2014,1999],{"class":139},[122,2016,2017],{"class":365},"region = \"us-east-1\"",[122,2019,1999],{"class":139},[122,2021,2022],{"class":365},"replicas = 4",[122,2024,1999],{"class":139},[122,2026,2027],{"class":365},"tags = [\"web\", \"blue\"]",[122,2029,1999],{"class":139},[122,2031,2032],{"class":365},"'",[122,2034,1717],{"class":146},[122,2036,2037,2039,2042,2044,2047,2050,2053,2055,2058,2060,2063,2065,2067,2069,2071,2073],{"class":124,"line":353},[122,2038,1950],{"class":135},[122,2040,2041],{"class":146}," cfg.timeout ",[122,2043,1963],{"class":135},[122,2045,2046],{"class":139}," 60.0",[122,2048,2049],{"class":135}," and",[122,2051,2052],{"class":146}," cfg.deploy.replicas ",[122,2054,1963],{"class":135},[122,2056,2057],{"class":139}," 4",[122,2059,2049],{"class":135},[122,2061,2062],{"class":146}," cfg.deploy.tags ",[122,2064,1963],{"class":135},[122,2066,94],{"class":146},[122,2068,1547],{"class":365},[122,2070,17],{"class":146},[122,2072,1552],{"class":365},[122,2074,339],{"class":146},[122,2076,2077],{"class":124,"line":369},[122,2078,154],{"emptyLinePlaceholder":153},[122,2080,2081],{"class":124,"line":383},[122,2082,154],{"emptyLinePlaceholder":153},[122,2084,2085,2088,2090,2093],{"class":124,"line":404},[122,2086,2087],{"class":293},"@pytest.mark.parametrize",[122,2089,297],{"class":146},[122,2091,2092],{"class":365},"\"text, fragment\"",[122,2094,2095],{"class":146},", [\n",[122,2097,2098,2101,2104,2106,2108,2110,2113],{"class":124,"line":409},[122,2099,2100],{"class":146},"    (",[122,2102,2103],{"class":365},"'timeout = ",[122,2105,1999],{"class":139},[122,2107,2032],{"class":365},[122,2109,17],{"class":146},[122,2111,2112],{"class":365},"\"invalid TOML\"",[122,2114,1097],{"class":146},[122,2116,2117,2119,2122,2124,2126,2128,2130,2132,2135],{"class":124,"line":414},[122,2118,2100],{"class":146},[122,2120,2121],{"class":365},"'[deploy]",[122,2123,1999],{"class":139},[122,2125,1588],{"class":365},[122,2127,1999],{"class":139},[122,2129,2032],{"class":365},[122,2131,17],{"class":146},[122,2133,2134],{"class":365},"\"did you mean 'replicas'\"",[122,2136,1097],{"class":146},[122,2138,2139,2141,2143,2145,2147,2149,2151,2153,2156],{"class":124,"line":429},[122,2140,2100],{"class":146},[122,2142,2121],{"class":365},[122,2144,1999],{"class":139},[122,2146,35],{"class":365},[122,2148,1999],{"class":139},[122,2150,2032],{"class":365},[122,2152,17],{"class":146},[122,2154,2155],{"class":365},"\"replicas must be int, got str\"",[122,2157,1097],{"class":146},[122,2159,2160,2162,2164,2166,2168,2170,2172,2174,2177],{"class":124,"line":439},[122,2161,2100],{"class":146},[122,2163,2121],{"class":365},[122,2165,1999],{"class":139},[122,2167,1617],{"class":365},[122,2169,1999],{"class":139},[122,2171,2032],{"class":365},[122,2173,17],{"class":146},[122,2175,2176],{"class":365},"\"got a boolean\"",[122,2178,1097],{"class":146},[122,2180,2181,2183,2185,2187,2190,2192,2194,2196,2199],{"class":124,"line":452},[122,2182,2100],{"class":146},[122,2184,2121],{"class":365},[122,2186,1999],{"class":139},[122,2188,2189],{"class":365},"replicas = 0",[122,2191,1999],{"class":139},[122,2193,2032],{"class":365},[122,2195,17],{"class":146},[122,2197,2198],{"class":365},"\"at least 1\"",[122,2200,1097],{"class":146},[122,2202,2203,2205,2208,2210,2212,2214,2217],{"class":124,"line":466},[122,2204,2100],{"class":146},[122,2206,2207],{"class":365},"'deploy = \"eu\"",[122,2209,1999],{"class":139},[122,2211,2032],{"class":365},[122,2213,17],{"class":146},[122,2215,2216],{"class":365},"\"must be a table\"",[122,2218,1097],{"class":146},[122,2220,2221],{"class":124,"line":485},[122,2222,2223],{"class":146},"])\n",[122,2225,2226,2228,2231],{"class":124,"line":490},[122,2227,498],{"class":135},[122,2229,2230],{"class":293}," test_errors_name_the_problem",[122,2232,2233],{"class":146},"(tmp_path, text, fragment):\n",[122,2235,2236,2239,2242,2245,2247,2250,2252],{"class":124,"line":495},[122,2237,2238],{"class":135},"    with",[122,2240,2241],{"class":146}," pytest.raises(ConfigError, ",[122,2243,2244],{"class":329},"match",[122,2246,333],{"class":135},[122,2248,2249],{"class":146},"fragment) ",[122,2251,272],{"class":135},[122,2253,2254],{"class":146}," info:\n",[122,2256,2257],{"class":124,"line":528},[122,2258,2259],{"class":146},"        load(write(tmp_path, text))\n",[122,2261,2262,2264,2267,2269,2272],{"class":124,"line":557},[122,2263,1950],{"class":135},[122,2265,2266],{"class":365}," \"mytool.toml\"",[122,2268,582],{"class":135},[122,2270,2271],{"class":139}," str",[122,2273,2274],{"class":146},"(info.value)\n",[10,2276,2277,2278,2281],{},"Each failure case asserts on the specific message ",[2279,2280,768],"em",{}," that the file name appears, so a refactor cannot quietly degrade the errors users depend on.",[44,2283,2285],{"id":2284},"conclusion","Conclusion",[10,2287,2288,2290,2291,2293],{},[14,2289,27],{}," makes TOML configuration free in Python 3.11+, but the dictionary it returns is only the start. Open files in binary mode, treat a missing file as defaults, keep the line and column of syntax errors, reject unknown keys with a suggestion, check types strictly — remembering that booleans are integers in Python — enforce constraints where you build the typed object, and return a frozen dataclass. Use ",[14,2292,1679],{}," when you need to edit files users also edit. Every configuration mistake then produces one clear message that names the file and the fix.",[44,2295,2297],{"id":2296},"frequently-asked-questions","Frequently asked questions",[1556,2299,2301],{"id":2300},"toml-or-yaml-for-a-clis-config","TOML or YAML for a CLI's config?",[10,2303,2304,2305,42],{},"TOML for most CLIs: it is unambiguous, typed, and parsed by the standard library. YAML suits deeply nested data and is familiar to Kubernetes users, but it has surprising implicit typing and needs a third-party parser; if you do use it, follow ",[38,2306,2308],{"href":2307},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Floading-yaml-configs-safely-in-cli-apps\u002F","loading YAML configs safely in CLI apps",[1556,2310,2312,2313,2315],{"id":2311},"can-my-tool-read-its-settings-from-pyprojecttoml","Can my tool read its settings from ",[14,2314,16],{},"?",[10,2317,2318,2319,2322,2323,2325,2326,2328,2329,2332,2333,2336],{},"Yes — many tools read a ",[14,2320,2321],{},"[tool.mytool]"," table from the project's ",[14,2324,16],{},". Load the file with ",[14,2327,27],{}," and take ",[14,2330,2331],{},"data.get(\"tool\", {}).get(\"mytool\", {})",", then pass that dictionary to the same ",[14,2334,2335],{},"parse"," function.",[1556,2338,2340],{"id":2339},"how-do-i-support-environment-variables-for-the-same-settings","How do I support environment variables for the same settings?",[10,2342,2343],{},"Build the dataclass from the file, then override fields from environment variables and flags in a fixed order. pydantic-settings does this automatically; by hand, it is a few lines per field.",[1556,2345,2347,2348,2351],{"id":2346},"why-not-just-use-configparser-and-ini-files","Why not just use ",[14,2349,2350],{},"configparser"," and INI files?",[10,2353,2354,2355,2357],{},"INI has no types (everything is a string), no nesting beyond one level of sections, and inconsistent quoting rules. TOML fixes all three, and ",[14,2356,27],{}," is equally standard.",[44,2359,2361],{"id":2360},"related","Related",[49,2363,2364,2370,2374,2379,2384],{},[52,2365,2366,2367],{},"Up: ",[38,2368,2369],{"href":40},"Handling configuration files and environment variables",[52,2371,2372],{},[38,2373,1648],{"href":1647},[52,2375,2376],{},[38,2377,2378],{"href":76},"Discovering project config files by walking up directories",[52,2380,2381],{},[38,2382,2383],{"href":1800},"Config precedence: flags, env, files and defaults",[52,2385,2386],{},[38,2387,2388],{"href":2307},"Loading YAML configs safely in CLI apps",[2390,2391,2392],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":118,"searchDepth":132,"depth":132,"links":2394},[2395,2396,2397,2400,2401,2402,2403,2404,2412],{"id":46,"depth":132,"text":47},{"id":81,"depth":132,"text":82},{"id":110,"depth":132,"text":111,"children":2398},[2399],{"id":1558,"depth":150,"text":1559},{"id":1652,"depth":132,"text":1653},{"id":1766,"depth":132,"text":1767},{"id":1813,"depth":132,"text":1814},{"id":2284,"depth":132,"text":2285},{"id":2296,"depth":132,"text":2297,"children":2405},[2406,2407,2409,2410],{"id":2300,"depth":150,"text":2301},{"id":2311,"depth":150,"text":2408},"Can my tool read its settings from pyproject.toml?",{"id":2339,"depth":150,"text":2340},{"id":2346,"depth":150,"text":2411},"Why not just use configparser and INI files?",{"id":2360,"depth":132,"text":2361},"2026-09-18","Load a Python CLI’s TOML configuration with the standard-library tomllib: binary mode, typed dataclasses, unknown-key detection, clear errors and writing TOML back.","beginner",false,"md",{},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Freading-toml-config-with-tomllib",{"title":5,"description":2414},"advanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Freading-toml-config-with-tomllib\u002Findex",[1485,27,2423,2424],"configuration","validation","8lf0mKEUOAbE_EBZQHbPLB9m0wZxdOhysYCks8Ws_ys",[2427,2430,2433,2436,2439,2442,2445,2448,2451,2454,2457,2460,2463,2466,2469,2472,2475,2478,2481,2484,2487,2490,2493,2496,2499,2502,2505,2508,2511,2514,2517,2520,2523,2526,2529,2532,2535,2538,2541,2544,2547,2549,2550,2553,2556,2559,2562,2565,2568,2571,2574,2577,2580,2583,2586,2589,2592,2595,2598,2601,2604,2607,2610,2613,2616,2619,2622,2625,2628,2631,2634,2637,2640,2643,2646,2649,2652,2655,2658,2661,2664,2667,2670,2673,2676,2679,2682,2685,2688,2691,2694,2697,2700,2703,2706,2709,2712,2715,2718,2721,2724,2727,2730,2733,2736,2739,2742,2745,2748,2751,2754,2757,2760,2763,2766,2769,2772,2775,2778,2781,2784,2787,2790,2793,2796,2799,2802,2805,2808,2811,2814,2817,2820,2823,2826,2829,2832,2835,2838,2841,2843,2846,2849,2852,2855,2858,2861,2864,2867,2870,2873,2876,2879,2882,2885,2888,2891,2894,2897,2900,2903,2906,2909,2912,2915,2918,2921,2924,2927,2930,2933,2936,2939,2942,2945,2948,2951,2954,2957,2960,2963,2966,2969,2972,2975,2978,2981,2984,2987,2990,2993,2996,2999,3002,3005,3008,3011,3014,3017,3020,3023,3026,3029,3032,3035,3038,3041,3044,3047,3050,3053,3056,3059,3062,3065,3068,3071,3074,3077,3080,3083,3086,3089,3092,3095,3098,3101,3104,3107,3110,3113,3116,3119,3122,3125,3128,3131,3134,3137,3140,3143,3146,3149,3152,3155,3158,3161,3164,3167,3170,3173,3176,3179,3182,3185,3188,3191,3194,3197,3200,3203,3206,3209,3212,3215,3218,3221,3224,3227,3230,3233,3236,3239,3242,3245,3248,3251,3254,3257,3260,3263,3266],{"path":2428,"title":2429},"\u002Fabout","About Python CLI Toolcraft",{"path":2431,"title":2432},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies","Advanced Argument Validation Strategies",{"path":2434,"title":2435},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fparsing-nested-json-arguments-in-python-clis","Parsing Nested JSON Args in Python CLIs",{"path":2437,"title":2438},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fvalidating-dates-and-durations-in-cli-arguments","Validating Dates and Durations in Python CLI Arguments",{"path":2440,"title":2441},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fvalidating-dependent-and-conflicting-options","Validating Dependent and Conflicting CLI Options",{"path":2443,"title":2444},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fvalidating-file-and-directory-paths-in-clis","Validating File and Directory Paths in CLIs",{"path":2446,"title":2447},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fvalidating-urls-hosts-and-ports","Validating URLs, Hosts and Ports in Python CLI Arguments",{"path":2449,"title":2450},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fwriting-custom-click-parameter-types","Writing Custom Click Parameter Types",{"path":2452,"title":2453},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual\u002Fbrowsing-records-with-a-textual-datatable","Browsing Records with a Textual DataTable Picker",{"path":2455,"title":2456},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual\u002Fbuilding-your-first-textual-app","Building Your First Textual App for a Python CLI",{"path":2458,"title":2459},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual\u002Fchoosing-between-a-cli-a-prompt-flow-and-a-tui","Choosing Between a CLI, a Prompt Flow and a TUI",{"path":2461,"title":2462},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual","Building Terminal UIs with Textual for Python CLIs",{"path":2464,"title":2465},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual\u002Frunning-background-work-in-textual-with-workers","Running Background Work in Textual with Workers",{"path":2467,"title":2468},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual\u002Fstyling-textual-apps-with-tcss","Styling Textual Apps with TCSS",{"path":2470,"title":2471},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual\u002Ftesting-textual-apps-with-pilot","Testing Textual Apps with Pilot",{"path":2473,"title":2474},"\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":2476,"title":2477},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fdocumenting-environment-variables-in-help","Documenting Environment Variables in CLI Help",{"path":2479,"title":2480},"\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":2482,"title":2483},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation","CLI Help Output and Documentation",{"path":2485,"title":2486},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Frich-formatted-help-with-rich-click","Rich-Formatted Help for Click CLIs with rich-click",{"path":2488,"title":2489},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fversioning-and-deprecating-cli-flags","Versioning and Deprecating CLI Flags",{"path":2491,"title":2492},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fwriting-help-text-users-actually-read","Writing Help Text Users Actually Read",{"path":2494,"title":2495},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Fadapting-output-to-terminal-width","Adapting Python CLI Output to Terminal Width",{"path":2497,"title":2498},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Fdetecting-ci-environments-and-non-interactive-shells","Detecting CI Environments and Non-Interactive Shells",{"path":2500,"title":2501},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Ffixing-unicode-and-encoding-errors-on-windows","Fixing Unicode and Encoding Errors on Windows in Python CLIs",{"path":2503,"title":2504},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Fhandling-ansi-escape-codes-on-windows-consoles","Handling ANSI Escape Codes on Windows Consoles",{"path":2506,"title":2507},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility","Cross-Platform Terminal Compatibility for Python CLIs",{"path":2509,"title":2510},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Frespecting-no-color-and-force-color","Respecting NO_COLOR and FORCE_COLOR in Python CLIs",{"path":2512,"title":2513},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Fsupporting-dumb-terminals-and-screen-readers","Supporting Dumb Terminals and Screen Readers in a Python CLI",{"path":2515,"title":2516},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fchoosing-exit-codes-for-cli-tools","Choosing Exit Codes for CLI Tools",{"path":2518,"title":2519},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fdesigning-an-exception-hierarchy-for-a-cli","Designing an Exception Hierarchy for a Python CLI",{"path":2521,"title":2522},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fdid-you-mean-suggestions-for-mistyped-input","Did You Mean…? Suggestions for Mistyped CLI Input",{"path":2524,"title":2525},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Ffriendly-error-messages-and-tracebacks","Friendly Error Messages and Tracebacks",{"path":2527,"title":2528},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fhandling-keyboard-interrupt-cleanly","Handling Keyboard Interrupt Cleanly",{"path":2530,"title":2531},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes","Error Handling and Exit Codes for CLIs",{"path":2533,"title":2534},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Freporting-machine-readable-errors-in-json-mode","Reporting Machine-Readable Errors in JSON Mode",{"path":2536,"title":2537},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fwriting-crash-reports-users-can-send","Writing Crash Reports Users Can Send",{"path":2539,"title":2540},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Fconfig-precedence-flags-env-files-defaults","Config Precedence: Flags, Env, Files, Defaults",{"path":2542,"title":2543},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Fdiscovering-project-config-files-by-walking-up-directories","Discovering Project Config Files by Walking Up Directories",{"path":2545,"title":2546},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars","Handling Config Files and Env Vars in CLIs",{"path":2548,"title":2388},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Floading-yaml-configs-safely-in-cli-apps",{"path":2419,"title":5},{"path":2551,"title":2552},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Ftyped-settings-with-pydantic-settings","Typed Settings with pydantic-settings in Python CLIs",{"path":2554,"title":2555},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Fvalidating-config-files-with-json-schema","Validating Config Files with JSON Schema in a Python CLI",{"path":2557,"title":2558},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Fwriting-a-config-init-and-edit-command","Writing a Config Init and Edit Command for a Python CLI",{"path":2560,"title":2561},"\u002Fadvanced-input-parsing-user-experience","Advanced Input Parsing for Python CLIs",{"path":2563,"title":2564},"\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":2566,"title":2567},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Fbuilding-interactive-prompts-and-menus","Building Interactive Prompts and Menus in Python CLIs",{"path":2569,"title":2570},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Fbuilding-tree-views-with-rich","Building Tree Views with Rich in a Python CLI",{"path":2572,"title":2573},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich","Interactive Terminal UI with Rich",{"path":2575,"title":2576},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Flive-dashboards-with-rich-live","Live Dashboards with Rich Live in Python CLIs",{"path":2578,"title":2579},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Frendering-markdown-and-syntax-highlighting-with-rich","Rendering Markdown and Syntax Highlighting with Rich",{"path":2581,"title":2582},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Frendering-tables-and-json-with-rich","Rendering Tables and JSON with Rich",{"path":2584,"title":2585},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Ftheming-rich-output-consistently","Theming Rich Output Consistently in Python CLIs",{"path":2587,"title":2588},"\u002Fadvanced-input-parsing-user-experience\u002Foutput-formats-for-data-clis\u002Fadding-a-format-flag-for-table-json-and-csv","Adding a Format Flag for Table, JSON and CSV Output",{"path":2590,"title":2591},"\u002Fadvanced-input-parsing-user-experience\u002Foutput-formats-for-data-clis\u002Fcustom-output-templates-with-a-format-string","Custom Output Templates with a Format String in Python CLIs",{"path":2593,"title":2594},"\u002Fadvanced-input-parsing-user-experience\u002Foutput-formats-for-data-clis\u002Fexporting-cli-results-to-files","Exporting CLI Results to Files from a Python CLI",{"path":2596,"title":2597},"\u002Fadvanced-input-parsing-user-experience\u002Foutput-formats-for-data-clis","Output Formats for Data-Heavy Python CLIs",{"path":2599,"title":2600},"\u002Fadvanced-input-parsing-user-experience\u002Foutput-formats-for-data-clis\u002Fselecting-fields-and-columns-from-cli-output","Selecting Fields and Columns from Python CLI Output",{"path":2602,"title":2603},"\u002Fadvanced-input-parsing-user-experience\u002Foutput-formats-for-data-clis\u002Fwriting-csv-and-tsv-output-correctly","Writing CSV and TSV Output Correctly from a Python CLI",{"path":2605,"title":2606},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Fargcomplete-for-argparse-clis","Tab Completion for argparse CLIs with argcomplete",{"path":2608,"title":2609},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Fdynamic-completion-values-from-apis-and-files","Dynamic Completion Values from APIs and Files",{"path":2611,"title":2612},"\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":2614,"title":2615},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis","Shell Completion for Python CLIs",{"path":2617,"title":2618},"\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":2620,"title":2621},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Fshipping-completion-scripts-with-packages","Shipping Shell Completion Scripts with a Python CLI",{"path":2623,"title":2624},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Ftesting-shell-completion-in-python-clis","Testing Shell Completion in Python CLIs",{"path":2626,"title":2627},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fadding-trace-ids-and-context-to-cli-logs","Adding Trace IDs and Context to Python CLI Logs",{"path":2629,"title":2630},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fadding-verbose-and-quiet-logging-flags","Adding Verbose and Quiet Logging Flags",{"path":2632,"title":2633},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps","Structured Logging for CLI Apps",{"path":2635,"title":2636},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Flogging-with-structlog-in-a-cli","Logging with structlog in a Python CLI",{"path":2638,"title":2639},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fseparating-logs-from-program-output","Separating Logs from Program Output in a Python CLI",{"path":2641,"title":2642},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fstructured-json-logging-in-python-clis","Structured JSON Logging in Python CLIs",{"path":2644,"title":2645},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fwriting-rotating-log-files-from-a-cli","Writing Rotating Log Files from a Python CLI",{"path":2647,"title":2648},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Fdetecting-tty-and-adapting-output","Detecting a TTY and Adapting Output",{"path":2650,"title":2651},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Femitting-json-output-for-scripting","Emitting JSON Output for Scripting",{"path":2653,"title":2654},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Fhandling-broken-pipe-and-sigpipe","Handling Broken Pipe and SIGPIPE",{"path":2656,"title":2657},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes","Working with stdin, stdout and Pipes",{"path":2659,"title":2660},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Fnull-delimited-input-and-xargs-compatibility","Null-Delimited Input and xargs Compatibility in Python CLIs",{"path":2662,"title":2663},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Fprocessing-large-files-and-ndjson-streams","Processing Large Files and NDJSON Streams in Python CLIs",{"path":2665,"title":2666},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Freading-piped-input-in-python-clis","Reading Piped Input in Python CLIs",{"path":2668,"title":2669},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Fbuilding-an-api-client-cli-with-httpx","Building an API Client CLI with httpx",{"path":2671,"title":2672},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Fdownloading-files-with-progress-in-python","Downloading Files with Progress in Python CLIs",{"path":2674,"title":2675},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis","Calling HTTP APIs from Python CLIs",{"path":2677,"title":2678},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Fmocking-http-in-cli-tests-with-respx","Mocking HTTP in Python CLI Tests with respx",{"path":2680,"title":2681},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Foauth-device-flow-login-for-clis","OAuth Device Flow Login for Python CLIs",{"path":2683,"title":2684},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Fpaginating-api-results-in-a-cli","Paginating API Results in a Python CLI",{"path":2686,"title":2687},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Fretries-and-backoff-for-cli-http-calls","Retries and Backoff for CLI HTTP Calls",{"path":2689,"title":2690},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Fuploading-files-with-multipart-and-progress","Uploading Files with Multipart and Progress in a Python CLI",{"path":2692,"title":2693},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Fcancelling-async-tasks-on-ctrl-c","Cancelling Async Tasks on Ctrl+C in Python CLIs",{"path":2695,"title":2696},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis","Concurrency and Async in Python CLIs",{"path":2698,"title":2699},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Fmultiprocessing-for-cpu-bound-cli-tasks","Multiprocessing for CPU-Bound CLI Tasks",{"path":2701,"title":2702},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Fparallelising-cli-work-with-thread-pools","Parallelising CLI Work with Thread Pools",{"path":2704,"title":2705},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Frate-limiting-concurrent-requests-in-clis","Rate-Limiting Concurrent Requests in Python CLIs",{"path":2707,"title":2708},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Frunning-async-code-in-typer-and-click","Running Async Code in Typer and Click",{"path":2710,"title":2711},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Fshowing-progress-for-concurrent-tasks","Showing Progress for Concurrent Tasks in a Python CLI",{"path":2713,"title":2714},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Fstructured-concurrency-with-taskgroups-in-clis","Structured Concurrency with TaskGroups in Python CLIs",{"path":2716,"title":2717},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fcross-platform-paths-with-pathlib","Cross-Platform Paths with pathlib in CLIs",{"path":2719,"title":2720},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Ffile-locking-for-concurrent-cli-runs","File Locking for Concurrent CLI Runs in Python",{"path":2722,"title":2723},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fhandling-file-permissions-and-umask-in-clis","Handling File Permissions and umask in Python CLIs",{"path":2725,"title":2726},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes","Filesystem Paths and Atomic Writes for CLIs",{"path":2728,"title":2729},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fsafe-temporary-files-and-directories","Safe Temporary Files and Directories in CLIs",{"path":2731,"title":2732},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fstoring-app-data-with-platformdirs","Storing CLI App Data with platformdirs",{"path":2734,"title":2735},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fwalking-directory-trees-with-ignore-rules","Walking Directory Trees with Ignore Rules in a Python CLI",{"path":2737,"title":2738},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fwriting-files-atomically-in-python-clis","Writing Files Atomically in Python CLIs",{"path":2740,"title":2741},"\u002Fcli-runtime-systems-integration","CLI Runtime & Systems Integration for Python",{"path":2743,"title":2744},"\u002Fcli-runtime-systems-integration\u002Flocal-state-and-sqlite-in-python-clis\u002Fcaching-http-responses-on-disk-in-a-cli","Caching HTTP Responses on Disk in a Python CLI",{"path":2746,"title":2747},"\u002Fcli-runtime-systems-integration\u002Flocal-state-and-sqlite-in-python-clis","Local State and SQLite in Python CLIs",{"path":2749,"title":2750},"\u002Fcli-runtime-systems-integration\u002Flocal-state-and-sqlite-in-python-clis\u002Fmigrating-a-cli-sqlite-schema","Migrating a CLI’s SQLite Schema Between Releases",{"path":2752,"title":2753},"\u002Fcli-runtime-systems-integration\u002Flocal-state-and-sqlite-in-python-clis\u002Frecording-and-querying-cli-run-history","Recording and Querying CLI Run History in SQLite",{"path":2755,"title":2756},"\u002Fcli-runtime-systems-integration\u002Flocal-state-and-sqlite-in-python-clis\u002Fstoring-cli-state-in-sqlite","Storing CLI State in SQLite with a Small Repository Class",{"path":2758,"title":2759},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis\u002Fbuilding-a-watch-mode-with-watchfiles","Building a Watch Mode with watchfiles in Python",{"path":2761,"title":2762},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis\u002Fhandling-sigterm-and-graceful-shutdown","Handling SIGTERM and Graceful Shutdown in CLIs",{"path":2764,"title":2765},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis\u002Fhealth-checks-and-heartbeats-for-long-running-clis","Health Checks and Heartbeats for Long-Running CLIs",{"path":2767,"title":2768},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis","Long-Running and Watch-Mode Python CLIs",{"path":2770,"title":2771},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis\u002Freloading-config-on-sighup","Reloading Configuration on SIGHUP in a Python CLI",{"path":2773,"title":2774},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis\u002Frunning-a-cli-as-a-systemd-service","Running a Python CLI as a systemd Service",{"path":2776,"title":2777},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis\u002Frunning-a-cli-on-a-schedule-with-cron-and-systemd","Running a Python CLI on a Schedule with cron and systemd",{"path":2779,"title":2780},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis\u002Fsending-cli-logs-to-journald-and-syslog","Sending Python CLI Logs to journald and syslog",{"path":2782,"title":2783},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Favoiding-shell-injection-in-python-clis","Avoiding Shell Injection in Python CLIs",{"path":2785,"title":2786},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fcalling-external-commands-safely-with-subprocess","Calling External Commands Safely with subprocess",{"path":2788,"title":2789},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fhandling-subprocess-timeouts-and-exit-codes","Handling Subprocess Timeouts and Exit Codes",{"path":2791,"title":2792},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis","Running Subprocesses from Python CLIs",{"path":2794,"title":2795},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Flaunching-the-users-editor-from-a-cli","Launching the User’s Editor from a Python CLI",{"path":2797,"title":2798},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fpiping-between-subprocesses-in-python","Piping Between Subprocesses in Python Without a Shell",{"path":2800,"title":2801},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fstreaming-subprocess-output-in-real-time","Streaming Subprocess Output in Real Time",{"path":2803,"title":2804},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fwrapping-git-and-other-tools-from-a-python-cli","Wrapping git and Other Tools from a Python CLI",{"path":2806,"title":2807},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis","Secrets and Credentials in Python CLIs",{"path":2809,"title":2810},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Fprompting-for-passwords-securely","Prompting for Passwords Securely in Python CLIs",{"path":2812,"title":2813},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Freading-secrets-from-env-and-files","Reading Secrets from Env Vars and Files in CLIs",{"path":2815,"title":2816},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Fredacting-secrets-from-cli-output-and-logs","Redacting Secrets from CLI Output and Logs",{"path":2818,"title":2819},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Frefreshing-expired-tokens-automatically","Refreshing Expired Tokens Automatically in a Python CLI",{"path":2821,"title":2822},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Fstoring-tokens-with-keyring","Storing CLI Tokens Securely with keyring",{"path":2824,"title":2825},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Fsupporting-multiple-profiles-and-accounts","Supporting Multiple Profiles and Accounts in CLIs",{"path":2827,"title":2828},"\u002Fcli-runtime-systems-integration\u002Fupdate-checks-and-self-updating-clis\u002Fchecking-pypi-for-a-newer-version","Checking PyPI for a Newer Version of Your Python CLI",{"path":2830,"title":2831},"\u002Fcli-runtime-systems-integration\u002Fupdate-checks-and-self-updating-clis","Update Checks, Upgrade Commands and Telemetry for Python CLIs",{"path":2833,"title":2834},"\u002Fcli-runtime-systems-integration\u002Fupdate-checks-and-self-updating-clis\u002Fopt-in-usage-telemetry-for-python-clis","Opt-In Usage Telemetry for Python CLIs Done Responsibly",{"path":2836,"title":2837},"\u002Fcli-runtime-systems-integration\u002Fupdate-checks-and-self-updating-clis\u002Fself-upgrading-a-cli-installed-with-pipx-or-uv","Self-Upgrading a Python CLI Installed with pipx or uv",{"path":2839,"title":2840},"\u002Fcli-runtime-systems-integration\u002Fupdate-checks-and-self-updating-clis\u002Fshowing-non-blocking-update-notices","Showing Non-Blocking Update Notices in a Python CLI",{"path":1904,"title":2842},"Python CLI Toolcraft",{"path":2844,"title":2845},"\u002Fmodern-python-cli-frameworks-architecture\u002Falternative-python-cli-frameworks\u002Fbuilding-a-cli-with-cleo","Building a Python CLI with Cleo Command Classes",{"path":2847,"title":2848},"\u002Fmodern-python-cli-frameworks-architecture\u002Falternative-python-cli-frameworks\u002Fbuilding-a-cli-with-cyclopts","Building a Type-Hinted Python CLI with Cyclopts",{"path":2850,"title":2851},"\u002Fmodern-python-cli-frameworks-architecture\u002Falternative-python-cli-frameworks","Beyond Click and Typer: Alternative Python CLI Frameworks",{"path":2853,"title":2854},"\u002Fmodern-python-cli-frameworks-architecture\u002Falternative-python-cli-frameworks\u002Fquick-clis-from-functions-with-python-fire","Quick CLIs from Functions with Python Fire",{"path":2856,"title":2857},"\u002Fmodern-python-cli-frameworks-architecture\u002Falternative-python-cli-frameworks\u002Fusage-string-driven-clis-with-docopt-ng","Usage-String Driven Python CLIs with docopt-ng",{"path":2859,"title":2860},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Favoiding-import-time-side-effects","Avoiding Import-Time Side Effects in a Python CLI",{"path":2862,"title":2863},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Fcaching-expensive-work-between-cli-runs","Caching Expensive Work Between Python CLI Runs",{"path":2865,"title":2866},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Fguarding-startup-with-import-tests","Guarding CLI Startup with Import Tests",{"path":2868,"title":2869},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading","CLI Startup Performance and Lazy Loading",{"path":2871,"title":2872},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Flazy-loading-subcommands-for-faster-startup","Lazy Loading Subcommands for Faster Startup",{"path":2874,"title":2875},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Fprofiling-python-cli-startup-time","Profiling Python CLI Startup Time",{"path":2877,"title":2878},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Freducing-cli-dependency-weight","Reducing CLI Dependency Weight",{"path":2880,"title":2881},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargparse-subparsers-for-subcommands","argparse Subparsers for Subcommands",{"path":2883,"title":2884},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargparse-vs-click-vs-typer-comparison","argparse vs Click vs Typer Compared",{"path":2886,"title":2887},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargument-groups-and-help-formatting-in-argparse","Argument Groups and Help Formatting in argparse",{"path":2889,"title":2890},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse","Command-Line Parsing with argparse",{"path":2892,"title":2893},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fmigrating-from-argparse-to-typer","Migrating from argparse to Typer",{"path":2895,"title":2896},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fmutually-exclusive-options-in-argparse","Mutually Exclusive Options in argparse",{"path":2898,"title":2899},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Freading-arguments-from-files-with-fromfile-prefix-chars","Reading Arguments from Files with argparse’s fromfile_prefix_chars",{"path":2901,"title":2902},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fwriting-custom-argparse-actions","Writing Custom argparse Actions in Python",{"path":2904,"title":2905},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Fadding-dry-run-and-confirmation-to-destructive-commands","Adding Dry-Run and Confirmation to Destructive Commands",{"path":2907,"title":2908},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Fdesigning-idempotent-commands","Designing Idempotent Commands in a Python CLI",{"path":2910,"title":2911},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Ffollowing-posix-and-gnu-argument-conventions","Following POSIX and GNU Argument Conventions in Python",{"path":2913,"title":2914},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Fglobal-options-vs-per-command-options","Global Options vs Per-Command Options in Python CLIs",{"path":2916,"title":2917},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions","Designing CLI Interfaces and Conventions in Python",{"path":2919,"title":2920},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Fnaming-commands-and-flags-consistently","Naming Commands and Flags Consistently in Python CLIs",{"path":2922,"title":2923},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Fpositional-arguments-vs-options","Positional Arguments vs Options: Designing a CLI Signature",{"path":2925,"title":2926},"\u002Fmodern-python-cli-frameworks-architecture","Python CLI Frameworks and Architecture",{"path":2928,"title":2929},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fdiscovering-plugins-with-entry-points","Discovering Plugins with Entry Points in Python CLIs",{"path":2931,"title":2932},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fhook-based-plugins-with-pluggy","Hook-Based Plugins for Python CLIs with pluggy",{"path":2934,"title":2935},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis","Plugin Architectures for Extensible CLIs",{"path":2937,"title":2938},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fisolating-plugin-failures","Isolating Plugin Failures in an Extensible Python CLI",{"path":2940,"title":2941},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Ftesting-plugins-against-the-host-cli","Testing Plugins Against the Host CLI",{"path":2943,"title":2944},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fversioning-a-plugin-api","Versioning a Plugin API for a Python CLI",{"path":2946,"title":2947},"\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":2949,"title":2950},"\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":2952,"title":2953},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fdependency-injection-patterns-for-cli-commands","Dependency Injection Patterns for CLI Commands",{"path":2955,"title":2956},"\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":2958,"title":2959},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis","Structuring Multi-Command Python CLIs",{"path":2961,"title":2962},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Foffering-a-python-api-alongside-your-cli","Offering a Python API Alongside Your CLI",{"path":2964,"title":2965},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fregistering-commands-from-modules-automatically","Registering CLI Commands from Modules Automatically",{"path":2967,"title":2968},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fsharing-common-options-across-commands","Sharing Common Options Across Python CLI Commands",{"path":2970,"title":2971},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fsharing-state-with-click-context-objects","Sharing State with Click Context Objects",{"path":2973,"title":2974},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fend-to-end-testing-an-installed-cli","End-to-End Testing an Installed Python CLI",{"path":2976,"title":2977},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications","Testing Python CLI Applications",{"path":2979,"title":2980},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fmeasuring-cli-test-coverage","Measuring CLI Test Coverage",{"path":2982,"title":2983},"\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":2985,"title":2986},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fproperty-based-testing-cli-arguments-with-hypothesis","Property-Based Testing CLI Arguments with Hypothesis",{"path":2988,"title":2989},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fsnapshot-testing-cli-output","Snapshot Testing CLI Output",{"path":2991,"title":2992},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftesting-click-commands-with-clirunner","Testing Click Commands with CliRunner",{"path":2994,"title":2995},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftesting-commands-that-call-subprocesses","Testing Python CLI Commands That Call Subprocesses",{"path":2997,"title":2998},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftesting-interactive-prompts-and-stdin","Testing Interactive Prompts and stdin",{"path":3000,"title":3001},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftranscript-tests-for-cli-commands","Transcript Tests for Python CLI Commands",{"path":3003,"title":3004},"\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":3006,"title":3007},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Fbuilding-dynamic-commands-in-click","Building Dynamic Commands in Click",{"path":3009,"title":3010},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Fchoices-and-enums-in-typer-and-click","Choices and Enums in Typer and Click Options",{"path":3012,"title":3013},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Fclick-option-callbacks-and-eager-options","Click Option Callbacks and Eager Options Explained",{"path":3015,"title":3016},"\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":3018,"title":3019},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each","Typer vs Click: When to Use Each",{"path":3021,"title":3022},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Frich-markup-and-help-panels-in-typer","Rich Markup and Help Panels in Typer",{"path":3024,"title":3025},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Ftyper-callback-functions-explained","Typer callback functions explained",{"path":3027,"title":3028},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Fusing-annotated-options-in-typer","Using Annotated Options in Typer",{"path":3030,"title":3031},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis\u002Fautomating-releases-from-git-tags","Automating Python CLI Releases from Git Tags",{"path":3033,"title":3034},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis\u002Fcaching-uv-dependencies-in-ci","Caching uv Dependencies in CI for Python CLIs",{"path":3036,"title":3037},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis","CI\u002FCD Pipelines for Python CLIs",{"path":3039,"title":3040},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis\u002Fpublishing-a-cli-docker-image-from-ci","Publishing a Python CLI as a Docker Image from CI",{"path":3042,"title":3043},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis\u002Fpublishing-to-pypi-with-trusted-publishing","Publishing a CLI to PyPI with Trusted Publishing",{"path":3045,"title":3046},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis\u002Frunning-cli-tests-on-windows-and-macos-runners","Running Python CLI Tests on Windows and macOS Runners",{"path":3048,"title":3049},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis\u002Fsmoke-testing-the-built-wheel-in-ci","Smoke-Testing the Built Wheel of a Python CLI in CI",{"path":3051,"title":3052},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis\u002Ftesting-a-cli-across-python-versions-with-github-actions","Testing a CLI Across Python Versions in GitHub Actions",{"path":3054,"title":3055},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fbuilding-a-cookiecutter-template-for-typer-clis","Building a Cookiecutter Template for Typer CLIs",{"path":3057,"title":3058},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fcopier-vs-cookiecutter-for-cli-templates","Copier vs Cookiecutter for CLI Templates",{"path":3060,"title":3061},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter","CLI Project Scaffolding with Cookiecutter",{"path":3063,"title":3064},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fpost-generation-hooks-in-cli-templates","Post-Generation Hooks in Python CLI Templates",{"path":3066,"title":3067},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Ftemplate-variables-and-conditional-files","Template Variables and Conditional Files in CLI Templates",{"path":3069,"title":3070},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Ftesting-a-project-template-with-pytest","Testing a CLI Project Template with pytest",{"path":3072,"title":3073},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fupdating-generated-projects-with-copier-update","Updating Generated CLI Projects with copier update",{"path":3075,"title":3076},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fbuilding-cross-platform-release-binaries-in-ci","Building Cross-Platform Release Binaries in CI",{"path":3078,"title":3079},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fbundling-a-python-cli-with-pyinstaller","Bundling a Python CLI with PyInstaller",{"path":3081,"title":3082},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fcode-signing-and-notarizing-cli-binaries","Code Signing and Notarizing Python CLI Binaries",{"path":3084,"title":3085},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fhomebrew-and-scoop-packaging-for-python-clis","Homebrew and Scoop Packaging for Python CLIs",{"path":3087,"title":3088},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries","Distributing CLIs as Standalone Binaries",{"path":3090,"title":3091},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fnuitka-vs-pyinstaller-for-python-clis","Nuitka vs PyInstaller for Python CLI Binaries",{"path":3093,"title":3094},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Freducing-pyinstaller-binary-size","Reducing the Size of a PyInstaller CLI Binary",{"path":3096,"title":3097},"\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":3099,"title":3100},"\u002Fproject-setup-dependency-management","Project Setup & Dependency Management",{"path":3102,"title":3103},"\u002Fproject-setup-dependency-management\u002Flinting-and-type-checking-cli-code\u002Fconfiguring-ruff-for-a-cli-project","Configuring Ruff for a Python CLI Project",{"path":3105,"title":3106},"\u002Fproject-setup-dependency-management\u002Flinting-and-type-checking-cli-code\u002Fenforcing-import-boundaries-in-a-cli-codebase","Enforcing Import Boundaries in a Python CLI Codebase",{"path":3108,"title":3109},"\u002Fproject-setup-dependency-management\u002Flinting-and-type-checking-cli-code\u002Ffinding-unused-code-and-dependencies-with-vulture-and-deptry","Finding Unused Code and Dependencies with vulture and deptry",{"path":3111,"title":3112},"\u002Fproject-setup-dependency-management\u002Flinting-and-type-checking-cli-code","Linting and Type-Checking Python CLI Code",{"path":3114,"title":3115},"\u002Fproject-setup-dependency-management\u002Flinting-and-type-checking-cli-code\u002Frunning-pyright-in-strict-mode-on-a-cli","Running Pyright in Strict Mode on a Python CLI",{"path":3117,"title":3118},"\u002Fproject-setup-dependency-management\u002Flinting-and-type-checking-cli-code\u002Frunning-ruff-and-mypy-in-ci-with-annotations","Running Ruff and mypy in CI with Inline Annotations",{"path":3120,"title":3121},"\u002Fproject-setup-dependency-management\u002Flinting-and-type-checking-cli-code\u002Ftype-checking-click-and-typer-code-with-mypy","Type-Checking Click and Typer Code with mypy",{"path":3123,"title":3124},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fautomating-changelogs-with-conventional-commits","Automating Changelogs with Conventional Commits",{"path":3126,"title":3127},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fbumping-versions-consistently-across-a-cli-project","Bumping Versions Consistently Across a Python CLI Project",{"path":3129,"title":3130},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fderiving-versions-from-git-tags-with-hatch-vcs","Deriving CLI Versions from Git Tags with hatch-vcs",{"path":3132,"title":3133},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fexposing-version-info-and-build-metadata","Exposing Version Info and Build Metadata",{"path":3135,"title":3136},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs","Managing CLI Versioning & Changelogs",{"path":3138,"title":3139},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fsemantic-versioning-policy-for-cli-tools","A Semantic Versioning Policy for CLI Tools",{"path":3141,"title":3142},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fshipping-pre-releases-and-release-candidates","Shipping Pre-Releases and Release Candidates of a Python CLI",{"path":3144,"title":3145},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fbuilding-wheels-and-sdists-for-python-clis","Building Wheels and sdists for Python CLIs",{"path":3147,"title":3148},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fbundling-data-files-with-importlib-resources","Bundling Data Files with importlib.resources in CLIs",{"path":3150,"title":3151},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fchoosing-a-build-backend-for-a-python-cli","Choosing a Build Backend for a Python CLI",{"path":3153,"title":3154},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution","Packaging Python CLIs for Distribution",{"path":3156,"title":3157},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Finstalling-and-distributing-clis-with-pipx","Installing and Distributing CLIs with pipx",{"path":3159,"title":3160},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Foptional-dependencies-and-extras-for-clis","Optional Dependencies and Extras for Python CLIs",{"path":3162,"title":3163},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fpublishing-a-python-cli-to-pypi","Publishing a Python CLI to PyPI",{"path":3165,"title":3166},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fwriting-pyproject-toml-metadata-for-a-cli","Writing pyproject.toml Metadata for a Python CLI",{"path":3168,"title":3169},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fdynamic-versioning-with-poetry-plugins","Dynamic Versioning for a Poetry CLI from Git Tags",{"path":3171,"title":3172},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development","Poetry Workflows for CLI Development",{"path":3174,"title":3175},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fmanaging-poetry-lock-files-for-cli-tools","Managing Poetry Lock Files for CLI Tools",{"path":3177,"title":3178},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fmigrating-a-cli-from-poetry-to-uv","Migrating a Python CLI from Poetry to uv",{"path":3180,"title":3181},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpoetry-dependency-groups-for-cli-tooling","Poetry Dependency Groups for CLI Tooling",{"path":3183,"title":3184},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpoetry-entry-points-and-scripts-for-clis","Poetry Entry Points and Scripts for CLIs",{"path":3186,"title":3187},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpublishing-a-cli-with-poetry","Building and Publishing a Python CLI with Poetry",{"path":3189,"title":3190},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects","Pre-commit Hooks for CLI Projects",{"path":3192,"title":3193},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects\u002Fkeeping-hook-versions-current-with-autoupdate","Keeping pre-commit Hook Versions Current with autoupdate",{"path":3195,"title":3196},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects\u002Frunning-pre-commit-in-ci","Running pre-commit in CI for a Python CLI Repository",{"path":3198,"title":3199},"\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":3201,"title":3202},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects\u002Fshipping-your-cli-as-a-pre-commit-hook","Shipping Your Python CLI as a pre-commit Hook",{"path":3204,"title":3205},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects\u002Fspeeding-up-slow-pre-commit-hooks","Speeding Up Slow pre-commit Hooks in a CLI Repository",{"path":3207,"title":3208},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects\u002Fwriting-local-pre-commit-hooks-in-python","Writing Local pre-commit Hooks in Python",{"path":3210,"title":3211},"\u002Fproject-setup-dependency-management\u002Fsecuring-a-python-cli-supply-chain\u002Fauditing-dependencies-with-pip-audit","Auditing a Python CLI’s Dependencies with pip-audit",{"path":3213,"title":3214},"\u002Fproject-setup-dependency-management\u002Fsecuring-a-python-cli-supply-chain\u002Fgenerating-an-sbom-for-a-python-cli","Generating an SBOM for a Python CLI Release",{"path":3216,"title":3217},"\u002Fproject-setup-dependency-management\u002Fsecuring-a-python-cli-supply-chain","Securing the Supply Chain of a Python CLI",{"path":3219,"title":3220},"\u002Fproject-setup-dependency-management\u002Fsecuring-a-python-cli-supply-chain\u002Fpinning-dependencies-with-hashes","Pinning a Python CLI’s Dependencies with Hashes",{"path":3222,"title":3223},"\u002Fproject-setup-dependency-management\u002Fsecuring-a-python-cli-supply-chain\u002Fpublishing-attestations-and-verifying-releases","Publishing Attestations and Verifying Python CLI Releases",{"path":3225,"title":3226},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002Fbuilding-and-publishing-a-cli-with-uv","Building and Publishing a Python CLI with uv",{"path":3228,"title":3229},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management","uv for Python CLI Dependency Management",{"path":3231,"title":3232},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002Flocking-and-syncing-cli-dependencies-with-uv","Locking and Syncing a Python CLI’s Dependencies with uv",{"path":3234,"title":3235},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002Frunning-one-off-cli-scripts-with-uv-run","Running One-Off CLI Scripts with uv run and PEP 723",{"path":3237,"title":3238},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002Fusing-private-package-indexes-with-uv","Using Private Package Indexes with uv for Internal CLIs",{"path":3240,"title":3241},"\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":3243,"title":3244},"\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":3246,"title":3247},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002Fuv-workspaces-for-multi-package-clis","uv Workspaces for Multi-Package Python CLIs",{"path":3249,"title":3250},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fdebugging-wrong-python-and-wrong-venv-problems","Debugging Wrong-Python and Wrong-Venv Problems in CLIs",{"path":3252,"title":3253},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fexternally-managed-environments-and-pep-668","PEP 668 and Python CLIs: the externally-managed-environment Error",{"path":3255,"title":3256},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices","Python CLI Env Isolation Best Practices",{"path":3258,"title":3259},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fisolating-cli-tools-from-project-dependencies","Isolating CLI Tools from Your Project’s Dependencies",{"path":3261,"title":3262},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fmanaging-virtual-environments-for-cross-platform-clis","Managing Python CLI Virtual Environments",{"path":3264,"title":3265},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fpinning-the-python-version-for-a-cli","Pinning the Python Version for a CLI",{"path":3267,"title":3268},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fsupporting-multiple-python-versions-with-nox","Supporting Multiple Python Versions with nox",1790967537208]