[{"data":1,"prerenderedAt":2563},["ShallowReactive",2],{"page-\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Freading-arguments-from-files-with-fromfile-prefix-chars\u002F":3,"content-directory":1719},{"id":4,"title":5,"body":6,"date":1704,"description":1705,"difficulty":1706,"draft":1707,"extension":1708,"meta":1709,"navigation":137,"path":1710,"seo":1711,"stem":1712,"tags":1713,"updated":1704,"__hash__":1718},"content\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Freading-arguments-from-files-with-fromfile-prefix-chars\u002Findex.md","Reading Arguments from Files with argparse’s fromfile_prefix_chars",{"type":7,"value":8,"toc":1682},"minimark",[9,37,42,52,56,70,74,93,97,555,569,577,598,625,628,637,644,650,676,680,691,695,702,1058,1086,1090,1135,1138,1142,1153,1548,1551,1555,1576,1580,1587,1601,1605,1608,1615,1631,1635,1642,1646,1678],[10,11,12,13,17,18,22,23,26,27,30,31,36],"p",{},"Some command lines get long. A build tool invoked with three hundred source files exceeds the Windows command-line length limit of about 32,000 characters. A nightly job runs the same fifteen flags every time, and the cron line has become unreadable. A team shares a set of \"standard\" options that everyone is supposed to pass. Compilers and linkers solved this decades ago with ",[14,15,16],"strong",{},"response files",": write the arguments into a file and pass ",[19,20,21],"code",{},"@args.txt"," instead. argparse supports exactly this with one constructor argument, ",[19,24,25],{},"fromfile_prefix_chars",". Its defaults are minimal, though — one argument per line, no comments, no quoting — and it has a sharp edge where real values begin with ",[19,28,29],{},"@",". This guide enables response files, makes them pleasant to write, explains the edge cases, and tests them. It belongs to the ",[32,33,35],"a",{"href":34},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002F","argparse topic",".",[38,39,41],"h2",{"id":40},"prerequisites","Prerequisites",[43,44,45,49],"ul",{},[46,47,48],"li",{},"Python 3.10+ and an argparse-based CLI.",[46,50,51],{},"Familiarity with shell quoting; response-file lines will follow the same rules.",[38,53,55],{"id":54},"how-response-files-work","How response files work",[10,57,58,59,62,63,65,66,69],{},"When ",[19,60,61],{},"fromfile_prefix_chars=\"@\""," is set, any command-line argument that starts with ",[19,64,29],{}," is replaced by the arguments read from the named file, in place, before parsing. The replacement can contain further ",[19,67,68],{},"@file"," references, which are expanded too, so presets can include other presets.",[71,72],"inline-diagram",{"name":73},"respfile-expansion",[10,75,76,77,81,82,85,86,89,90,92],{},"By default, each ",[78,79,80],"em",{},"line"," of the file is exactly one argument. That is unambiguous, but it means ",[19,83,84],{},"--to \u002Fmnt\u002Fbackup"," must be written on two lines, and a ",[19,87,88],{},"# comment"," line becomes a literal positional argument named ",[19,91,88],{},". Both are easy to fix.",[38,94,96],{"id":95},"the-recipe","The recipe",[98,99,104],"pre",{"className":100,"code":101,"language":102,"meta":103,"style":103},"language-python shiki shiki-themes github-light github-dark","# src\u002Fbackup\u002Fcli.py\nfrom __future__ import annotations\n\nimport argparse\nimport shlex\n\n\nclass ResponseFileParser(argparse.ArgumentParser):\n    \"\"\"Response files with shell-style quoting and # comments.\"\"\"\n\n    def convert_arg_line_to_args(self, arg_line: str) -> list[str]:\n        return shlex.split(arg_line, comments=True)\n\n\ndef build_parser() -> argparse.ArgumentParser:\n    parser = ResponseFileParser(\n        prog=\"backup\",\n        fromfile_prefix_chars=\"@\",\n        description=\"Back up directories. Arguments can be read from a file with @FILE.\",\n    )\n    parser.add_argument(\"sources\", nargs=\"*\", metavar=\"SOURCE\")\n    parser.add_argument(\"--to\", metavar=\"DEST\")\n    parser.add_argument(\"--exclude\", action=\"append\", default=[], metavar=\"GLOB\")\n    parser.add_argument(\"--notify\", metavar=\"CHANNEL\")\n    return parser\n\n\ndef main(argv: list[str] | None = None) -> int:\n    args = build_parser().parse_args(argv)\n    print(f\"{len(args.sources)} source(s) -> {args.to}, excluding {args.exclude}\")\n    return 0\n","python","",[19,105,106,114,132,139,148,156,161,166,190,197,202,225,247,252,257,269,280,294,307,320,326,358,377,414,433,442,447,452,488,499,547],{"__ignoreMap":103},[107,108,110],"span",{"class":80,"line":109},1,[107,111,113],{"class":112},"sJ8bj","# src\u002Fbackup\u002Fcli.py\n",[107,115,117,121,125,128],{"class":80,"line":116},2,[107,118,120],{"class":119},"szBVR","from",[107,122,124],{"class":123},"sj4cs"," __future__",[107,126,127],{"class":119}," import",[107,129,131],{"class":130},"sVt8B"," annotations\n",[107,133,135],{"class":80,"line":134},3,[107,136,138],{"emptyLinePlaceholder":137},true,"\n",[107,140,142,145],{"class":80,"line":141},4,[107,143,144],{"class":119},"import",[107,146,147],{"class":130}," argparse\n",[107,149,151,153],{"class":80,"line":150},5,[107,152,144],{"class":119},[107,154,155],{"class":130}," shlex\n",[107,157,159],{"class":80,"line":158},6,[107,160,138],{"emptyLinePlaceholder":137},[107,162,164],{"class":80,"line":163},7,[107,165,138],{"emptyLinePlaceholder":137},[107,167,169,172,176,179,182,184,187],{"class":80,"line":168},8,[107,170,171],{"class":119},"class",[107,173,175],{"class":174},"sScJk"," ResponseFileParser",[107,177,178],{"class":130},"(",[107,180,181],{"class":174},"argparse",[107,183,36],{"class":130},[107,185,186],{"class":174},"ArgumentParser",[107,188,189],{"class":130},"):\n",[107,191,193],{"class":80,"line":192},9,[107,194,196],{"class":195},"sZZnC","    \"\"\"Response files with shell-style quoting and # comments.\"\"\"\n",[107,198,200],{"class":80,"line":199},10,[107,201,138],{"emptyLinePlaceholder":137},[107,203,205,208,211,214,217,220,222],{"class":80,"line":204},11,[107,206,207],{"class":119},"    def",[107,209,210],{"class":174}," convert_arg_line_to_args",[107,212,213],{"class":130},"(self, arg_line: ",[107,215,216],{"class":123},"str",[107,218,219],{"class":130},") -> list[",[107,221,216],{"class":123},[107,223,224],{"class":130},"]:\n",[107,226,228,231,234,238,241,244],{"class":80,"line":227},12,[107,229,230],{"class":119},"        return",[107,232,233],{"class":130}," shlex.split(arg_line, ",[107,235,237],{"class":236},"s4XuR","comments",[107,239,240],{"class":119},"=",[107,242,243],{"class":123},"True",[107,245,246],{"class":130},")\n",[107,248,250],{"class":80,"line":249},13,[107,251,138],{"emptyLinePlaceholder":137},[107,253,255],{"class":80,"line":254},14,[107,256,138],{"emptyLinePlaceholder":137},[107,258,260,263,266],{"class":80,"line":259},15,[107,261,262],{"class":119},"def",[107,264,265],{"class":174}," build_parser",[107,267,268],{"class":130},"() -> argparse.ArgumentParser:\n",[107,270,272,275,277],{"class":80,"line":271},16,[107,273,274],{"class":130},"    parser ",[107,276,240],{"class":119},[107,278,279],{"class":130}," ResponseFileParser(\n",[107,281,283,286,288,291],{"class":80,"line":282},17,[107,284,285],{"class":236},"        prog",[107,287,240],{"class":119},[107,289,290],{"class":195},"\"backup\"",[107,292,293],{"class":130},",\n",[107,295,297,300,302,305],{"class":80,"line":296},18,[107,298,299],{"class":236},"        fromfile_prefix_chars",[107,301,240],{"class":119},[107,303,304],{"class":195},"\"@\"",[107,306,293],{"class":130},[107,308,310,313,315,318],{"class":80,"line":309},19,[107,311,312],{"class":236},"        description",[107,314,240],{"class":119},[107,316,317],{"class":195},"\"Back up directories. Arguments can be read from a file with @FILE.\"",[107,319,293],{"class":130},[107,321,323],{"class":80,"line":322},20,[107,324,325],{"class":130},"    )\n",[107,327,329,332,335,338,341,343,346,348,351,353,356],{"class":80,"line":328},21,[107,330,331],{"class":130},"    parser.add_argument(",[107,333,334],{"class":195},"\"sources\"",[107,336,337],{"class":130},", ",[107,339,340],{"class":236},"nargs",[107,342,240],{"class":119},[107,344,345],{"class":195},"\"*\"",[107,347,337],{"class":130},[107,349,350],{"class":236},"metavar",[107,352,240],{"class":119},[107,354,355],{"class":195},"\"SOURCE\"",[107,357,246],{"class":130},[107,359,361,363,366,368,370,372,375],{"class":80,"line":360},22,[107,362,331],{"class":130},[107,364,365],{"class":195},"\"--to\"",[107,367,337],{"class":130},[107,369,350],{"class":236},[107,371,240],{"class":119},[107,373,374],{"class":195},"\"DEST\"",[107,376,246],{"class":130},[107,378,380,382,385,387,390,392,395,397,400,402,405,407,409,412],{"class":80,"line":379},23,[107,381,331],{"class":130},[107,383,384],{"class":195},"\"--exclude\"",[107,386,337],{"class":130},[107,388,389],{"class":236},"action",[107,391,240],{"class":119},[107,393,394],{"class":195},"\"append\"",[107,396,337],{"class":130},[107,398,399],{"class":236},"default",[107,401,240],{"class":119},[107,403,404],{"class":130},"[], ",[107,406,350],{"class":236},[107,408,240],{"class":119},[107,410,411],{"class":195},"\"GLOB\"",[107,413,246],{"class":130},[107,415,417,419,422,424,426,428,431],{"class":80,"line":416},24,[107,418,331],{"class":130},[107,420,421],{"class":195},"\"--notify\"",[107,423,337],{"class":130},[107,425,350],{"class":236},[107,427,240],{"class":119},[107,429,430],{"class":195},"\"CHANNEL\"",[107,432,246],{"class":130},[107,434,436,439],{"class":80,"line":435},25,[107,437,438],{"class":119},"    return",[107,440,441],{"class":130}," parser\n",[107,443,445],{"class":80,"line":444},26,[107,446,138],{"emptyLinePlaceholder":137},[107,448,450],{"class":80,"line":449},27,[107,451,138],{"emptyLinePlaceholder":137},[107,453,455,457,460,463,465,468,471,474,477,479,482,485],{"class":80,"line":454},28,[107,456,262],{"class":119},[107,458,459],{"class":174}," main",[107,461,462],{"class":130},"(argv: list[",[107,464,216],{"class":123},[107,466,467],{"class":130},"] ",[107,469,470],{"class":119},"|",[107,472,473],{"class":123}," None",[107,475,476],{"class":119}," =",[107,478,473],{"class":123},[107,480,481],{"class":130},") -> ",[107,483,484],{"class":123},"int",[107,486,487],{"class":130},":\n",[107,489,491,494,496],{"class":80,"line":490},29,[107,492,493],{"class":130},"    args ",[107,495,240],{"class":119},[107,497,498],{"class":130}," build_parser().parse_args(argv)\n",[107,500,502,505,507,510,513,516,519,522,525,528,531,533,536,538,541,543,545],{"class":80,"line":501},30,[107,503,504],{"class":123},"    print",[107,506,178],{"class":130},[107,508,509],{"class":119},"f",[107,511,512],{"class":195},"\"",[107,514,515],{"class":123},"{len",[107,517,518],{"class":130},"(args.sources)",[107,520,521],{"class":123},"}",[107,523,524],{"class":195}," source(s) -> ",[107,526,527],{"class":123},"{",[107,529,530],{"class":130},"args.to",[107,532,521],{"class":123},[107,534,535],{"class":195},", excluding ",[107,537,527],{"class":123},[107,539,540],{"class":130},"args.exclude",[107,542,521],{"class":123},[107,544,512],{"class":195},[107,546,246],{"class":130},[107,548,550,552],{"class":80,"line":549},31,[107,551,438],{"class":119},[107,553,554],{"class":123}," 0\n",[10,556,557,560,561,564,565,568],{},[19,558,559],{},"convert_arg_line_to_args"," is the documented hook for changing how lines become arguments. ",[19,562,563],{},"shlex.split(..., comments=True)"," gives each line shell semantics: several arguments per line, quotes for values with spaces, and ",[19,566,567],{},"#"," starting a comment. A preset file now reads naturally:",[98,570,575],{"className":571,"code":573,"language":574,"meta":103},[572],"language-text","# nightly.args — standard options for the nightly backup\n--to \u002Fmnt\u002Fbackup            # local disk; the S3 copy runs separately\n--exclude \"*.tmp\" --exclude \"my docs\u002Fcache\"\n@notify.args                # shared notification settings\n","text",[19,576,573],{"__ignoreMap":103},[98,578,582],{"className":579,"code":580,"language":581,"meta":103,"style":103},"language-bash shiki shiki-themes github-light github-dark","backup @nightly.args ~\u002Fprojects ~\u002Fdocuments\n","bash",[19,583,584],{"__ignoreMap":103},[107,585,586,589,592,595],{"class":80,"line":109},[107,587,588],{"class":174},"backup",[107,590,591],{"class":195}," @nightly.args",[107,593,594],{"class":195}," ~\u002Fprojects",[107,596,597],{"class":195}," ~\u002Fdocuments\n",[10,599,600,601,604,605,608,609,612,613,616,617,620,621,624],{},"Arguments from the file are inserted where ",[19,602,603],{},"@nightly.args"," appears, so options given ",[78,606,607],{},"after"," it on the command line override the preset — ",[19,610,611],{},"backup @nightly.args --to \u002Fmnt\u002Fother ~\u002Fprojects"," uses ",[19,614,615],{},"\u002Fmnt\u002Fother"," for a single-value option. For ",[19,618,619],{},"append"," options such as ",[19,622,623],{},"--exclude",", both sources accumulate.",[71,626],{"name":627},"respfile-terminal",[629,630,632,633,636],"h3",{"id":631},"the-value-gotcha","The ",[19,634,635],{},"@value"," gotcha",[10,638,639,640,643],{},"Expansion applies to ",[78,641,642],{},"every"," argument that starts with the prefix — not just ones that look like file names. A perfectly reasonable value fails:",[98,645,648],{"className":646,"code":647,"language":574,"meta":103},[572],"$ backup --notify @alice\nbackup: error: [Errno 2] No such file or directory: 'alice'\n",[19,649,647],{"__ignoreMap":103},[10,651,652,653,655,656,658,659,662,663,665,666,668,669,671,672,675],{},"Users can work around it with the ",[19,654,240],{}," form, which makes the value part of the option argument and therefore does not start with ",[19,657,29],{},": ",[19,660,661],{},"backup --notify=@alice"," works. Better, avoid the conflict by design: if your CLI takes values that commonly start with ",[19,664,29],{}," — user handles, npm scopes, decorator names — choose a different prefix character, or document the ",[19,667,240],{}," form prominently in the option's help text. ",[19,670,25],{}," accepts several characters (for example ",[19,673,674],{},"\"@+\"",") if you need to support an established convention alongside your own.",[629,677,679],{"id":678},"encoding-and-windows","Encoding and Windows",[10,681,682,683,686,687,36],{},"Response files are most useful on Windows, where the command-line limit bites first. Since Python 3.12, argparse reads them with the filesystem encoding and error handler (UTF-8 on modern systems), rather than the locale encoding used by earlier versions. If your users generate response files with tools that write UTF-16 — some Windows editors and PowerShell's older ",[19,684,685],{},"Out-File"," default — they will see decoding errors; tell them to save files as UTF-8, as discussed in ",[32,688,690],{"href":689},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Ffixing-unicode-and-encoding-errors-on-windows\u002F","fixing Unicode and encoding errors on Windows",[38,692,694],{"id":693},"the-same-feature-for-click-and-typer","The same feature for Click and Typer",[10,696,697,698,701],{},"Click and Typer have no response-file support, but expansion is simple to do yourself before the framework sees the arguments — and doing it yourself lets you fix the ",[19,699,700],{},"--"," behaviour too:",[98,703,705],{"className":100,"code":704,"language":102,"meta":103,"style":103},"# src\u002Fmytool\u002Frespfiles.py\nfrom __future__ import annotations\n\nimport shlex\nfrom pathlib import Path\n\n\ndef expand(argv: list[str], prefix: str = \"@\", _depth: int = 0) -> list[str]:\n    \"\"\"Replace @FILE arguments with the shell-split contents of FILE (before any '--').\"\"\"\n    if _depth > 10:\n        raise ValueError(\"response files nested too deeply\")\n    out: list[str] = []\n    for i, arg in enumerate(argv):\n        if arg == \"--\":\n            return out + argv[i:]               # everything after -- is literal\n        if arg.startswith(prefix) and len(arg) > 1:\n            text = Path(arg[1:]).read_text(encoding=\"utf-8\")\n            out += expand(shlex.split(text, comments=True), prefix, _depth + 1)\n        else:\n            out.append(arg)\n    return out\n\n\ndef main() -> None:\n    import sys\n\n    from mytool.cli import app\n    app(args=expand(sys.argv[1:]), prog_name=\"mytool\")\n",[19,706,707,712,722,726,732,744,748,752,789,794,810,825,839,856,872,889,912,938,964,971,976,983,987,991,1005,1013,1017,1030],{"__ignoreMap":103},[107,708,709],{"class":80,"line":109},[107,710,711],{"class":112},"# src\u002Fmytool\u002Frespfiles.py\n",[107,713,714,716,718,720],{"class":80,"line":116},[107,715,120],{"class":119},[107,717,124],{"class":123},[107,719,127],{"class":119},[107,721,131],{"class":130},[107,723,724],{"class":80,"line":134},[107,725,138],{"emptyLinePlaceholder":137},[107,727,728,730],{"class":80,"line":141},[107,729,144],{"class":119},[107,731,155],{"class":130},[107,733,734,736,739,741],{"class":80,"line":150},[107,735,120],{"class":119},[107,737,738],{"class":130}," pathlib ",[107,740,144],{"class":119},[107,742,743],{"class":130}," Path\n",[107,745,746],{"class":80,"line":158},[107,747,138],{"emptyLinePlaceholder":137},[107,749,750],{"class":80,"line":163},[107,751,138],{"emptyLinePlaceholder":137},[107,753,754,756,759,761,763,766,768,770,773,776,778,780,783,785,787],{"class":80,"line":168},[107,755,262],{"class":119},[107,757,758],{"class":174}," expand",[107,760,462],{"class":130},[107,762,216],{"class":123},[107,764,765],{"class":130},"], prefix: ",[107,767,216],{"class":123},[107,769,476],{"class":119},[107,771,772],{"class":195}," \"@\"",[107,774,775],{"class":130},", _depth: ",[107,777,484],{"class":123},[107,779,476],{"class":119},[107,781,782],{"class":123}," 0",[107,784,219],{"class":130},[107,786,216],{"class":123},[107,788,224],{"class":130},[107,790,791],{"class":80,"line":192},[107,792,793],{"class":195},"    \"\"\"Replace @FILE arguments with the shell-split contents of FILE (before any '--').\"\"\"\n",[107,795,796,799,802,805,808],{"class":80,"line":199},[107,797,798],{"class":119},"    if",[107,800,801],{"class":130}," _depth ",[107,803,804],{"class":119},">",[107,806,807],{"class":123}," 10",[107,809,487],{"class":130},[107,811,812,815,818,820,823],{"class":80,"line":204},[107,813,814],{"class":119},"        raise",[107,816,817],{"class":123}," ValueError",[107,819,178],{"class":130},[107,821,822],{"class":195},"\"response files nested too deeply\"",[107,824,246],{"class":130},[107,826,827,830,832,834,836],{"class":80,"line":227},[107,828,829],{"class":130},"    out: list[",[107,831,216],{"class":123},[107,833,467],{"class":130},[107,835,240],{"class":119},[107,837,838],{"class":130}," []\n",[107,840,841,844,847,850,853],{"class":80,"line":249},[107,842,843],{"class":119},"    for",[107,845,846],{"class":130}," i, arg ",[107,848,849],{"class":119},"in",[107,851,852],{"class":123}," enumerate",[107,854,855],{"class":130},"(argv):\n",[107,857,858,861,864,867,870],{"class":80,"line":254},[107,859,860],{"class":119},"        if",[107,862,863],{"class":130}," arg ",[107,865,866],{"class":119},"==",[107,868,869],{"class":195}," \"--\"",[107,871,487],{"class":130},[107,873,874,877,880,883,886],{"class":80,"line":259},[107,875,876],{"class":119},"            return",[107,878,879],{"class":130}," out ",[107,881,882],{"class":119},"+",[107,884,885],{"class":130}," argv[i:]               ",[107,887,888],{"class":112},"# everything after -- is literal\n",[107,890,891,893,896,899,902,905,907,910],{"class":80,"line":271},[107,892,860],{"class":119},[107,894,895],{"class":130}," arg.startswith(prefix) ",[107,897,898],{"class":119},"and",[107,900,901],{"class":123}," len",[107,903,904],{"class":130},"(arg) ",[107,906,804],{"class":119},[107,908,909],{"class":123}," 1",[107,911,487],{"class":130},[107,913,914,917,919,922,925,928,931,933,936],{"class":80,"line":282},[107,915,916],{"class":130},"            text ",[107,918,240],{"class":119},[107,920,921],{"class":130}," Path(arg[",[107,923,924],{"class":123},"1",[107,926,927],{"class":130},":]).read_text(",[107,929,930],{"class":236},"encoding",[107,932,240],{"class":119},[107,934,935],{"class":195},"\"utf-8\"",[107,937,246],{"class":130},[107,939,940,943,946,949,951,953,955,958,960,962],{"class":80,"line":296},[107,941,942],{"class":130},"            out ",[107,944,945],{"class":119},"+=",[107,947,948],{"class":130}," expand(shlex.split(text, ",[107,950,237],{"class":236},[107,952,240],{"class":119},[107,954,243],{"class":123},[107,956,957],{"class":130},"), prefix, _depth ",[107,959,882],{"class":119},[107,961,909],{"class":123},[107,963,246],{"class":130},[107,965,966,969],{"class":80,"line":309},[107,967,968],{"class":119},"        else",[107,970,487],{"class":130},[107,972,973],{"class":80,"line":322},[107,974,975],{"class":130},"            out.append(arg)\n",[107,977,978,980],{"class":80,"line":328},[107,979,438],{"class":119},[107,981,982],{"class":130}," out\n",[107,984,985],{"class":80,"line":360},[107,986,138],{"emptyLinePlaceholder":137},[107,988,989],{"class":80,"line":379},[107,990,138],{"emptyLinePlaceholder":137},[107,992,993,995,997,1000,1003],{"class":80,"line":416},[107,994,262],{"class":119},[107,996,459],{"class":174},[107,998,999],{"class":130},"() -> ",[107,1001,1002],{"class":123},"None",[107,1004,487],{"class":130},[107,1006,1007,1010],{"class":80,"line":435},[107,1008,1009],{"class":119},"    import",[107,1011,1012],{"class":130}," sys\n",[107,1014,1015],{"class":80,"line":444},[107,1016,138],{"emptyLinePlaceholder":137},[107,1018,1019,1022,1025,1027],{"class":80,"line":449},[107,1020,1021],{"class":119},"    from",[107,1023,1024],{"class":130}," mytool.cli ",[107,1026,144],{"class":119},[107,1028,1029],{"class":130}," app\n",[107,1031,1032,1035,1038,1040,1043,1045,1048,1051,1053,1056],{"class":80,"line":454},[107,1033,1034],{"class":130},"    app(",[107,1036,1037],{"class":236},"args",[107,1039,240],{"class":119},[107,1041,1042],{"class":130},"expand(sys.argv[",[107,1044,924],{"class":123},[107,1046,1047],{"class":130},":]), ",[107,1049,1050],{"class":236},"prog_name",[107,1052,240],{"class":119},[107,1054,1055],{"class":195},"\"mytool\"",[107,1057,246],{"class":130},[10,1059,1060,1061,1064,1065,1068,1069,1071,1072,1074,1075,1078,1079,1082,1083,36],{},"Passing ",[19,1062,1063],{},"args="," to the Typer or Click app replaces ",[19,1066,1067],{},"sys.argv[1:]",", and ",[19,1070,1050],{}," keeps usage messages naming the command correctly. The depth limit stops a file that includes itself from recursing forever, and stopping at ",[19,1073,700],{}," gives users a way to pass a literal ",[19,1076,1077],{},"@alice"," as a positional. Turn a missing file into a usage error (exit code 2) rather than a traceback by catching ",[19,1080,1081],{},"FileNotFoundError"," in ",[19,1084,1085],{},"main()",[38,1087,1089],{"id":1088},"ux-considerations","UX considerations",[43,1091,1092,1098,1104,1115,1129],{},[46,1093,1094,1097],{},[14,1095,1096],{},"Mention it in help."," The description above says \"Arguments can be read from a file with @FILE\". Nobody discovers response files on their own.",[46,1099,1100,1103],{},[14,1101,1102],{},"Keep paths relative to the working directory"," — that is how argparse resolves them, and it matches what users expect from compilers. A preset that includes another preset by relative path works only from the right directory; document that, or use absolute paths in shared presets.",[46,1105,1106,1109,1110,1114],{},[14,1107,1108],{},"Prefer config files for settings, response files for invocations."," A TOML config with precedence rules, as in ",[32,1111,1113],{"href":1112},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Fconfig-precedence-flags-env-files-defaults\u002F","config precedence: flags, env, files, defaults",", is better for long-lived settings; response files shine for long or generated argument lists and named presets of a particular run.",[46,1116,1117,1120,1121,1124,1125,1128],{},[14,1118,1119],{},"Show the expanded command line when verbose."," Printing ",[19,1122,1123],{},"shlex.join(expanded_args)"," at ",[19,1126,1127],{},"-v"," makes presets debuggable.",[46,1130,1131,1134],{},[14,1132,1133],{},"Never treat response files as trusted input"," if they can come from someone else; they are exactly as powerful as typing the arguments.",[71,1136],{"name":1137},"respfile-compare",[38,1139,1141],{"id":1140},"testing-the-behaviour","Testing the behaviour",[10,1143,1144,1145,1148,1149,1152],{},"Response files are easy to test with ",[19,1146,1147],{},"tmp_path"," and ",[19,1150,1151],{},"monkeypatch.chdir",", since paths resolve against the working directory:",[98,1154,1156],{"className":100,"code":1155,"language":102,"meta":103,"style":103},"# tests\u002Ftest_response_files.py\nimport pytest\n\nfrom backup.cli import build_parser\n\n\n@pytest.fixture\ndef in_tmp(tmp_path, monkeypatch):\n    monkeypatch.chdir(tmp_path)\n    return tmp_path\n\n\ndef test_lines_support_quotes_comments_and_nesting(in_tmp):\n    (in_tmp \u002F \"notify.args\").write_text(\"--notify ops\\n\")\n    (in_tmp \u002F \"nightly.args\").write_text(\n        \"# preset\\n--to \u002Fmnt\u002Fbackup   # local\\n--exclude \\\"*.tmp\\\" --exclude \\\"my docs\u002Fcache\\\"\\n\"\n        \"@notify.args\\n\"\n    )\n    args = build_parser().parse_args([\"@nightly.args\", \"src\"])\n    assert args.to == \"\u002Fmnt\u002Fbackup\"\n    assert args.exclude == [\"*.tmp\", \"my docs\u002Fcache\"]\n    assert args.notify == \"ops\" and args.sources == [\"src\"]\n\n\ndef test_later_arguments_override_the_preset(in_tmp):\n    (in_tmp \u002F \"p.args\").write_text(\"--to \u002Fmnt\u002Fbackup\\n\")\n    assert build_parser().parse_args([\"@p.args\", \"--to\", \"\u002Felsewhere\"]).to == \"\u002Felsewhere\"\n\n\ndef test_at_values_need_the_equals_form(in_tmp, capsys):\n    with pytest.raises(SystemExit):\n        build_parser().parse_args([\"--notify\", \"@alice\"])\n    assert \"No such file or directory\" in capsys.readouterr().err\n    assert build_parser().parse_args([\"--notify=@alice\"]).notify == \"@alice\"\n",[19,1157,1158,1163,1170,1174,1186,1190,1194,1199,1209,1214,1221,1225,1229,1239,1263,1275,1312,1321,1325,1345,1358,1381,1407,1411,1415,1424,1444,1470,1474,1478,1488,1501,1516,1530],{"__ignoreMap":103},[107,1159,1160],{"class":80,"line":109},[107,1161,1162],{"class":112},"# tests\u002Ftest_response_files.py\n",[107,1164,1165,1167],{"class":80,"line":116},[107,1166,144],{"class":119},[107,1168,1169],{"class":130}," pytest\n",[107,1171,1172],{"class":80,"line":134},[107,1173,138],{"emptyLinePlaceholder":137},[107,1175,1176,1178,1181,1183],{"class":80,"line":141},[107,1177,120],{"class":119},[107,1179,1180],{"class":130}," backup.cli ",[107,1182,144],{"class":119},[107,1184,1185],{"class":130}," build_parser\n",[107,1187,1188],{"class":80,"line":150},[107,1189,138],{"emptyLinePlaceholder":137},[107,1191,1192],{"class":80,"line":158},[107,1193,138],{"emptyLinePlaceholder":137},[107,1195,1196],{"class":80,"line":163},[107,1197,1198],{"class":174},"@pytest.fixture\n",[107,1200,1201,1203,1206],{"class":80,"line":168},[107,1202,262],{"class":119},[107,1204,1205],{"class":174}," in_tmp",[107,1207,1208],{"class":130},"(tmp_path, monkeypatch):\n",[107,1210,1211],{"class":80,"line":192},[107,1212,1213],{"class":130},"    monkeypatch.chdir(tmp_path)\n",[107,1215,1216,1218],{"class":80,"line":199},[107,1217,438],{"class":119},[107,1219,1220],{"class":130}," tmp_path\n",[107,1222,1223],{"class":80,"line":204},[107,1224,138],{"emptyLinePlaceholder":137},[107,1226,1227],{"class":80,"line":227},[107,1228,138],{"emptyLinePlaceholder":137},[107,1230,1231,1233,1236],{"class":80,"line":249},[107,1232,262],{"class":119},[107,1234,1235],{"class":174}," test_lines_support_quotes_comments_and_nesting",[107,1237,1238],{"class":130},"(in_tmp):\n",[107,1240,1241,1244,1247,1250,1253,1256,1259,1261],{"class":80,"line":254},[107,1242,1243],{"class":130},"    (in_tmp ",[107,1245,1246],{"class":119},"\u002F",[107,1248,1249],{"class":195}," \"notify.args\"",[107,1251,1252],{"class":130},").write_text(",[107,1254,1255],{"class":195},"\"--notify ops",[107,1257,1258],{"class":123},"\\n",[107,1260,512],{"class":195},[107,1262,246],{"class":130},[107,1264,1265,1267,1269,1272],{"class":80,"line":259},[107,1266,1243],{"class":130},[107,1268,1246],{"class":119},[107,1270,1271],{"class":195}," \"nightly.args\"",[107,1273,1274],{"class":130},").write_text(\n",[107,1276,1277,1280,1282,1285,1287,1290,1293,1296,1298,1301,1303,1306,1309],{"class":80,"line":271},[107,1278,1279],{"class":195},"        \"# preset",[107,1281,1258],{"class":123},[107,1283,1284],{"class":195},"--to \u002Fmnt\u002Fbackup   # local",[107,1286,1258],{"class":123},[107,1288,1289],{"class":195},"--exclude ",[107,1291,1292],{"class":123},"\\\"",[107,1294,1295],{"class":195},"*.tmp",[107,1297,1292],{"class":123},[107,1299,1300],{"class":195}," --exclude ",[107,1302,1292],{"class":123},[107,1304,1305],{"class":195},"my docs\u002Fcache",[107,1307,1308],{"class":123},"\\\"\\n",[107,1310,1311],{"class":195},"\"\n",[107,1313,1314,1317,1319],{"class":80,"line":282},[107,1315,1316],{"class":195},"        \"@notify.args",[107,1318,1258],{"class":123},[107,1320,1311],{"class":195},[107,1322,1323],{"class":80,"line":296},[107,1324,325],{"class":130},[107,1326,1327,1329,1331,1334,1337,1339,1342],{"class":80,"line":309},[107,1328,493],{"class":130},[107,1330,240],{"class":119},[107,1332,1333],{"class":130}," build_parser().parse_args([",[107,1335,1336],{"class":195},"\"@nightly.args\"",[107,1338,337],{"class":130},[107,1340,1341],{"class":195},"\"src\"",[107,1343,1344],{"class":130},"])\n",[107,1346,1347,1350,1353,1355],{"class":80,"line":322},[107,1348,1349],{"class":119},"    assert",[107,1351,1352],{"class":130}," args.to ",[107,1354,866],{"class":119},[107,1356,1357],{"class":195}," \"\u002Fmnt\u002Fbackup\"\n",[107,1359,1360,1362,1365,1367,1370,1373,1375,1378],{"class":80,"line":328},[107,1361,1349],{"class":119},[107,1363,1364],{"class":130}," args.exclude ",[107,1366,866],{"class":119},[107,1368,1369],{"class":130}," [",[107,1371,1372],{"class":195},"\"*.tmp\"",[107,1374,337],{"class":130},[107,1376,1377],{"class":195},"\"my docs\u002Fcache\"",[107,1379,1380],{"class":130},"]\n",[107,1382,1383,1385,1388,1390,1393,1396,1399,1401,1403,1405],{"class":80,"line":360},[107,1384,1349],{"class":119},[107,1386,1387],{"class":130}," args.notify ",[107,1389,866],{"class":119},[107,1391,1392],{"class":195}," \"ops\"",[107,1394,1395],{"class":119}," and",[107,1397,1398],{"class":130}," args.sources ",[107,1400,866],{"class":119},[107,1402,1369],{"class":130},[107,1404,1341],{"class":195},[107,1406,1380],{"class":130},[107,1408,1409],{"class":80,"line":379},[107,1410,138],{"emptyLinePlaceholder":137},[107,1412,1413],{"class":80,"line":416},[107,1414,138],{"emptyLinePlaceholder":137},[107,1416,1417,1419,1422],{"class":80,"line":435},[107,1418,262],{"class":119},[107,1420,1421],{"class":174}," test_later_arguments_override_the_preset",[107,1423,1238],{"class":130},[107,1425,1426,1428,1430,1433,1435,1438,1440,1442],{"class":80,"line":444},[107,1427,1243],{"class":130},[107,1429,1246],{"class":119},[107,1431,1432],{"class":195}," \"p.args\"",[107,1434,1252],{"class":130},[107,1436,1437],{"class":195},"\"--to \u002Fmnt\u002Fbackup",[107,1439,1258],{"class":123},[107,1441,512],{"class":195},[107,1443,246],{"class":130},[107,1445,1446,1448,1450,1453,1455,1457,1459,1462,1465,1467],{"class":80,"line":449},[107,1447,1349],{"class":119},[107,1449,1333],{"class":130},[107,1451,1452],{"class":195},"\"@p.args\"",[107,1454,337],{"class":130},[107,1456,365],{"class":195},[107,1458,337],{"class":130},[107,1460,1461],{"class":195},"\"\u002Felsewhere\"",[107,1463,1464],{"class":130},"]).to ",[107,1466,866],{"class":119},[107,1468,1469],{"class":195}," \"\u002Felsewhere\"\n",[107,1471,1472],{"class":80,"line":454},[107,1473,138],{"emptyLinePlaceholder":137},[107,1475,1476],{"class":80,"line":490},[107,1477,138],{"emptyLinePlaceholder":137},[107,1479,1480,1482,1485],{"class":80,"line":501},[107,1481,262],{"class":119},[107,1483,1484],{"class":174}," test_at_values_need_the_equals_form",[107,1486,1487],{"class":130},"(in_tmp, capsys):\n",[107,1489,1490,1493,1496,1499],{"class":80,"line":549},[107,1491,1492],{"class":119},"    with",[107,1494,1495],{"class":130}," pytest.raises(",[107,1497,1498],{"class":123},"SystemExit",[107,1500,189],{"class":130},[107,1502,1504,1507,1509,1511,1514],{"class":80,"line":1503},32,[107,1505,1506],{"class":130},"        build_parser().parse_args([",[107,1508,421],{"class":195},[107,1510,337],{"class":130},[107,1512,1513],{"class":195},"\"@alice\"",[107,1515,1344],{"class":130},[107,1517,1519,1521,1524,1527],{"class":80,"line":1518},33,[107,1520,1349],{"class":119},[107,1522,1523],{"class":195}," \"No such file or directory\"",[107,1525,1526],{"class":119}," in",[107,1528,1529],{"class":130}," capsys.readouterr().err\n",[107,1531,1533,1535,1537,1540,1543,1545],{"class":80,"line":1532},34,[107,1534,1349],{"class":119},[107,1536,1333],{"class":130},[107,1538,1539],{"class":195},"\"--notify=@alice\"",[107,1541,1542],{"class":130},"]).notify ",[107,1544,866],{"class":119},[107,1546,1547],{"class":195}," \"@alice\"\n",[10,1549,1550],{},"The last test documents the gotcha so a future refactor does not \"fix\" it in a way that silently changes behaviour — and makes the trade-off visible to whoever decides on the prefix character.",[38,1552,1554],{"id":1553},"conclusion","Conclusion",[10,1556,1557,1559,1560,1562,1563,1565,1566,1568,1569,1572,1573,1575],{},[19,1558,61],{}," gives an argparse CLI response files for free: long argument lists that dodge command-line limits, and named presets that make scheduled jobs readable. Override ",[19,1561,559],{}," with ",[19,1564,563],{}," so files can use quoting, several arguments per line and comments; remember that options after the ",[19,1567,68],{}," override it; document the ",[19,1570,1571],{},"--opt=@value"," form or choose another prefix if values legitimately start with ",[19,1574,29],{},"; and test expansion from a temporary directory.",[38,1577,1579],{"id":1578},"frequently-asked-questions","Frequently asked questions",[629,1581,1583,1584,1586],{"id":1582},"do-click-and-typer-support-file-arguments","Do Click and Typer support ",[19,1585,68],{}," arguments?",[10,1588,1589,1590,1593,1594,1596,1597,1600],{},"Not built in. You can expand response files yourself before handing ",[19,1591,1592],{},"sys.argv"," to the framework — read any ",[19,1595,29],{},"-prefixed argument and splice in ",[19,1598,1599],{},"shlex.split"," of its contents — which gives the same behaviour in a few lines. Do it in the entry point, before the framework parses anything — the section above shows a version you can copy.",[629,1602,1604],{"id":1603},"can-a-response-file-contain-a-subcommand","Can a response file contain a subcommand?",[10,1606,1607],{},"Yes. Expansion happens before parsing, so the file can contain anything you could type, including the subcommand name and its options, as long as the resulting argument order is valid.",[629,1609,1611,1612,1614],{"id":1610},"how-do-i-pass-a-literal-argument-that-starts-with-as-a-positional","How do I pass a literal argument that starts with ",[19,1613,29],{}," as a positional?",[10,1616,1617,1618,1620,1621,1623,1624,1627,1628,1630],{},"You cannot with argparse's built-in expansion: unlike option parsing, ",[19,1619,29],{}," expansion also applies to arguments after ",[19,1622,700],{},", so ",[19,1625,1626],{},"backup -- @alice"," still tries to open a file. Use a different prefix character, or accept such values through an option with the ",[19,1629,240],{}," form. This is the strongest argument for choosing the prefix carefully up front.",[629,1632,1634],{"id":1633},"are-response-files-a-security-risk","Are response files a security risk?",[10,1636,1637,1638,1641],{},"Only in the sense that they let a file supply arguments. If your CLI runs with elevated privileges or processes arguments from untrusted sources, consider not enabling them, since ",[19,1639,1640],{},"@\u002Fetc\u002F…"," would read files the caller might not otherwise control.",[38,1643,1645],{"id":1644},"related","Related",[43,1647,1648,1654,1660,1666,1672],{},[46,1649,1650,1651],{},"Up: ",[32,1652,1653],{"href":34},"Command-line parsing with argparse",[46,1655,1656],{},[32,1657,1659],{"href":1658},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargument-groups-and-help-formatting-in-argparse\u002F","Argument groups and help formatting in argparse",[46,1661,1662],{},[32,1663,1665],{"href":1664},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fwriting-custom-argparse-actions\u002F","Writing custom argparse actions",[46,1667,1668],{},[32,1669,1671],{"href":1670},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Ffollowing-posix-and-gnu-argument-conventions\u002F","Following POSIX and GNU argument conventions",[46,1673,1674],{},[32,1675,1677],{"href":1676},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis\u002Frunning-a-cli-on-a-schedule-with-cron-and-systemd\u002F","Running a CLI on a schedule with cron and systemd",[1679,1680,1681],"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 .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}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":103,"searchDepth":116,"depth":116,"links":1683},[1684,1685,1686,1691,1692,1693,1694,1695,1703],{"id":40,"depth":116,"text":41},{"id":54,"depth":116,"text":55},{"id":95,"depth":116,"text":96,"children":1687},[1688,1690],{"id":631,"depth":134,"text":1689},"The @value gotcha",{"id":678,"depth":134,"text":679},{"id":693,"depth":116,"text":694},{"id":1088,"depth":116,"text":1089},{"id":1140,"depth":116,"text":1141},{"id":1553,"depth":116,"text":1554},{"id":1578,"depth":116,"text":1579,"children":1696},[1697,1699,1700,1702],{"id":1582,"depth":134,"text":1698},"Do Click and Typer support @file arguments?",{"id":1603,"depth":134,"text":1604},{"id":1610,"depth":134,"text":1701},"How do I pass a literal argument that starts with @ as a positional?",{"id":1633,"depth":134,"text":1634},{"id":1644,"depth":116,"text":1645},"2026-10-02","Let users pass @args.txt to an argparse CLI: response files for long argument lists and presets, shlex-style lines with comments, nesting, the @value gotcha, and tests.","intermediate",false,"md",{},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Freading-arguments-from-files-with-fromfile-prefix-chars",{"title":5,"description":1705},"modern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Freading-arguments-from-files-with-fromfile-prefix-chars\u002Findex",[181,1714,1715,1716,1717],"response-files","presets","standard-library","windows","RlVL1uZRJ4a_Abg2LiqKZ5NmUkD5tyyQdFIH0LVA2SM",[1720,1723,1726,1729,1732,1735,1738,1741,1744,1747,1750,1753,1756,1759,1762,1765,1768,1771,1774,1777,1780,1783,1786,1789,1792,1795,1798,1801,1804,1807,1810,1813,1816,1819,1822,1825,1828,1831,1834,1837,1840,1843,1846,1849,1852,1855,1858,1861,1864,1867,1870,1873,1876,1879,1882,1885,1888,1891,1894,1897,1900,1903,1906,1909,1912,1915,1918,1921,1924,1927,1930,1933,1936,1939,1942,1945,1948,1951,1954,1957,1960,1963,1966,1969,1972,1975,1978,1981,1984,1987,1990,1993,1996,1999,2002,2005,2008,2011,2014,2017,2020,2023,2026,2029,2032,2035,2038,2041,2044,2047,2050,2053,2056,2059,2062,2065,2068,2071,2074,2077,2080,2083,2086,2089,2092,2095,2098,2101,2104,2107,2110,2113,2116,2119,2122,2125,2128,2131,2134,2137,2139,2142,2145,2148,2151,2154,2157,2160,2163,2166,2169,2172,2175,2178,2181,2184,2187,2190,2193,2194,2197,2200,2203,2206,2209,2212,2215,2218,2221,2224,2227,2230,2233,2236,2239,2242,2245,2248,2251,2254,2257,2260,2263,2266,2269,2272,2275,2278,2281,2284,2287,2290,2293,2296,2299,2302,2305,2308,2311,2314,2317,2320,2323,2326,2329,2332,2335,2338,2341,2344,2347,2350,2353,2356,2359,2362,2365,2368,2371,2374,2377,2380,2383,2386,2389,2392,2395,2398,2401,2404,2407,2410,2413,2416,2419,2422,2425,2428,2431,2434,2437,2440,2443,2446,2449,2452,2455,2458,2461,2464,2467,2470,2473,2476,2479,2482,2485,2488,2491,2494,2497,2500,2503,2506,2509,2512,2515,2518,2521,2524,2527,2530,2533,2536,2539,2542,2545,2548,2551,2554,2557,2560],{"path":1721,"title":1722},"\u002Fabout","About Python CLI Toolcraft",{"path":1724,"title":1725},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies","Advanced Argument Validation Strategies",{"path":1727,"title":1728},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fparsing-nested-json-arguments-in-python-clis","Parsing Nested JSON Args in Python CLIs",{"path":1730,"title":1731},"\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":1733,"title":1734},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fvalidating-dependent-and-conflicting-options","Validating Dependent and Conflicting CLI Options",{"path":1736,"title":1737},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fvalidating-file-and-directory-paths-in-clis","Validating File and Directory Paths in CLIs",{"path":1739,"title":1740},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fvalidating-urls-hosts-and-ports","Validating URLs, Hosts and Ports in Python CLI Arguments",{"path":1742,"title":1743},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fwriting-custom-click-parameter-types","Writing Custom Click Parameter Types",{"path":1745,"title":1746},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual\u002Fbrowsing-records-with-a-textual-datatable","Browsing Records with a Textual DataTable Picker",{"path":1748,"title":1749},"\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":1751,"title":1752},"\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":1754,"title":1755},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual","Building Terminal UIs with Textual for Python CLIs",{"path":1757,"title":1758},"\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":1760,"title":1761},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual\u002Fstyling-textual-apps-with-tcss","Styling Textual Apps with TCSS",{"path":1763,"title":1764},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual\u002Ftesting-textual-apps-with-pilot","Testing Textual Apps with Pilot",{"path":1766,"title":1767},"\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":1769,"title":1770},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fdocumenting-environment-variables-in-help","Documenting Environment Variables in CLI Help",{"path":1772,"title":1773},"\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":1775,"title":1776},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation","CLI Help Output and Documentation",{"path":1778,"title":1779},"\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":1781,"title":1782},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fversioning-and-deprecating-cli-flags","Versioning and Deprecating CLI Flags",{"path":1784,"title":1785},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fwriting-help-text-users-actually-read","Writing Help Text Users Actually Read",{"path":1787,"title":1788},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Fadapting-output-to-terminal-width","Adapting Python CLI Output to Terminal Width",{"path":1790,"title":1791},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Fdetecting-ci-environments-and-non-interactive-shells","Detecting CI Environments and Non-Interactive Shells",{"path":1793,"title":1794},"\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":1796,"title":1797},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Fhandling-ansi-escape-codes-on-windows-consoles","Handling ANSI Escape Codes on Windows Consoles",{"path":1799,"title":1800},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility","Cross-Platform Terminal Compatibility for Python CLIs",{"path":1802,"title":1803},"\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":1805,"title":1806},"\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":1808,"title":1809},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fchoosing-exit-codes-for-cli-tools","Choosing Exit Codes for CLI Tools",{"path":1811,"title":1812},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fdesigning-an-exception-hierarchy-for-a-cli","Designing an Exception Hierarchy for a Python CLI",{"path":1814,"title":1815},"\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":1817,"title":1818},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Ffriendly-error-messages-and-tracebacks","Friendly Error Messages and Tracebacks",{"path":1820,"title":1821},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fhandling-keyboard-interrupt-cleanly","Handling Keyboard Interrupt Cleanly",{"path":1823,"title":1824},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes","Error Handling and Exit Codes for CLIs",{"path":1826,"title":1827},"\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":1829,"title":1830},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fwriting-crash-reports-users-can-send","Writing Crash Reports Users Can Send",{"path":1832,"title":1833},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Fconfig-precedence-flags-env-files-defaults","Config Precedence: Flags, Env, Files, Defaults",{"path":1835,"title":1836},"\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":1838,"title":1839},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars","Handling Config Files and Env Vars in CLIs",{"path":1841,"title":1842},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Floading-yaml-configs-safely-in-cli-apps","Loading YAML configs safely in CLI apps",{"path":1844,"title":1845},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Freading-toml-config-with-tomllib","Reading TOML Config with tomllib in Python CLIs",{"path":1847,"title":1848},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Ftyped-settings-with-pydantic-settings","Typed Settings with pydantic-settings in Python CLIs",{"path":1850,"title":1851},"\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":1853,"title":1854},"\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":1856,"title":1857},"\u002Fadvanced-input-parsing-user-experience","Advanced Input Parsing for Python CLIs",{"path":1859,"title":1860},"\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":1862,"title":1863},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Fbuilding-interactive-prompts-and-menus","Building Interactive Prompts and Menus in Python CLIs",{"path":1865,"title":1866},"\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":1868,"title":1869},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich","Interactive Terminal UI with Rich",{"path":1871,"title":1872},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Flive-dashboards-with-rich-live","Live Dashboards with Rich Live in Python CLIs",{"path":1874,"title":1875},"\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":1877,"title":1878},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Frendering-tables-and-json-with-rich","Rendering Tables and JSON with Rich",{"path":1880,"title":1881},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Ftheming-rich-output-consistently","Theming Rich Output Consistently in Python CLIs",{"path":1883,"title":1884},"\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":1886,"title":1887},"\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":1889,"title":1890},"\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":1892,"title":1893},"\u002Fadvanced-input-parsing-user-experience\u002Foutput-formats-for-data-clis","Output Formats for Data-Heavy Python CLIs",{"path":1895,"title":1896},"\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":1898,"title":1899},"\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":1901,"title":1902},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Fargcomplete-for-argparse-clis","Tab Completion for argparse CLIs with argcomplete",{"path":1904,"title":1905},"\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":1907,"title":1908},"\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":1910,"title":1911},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis","Shell Completion for Python CLIs",{"path":1913,"title":1914},"\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":1916,"title":1917},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Fshipping-completion-scripts-with-packages","Shipping Shell Completion Scripts with a Python CLI",{"path":1919,"title":1920},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Ftesting-shell-completion-in-python-clis","Testing Shell Completion in Python CLIs",{"path":1922,"title":1923},"\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":1925,"title":1926},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fadding-verbose-and-quiet-logging-flags","Adding Verbose and Quiet Logging Flags",{"path":1928,"title":1929},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps","Structured Logging for CLI Apps",{"path":1931,"title":1932},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Flogging-with-structlog-in-a-cli","Logging with structlog in a Python CLI",{"path":1934,"title":1935},"\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":1937,"title":1938},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fstructured-json-logging-in-python-clis","Structured JSON Logging in Python CLIs",{"path":1940,"title":1941},"\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":1943,"title":1944},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Fdetecting-tty-and-adapting-output","Detecting a TTY and Adapting Output",{"path":1946,"title":1947},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Femitting-json-output-for-scripting","Emitting JSON Output for Scripting",{"path":1949,"title":1950},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Fhandling-broken-pipe-and-sigpipe","Handling Broken Pipe and SIGPIPE",{"path":1952,"title":1953},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes","Working with stdin, stdout and Pipes",{"path":1955,"title":1956},"\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":1958,"title":1959},"\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":1961,"title":1962},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Freading-piped-input-in-python-clis","Reading Piped Input in Python CLIs",{"path":1964,"title":1965},"\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":1967,"title":1968},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Fdownloading-files-with-progress-in-python","Downloading Files with Progress in Python CLIs",{"path":1970,"title":1971},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis","Calling HTTP APIs from Python CLIs",{"path":1973,"title":1974},"\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":1976,"title":1977},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Foauth-device-flow-login-for-clis","OAuth Device Flow Login for Python CLIs",{"path":1979,"title":1980},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Fpaginating-api-results-in-a-cli","Paginating API Results in a Python CLI",{"path":1982,"title":1983},"\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":1985,"title":1986},"\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":1988,"title":1989},"\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":1991,"title":1992},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis","Concurrency and Async in Python CLIs",{"path":1994,"title":1995},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Fmultiprocessing-for-cpu-bound-cli-tasks","Multiprocessing for CPU-Bound CLI Tasks",{"path":1997,"title":1998},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Fparallelising-cli-work-with-thread-pools","Parallelising CLI Work with Thread Pools",{"path":2000,"title":2001},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Frate-limiting-concurrent-requests-in-clis","Rate-Limiting Concurrent Requests in Python CLIs",{"path":2003,"title":2004},"\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":2006,"title":2007},"\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":2009,"title":2010},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Fstructured-concurrency-with-taskgroups-in-clis","Structured Concurrency with TaskGroups in Python CLIs",{"path":2012,"title":2013},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fcross-platform-paths-with-pathlib","Cross-Platform Paths with pathlib in CLIs",{"path":2015,"title":2016},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Ffile-locking-for-concurrent-cli-runs","File Locking for Concurrent CLI Runs in Python",{"path":2018,"title":2019},"\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":2021,"title":2022},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes","Filesystem Paths and Atomic Writes for CLIs",{"path":2024,"title":2025},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fsafe-temporary-files-and-directories","Safe Temporary Files and Directories in CLIs",{"path":2027,"title":2028},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fstoring-app-data-with-platformdirs","Storing CLI App Data with platformdirs",{"path":2030,"title":2031},"\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":2033,"title":2034},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fwriting-files-atomically-in-python-clis","Writing Files Atomically in Python CLIs",{"path":2036,"title":2037},"\u002Fcli-runtime-systems-integration","CLI Runtime & Systems Integration for Python",{"path":2039,"title":2040},"\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":2042,"title":2043},"\u002Fcli-runtime-systems-integration\u002Flocal-state-and-sqlite-in-python-clis","Local State and SQLite in Python CLIs",{"path":2045,"title":2046},"\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":2048,"title":2049},"\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":2051,"title":2052},"\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":2054,"title":2055},"\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":2057,"title":2058},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis\u002Fhandling-sigterm-and-graceful-shutdown","Handling SIGTERM and Graceful Shutdown in CLIs",{"path":2060,"title":2061},"\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":2063,"title":2064},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis","Long-Running and Watch-Mode Python CLIs",{"path":2066,"title":2067},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis\u002Freloading-config-on-sighup","Reloading Configuration on SIGHUP in a Python CLI",{"path":2069,"title":2070},"\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":2072,"title":2073},"\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":2075,"title":2076},"\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":2078,"title":2079},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Favoiding-shell-injection-in-python-clis","Avoiding Shell Injection in Python CLIs",{"path":2081,"title":2082},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fcalling-external-commands-safely-with-subprocess","Calling External Commands Safely with subprocess",{"path":2084,"title":2085},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fhandling-subprocess-timeouts-and-exit-codes","Handling Subprocess Timeouts and Exit Codes",{"path":2087,"title":2088},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis","Running Subprocesses from Python CLIs",{"path":2090,"title":2091},"\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":2093,"title":2094},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fpiping-between-subprocesses-in-python","Piping Between Subprocesses in Python Without a Shell",{"path":2096,"title":2097},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fstreaming-subprocess-output-in-real-time","Streaming Subprocess Output in Real Time",{"path":2099,"title":2100},"\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":2102,"title":2103},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis","Secrets and Credentials in Python CLIs",{"path":2105,"title":2106},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Fprompting-for-passwords-securely","Prompting for Passwords Securely in Python CLIs",{"path":2108,"title":2109},"\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":2111,"title":2112},"\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":2114,"title":2115},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Frefreshing-expired-tokens-automatically","Refreshing Expired Tokens Automatically in a Python CLI",{"path":2117,"title":2118},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Fstoring-tokens-with-keyring","Storing CLI Tokens Securely with keyring",{"path":2120,"title":2121},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Fsupporting-multiple-profiles-and-accounts","Supporting Multiple Profiles and Accounts in CLIs",{"path":2123,"title":2124},"\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":2126,"title":2127},"\u002Fcli-runtime-systems-integration\u002Fupdate-checks-and-self-updating-clis","Update Checks, Upgrade Commands and Telemetry for Python CLIs",{"path":2129,"title":2130},"\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":2132,"title":2133},"\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":2135,"title":2136},"\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":1246,"title":2138},"Python CLI Toolcraft",{"path":2140,"title":2141},"\u002Fmodern-python-cli-frameworks-architecture\u002Falternative-python-cli-frameworks\u002Fbuilding-a-cli-with-cleo","Building a Python CLI with Cleo Command Classes",{"path":2143,"title":2144},"\u002Fmodern-python-cli-frameworks-architecture\u002Falternative-python-cli-frameworks\u002Fbuilding-a-cli-with-cyclopts","Building a Type-Hinted Python CLI with Cyclopts",{"path":2146,"title":2147},"\u002Fmodern-python-cli-frameworks-architecture\u002Falternative-python-cli-frameworks","Beyond Click and Typer: Alternative Python CLI Frameworks",{"path":2149,"title":2150},"\u002Fmodern-python-cli-frameworks-architecture\u002Falternative-python-cli-frameworks\u002Fquick-clis-from-functions-with-python-fire","Quick CLIs from Functions with Python Fire",{"path":2152,"title":2153},"\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":2155,"title":2156},"\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":2158,"title":2159},"\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":2161,"title":2162},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Fguarding-startup-with-import-tests","Guarding CLI Startup with Import Tests",{"path":2164,"title":2165},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading","CLI Startup Performance and Lazy Loading",{"path":2167,"title":2168},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Flazy-loading-subcommands-for-faster-startup","Lazy Loading Subcommands for Faster Startup",{"path":2170,"title":2171},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Fprofiling-python-cli-startup-time","Profiling Python CLI Startup Time",{"path":2173,"title":2174},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Freducing-cli-dependency-weight","Reducing CLI Dependency Weight",{"path":2176,"title":2177},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargparse-subparsers-for-subcommands","argparse Subparsers for Subcommands",{"path":2179,"title":2180},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargparse-vs-click-vs-typer-comparison","argparse vs Click vs Typer Compared",{"path":2182,"title":2183},"\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":2185,"title":2186},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse","Command-Line Parsing with argparse",{"path":2188,"title":2189},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fmigrating-from-argparse-to-typer","Migrating from argparse to Typer",{"path":2191,"title":2192},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fmutually-exclusive-options-in-argparse","Mutually Exclusive Options in argparse",{"path":1710,"title":5},{"path":2195,"title":2196},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fwriting-custom-argparse-actions","Writing Custom argparse Actions in Python",{"path":2198,"title":2199},"\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":2201,"title":2202},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Fdesigning-idempotent-commands","Designing Idempotent Commands in a Python CLI",{"path":2204,"title":2205},"\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":2207,"title":2208},"\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":2210,"title":2211},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions","Designing CLI Interfaces and Conventions in Python",{"path":2213,"title":2214},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Fnaming-commands-and-flags-consistently","Naming Commands and Flags Consistently in Python CLIs",{"path":2216,"title":2217},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Fpositional-arguments-vs-options","Positional Arguments vs Options: Designing a CLI Signature",{"path":2219,"title":2220},"\u002Fmodern-python-cli-frameworks-architecture","Python CLI Frameworks and Architecture",{"path":2222,"title":2223},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fdiscovering-plugins-with-entry-points","Discovering Plugins with Entry Points in Python CLIs",{"path":2225,"title":2226},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fhook-based-plugins-with-pluggy","Hook-Based Plugins for Python CLIs with pluggy",{"path":2228,"title":2229},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis","Plugin Architectures for Extensible CLIs",{"path":2231,"title":2232},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fisolating-plugin-failures","Isolating Plugin Failures in an Extensible Python CLI",{"path":2234,"title":2235},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Ftesting-plugins-against-the-host-cli","Testing Plugins Against the Host CLI",{"path":2237,"title":2238},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fversioning-a-plugin-api","Versioning a Plugin API for a Python CLI",{"path":2240,"title":2241},"\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":2243,"title":2244},"\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":2246,"title":2247},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fdependency-injection-patterns-for-cli-commands","Dependency Injection Patterns for CLI Commands",{"path":2249,"title":2250},"\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":2252,"title":2253},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis","Structuring Multi-Command Python CLIs",{"path":2255,"title":2256},"\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":2258,"title":2259},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fregistering-commands-from-modules-automatically","Registering CLI Commands from Modules Automatically",{"path":2261,"title":2262},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fsharing-common-options-across-commands","Sharing Common Options Across Python CLI Commands",{"path":2264,"title":2265},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fsharing-state-with-click-context-objects","Sharing State with Click Context Objects",{"path":2267,"title":2268},"\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":2270,"title":2271},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications","Testing Python CLI Applications",{"path":2273,"title":2274},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fmeasuring-cli-test-coverage","Measuring CLI Test Coverage",{"path":2276,"title":2277},"\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":2279,"title":2280},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fproperty-based-testing-cli-arguments-with-hypothesis","Property-Based Testing CLI Arguments with Hypothesis",{"path":2282,"title":2283},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fsnapshot-testing-cli-output","Snapshot Testing CLI Output",{"path":2285,"title":2286},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftesting-click-commands-with-clirunner","Testing Click Commands with CliRunner",{"path":2288,"title":2289},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftesting-commands-that-call-subprocesses","Testing Python CLI Commands That Call Subprocesses",{"path":2291,"title":2292},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftesting-interactive-prompts-and-stdin","Testing Interactive Prompts and stdin",{"path":2294,"title":2295},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftranscript-tests-for-cli-commands","Transcript Tests for Python CLI Commands",{"path":2297,"title":2298},"\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":2300,"title":2301},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Fbuilding-dynamic-commands-in-click","Building Dynamic Commands in Click",{"path":2303,"title":2304},"\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":2306,"title":2307},"\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":2309,"title":2310},"\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":2312,"title":2313},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each","Typer vs Click: When to Use Each",{"path":2315,"title":2316},"\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":2318,"title":2319},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Ftyper-callback-functions-explained","Typer callback functions explained",{"path":2321,"title":2322},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Fusing-annotated-options-in-typer","Using Annotated Options in Typer",{"path":2324,"title":2325},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis\u002Fautomating-releases-from-git-tags","Automating Python CLI Releases from Git Tags",{"path":2327,"title":2328},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis\u002Fcaching-uv-dependencies-in-ci","Caching uv Dependencies in CI for Python CLIs",{"path":2330,"title":2331},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis","CI\u002FCD Pipelines for Python CLIs",{"path":2333,"title":2334},"\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":2336,"title":2337},"\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":2339,"title":2340},"\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":2342,"title":2343},"\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":2345,"title":2346},"\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":2348,"title":2349},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fbuilding-a-cookiecutter-template-for-typer-clis","Building a Cookiecutter Template for Typer CLIs",{"path":2351,"title":2352},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fcopier-vs-cookiecutter-for-cli-templates","Copier vs Cookiecutter for CLI Templates",{"path":2354,"title":2355},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter","CLI Project Scaffolding with Cookiecutter",{"path":2357,"title":2358},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fpost-generation-hooks-in-cli-templates","Post-Generation Hooks in Python CLI Templates",{"path":2360,"title":2361},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Ftemplate-variables-and-conditional-files","Template Variables and Conditional Files in CLI Templates",{"path":2363,"title":2364},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Ftesting-a-project-template-with-pytest","Testing a CLI Project Template with pytest",{"path":2366,"title":2367},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fupdating-generated-projects-with-copier-update","Updating Generated CLI Projects with copier update",{"path":2369,"title":2370},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fbuilding-cross-platform-release-binaries-in-ci","Building Cross-Platform Release Binaries in CI",{"path":2372,"title":2373},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fbundling-a-python-cli-with-pyinstaller","Bundling a Python CLI with PyInstaller",{"path":2375,"title":2376},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fcode-signing-and-notarizing-cli-binaries","Code Signing and Notarizing Python CLI Binaries",{"path":2378,"title":2379},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fhomebrew-and-scoop-packaging-for-python-clis","Homebrew and Scoop Packaging for Python CLIs",{"path":2381,"title":2382},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries","Distributing CLIs as Standalone Binaries",{"path":2384,"title":2385},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fnuitka-vs-pyinstaller-for-python-clis","Nuitka vs PyInstaller for Python CLI Binaries",{"path":2387,"title":2388},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Freducing-pyinstaller-binary-size","Reducing the Size of a PyInstaller CLI Binary",{"path":2390,"title":2391},"\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":2393,"title":2394},"\u002Fproject-setup-dependency-management","Project Setup & Dependency Management",{"path":2396,"title":2397},"\u002Fproject-setup-dependency-management\u002Flinting-and-type-checking-cli-code\u002Fconfiguring-ruff-for-a-cli-project","Configuring Ruff for a Python CLI Project",{"path":2399,"title":2400},"\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":2402,"title":2403},"\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":2405,"title":2406},"\u002Fproject-setup-dependency-management\u002Flinting-and-type-checking-cli-code","Linting and Type-Checking Python CLI Code",{"path":2408,"title":2409},"\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":2411,"title":2412},"\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":2414,"title":2415},"\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":2417,"title":2418},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fautomating-changelogs-with-conventional-commits","Automating Changelogs with Conventional Commits",{"path":2420,"title":2421},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fbumping-versions-consistently-across-a-cli-project","Bumping Versions Consistently Across a Python CLI Project",{"path":2423,"title":2424},"\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":2426,"title":2427},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fexposing-version-info-and-build-metadata","Exposing Version Info and Build Metadata",{"path":2429,"title":2430},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs","Managing CLI Versioning & Changelogs",{"path":2432,"title":2433},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fsemantic-versioning-policy-for-cli-tools","A Semantic Versioning Policy for CLI Tools",{"path":2435,"title":2436},"\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":2438,"title":2439},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fbuilding-wheels-and-sdists-for-python-clis","Building Wheels and sdists for Python CLIs",{"path":2441,"title":2442},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fbundling-data-files-with-importlib-resources","Bundling Data Files with importlib.resources in CLIs",{"path":2444,"title":2445},"\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":2447,"title":2448},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution","Packaging Python CLIs for Distribution",{"path":2450,"title":2451},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Finstalling-and-distributing-clis-with-pipx","Installing and Distributing CLIs with pipx",{"path":2453,"title":2454},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Foptional-dependencies-and-extras-for-clis","Optional Dependencies and Extras for Python CLIs",{"path":2456,"title":2457},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fpublishing-a-python-cli-to-pypi","Publishing a Python CLI to PyPI",{"path":2459,"title":2460},"\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":2462,"title":2463},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fdynamic-versioning-with-poetry-plugins","Dynamic Versioning for a Poetry CLI from Git Tags",{"path":2465,"title":2466},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development","Poetry Workflows for CLI Development",{"path":2468,"title":2469},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fmanaging-poetry-lock-files-for-cli-tools","Managing Poetry Lock Files for CLI Tools",{"path":2471,"title":2472},"\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":2474,"title":2475},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpoetry-dependency-groups-for-cli-tooling","Poetry Dependency Groups for CLI Tooling",{"path":2477,"title":2478},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpoetry-entry-points-and-scripts-for-clis","Poetry Entry Points and Scripts for CLIs",{"path":2480,"title":2481},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpublishing-a-cli-with-poetry","Building and Publishing a Python CLI with Poetry",{"path":2483,"title":2484},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects","Pre-commit Hooks for CLI Projects",{"path":2486,"title":2487},"\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":2489,"title":2490},"\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":2492,"title":2493},"\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":2495,"title":2496},"\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":2498,"title":2499},"\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":2501,"title":2502},"\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":2504,"title":2505},"\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":2507,"title":2508},"\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":2510,"title":2511},"\u002Fproject-setup-dependency-management\u002Fsecuring-a-python-cli-supply-chain","Securing the Supply Chain of a Python CLI",{"path":2513,"title":2514},"\u002Fproject-setup-dependency-management\u002Fsecuring-a-python-cli-supply-chain\u002Fpinning-dependencies-with-hashes","Pinning a Python CLI’s Dependencies with Hashes",{"path":2516,"title":2517},"\u002Fproject-setup-dependency-management\u002Fsecuring-a-python-cli-supply-chain\u002Fpublishing-attestations-and-verifying-releases","Publishing Attestations and Verifying Python CLI Releases",{"path":2519,"title":2520},"\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":2522,"title":2523},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management","uv for Python CLI Dependency Management",{"path":2525,"title":2526},"\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":2528,"title":2529},"\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":2531,"title":2532},"\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":2534,"title":2535},"\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":2537,"title":2538},"\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":2540,"title":2541},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002Fuv-workspaces-for-multi-package-clis","uv Workspaces for Multi-Package Python CLIs",{"path":2543,"title":2544},"\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":2546,"title":2547},"\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":2549,"title":2550},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices","Python CLI Env Isolation Best Practices",{"path":2552,"title":2553},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fisolating-cli-tools-from-project-dependencies","Isolating CLI Tools from Your Project’s Dependencies",{"path":2555,"title":2556},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fmanaging-virtual-environments-for-cross-platform-clis","Managing Python CLI Virtual Environments",{"path":2558,"title":2559},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fpinning-the-python-version-for-a-cli","Pinning the Python Version for a CLI",{"path":2561,"title":2562},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fsupporting-multiple-python-versions-with-nox","Supporting Multiple Python Versions with nox",1790967540270]