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