[{"data":1,"prerenderedAt":2258},["ShallowReactive",2],{"page-\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fmigrating-from-argparse-to-typer\u002F":3,"content-directory":2109},{"id":4,"title":5,"body":6,"date":2094,"description":2095,"difficulty":2096,"draft":2097,"extension":2098,"meta":2099,"navigation":160,"path":2100,"seo":2101,"stem":2102,"tags":2103,"updated":2094,"__hash__":2108},"content\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fmigrating-from-argparse-to-typer\u002Findex.md","Migrating from argparse to Typer",{"type":7,"value":8,"toc":2079},"minimark",[9,26,31,119,123,126,563,567,574,938,985,1016,1020,1023,1256,1259,1292,1297,1320,1365,1369,1378,1425,1429,1445,1561,1571,1575,1578,1628,1639,1643,1646,1692,1695,1699,1706,1956,1963,1967,2046,2050,2075],[10,11,12,13,17,18,25],"p",{},"You have an ",[14,15,16],"code",{},"argparse"," CLI that works, but adding features means more boilerplate, nested\nsubcommands are painful, and you want shell completion for free. ",[19,20,24],"a",{"href":21,"rel":22},"https:\u002F\u002Ftyper.tiangolo.com",[23],"nofollow","Typer","\ngives you all of that by reading your function's type hints instead of making you declare\nevery argument. This guide moves a real CLI across, one construct at a time, so behaviour\nstays identical while the code shrinks.",[27,28,30],"h2",{"id":29},"tldr","TL;DR",[32,33,34,55,94,105,108],"ul",{},[35,36,37,38,41,42,45,46,50,51,54],"li",{},"A Typer app replaces ",[14,39,40],{},"ArgumentParser","; each ",[14,43,44],{},"@app.command()"," function's ",[47,48,49],"strong",{},"parameters","\nreplace ",[14,52,53],{},"add_argument()"," calls.",[35,56,57,60,61,64,65,68,69,64,74,77,78,83,84,87,88,93],{},[14,58,59],{},"type="," becomes a ",[47,62,63],{},"type annotation",", ",[14,66,67],{},"choices="," becomes an ",[47,70,71],{},[14,72,73],{},"Enum",[14,75,76],{},"action=\"store_true\"","\nbecomes a ",[47,79,80],{},[14,81,82],{},"bool",", and ",[14,85,86],{},"add_subparsers()"," becomes multiple ",[47,89,90,92],{},[14,91,44],{},"s",".",[35,95,96,97,101,102,93],{},"Positional parameters become Typer ",[98,99,100],"em",{},"arguments","; parameters with defaults become ",[98,103,104],{},"options",[35,106,107],{},"Migrate incrementally: Typer is built on Click, so you can wrap an existing argparse parser\nbehind a single Typer command and peel it apart command by command.",[35,109,110,111,114,115,118],{},"Lock behaviour with ",[14,112,113],{},"CliRunner"," tests written ",[98,116,117],{},"before"," you change anything.",[27,120,122],{"id":121},"the-starting-point-an-argparse-cli","The starting point: an argparse CLI",[10,124,125],{},"Here is a small but representative tool — it greets people, takes a count, a log level with\nfixed choices, and a verbose flag.",[127,128,133],"pre",{"className":129,"code":130,"language":131,"meta":132,"style":132},"language-python shiki shiki-themes github-light github-dark","# greet_argparse.py\nimport argparse\n\ndef main() -> None:\n    parser = argparse.ArgumentParser(\n        prog=\"greet\", description=\"Greet someone a number of times.\",\n    )\n    parser.add_argument(\"name\", help=\"Who to greet.\")\n    parser.add_argument(\n        \"--count\", type=int, default=1, help=\"How many times (default: 1).\",\n    )\n    parser.add_argument(\n        \"--level\", choices=[\"quiet\", \"normal\", \"loud\"], default=\"normal\",\n        help=\"Greeting volume.\",\n    )\n    parser.add_argument(\"--verbose\", action=\"store_true\", help=\"Explain what is happening.\")\n\n    args = parser.parse_args()\n    if args.verbose:\n        print(f\"[verbose] level={args.level}\")\n    greeting = \"hi\" if args.level == \"quiet\" else \"HELLO\" if args.level == \"loud\" else \"Hello\"\n    for _ in range(args.count):\n        print(f\"{greeting}, {args.name}!\")\n\nif __name__ == \"__main__\":\n    main()\n","python","",[14,134,135,144,155,162,182,194,220,226,248,254,291,296,301,341,354,359,388,393,404,413,442,485,503,535,540,557],{"__ignoreMap":132},[136,137,140],"span",{"class":138,"line":139},"line",1,[136,141,143],{"class":142},"sJ8bj","# greet_argparse.py\n",[136,145,147,151],{"class":138,"line":146},2,[136,148,150],{"class":149},"szBVR","import",[136,152,154],{"class":153},"sVt8B"," argparse\n",[136,156,158],{"class":138,"line":157},3,[136,159,161],{"emptyLinePlaceholder":160},true,"\n",[136,163,165,168,172,175,179],{"class":138,"line":164},4,[136,166,167],{"class":149},"def",[136,169,171],{"class":170},"sScJk"," main",[136,173,174],{"class":153},"() -> ",[136,176,178],{"class":177},"sj4cs","None",[136,180,181],{"class":153},":\n",[136,183,185,188,191],{"class":138,"line":184},5,[136,186,187],{"class":153},"    parser ",[136,189,190],{"class":149},"=",[136,192,193],{"class":153}," argparse.ArgumentParser(\n",[136,195,197,201,203,207,209,212,214,217],{"class":138,"line":196},6,[136,198,200],{"class":199},"s4XuR","        prog",[136,202,190],{"class":149},[136,204,206],{"class":205},"sZZnC","\"greet\"",[136,208,64],{"class":153},[136,210,211],{"class":199},"description",[136,213,190],{"class":149},[136,215,216],{"class":205},"\"Greet someone a number of times.\"",[136,218,219],{"class":153},",\n",[136,221,223],{"class":138,"line":222},7,[136,224,225],{"class":153},"    )\n",[136,227,229,232,235,237,240,242,245],{"class":138,"line":228},8,[136,230,231],{"class":153},"    parser.add_argument(",[136,233,234],{"class":205},"\"name\"",[136,236,64],{"class":153},[136,238,239],{"class":199},"help",[136,241,190],{"class":149},[136,243,244],{"class":205},"\"Who to greet.\"",[136,246,247],{"class":153},")\n",[136,249,251],{"class":138,"line":250},9,[136,252,253],{"class":153},"    parser.add_argument(\n",[136,255,257,260,262,265,267,270,272,275,277,280,282,284,286,289],{"class":138,"line":256},10,[136,258,259],{"class":205},"        \"--count\"",[136,261,64],{"class":153},[136,263,264],{"class":199},"type",[136,266,190],{"class":149},[136,268,269],{"class":177},"int",[136,271,64],{"class":153},[136,273,274],{"class":199},"default",[136,276,190],{"class":149},[136,278,279],{"class":177},"1",[136,281,64],{"class":153},[136,283,239],{"class":199},[136,285,190],{"class":149},[136,287,288],{"class":205},"\"How many times (default: 1).\"",[136,290,219],{"class":153},[136,292,294],{"class":138,"line":293},11,[136,295,225],{"class":153},[136,297,299],{"class":138,"line":298},12,[136,300,253],{"class":153},[136,302,304,307,309,312,314,317,320,322,325,327,330,333,335,337,339],{"class":138,"line":303},13,[136,305,306],{"class":205},"        \"--level\"",[136,308,64],{"class":153},[136,310,311],{"class":199},"choices",[136,313,190],{"class":149},[136,315,316],{"class":153},"[",[136,318,319],{"class":205},"\"quiet\"",[136,321,64],{"class":153},[136,323,324],{"class":205},"\"normal\"",[136,326,64],{"class":153},[136,328,329],{"class":205},"\"loud\"",[136,331,332],{"class":153},"], ",[136,334,274],{"class":199},[136,336,190],{"class":149},[136,338,324],{"class":205},[136,340,219],{"class":153},[136,342,344,347,349,352],{"class":138,"line":343},14,[136,345,346],{"class":199},"        help",[136,348,190],{"class":149},[136,350,351],{"class":205},"\"Greeting volume.\"",[136,353,219],{"class":153},[136,355,357],{"class":138,"line":356},15,[136,358,225],{"class":153},[136,360,362,364,367,369,372,374,377,379,381,383,386],{"class":138,"line":361},16,[136,363,231],{"class":153},[136,365,366],{"class":205},"\"--verbose\"",[136,368,64],{"class":153},[136,370,371],{"class":199},"action",[136,373,190],{"class":149},[136,375,376],{"class":205},"\"store_true\"",[136,378,64],{"class":153},[136,380,239],{"class":199},[136,382,190],{"class":149},[136,384,385],{"class":205},"\"Explain what is happening.\"",[136,387,247],{"class":153},[136,389,391],{"class":138,"line":390},17,[136,392,161],{"emptyLinePlaceholder":160},[136,394,396,399,401],{"class":138,"line":395},18,[136,397,398],{"class":153},"    args ",[136,400,190],{"class":149},[136,402,403],{"class":153}," parser.parse_args()\n",[136,405,407,410],{"class":138,"line":406},19,[136,408,409],{"class":149},"    if",[136,411,412],{"class":153}," args.verbose:\n",[136,414,416,419,422,425,428,431,434,437,440],{"class":138,"line":415},20,[136,417,418],{"class":177},"        print",[136,420,421],{"class":153},"(",[136,423,424],{"class":149},"f",[136,426,427],{"class":205},"\"[verbose] level=",[136,429,430],{"class":177},"{",[136,432,433],{"class":153},"args.level",[136,435,436],{"class":177},"}",[136,438,439],{"class":205},"\"",[136,441,247],{"class":153},[136,443,445,448,450,453,456,459,462,465,468,471,473,475,477,480,482],{"class":138,"line":444},21,[136,446,447],{"class":153},"    greeting ",[136,449,190],{"class":149},[136,451,452],{"class":205}," \"hi\"",[136,454,455],{"class":149}," if",[136,457,458],{"class":153}," args.level ",[136,460,461],{"class":149},"==",[136,463,464],{"class":205}," \"quiet\"",[136,466,467],{"class":149}," else",[136,469,470],{"class":205}," \"HELLO\"",[136,472,455],{"class":149},[136,474,458],{"class":153},[136,476,461],{"class":149},[136,478,479],{"class":205}," \"loud\"",[136,481,467],{"class":149},[136,483,484],{"class":205}," \"Hello\"\n",[136,486,488,491,494,497,500],{"class":138,"line":487},22,[136,489,490],{"class":149},"    for",[136,492,493],{"class":153}," _ ",[136,495,496],{"class":149},"in",[136,498,499],{"class":177}," range",[136,501,502],{"class":153},"(args.count):\n",[136,504,506,508,510,512,514,516,519,521,523,525,528,530,533],{"class":138,"line":505},23,[136,507,418],{"class":177},[136,509,421],{"class":153},[136,511,424],{"class":149},[136,513,439],{"class":205},[136,515,430],{"class":177},[136,517,518],{"class":153},"greeting",[136,520,436],{"class":177},[136,522,64],{"class":205},[136,524,430],{"class":177},[136,526,527],{"class":153},"args.name",[136,529,436],{"class":177},[136,531,532],{"class":205},"!\"",[136,534,247],{"class":153},[136,536,538],{"class":138,"line":537},24,[136,539,161],{"emptyLinePlaceholder":160},[136,541,543,546,549,552,555],{"class":138,"line":542},25,[136,544,545],{"class":149},"if",[136,547,548],{"class":177}," __name__",[136,550,551],{"class":149}," ==",[136,553,554],{"class":205}," \"__main__\"",[136,556,181],{"class":153},[136,558,560],{"class":138,"line":559},26,[136,561,562],{"class":153},"    main()\n",[27,564,566],{"id":565},"the-destination-the-same-cli-in-typer","The destination: the same CLI in Typer",[10,568,569,570,573],{},"The equivalent Typer program produces the same ",[14,571,572],{},"--help",", the same defaults, and the same\noutput, with the parser gone:",[127,575,577],{"className":129,"code":576,"language":131,"meta":132,"style":132},"# greet_typer.py\nfrom enum import Enum\nfrom typing import Annotated\nimport typer\n\nclass Level(str, Enum):\n    quiet = \"quiet\"\n    normal = \"normal\"\n    loud = \"loud\"\n\napp = typer.Typer(help=\"Greet someone a number of times.\")\n\n@app.command()\ndef greet(\n    name: Annotated[str, typer.Argument(help=\"Who to greet.\")],\n    count: Annotated[int, typer.Option(help=\"How many times.\")] = 1,\n    level: Annotated[Level, typer.Option(help=\"Greeting volume.\")] = Level.normal,\n    verbose: Annotated[bool, typer.Option(help=\"Explain what is happening.\")] = False,\n) -> None:\n    if verbose:\n        typer.echo(f\"[verbose] level={level.value}\")\n    greeting = {\"quiet\": \"hi\", \"normal\": \"Hello\", \"loud\": \"HELLO\"}[level.value]\n    for _ in range(count):\n        typer.echo(f\"{greeting}, {name}!\")\n\nif __name__ == \"__main__\":\n    app()\n",[14,578,579,584,597,609,616,620,640,650,660,670,674,692,696,704,714,733,760,778,802,811,818,838,876,889,916,920,932],{"__ignoreMap":132},[136,580,581],{"class":138,"line":139},[136,582,583],{"class":142},"# greet_typer.py\n",[136,585,586,589,592,594],{"class":138,"line":146},[136,587,588],{"class":149},"from",[136,590,591],{"class":153}," enum ",[136,593,150],{"class":149},[136,595,596],{"class":153}," Enum\n",[136,598,599,601,604,606],{"class":138,"line":157},[136,600,588],{"class":149},[136,602,603],{"class":153}," typing ",[136,605,150],{"class":149},[136,607,608],{"class":153}," Annotated\n",[136,610,611,613],{"class":138,"line":164},[136,612,150],{"class":149},[136,614,615],{"class":153}," typer\n",[136,617,618],{"class":138,"line":184},[136,619,161],{"emptyLinePlaceholder":160},[136,621,622,625,628,630,633,635,637],{"class":138,"line":196},[136,623,624],{"class":149},"class",[136,626,627],{"class":170}," Level",[136,629,421],{"class":153},[136,631,632],{"class":177},"str",[136,634,64],{"class":153},[136,636,73],{"class":170},[136,638,639],{"class":153},"):\n",[136,641,642,645,647],{"class":138,"line":222},[136,643,644],{"class":153},"    quiet ",[136,646,190],{"class":149},[136,648,649],{"class":205}," \"quiet\"\n",[136,651,652,655,657],{"class":138,"line":228},[136,653,654],{"class":153},"    normal ",[136,656,190],{"class":149},[136,658,659],{"class":205}," \"normal\"\n",[136,661,662,665,667],{"class":138,"line":250},[136,663,664],{"class":153},"    loud ",[136,666,190],{"class":149},[136,668,669],{"class":205}," \"loud\"\n",[136,671,672],{"class":138,"line":256},[136,673,161],{"emptyLinePlaceholder":160},[136,675,676,679,681,684,686,688,690],{"class":138,"line":293},[136,677,678],{"class":153},"app ",[136,680,190],{"class":149},[136,682,683],{"class":153}," typer.Typer(",[136,685,239],{"class":199},[136,687,190],{"class":149},[136,689,216],{"class":205},[136,691,247],{"class":153},[136,693,694],{"class":138,"line":298},[136,695,161],{"emptyLinePlaceholder":160},[136,697,698,701],{"class":138,"line":303},[136,699,700],{"class":170},"@app.command",[136,702,703],{"class":153},"()\n",[136,705,706,708,711],{"class":138,"line":343},[136,707,167],{"class":149},[136,709,710],{"class":170}," greet",[136,712,713],{"class":153},"(\n",[136,715,716,719,721,724,726,728,730],{"class":138,"line":356},[136,717,718],{"class":153},"    name: Annotated[",[136,720,632],{"class":177},[136,722,723],{"class":153},", typer.Argument(",[136,725,239],{"class":199},[136,727,190],{"class":149},[136,729,244],{"class":205},[136,731,732],{"class":153},")],\n",[136,734,735,738,740,743,745,747,750,753,755,758],{"class":138,"line":361},[136,736,737],{"class":153},"    count: Annotated[",[136,739,269],{"class":177},[136,741,742],{"class":153},", typer.Option(",[136,744,239],{"class":199},[136,746,190],{"class":149},[136,748,749],{"class":205},"\"How many times.\"",[136,751,752],{"class":153},")] ",[136,754,190],{"class":149},[136,756,757],{"class":177}," 1",[136,759,219],{"class":153},[136,761,762,765,767,769,771,773,775],{"class":138,"line":390},[136,763,764],{"class":153},"    level: Annotated[Level, typer.Option(",[136,766,239],{"class":199},[136,768,190],{"class":149},[136,770,351],{"class":205},[136,772,752],{"class":153},[136,774,190],{"class":149},[136,776,777],{"class":153}," Level.normal,\n",[136,779,780,783,785,787,789,791,793,795,797,800],{"class":138,"line":395},[136,781,782],{"class":153},"    verbose: Annotated[",[136,784,82],{"class":177},[136,786,742],{"class":153},[136,788,239],{"class":199},[136,790,190],{"class":149},[136,792,385],{"class":205},[136,794,752],{"class":153},[136,796,190],{"class":149},[136,798,799],{"class":177}," False",[136,801,219],{"class":153},[136,803,804,807,809],{"class":138,"line":406},[136,805,806],{"class":153},") -> ",[136,808,178],{"class":177},[136,810,181],{"class":153},[136,812,813,815],{"class":138,"line":415},[136,814,409],{"class":149},[136,816,817],{"class":153}," verbose:\n",[136,819,820,823,825,827,829,832,834,836],{"class":138,"line":444},[136,821,822],{"class":153},"        typer.echo(",[136,824,424],{"class":149},[136,826,427],{"class":205},[136,828,430],{"class":177},[136,830,831],{"class":153},"level.value",[136,833,436],{"class":177},[136,835,439],{"class":205},[136,837,247],{"class":153},[136,839,840,842,844,847,849,852,855,857,859,861,864,866,868,870,873],{"class":138,"line":487},[136,841,447],{"class":153},[136,843,190],{"class":149},[136,845,846],{"class":153}," {",[136,848,319],{"class":205},[136,850,851],{"class":153},": ",[136,853,854],{"class":205},"\"hi\"",[136,856,64],{"class":153},[136,858,324],{"class":205},[136,860,851],{"class":153},[136,862,863],{"class":205},"\"Hello\"",[136,865,64],{"class":153},[136,867,329],{"class":205},[136,869,851],{"class":153},[136,871,872],{"class":205},"\"HELLO\"",[136,874,875],{"class":153},"}[level.value]\n",[136,877,878,880,882,884,886],{"class":138,"line":505},[136,879,490],{"class":149},[136,881,493],{"class":153},[136,883,496],{"class":149},[136,885,499],{"class":177},[136,887,888],{"class":153},"(count):\n",[136,890,891,893,895,897,899,901,903,905,907,910,912,914],{"class":138,"line":537},[136,892,822],{"class":153},[136,894,424],{"class":149},[136,896,439],{"class":205},[136,898,430],{"class":177},[136,900,518],{"class":153},[136,902,436],{"class":177},[136,904,64],{"class":205},[136,906,430],{"class":177},[136,908,909],{"class":153},"name",[136,911,436],{"class":177},[136,913,532],{"class":205},[136,915,247],{"class":153},[136,917,918],{"class":138,"line":542},[136,919,161],{"emptyLinePlaceholder":160},[136,921,922,924,926,928,930],{"class":138,"line":559},[136,923,545],{"class":149},[136,925,548],{"class":177},[136,927,551],{"class":149},[136,929,554],{"class":205},[136,931,181],{"class":153},[136,933,935],{"class":138,"line":934},27,[136,936,937],{"class":153},"    app()\n",[127,939,943],{"className":940,"code":941,"language":942,"meta":132,"style":132},"language-bash shiki shiki-themes github-light github-dark","$ python greet_typer.py World --count 2 --level loud\nHELLO, World!\nHELLO, World!\n","bash",[14,944,945,971,979],{"__ignoreMap":132},[136,946,947,950,953,956,959,962,965,968],{"class":138,"line":139},[136,948,949],{"class":170},"$",[136,951,952],{"class":205}," python",[136,954,955],{"class":205}," greet_typer.py",[136,957,958],{"class":205}," World",[136,960,961],{"class":177}," --count",[136,963,964],{"class":177}," 2",[136,966,967],{"class":177}," --level",[136,969,970],{"class":205}," loud\n",[136,972,973,976],{"class":138,"line":146},[136,974,975],{"class":170},"HELLO,",[136,977,978],{"class":205}," World!\n",[136,980,981,983],{"class":138,"line":157},[136,982,975],{"class":170},[136,984,978],{"class":205},[10,986,987,988,991,992,994,995,991,997,1000,1001,1003,1004,1006,1007,1009,1010,1012,1013,1015],{},"The annotation ",[98,989,990],{},"is"," the ",[14,993,59],{},". The default value ",[98,996,990],{},[14,998,999],{},"default=",". The ",[14,1002,73],{}," ",[98,1005,990],{}," the\n",[14,1008,67],{},". Typer even validates the enum and lists the options in ",[14,1011,572],{},", exactly like\nargparse's ",[14,1014,311],{},". Roughly a third of the lines are gone, and everything left describes\nintent rather than plumbing.",[27,1017,1019],{"id":1018},"the-mapping-construct-by-construct","The mapping, construct by construct",[10,1021,1022],{},"Keep this table next to you while you translate. Almost every argparse pattern has a direct\nTyper equivalent.",[1024,1025,1026,1037],"table",{},[1027,1028,1029],"thead",{},[1030,1031,1032,1035],"tr",{},[1033,1034,16],"th",{},[1033,1036,24],{},[1038,1039,1040,1053,1071,1088,1107,1120,1136,1150,1163,1175,1193,1205,1219,1231,1243],"tbody",{},[1030,1041,1042,1048],{},[1043,1044,1045],"td",{},[14,1046,1047],{},"ArgumentParser(description=...)",[1043,1049,1050],{},[14,1051,1052],{},"typer.Typer(help=...)",[1030,1054,1055,1061],{},[1043,1056,1057,1060],{},[14,1058,1059],{},"add_argument(\"name\")"," (positional)",[1043,1062,1063,1064,1067,1068],{},"function parameter with ",[47,1065,1066],{},"no default"," → ",[14,1069,1070],{},"typer.Argument",[1030,1072,1073,1079],{},[1043,1074,1075,1078],{},[14,1076,1077],{},"add_argument(\"--opt\")"," (optional)",[1043,1080,1081,1082,1067,1085],{},"function parameter ",[47,1083,1084],{},"with a default",[14,1086,1087],{},"typer.Option",[1030,1089,1090,1099],{},[1043,1091,1092,1095,1096],{},[14,1093,1094],{},"type=int"," \u002F ",[14,1097,1098],{},"type=Path",[1043,1100,1101,1102,1095,1104],{},"annotation ",[14,1103,269],{},[14,1105,1106],{},"Path",[1030,1108,1109,1114],{},[1043,1110,1111],{},[14,1112,1113],{},"default=1",[1043,1115,1116,1119],{},[14,1117,1118],{},"= 1"," on the parameter",[1030,1121,1122,1127],{},[1043,1123,1124],{},[14,1125,1126],{},"choices=[...]",[1043,1128,1129,1130,1132,1133,1135],{},"a ",[14,1131,632],{},"-",[14,1134,73],{}," type",[1030,1137,1138,1142],{},[1043,1139,1140],{},[14,1141,76],{},[1043,1143,1144,1146,1147],{},[14,1145,82],{}," parameter defaulting to ",[14,1148,1149],{},"False",[1030,1151,1152,1157],{},[1043,1153,1154],{},[14,1155,1156],{},"nargs=\"+\"",[1043,1158,1159,1162],{},[14,1160,1161],{},"list[str]"," parameter",[1030,1164,1165,1170],{},[1043,1166,1167],{},[14,1168,1169],{},"action=\"append\"",[1043,1171,1172,1174],{},[14,1173,1161],{}," option (repeat the flag)",[1030,1176,1177,1182],{},[1043,1178,1179],{},[14,1180,1181],{},"help=\"...\"",[1043,1183,1184,1187,1188,1190,1191],{},[14,1185,1186],{},"help="," on ",[14,1189,1070],{},"\u002F",[14,1192,1087],{},[1030,1194,1195,1200],{},[1043,1196,1197],{},[14,1198,1199],{},"metavar=\"X\"",[1043,1201,1202,1204],{},[14,1203,1199],{}," on the annotation",[1030,1206,1207,1212],{},[1043,1208,1209,1078],{},[14,1210,1211],{},"required=True",[1043,1213,1214,1215,1218],{},"option with no default, or ",[14,1216,1217],{},"..."," as the default",[1030,1220,1221,1225],{},[1043,1222,1223],{},[14,1224,86],{},[1043,1226,1227,1228,1230],{},"one ",[14,1229,44],{}," per subcommand",[1030,1232,1233,1238],{},[1043,1234,1235],{},[14,1236,1237],{},"parser.error(\"msg\")",[1043,1239,1240],{},[14,1241,1242],{},"raise typer.BadParameter(\"msg\")",[1030,1244,1245,1251],{},[1043,1246,1247,1250],{},[14,1248,1249],{},"parse_args()"," + dispatch",[1043,1252,1253],{},[14,1254,1255],{},"app()",[10,1257,1258],{},"Two rules resolve most confusion:",[1260,1261,1262,1278],"ol",{},[35,1263,1264,1267,1268,1271,1272,1275,1276,93],{},[47,1265,1266],{},"Positional vs option is decided by the default."," A parameter with no default becomes a\nrequired positional argument; a parameter with a default becomes an option. Force the\nissue with ",[14,1269,1270],{},"typer.Argument(...)"," or ",[14,1273,1274],{},"typer.Option(...)"," when you want to override the\nname, help, or make an option required with ",[14,1277,1217],{},[35,1279,1280,1283,1284,1287,1288,1291],{},[47,1281,1282],{},"Hyphenation is automatic."," A parameter ",[14,1285,1286],{},"dest_dir"," becomes the ",[14,1289,1290],{},"--dest-dir"," option, the\nsame normalization argparse did in reverse.",[1293,1294,1296],"h3",{"id":1295},"choices-enum","choices → Enum",[10,1298,1299,1300,1302,1303,1305,1306,1308,1309,1311,1312,1315,1316,1319],{},"argparse ",[14,1301,311],{}," become a ",[14,1304,632],{}," subclass of ",[14,1307,73],{},". Subclassing ",[14,1310,632],{}," matters — it lets\nyou compare ",[14,1313,1314],{},"level == \"loud\""," and serialize cleanly, and Typer uses the ",[98,1317,1318],{},"values"," in help and\ncompletion.",[127,1321,1323],{"className":129,"code":1322,"language":131,"meta":132,"style":132},"class Level(str, Enum):\n    quiet = \"quiet\"\n    normal = \"normal\"\n    loud = \"loud\"\n",[14,1324,1325,1341,1349,1357],{"__ignoreMap":132},[136,1326,1327,1329,1331,1333,1335,1337,1339],{"class":138,"line":139},[136,1328,624],{"class":149},[136,1330,627],{"class":170},[136,1332,421],{"class":153},[136,1334,632],{"class":177},[136,1336,64],{"class":153},[136,1338,73],{"class":170},[136,1340,639],{"class":153},[136,1342,1343,1345,1347],{"class":138,"line":146},[136,1344,644],{"class":153},[136,1346,190],{"class":149},[136,1348,649],{"class":205},[136,1350,1351,1353,1355],{"class":138,"line":157},[136,1352,654],{"class":153},[136,1354,190],{"class":149},[136,1356,659],{"class":205},[136,1358,1359,1361,1363],{"class":138,"line":164},[136,1360,664],{"class":153},[136,1362,190],{"class":149},[136,1364,669],{"class":205},[1293,1366,1368],{"id":1367},"nargs-list","nargs → list",[10,1370,1371,1372,60,1374,1377],{},"A positional ",[14,1373,1156],{},[14,1375,1376],{},"list"," parameter. Typer collects the trailing values into\nthe list just as argparse did:",[127,1379,1381],{"className":129,"code":1380,"language":131,"meta":132,"style":132},"@app.command()\ndef process(paths: list[str]) -> None:\n    for path in paths:\n        typer.echo(path)\n",[14,1382,1383,1389,1408,1420],{"__ignoreMap":132},[136,1384,1385,1387],{"class":138,"line":139},[136,1386,700],{"class":170},[136,1388,703],{"class":153},[136,1390,1391,1393,1396,1399,1401,1404,1406],{"class":138,"line":146},[136,1392,167],{"class":149},[136,1394,1395],{"class":170}," process",[136,1397,1398],{"class":153},"(paths: list[",[136,1400,632],{"class":177},[136,1402,1403],{"class":153},"]) -> ",[136,1405,178],{"class":177},[136,1407,181],{"class":153},[136,1409,1410,1412,1415,1417],{"class":138,"line":157},[136,1411,490],{"class":149},[136,1413,1414],{"class":153}," path ",[136,1416,496],{"class":149},[136,1418,1419],{"class":153}," paths:\n",[136,1421,1422],{"class":138,"line":164},[136,1423,1424],{"class":153},"        typer.echo(path)\n",[1293,1426,1428],{"id":1427},"subparsers-commands","subparsers → commands",[10,1430,1431,1432,1435,1436,1439,1440,1444],{},"This is where Typer wins hardest. Every ",[14,1433,1434],{},"sub.add_parser(\"build\")"," becomes a decorated\nfunction; the dispatch you wrote by hand with ",[14,1437,1438],{},"set_defaults(func=...)"," disappears because\nTyper routes to the function whose name matches the subcommand. If you are moving a\nsubcommand-heavy CLI, read\n",[19,1441,1443],{"href":1442},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargparse-subparsers-for-subcommands\u002F","argparse subparsers for subcommands","\nfirst to see exactly what you are replacing.",[127,1446,1448],{"className":129,"code":1447,"language":131,"meta":132,"style":132},"app = typer.Typer()\n\n@app.command()\ndef build(release: bool = False) -> None:\n    typer.echo(f\"build release={release}\")\n\n@app.command()\ndef deploy(target: str) -> None:\n    typer.echo(f\"deploy to {target}\")\n",[14,1449,1450,1459,1463,1469,1492,1513,1517,1523,1541],{"__ignoreMap":132},[136,1451,1452,1454,1456],{"class":138,"line":139},[136,1453,678],{"class":153},[136,1455,190],{"class":149},[136,1457,1458],{"class":153}," typer.Typer()\n",[136,1460,1461],{"class":138,"line":146},[136,1462,161],{"emptyLinePlaceholder":160},[136,1464,1465,1467],{"class":138,"line":157},[136,1466,700],{"class":170},[136,1468,703],{"class":153},[136,1470,1471,1473,1476,1479,1481,1484,1486,1488,1490],{"class":138,"line":164},[136,1472,167],{"class":149},[136,1474,1475],{"class":170}," build",[136,1477,1478],{"class":153},"(release: ",[136,1480,82],{"class":177},[136,1482,1483],{"class":149}," =",[136,1485,799],{"class":177},[136,1487,806],{"class":153},[136,1489,178],{"class":177},[136,1491,181],{"class":153},[136,1493,1494,1497,1499,1502,1504,1507,1509,1511],{"class":138,"line":184},[136,1495,1496],{"class":153},"    typer.echo(",[136,1498,424],{"class":149},[136,1500,1501],{"class":205},"\"build release=",[136,1503,430],{"class":177},[136,1505,1506],{"class":153},"release",[136,1508,436],{"class":177},[136,1510,439],{"class":205},[136,1512,247],{"class":153},[136,1514,1515],{"class":138,"line":196},[136,1516,161],{"emptyLinePlaceholder":160},[136,1518,1519,1521],{"class":138,"line":222},[136,1520,700],{"class":170},[136,1522,703],{"class":153},[136,1524,1525,1527,1530,1533,1535,1537,1539],{"class":138,"line":228},[136,1526,167],{"class":149},[136,1528,1529],{"class":170}," deploy",[136,1531,1532],{"class":153},"(target: ",[136,1534,632],{"class":177},[136,1536,806],{"class":153},[136,1538,178],{"class":177},[136,1540,181],{"class":153},[136,1542,1543,1545,1547,1550,1552,1555,1557,1559],{"class":138,"line":250},[136,1544,1496],{"class":153},[136,1546,424],{"class":149},[136,1548,1549],{"class":205},"\"deploy to ",[136,1551,430],{"class":177},[136,1553,1554],{"class":153},"target",[136,1556,436],{"class":177},[136,1558,439],{"class":205},[136,1560,247],{"class":153},[10,1562,1563,1566,1567,1570],{},[14,1564,1565],{},"app build --release"," and ",[14,1568,1569],{},"app deploy prod"," now work with no dispatch code at all.",[27,1572,1574],{"id":1573},"preserving-help-text-and-defaults","Preserving help text and defaults",[10,1576,1577],{},"The most common regression in a migration is silently changed help or defaults. Guard\nagainst it:",[32,1579,1580,1590,1613,1621],{},[35,1581,1582,1583,1585,1586,1190,1588,93],{},"Copy every ",[14,1584,1186],{}," string verbatim into the matching ",[14,1587,1070],{},[14,1589,1087],{},[35,1591,1592,1593,1596,1597,1600,1601,1603,1604,1606,1607,1609,1610,93],{},"Keep the same default ",[98,1594,1595],{},"value and type",". ",[14,1598,1599],{},"--count"," defaulting to ",[14,1602,279],{}," (an ",[14,1605,269],{},") must stay\n",[14,1608,1118],{},", not ",[14,1611,1612],{},"= \"1\"",[35,1614,1615,1616,1618,1619,93],{},"Set the program help from ",[14,1617,1052],{}," (or the callback docstring) to replace\n",[14,1620,1047],{},[35,1622,1623,1624,93],{},"If you relied on argparse's exit code 2 for usage errors, note that Typer also exits\nnon-zero on bad input; align on a scheme deliberately using\n",[19,1625,1627],{"href":1626},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fchoosing-exit-codes-for-cli-tools\u002F","choosing exit codes for CLI tools",[10,1629,1630,1631,1634,1635,93],{},"For cross-cutting behaviour that used to live in the parser body — a ",[14,1632,1633],{},"--version"," flag,\nglobal setup that ran before every subcommand — use a Typer callback. That mechanism is\ncovered end to end in\n",[19,1636,1638],{"href":1637},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Ftyper-callback-functions-explained\u002F","Typer callback functions explained",[27,1640,1642],{"id":1641},"an-incremental-migration-strategy","An incremental migration strategy",[10,1644,1645],{},"You do not have to rewrite everything in one commit. Because Typer sits on Click, you can\nadopt it at the edges and work inward:",[1260,1647,1648,1654,1666,1672,1682],{},[35,1649,1650,1653],{},[47,1651,1652],{},"Freeze behaviour with tests first"," (next section). Do not touch the argparse code until\nthe tests pass against it.",[35,1655,1656,1659,1660,1663,1664,93],{},[47,1657,1658],{},"Wrap, then split."," Add Typer as a dependency and expose the new app as the entry point,\nwith your existing ",[14,1661,1662],{},"main()"," behind a single passthrough command. Ship that, then peel one\nsubcommand at a time into a real ",[14,1665,44],{},[35,1667,1668,1671],{},[47,1669,1670],{},"Move shared setup into a callback"," once more than one command needs it.",[35,1673,1674,1677,1678,1681],{},[47,1675,1676],{},"Delete the parser"," only when the last ",[14,1679,1680],{},"add_argument"," is gone.",[35,1683,1684,1687,1688,1691],{},[47,1685,1686],{},"Add completion last."," Typer gives you ",[14,1689,1690],{},"--install-completion"," for free; wire it up once\nthe command tree is stable.",[10,1693,1694],{},"At every step the CLI stays shippable, which matters when other people depend on it.",[27,1696,1698],{"id":1697},"testing-parity","Testing parity",[10,1700,1701,1702,1705],{},"The safety net for the whole migration is a test suite that pins observable behaviour, run\nagainst ",[98,1703,1704],{},"both"," implementations. Typer reuses Click's runner, so the tests barely change:",[127,1707,1709],{"className":129,"code":1708,"language":131,"meta":132,"style":132},"# test_greet.py\nfrom typer.testing import CliRunner\nfrom greet_typer import app\n\nrunner = CliRunner()\n\ndef test_default_greeting() -> None:\n    result = runner.invoke(app, [\"World\"])\n    assert result.exit_code == 0\n    assert result.output == \"Hello, World!\\n\"\n\ndef test_count_and_level() -> None:\n    result = runner.invoke(app, [\"World\", \"--count\", \"2\", \"--level\", \"loud\"])\n    assert result.exit_code == 0\n    assert result.output.count(\"HELLO, World!\") == 2\n\ndef test_invalid_choice_rejected() -> None:\n    result = runner.invoke(app, [\"World\", \"--level\", \"screaming\"])\n    assert result.exit_code != 0\n    assert \"screaming\" in result.output\n",[14,1710,1711,1716,1728,1740,1744,1754,1758,1771,1787,1800,1818,1822,1835,1866,1876,1894,1898,1911,1932,1943],{"__ignoreMap":132},[136,1712,1713],{"class":138,"line":139},[136,1714,1715],{"class":142},"# test_greet.py\n",[136,1717,1718,1720,1723,1725],{"class":138,"line":146},[136,1719,588],{"class":149},[136,1721,1722],{"class":153}," typer.testing ",[136,1724,150],{"class":149},[136,1726,1727],{"class":153}," CliRunner\n",[136,1729,1730,1732,1735,1737],{"class":138,"line":157},[136,1731,588],{"class":149},[136,1733,1734],{"class":153}," greet_typer ",[136,1736,150],{"class":149},[136,1738,1739],{"class":153}," app\n",[136,1741,1742],{"class":138,"line":164},[136,1743,161],{"emptyLinePlaceholder":160},[136,1745,1746,1749,1751],{"class":138,"line":184},[136,1747,1748],{"class":153},"runner ",[136,1750,190],{"class":149},[136,1752,1753],{"class":153}," CliRunner()\n",[136,1755,1756],{"class":138,"line":196},[136,1757,161],{"emptyLinePlaceholder":160},[136,1759,1760,1762,1765,1767,1769],{"class":138,"line":222},[136,1761,167],{"class":149},[136,1763,1764],{"class":170}," test_default_greeting",[136,1766,174],{"class":153},[136,1768,178],{"class":177},[136,1770,181],{"class":153},[136,1772,1773,1776,1778,1781,1784],{"class":138,"line":228},[136,1774,1775],{"class":153},"    result ",[136,1777,190],{"class":149},[136,1779,1780],{"class":153}," runner.invoke(app, [",[136,1782,1783],{"class":205},"\"World\"",[136,1785,1786],{"class":153},"])\n",[136,1788,1789,1792,1795,1797],{"class":138,"line":250},[136,1790,1791],{"class":149},"    assert",[136,1793,1794],{"class":153}," result.exit_code ",[136,1796,461],{"class":149},[136,1798,1799],{"class":177}," 0\n",[136,1801,1802,1804,1807,1809,1812,1815],{"class":138,"line":256},[136,1803,1791],{"class":149},[136,1805,1806],{"class":153}," result.output ",[136,1808,461],{"class":149},[136,1810,1811],{"class":205}," \"Hello, World!",[136,1813,1814],{"class":177},"\\n",[136,1816,1817],{"class":205},"\"\n",[136,1819,1820],{"class":138,"line":293},[136,1821,161],{"emptyLinePlaceholder":160},[136,1823,1824,1826,1829,1831,1833],{"class":138,"line":298},[136,1825,167],{"class":149},[136,1827,1828],{"class":170}," test_count_and_level",[136,1830,174],{"class":153},[136,1832,178],{"class":177},[136,1834,181],{"class":153},[136,1836,1837,1839,1841,1843,1845,1847,1850,1852,1855,1857,1860,1862,1864],{"class":138,"line":303},[136,1838,1775],{"class":153},[136,1840,190],{"class":149},[136,1842,1780],{"class":153},[136,1844,1783],{"class":205},[136,1846,64],{"class":153},[136,1848,1849],{"class":205},"\"--count\"",[136,1851,64],{"class":153},[136,1853,1854],{"class":205},"\"2\"",[136,1856,64],{"class":153},[136,1858,1859],{"class":205},"\"--level\"",[136,1861,64],{"class":153},[136,1863,329],{"class":205},[136,1865,1786],{"class":153},[136,1867,1868,1870,1872,1874],{"class":138,"line":343},[136,1869,1791],{"class":149},[136,1871,1794],{"class":153},[136,1873,461],{"class":149},[136,1875,1799],{"class":177},[136,1877,1878,1880,1883,1886,1889,1891],{"class":138,"line":356},[136,1879,1791],{"class":149},[136,1881,1882],{"class":153}," result.output.count(",[136,1884,1885],{"class":205},"\"HELLO, World!\"",[136,1887,1888],{"class":153},") ",[136,1890,461],{"class":149},[136,1892,1893],{"class":177}," 2\n",[136,1895,1896],{"class":138,"line":361},[136,1897,161],{"emptyLinePlaceholder":160},[136,1899,1900,1902,1905,1907,1909],{"class":138,"line":390},[136,1901,167],{"class":149},[136,1903,1904],{"class":170}," test_invalid_choice_rejected",[136,1906,174],{"class":153},[136,1908,178],{"class":177},[136,1910,181],{"class":153},[136,1912,1913,1915,1917,1919,1921,1923,1925,1927,1930],{"class":138,"line":395},[136,1914,1775],{"class":153},[136,1916,190],{"class":149},[136,1918,1780],{"class":153},[136,1920,1783],{"class":205},[136,1922,64],{"class":153},[136,1924,1859],{"class":205},[136,1926,64],{"class":153},[136,1928,1929],{"class":205},"\"screaming\"",[136,1931,1786],{"class":153},[136,1933,1934,1936,1938,1941],{"class":138,"line":406},[136,1935,1791],{"class":149},[136,1937,1794],{"class":153},[136,1939,1940],{"class":149},"!=",[136,1942,1799],{"class":177},[136,1944,1945,1947,1950,1953],{"class":138,"line":415},[136,1946,1791],{"class":149},[136,1948,1949],{"class":205}," \"screaming\"",[136,1951,1952],{"class":149}," in",[136,1954,1955],{"class":153}," result.output\n",[10,1957,1958,1959,1962],{},"Run the same assertions against the argparse version first (invoke it as a subprocess or\ncall ",[14,1960,1961],{},"parse_args"," directly), confirm they pass, then point the suite at the Typer app. Green\non both means the migration preserved behaviour — which is the entire goal.",[27,1964,1966],{"id":1965},"production-notes","Production notes",[32,1968,1969,1990,2010,2023,2037],{},[35,1970,1971,1977,1978,1981,1982,1985,1986,1989],{},[47,1972,1973,1976],{},[14,1974,1975],{},"Annotated"," is the modern style."," Older Typer code put ",[14,1979,1980],{},"typer.Option()"," in the default\nslot (",[14,1983,1984],{},"count: int = typer.Option(1)","). That still works, but ",[14,1987,1988],{},"Annotated[int, typer.Option()] = 1","\nkeeps the real default in the default position and plays nicely with type checkers and\nnon-Typer callers. Use it in new code.",[35,1991,1992,1995,1996,1132,1998,64,2000,2002,2003,2005,2006,2009],{},[47,1993,1994],{},"Enums serialize by value, not name."," With a ",[14,1997,632],{},[14,1999,73],{},[14,2001,831],{}," is ",[14,2004,329],{},".\nCompare and format against ",[14,2007,2008],{},".value"," to match your old string logic exactly.",[35,2011,2012,2017,2018,2020,2021,93],{},[47,2013,2014,2015,93],{},"Required options use ",[14,2016,1217],{}," To reproduce argparse's ",[14,2019,1211],{}," on an optional\nflag, give it no default or use ",[14,2022,1274],{},[35,2024,2025,2028,2029,2031,2032,2036],{},[47,2026,2027],{},"Keep parsing thin during the move."," If your argparse ",[14,2030,1662],{}," mixed parsing with logic,\nextract the logic into plain functions first — see\n",[19,2033,2035],{"href":2034},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002F","structuring multi-command Python CLIs",".\nA thin command body makes the framework swap almost mechanical.",[35,2038,2039,2042,2043,2045],{},[47,2040,2041],{},"Pin Typer ≥0.12"," for the ",[14,2044,1975],{},"-first API and current completion support.",[27,2047,2049],{"id":2048},"related","Related",[32,2051,2052,2059,2066,2070],{},[35,2053,2054,2055],{},"Up: ",[19,2056,2058],{"href":2057},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002F","Command-Line Parsing with argparse",[35,2060,2061,2062],{},"Sideways: ",[19,2063,2065],{"href":2064},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002F","Typer vs Click: when to use each",[35,2067,2061,2068],{},[19,2069,1638],{"href":1637},[35,2071,2072,2073],{},"Related: ",[19,2074,1443],{"href":1442},[2076,2077,2078],"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 .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 .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":132,"searchDepth":146,"depth":146,"links":2080},[2081,2082,2083,2084,2089,2090,2091,2092,2093],{"id":29,"depth":146,"text":30},{"id":121,"depth":146,"text":122},{"id":565,"depth":146,"text":566},{"id":1018,"depth":146,"text":1019,"children":2085},[2086,2087,2088],{"id":1295,"depth":157,"text":1296},{"id":1367,"depth":157,"text":1368},{"id":1427,"depth":157,"text":1428},{"id":1573,"depth":146,"text":1574},{"id":1641,"depth":146,"text":1642},{"id":1697,"depth":146,"text":1698},{"id":1965,"depth":146,"text":1966},{"id":2048,"depth":146,"text":2049},"2026-07-05","Move a Python CLI from argparse to Typer step by step: map arguments and subparsers to Typer commands, keep behaviour, and cut boilerplate.","intermediate",false,"md",{},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fmigrating-from-argparse-to-typer",{"title":5,"description":2095},"modern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fmigrating-from-argparse-to-typer\u002Findex",[16,2104,2105,2106,2107],"typer","cli","migration","structure","fHo_7fkTOzXVg4hqc_Bwpa5WwQlan29nf_GJ7jqAXLI",[2110,2113,2116,2119,2122,2125,2128,2131,2134,2137,2140,2143,2146,2149,2152,2155,2158,2161,2164,2166,2169,2172,2175,2178,2180,2181,2184,2187,2190,2193,2196,2199,2202,2205,2207,2210,2213,2216,2219,2222,2225,2228,2231,2234,2237,2240,2243,2246,2249,2252,2255],{"path":2111,"title":2112},"\u002Fabout","About Python CLI Toolcraft",{"path":2114,"title":2115},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies","Advanced Argument Validation Strategies",{"path":2117,"title":2118},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fparsing-nested-json-arguments-in-python-clis","Parsing Nested JSON Args in Python CLIs",{"path":2120,"title":2121},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fchoosing-exit-codes-for-cli-tools","Choosing Exit Codes for CLI Tools",{"path":2123,"title":2124},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Ffriendly-error-messages-and-tracebacks","Friendly Error Messages and Tracebacks",{"path":2126,"title":2127},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes","Error Handling and Exit Codes for CLIs",{"path":2129,"title":2130},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Fconfig-precedence-flags-env-files-defaults","Config Precedence: Flags, Env, Files, Defaults",{"path":2132,"title":2133},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars","Handling Config Files and Env Vars in CLIs",{"path":2135,"title":2136},"\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":2138,"title":2139},"\u002Fadvanced-input-parsing-user-experience","Advanced Input Parsing for Python CLIs",{"path":2141,"title":2142},"\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":2144,"title":2145},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich","Interactive Terminal UI with Rich",{"path":2147,"title":2148},"\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":2150,"title":2151},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis","Shell Completion for Python CLIs",{"path":2153,"title":2154},"\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":2156,"title":2157},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fadding-verbose-and-quiet-logging-flags","Adding Verbose and Quiet Logging Flags",{"path":2159,"title":2160},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps","Structured Logging for CLI Apps",{"path":2162,"title":2163},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fstructured-json-logging-in-python-clis","Structured JSON Logging in Python CLIs",{"path":1190,"title":2165},"Python CLI Toolcraft",{"path":2167,"title":2168},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading","CLI Startup Performance and Lazy Loading",{"path":2170,"title":2171},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Flazy-loading-subcommands-for-faster-startup","Lazy Loading Subcommands for Faster Startup",{"path":2173,"title":2174},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Fprofiling-python-cli-startup-time","Profiling Python CLI Startup Time",{"path":2176,"title":2177},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargparse-subparsers-for-subcommands","argparse Subparsers for Subcommands",{"path":2179,"title":2058},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse",{"path":2100,"title":5},{"path":2182,"title":2183},"\u002Fmodern-python-cli-frameworks-architecture","Python CLI Frameworks and Architecture",{"path":2185,"title":2186},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis","Plugin Architectures for Extensible CLIs",{"path":2188,"title":2189},"\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":2191,"title":2192},"\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":2194,"title":2195},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis","Structuring Multi-Command Python CLIs",{"path":2197,"title":2198},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fsharing-state-with-click-context-objects","Sharing State with Click Context Objects",{"path":2200,"title":2201},"\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":2203,"title":2204},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each","Typer vs Click: When to Use Each",{"path":2206,"title":1638},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Ftyper-callback-functions-explained",{"path":2208,"title":2209},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter","CLI Project Scaffolding with Cookiecutter",{"path":2211,"title":2212},"\u002Fproject-setup-dependency-management","Project Setup & Dependency Management",{"path":2214,"title":2215},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fautomating-changelogs-with-conventional-commits","Automating Changelogs with Conventional Commits",{"path":2217,"title":2218},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs","Managing CLI Versioning & Changelogs",{"path":2220,"title":2221},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fbuilding-wheels-and-sdists-for-python-clis","Building Wheels and sdists for Python CLIs",{"path":2223,"title":2224},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution","Packaging Python CLIs for Distribution",{"path":2226,"title":2227},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Finstalling-and-distributing-clis-with-pipx","Installing and Distributing CLIs with pipx",{"path":2229,"title":2230},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fpublishing-a-python-cli-to-pypi","Publishing a Python CLI to PyPI",{"path":2232,"title":2233},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development","Poetry Workflows for CLI Development",{"path":2235,"title":2236},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpoetry-entry-points-and-scripts-for-clis","Poetry Entry Points and Scripts for CLIs",{"path":2238,"title":2239},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects","Pre-commit Hooks for CLI Projects",{"path":2241,"title":2242},"\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":2244,"title":2245},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management","uv for Python CLI Dependency Management",{"path":2247,"title":2248},"\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":2250,"title":2251},"\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":2253,"title":2254},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices","Python CLI Env Isolation Best Practices",{"path":2256,"title":2257},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fmanaging-virtual-environments-for-cross-platform-clis","Managing Python CLI Virtual Environments",1783281867197]