[{"data":1,"prerenderedAt":1468},["ShallowReactive",2],{"page-\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fadding-examples-and-epilogs-to-help-output\u002F":3,"content-directory":1221},{"id":4,"title":5,"body":6,"date":1207,"description":1208,"difficulty":1209,"draft":1210,"extension":1211,"meta":1212,"navigation":107,"path":1213,"seo":1214,"stem":1215,"tags":1216,"updated":1207,"__hash__":1220},"content\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fadding-examples-and-epilogs-to-help-output\u002Findex.md","Adding Examples and Epilogs to Help Output",{"type":7,"value":8,"toc":1185},"minimark",[9,13,18,69,73,76,80,203,259,348,358,362,365,369,372,375,378,381,389,392,407,411,414,417,727,763,766,770,776,782,792,798,802,805,1022,1032,1040,1044,1047,1051,1056,1063,1067,1070,1074,1081,1085,1088,1092,1095,1099,1102,1106,1116,1120,1123,1127,1141,1145,1181],[10,11,12],"p",{},"An option list tells a reader what exists. It rarely tells them what to type. Two or three real\ninvocations in an epilog close that gap, and they are the part of a help screen people actually\ncopy.",[14,15,17],"h2",{"id":16},"tldr","TL;DR",[19,20,21,30,45,56,63],"ul",{},[22,23,24,25,29],"li",{},"Put examples in the ",[26,27,28],"strong",{},"epilog"," — the block after the option list.",[22,31,32,33,36,37,40,41,44],{},"Show the ",[26,34,35],{},"common case",", a ",[26,38,39],{},"piped"," invocation, and the ",[26,42,43],{},"non-interactive"," form.",[22,46,47,51,52,55],{},[48,49,50],"code",{},"argparse"," needs ",[48,53,54],{},"RawDescriptionHelpFormatter",", or the newlines collapse.",[22,57,58,59,62],{},"Keep the example strings in ",[26,60,61],{},"one module"," so the epilog, the docs and the tests share them.",[22,64,65,68],{},[26,66,67],{},"Run them in a test"," — an example that no longer works is worse than none.",[14,70,72],{"id":71},"prerequisites","Prerequisites",[10,74,75],{},"A CLI with at least one command worth demonstrating, and pytest if you want the examples verified.",[14,77,79],{"id":78},"where-the-epilog-goes","Where the epilog goes",[81,82,87],"pre",{"className":83,"code":84,"language":85,"meta":86,"style":86},"language-python shiki shiki-themes github-light github-dark","import typer\n\napp = typer.Typer(\n    help=\"Manage deployments and the data behind them.\",\n    epilog=(\n        \"Examples:\\n\"\n        \"  mytool sync .\u002Fdata --retries 5\\n\"\n        \"  mytool sync .\u002Fdata --dry-run | tee plan.txt\\n\"\n        \"  mytool db migrate --yes            # non-interactive, for CI\\n\"\n    ),\n)\n","python","",[48,88,89,102,109,121,137,148,161,171,181,191,197],{"__ignoreMap":86},[90,91,94,98],"span",{"class":92,"line":93},"line",1,[90,95,97],{"class":96},"szBVR","import",[90,99,101],{"class":100},"sVt8B"," typer\n",[90,103,105],{"class":92,"line":104},2,[90,106,108],{"emptyLinePlaceholder":107},true,"\n",[90,110,112,115,118],{"class":92,"line":111},3,[90,113,114],{"class":100},"app ",[90,116,117],{"class":96},"=",[90,119,120],{"class":100}," typer.Typer(\n",[90,122,124,128,130,134],{"class":92,"line":123},4,[90,125,127],{"class":126},"s4XuR","    help",[90,129,117],{"class":96},[90,131,133],{"class":132},"sZZnC","\"Manage deployments and the data behind them.\"",[90,135,136],{"class":100},",\n",[90,138,140,143,145],{"class":92,"line":139},5,[90,141,142],{"class":126},"    epilog",[90,144,117],{"class":96},[90,146,147],{"class":100},"(\n",[90,149,151,154,158],{"class":92,"line":150},6,[90,152,153],{"class":132},"        \"Examples:",[90,155,157],{"class":156},"sj4cs","\\n",[90,159,160],{"class":132},"\"\n",[90,162,164,167,169],{"class":92,"line":163},7,[90,165,166],{"class":132},"        \"  mytool sync .\u002Fdata --retries 5",[90,168,157],{"class":156},[90,170,160],{"class":132},[90,172,174,177,179],{"class":92,"line":173},8,[90,175,176],{"class":132},"        \"  mytool sync .\u002Fdata --dry-run | tee plan.txt",[90,178,157],{"class":156},[90,180,160],{"class":132},[90,182,184,187,189],{"class":92,"line":183},9,[90,185,186],{"class":132},"        \"  mytool db migrate --yes            # non-interactive, for CI",[90,188,157],{"class":156},[90,190,160],{"class":132},[90,192,194],{"class":92,"line":193},10,[90,195,196],{"class":100},"    ),\n",[90,198,200],{"class":92,"line":199},11,[90,201,202],{"class":100},")\n",[81,204,206],{"className":83,"code":205,"language":85,"meta":86,"style":86},"@click.command(epilog=\"Examples:\\n\\n  mytool sync .\u002Fdata --retries 5\\n\")\ndef sync() -> None:\n    ...\n",[48,207,208,237,254],{"__ignoreMap":86},[90,209,210,214,217,219,221,224,227,230,232,235],{"class":92,"line":93},[90,211,213],{"class":212},"sScJk","@click.command",[90,215,216],{"class":100},"(",[90,218,28],{"class":126},[90,220,117],{"class":96},[90,222,223],{"class":132},"\"Examples:",[90,225,226],{"class":156},"\\n\\n",[90,228,229],{"class":132},"  mytool sync .\u002Fdata --retries 5",[90,231,157],{"class":156},[90,233,234],{"class":132},"\"",[90,236,202],{"class":100},[90,238,239,242,245,248,251],{"class":92,"line":104},[90,240,241],{"class":96},"def",[90,243,244],{"class":212}," sync",[90,246,247],{"class":100},"() -> ",[90,249,250],{"class":156},"None",[90,252,253],{"class":100},":\n",[90,255,256],{"class":92,"line":111},[90,257,258],{"class":156},"    ...\n",[81,260,262],{"className":83,"code":261,"language":85,"meta":86,"style":86},"parser = argparse.ArgumentParser(\n    prog=\"mytool\",\n    description=\"Manage deployments and the data behind them.\",\n    epilog=(\n        \"examples:\\n\"\n        \"  mytool sync .\u002Fdata --retries 5\\n\"\n        \"  mytool sync .\u002Fdata --dry-run | tee plan.txt\\n\"\n    ),\n    formatter_class=argparse.RawDescriptionHelpFormatter,\n)\n",[48,263,264,274,286,297,305,314,322,330,334,344],{"__ignoreMap":86},[90,265,266,269,271],{"class":92,"line":93},[90,267,268],{"class":100},"parser ",[90,270,117],{"class":96},[90,272,273],{"class":100}," argparse.ArgumentParser(\n",[90,275,276,279,281,284],{"class":92,"line":104},[90,277,278],{"class":126},"    prog",[90,280,117],{"class":96},[90,282,283],{"class":132},"\"mytool\"",[90,285,136],{"class":100},[90,287,288,291,293,295],{"class":92,"line":111},[90,289,290],{"class":126},"    description",[90,292,117],{"class":96},[90,294,133],{"class":132},[90,296,136],{"class":100},[90,298,299,301,303],{"class":92,"line":123},[90,300,142],{"class":126},[90,302,117],{"class":96},[90,304,147],{"class":100},[90,306,307,310,312],{"class":92,"line":139},[90,308,309],{"class":132},"        \"examples:",[90,311,157],{"class":156},[90,313,160],{"class":132},[90,315,316,318,320],{"class":92,"line":150},[90,317,166],{"class":132},[90,319,157],{"class":156},[90,321,160],{"class":132},[90,323,324,326,328],{"class":92,"line":163},[90,325,176],{"class":132},[90,327,157],{"class":156},[90,329,160],{"class":132},[90,331,332],{"class":92,"line":173},[90,333,196],{"class":100},[90,335,336,339,341],{"class":92,"line":183},[90,337,338],{"class":126},"    formatter_class",[90,340,117],{"class":96},[90,342,343],{"class":100},"argparse.RawDescriptionHelpFormatter,\n",[90,345,346],{"class":92,"line":193},[90,347,202],{"class":100},[10,349,350,351,353,354,357],{},"The ",[48,352,50],{}," formatter is the detail that trips people up: without it the epilog is re-wrapped as\na paragraph and your carefully aligned commands become one run-on line. Typer preserves newlines by\ndefault when Rich formatting is on; with ",[48,355,356],{},"rich_markup_mode=None"," it behaves like plain Click.",[359,360],"inline-diagram",{"name":361},"epilog-placement-levels",[10,363,364],{},"Epilogs work at every level. The root epilog should show a couple of representative invocations\nacross the tool; a command's own epilog shows that command's real uses. Put them where the user\nwill be when the question arises.",[14,366,368],{"id":367},"choosing-which-examples-to-show","Choosing which examples to show",[359,370],{"name":371},"example-quality-checklist",[10,373,374],{},"Three or four examples is the useful range. Past that the epilog becomes documentation people\nscroll past, and the marginal example is answering a question nobody asked.",[359,376],{"name":377},"epilog-examples-matrix",[10,379,380],{},"The set that covers most needs:",[81,382,387],{"className":383,"code":385,"language":386},[384],"language-text","Examples:\n  mytool sync .\u002Fdata                     # the common case\n  mytool sync .\u002Fdata --dry-run           # see what would change first\n  mytool status --json | jq '.[] | select(.healthy == false)'\n  mytool db migrate --yes                # non-interactive, for CI\n","text",[48,388,385],{"__ignoreMap":86},[10,390,391],{},"Each is doing a different job. The first answers \"what do I type\". The second teaches the safe\nhabit before a destructive command. The third demonstrates that stdout is data — which is how\npeople discover your tool composes. The fourth shows the automation path exists, which is the\nquestion every CI author has.",[10,393,394,395,398,399,402,403,406],{},"Use realistic values. ",[48,396,397],{},"mytool sync .\u002Fdata"," reads as a real command; ",[48,400,401],{},"mytool sync \u003CSOURCE>"," is the\nusage line again, and ",[48,404,405],{},"mytool sync FOO"," teaches nothing.",[14,408,410],{"id":409},"keeping-examples-in-one-place","Keeping examples in one place",[359,412],{"name":413},"example-placement-flow",[10,415,416],{},"The same examples belong in the epilog, in the generated documentation and often in the README.\nCopying them into three files guarantees they diverge, so keep the strings in one module:",[81,418,420],{"className":83,"code":419,"language":85,"meta":86,"style":86},"# src\u002Fmytool\u002Fexamples.py\nfrom dataclasses import dataclass\n\n@dataclass(frozen=True)\nclass Example:\n    command: str\n    comment: str = \"\"\n\nROOT_EXAMPLES = (\n    Example(\"mytool sync .\u002Fdata\", \"upload changed files\"),\n    Example(\"mytool sync .\u002Fdata --dry-run\", \"show what would change\"),\n    Example(\"mytool status --json\", \"machine-readable status\"),\n)\n\ndef render_epilog(examples: tuple[Example, ...]) -> str:\n    width = max(len(e.command) for e in examples)\n    lines = \"\\n\".join(\n        f\"  {e.command.ljust(width)}   # {e.comment}\" if e.comment else f\"  {e.command}\"\n        for e in examples\n    )\n    return f\"Examples:\\n{lines}\\n\"\n",[48,421,422,428,441,445,462,472,480,494,498,508,525,539,554,559,564,585,616,634,687,700,706],{"__ignoreMap":86},[90,423,424],{"class":92,"line":93},[90,425,427],{"class":426},"sJ8bj","# src\u002Fmytool\u002Fexamples.py\n",[90,429,430,433,436,438],{"class":92,"line":104},[90,431,432],{"class":96},"from",[90,434,435],{"class":100}," dataclasses ",[90,437,97],{"class":96},[90,439,440],{"class":100}," dataclass\n",[90,442,443],{"class":92,"line":111},[90,444,108],{"emptyLinePlaceholder":107},[90,446,447,450,452,455,457,460],{"class":92,"line":123},[90,448,449],{"class":212},"@dataclass",[90,451,216],{"class":100},[90,453,454],{"class":126},"frozen",[90,456,117],{"class":96},[90,458,459],{"class":156},"True",[90,461,202],{"class":100},[90,463,464,467,470],{"class":92,"line":139},[90,465,466],{"class":96},"class",[90,468,469],{"class":212}," Example",[90,471,253],{"class":100},[90,473,474,477],{"class":92,"line":150},[90,475,476],{"class":100},"    command: ",[90,478,479],{"class":156},"str\n",[90,481,482,485,488,491],{"class":92,"line":163},[90,483,484],{"class":100},"    comment: ",[90,486,487],{"class":156},"str",[90,489,490],{"class":96}," =",[90,492,493],{"class":132}," \"\"\n",[90,495,496],{"class":92,"line":173},[90,497,108],{"emptyLinePlaceholder":107},[90,499,500,503,505],{"class":92,"line":183},[90,501,502],{"class":156},"ROOT_EXAMPLES",[90,504,490],{"class":96},[90,506,507],{"class":100}," (\n",[90,509,510,513,516,519,522],{"class":92,"line":193},[90,511,512],{"class":100},"    Example(",[90,514,515],{"class":132},"\"mytool sync .\u002Fdata\"",[90,517,518],{"class":100},", ",[90,520,521],{"class":132},"\"upload changed files\"",[90,523,524],{"class":100},"),\n",[90,526,527,529,532,534,537],{"class":92,"line":199},[90,528,512],{"class":100},[90,530,531],{"class":132},"\"mytool sync .\u002Fdata --dry-run\"",[90,533,518],{"class":100},[90,535,536],{"class":132},"\"show what would change\"",[90,538,524],{"class":100},[90,540,542,544,547,549,552],{"class":92,"line":541},12,[90,543,512],{"class":100},[90,545,546],{"class":132},"\"mytool status --json\"",[90,548,518],{"class":100},[90,550,551],{"class":132},"\"machine-readable status\"",[90,553,524],{"class":100},[90,555,557],{"class":92,"line":556},13,[90,558,202],{"class":100},[90,560,562],{"class":92,"line":561},14,[90,563,108],{"emptyLinePlaceholder":107},[90,565,567,569,572,575,578,581,583],{"class":92,"line":566},15,[90,568,241],{"class":96},[90,570,571],{"class":212}," render_epilog",[90,573,574],{"class":100},"(examples: tuple[Example, ",[90,576,577],{"class":156},"...",[90,579,580],{"class":100},"]) -> ",[90,582,487],{"class":156},[90,584,253],{"class":100},[90,586,588,591,593,596,598,601,604,607,610,613],{"class":92,"line":587},16,[90,589,590],{"class":100},"    width ",[90,592,117],{"class":96},[90,594,595],{"class":156}," max",[90,597,216],{"class":100},[90,599,600],{"class":156},"len",[90,602,603],{"class":100},"(e.command) ",[90,605,606],{"class":96},"for",[90,608,609],{"class":100}," e ",[90,611,612],{"class":96},"in",[90,614,615],{"class":100}," examples)\n",[90,617,619,622,624,627,629,631],{"class":92,"line":618},17,[90,620,621],{"class":100},"    lines ",[90,623,117],{"class":96},[90,625,626],{"class":132}," \"",[90,628,157],{"class":156},[90,630,234],{"class":132},[90,632,633],{"class":100},".join(\n",[90,635,637,640,643,646,649,652,655,657,660,662,664,667,670,673,676,678,680,683,685],{"class":92,"line":636},18,[90,638,639],{"class":96},"        f",[90,641,642],{"class":132},"\"  ",[90,644,645],{"class":156},"{",[90,647,648],{"class":100},"e.command.ljust(width)",[90,650,651],{"class":156},"}",[90,653,654],{"class":132},"   # ",[90,656,645],{"class":156},[90,658,659],{"class":100},"e.comment",[90,661,651],{"class":156},[90,663,234],{"class":132},[90,665,666],{"class":96}," if",[90,668,669],{"class":100}," e.comment ",[90,671,672],{"class":96},"else",[90,674,675],{"class":96}," f",[90,677,642],{"class":132},[90,679,645],{"class":156},[90,681,682],{"class":100},"e.command",[90,684,651],{"class":156},[90,686,160],{"class":132},[90,688,690,693,695,697],{"class":92,"line":689},19,[90,691,692],{"class":96},"        for",[90,694,609],{"class":100},[90,696,612],{"class":96},[90,698,699],{"class":100}," examples\n",[90,701,703],{"class":92,"line":702},20,[90,704,705],{"class":100},"    )\n",[90,707,709,712,714,716,719,722,725],{"class":92,"line":708},21,[90,710,711],{"class":96},"    return",[90,713,675],{"class":96},[90,715,223],{"class":132},[90,717,718],{"class":156},"\\n{",[90,720,721],{"class":100},"lines",[90,723,724],{"class":156},"}\\n",[90,726,160],{"class":132},[81,728,730],{"className":83,"code":729,"language":85,"meta":86,"style":86},"app = typer.Typer(help=\"…\", epilog=render_epilog(ROOT_EXAMPLES))\n",[48,731,732],{"__ignoreMap":86},[90,733,734,736,738,741,744,746,749,751,753,755,758,760],{"class":92,"line":93},[90,735,114],{"class":100},[90,737,117],{"class":96},[90,739,740],{"class":100}," typer.Typer(",[90,742,743],{"class":126},"help",[90,745,117],{"class":96},[90,747,748],{"class":132},"\"…\"",[90,750,518],{"class":100},[90,752,28],{"class":126},[90,754,117],{"class":96},[90,756,757],{"class":100},"render_epilog(",[90,759,502],{"class":156},[90,761,762],{"class":100},"))\n",[10,764,765],{},"Now the alignment is computed rather than maintained by hand, the documentation generator can\nimport the same tuple, and — most importantly — a test can run them.",[14,767,769],{"id":768},"ux-considerations","UX considerations",[10,771,772,775],{},[26,773,774],{},"Do not paste output."," An example is a command, not a transcript. Including twenty lines of\noutput for each one fills the screen and dates quickly; if the output is the point, that belongs in\ndocumentation with room for it.",[10,777,778,781],{},[26,779,780],{},"Comment sparingly and in the same column."," One short comment per line, aligned, reads as a\ntable. Comments of varying length at varying positions read as noise.",[10,783,784,787,788,791],{},[26,785,786],{},"Show flags that make the example safe."," ",[48,789,790],{},"--dry-run"," in an example next to a destructive command\nteaches a habit at the exact moment it matters. This is the highest-value line in most epilogs.",[10,793,794,797],{},[26,795,796],{},"Mind the width."," An example longer than about 76 characters wraps in a narrow terminal and loses\nits shape. If a realistic invocation is genuinely that long, that is worth noticing — it may mean\nthe command needs better defaults or a config file.",[14,799,801],{"id":800},"testing-the-behaviour","Testing the behaviour",[10,803,804],{},"Examples decay silently: a flag is renamed, an argument becomes required, and the epilog still\nshows the old form. Running them in a test removes the possibility.",[81,806,808],{"className":83,"code":807,"language":85,"meta":86,"style":86},"import shlex\nimport pytest\nfrom typer.testing import CliRunner\n\nfrom mytool.cli import app\nfrom mytool.examples import ROOT_EXAMPLES\n\nrunner = CliRunner()\n\n@pytest.mark.parametrize(\"example\", ROOT_EXAMPLES, ids=lambda e: e.command)\ndef test_documented_examples_still_parse(example, tmp_path, monkeypatch):\n    monkeypatch.chdir(tmp_path)\n    (tmp_path \u002F \"data\").mkdir()\n\n    argv = shlex.split(example.command)[1:]          # drop the program name\n    result = runner.invoke(app, [*argv, \"--dry-run\"] if \"--dry-run\" not in argv else argv)\n\n    assert result.exit_code == 0, result.output\n",[48,809,810,817,824,836,840,852,864,868,878,882,907,917,922,936,940,959,1001,1005],{"__ignoreMap":86},[90,811,812,814],{"class":92,"line":93},[90,813,97],{"class":96},[90,815,816],{"class":100}," shlex\n",[90,818,819,821],{"class":92,"line":104},[90,820,97],{"class":96},[90,822,823],{"class":100}," pytest\n",[90,825,826,828,831,833],{"class":92,"line":111},[90,827,432],{"class":96},[90,829,830],{"class":100}," typer.testing ",[90,832,97],{"class":96},[90,834,835],{"class":100}," CliRunner\n",[90,837,838],{"class":92,"line":123},[90,839,108],{"emptyLinePlaceholder":107},[90,841,842,844,847,849],{"class":92,"line":139},[90,843,432],{"class":96},[90,845,846],{"class":100}," mytool.cli ",[90,848,97],{"class":96},[90,850,851],{"class":100}," app\n",[90,853,854,856,859,861],{"class":92,"line":150},[90,855,432],{"class":96},[90,857,858],{"class":100}," mytool.examples ",[90,860,97],{"class":96},[90,862,863],{"class":156}," ROOT_EXAMPLES\n",[90,865,866],{"class":92,"line":163},[90,867,108],{"emptyLinePlaceholder":107},[90,869,870,873,875],{"class":92,"line":173},[90,871,872],{"class":100},"runner ",[90,874,117],{"class":96},[90,876,877],{"class":100}," CliRunner()\n",[90,879,880],{"class":92,"line":183},[90,881,108],{"emptyLinePlaceholder":107},[90,883,884,887,889,892,894,896,898,901,904],{"class":92,"line":193},[90,885,886],{"class":212},"@pytest.mark.parametrize",[90,888,216],{"class":100},[90,890,891],{"class":132},"\"example\"",[90,893,518],{"class":100},[90,895,502],{"class":156},[90,897,518],{"class":100},[90,899,900],{"class":126},"ids",[90,902,903],{"class":96},"=lambda",[90,905,906],{"class":100}," e: e.command)\n",[90,908,909,911,914],{"class":92,"line":199},[90,910,241],{"class":96},[90,912,913],{"class":212}," test_documented_examples_still_parse",[90,915,916],{"class":100},"(example, tmp_path, monkeypatch):\n",[90,918,919],{"class":92,"line":541},[90,920,921],{"class":100},"    monkeypatch.chdir(tmp_path)\n",[90,923,924,927,930,933],{"class":92,"line":556},[90,925,926],{"class":100},"    (tmp_path ",[90,928,929],{"class":96},"\u002F",[90,931,932],{"class":132}," \"data\"",[90,934,935],{"class":100},").mkdir()\n",[90,937,938],{"class":92,"line":561},[90,939,108],{"emptyLinePlaceholder":107},[90,941,942,945,947,950,953,956],{"class":92,"line":566},[90,943,944],{"class":100},"    argv ",[90,946,117],{"class":96},[90,948,949],{"class":100}," shlex.split(example.command)[",[90,951,952],{"class":156},"1",[90,954,955],{"class":100},":]          ",[90,957,958],{"class":426},"# drop the program name\n",[90,960,961,964,966,969,972,975,978,981,984,987,990,993,996,998],{"class":92,"line":587},[90,962,963],{"class":100},"    result ",[90,965,117],{"class":96},[90,967,968],{"class":100}," runner.invoke(app, [",[90,970,971],{"class":96},"*",[90,973,974],{"class":100},"argv, ",[90,976,977],{"class":132},"\"--dry-run\"",[90,979,980],{"class":100},"] ",[90,982,983],{"class":96},"if",[90,985,986],{"class":132}," \"--dry-run\"",[90,988,989],{"class":96}," not",[90,991,992],{"class":96}," in",[90,994,995],{"class":100}," argv ",[90,997,672],{"class":96},[90,999,1000],{"class":100}," argv)\n",[90,1002,1003],{"class":92,"line":618},[90,1004,108],{"emptyLinePlaceholder":107},[90,1006,1007,1010,1013,1016,1019],{"class":92,"line":636},[90,1008,1009],{"class":96},"    assert",[90,1011,1012],{"class":100}," result.exit_code ",[90,1014,1015],{"class":96},"==",[90,1017,1018],{"class":156}," 0",[90,1020,1021],{"class":100},", result.output\n",[10,1023,1024,1025,1027,1028,1031],{},"Two notes on that test. It forces ",[48,1026,790],{}," where the example does not already have it, so\nrunning the suite cannot upload anything. And it uses ",[48,1029,1030],{},"shlex.split",", which handles the quoting in\nan example the way a shell would — a small detail that stops the test disagreeing with reality.",[10,1033,1034,1035,1039],{},"For examples that genuinely cannot run in a test (they need a network, or a real bucket), assert at\nleast that they ",[1036,1037,1038],"em",{},"parse",": invoke with a mocked core and check for exit code 0 rather than 2.",[14,1041,1043],{"id":1042},"conclusion","Conclusion",[10,1045,1046],{},"An epilog is three or four lines of plain text that answer the question an option list cannot.\nKeep the examples realistic, keep them in one module, render the epilog from that module, and let\na test run them. The cost is an afternoon once; the benefit is that the first thing a new user\ncopies actually works.",[14,1048,1050],{"id":1049},"frequently-asked-questions","Frequently asked questions",[1052,1053,1055],"h3",{"id":1054},"should-every-command-have-an-epilog","Should every command have an epilog?",[10,1057,1058,1059,1062],{},"No — only the ones where the invocation is not obvious from the option list. A ",[48,1060,1061],{},"mytool doctor"," with\nno arguments needs none. A command with three interacting flags, or one whose output is meant to\nbe piped, benefits enormously.",[1052,1064,1066],{"id":1065},"where-should-examples-live-if-my-help-is-rich-formatted","Where should examples live if my help is Rich-formatted?",[10,1068,1069],{},"The same place. Typer renders the epilog inside its help panel and preserves the line breaks. If\nyou want emphasis, Rich markup works there too — but plain text is more portable, since the same\nstring ends up in generated documentation and man pages.",[1052,1071,1073],{"id":1072},"how-do-i-show-an-example-that-needs-a-config-file","How do I show an example that needs a config file?",[10,1075,1076,1077,1080],{},"Show the command and mention the prerequisite in a comment: ",[48,1078,1079],{},"mytool deploy --env prod   # needs a configured remote",". What to avoid is an example that silently assumes state, because the reader\ntries it, gets an error, and concludes the documentation is wrong.",[1052,1082,1084],{"id":1083},"can-i-generate-examples-from-tests-instead","Can I generate examples from tests instead?",[10,1086,1087],{},"That inversion works nicely: mark certain tests as documentation examples and render the epilog\nfrom their invocations. It guarantees the examples run, at the cost of tests that are now shaped by\ndocumentation needs. Keeping a shared module and testing it is the simpler arrangement.",[1052,1089,1091],{"id":1090},"do-examples-belong-in-the-man-page-too","Do examples belong in the man page too?",[10,1093,1094],{},"Yes — EXAMPLES is a conventional man page section, and readers look for it. If you generate the\nman page from your command tree, the epilog usually becomes that section automatically, which is\none more reason to keep the strings in a module both can import.",[1052,1096,1098],{"id":1097},"how-many-examples-is-too-many","How many examples is too many?",[10,1100,1101],{},"Past four, most readers stop scanning — and the fifth example is usually a variation rather than a\nnew idea. If you genuinely have more worth showing, that is documentation: link to a page from the\nepilog rather than growing it.",[1052,1103,1105],{"id":1104},"should-examples-use-long-or-short-flags","Should examples use long or short flags?",[10,1107,1108,1109,1111,1112,1115],{},"Long, in documentation and examples. ",[48,1110,790],{}," is self-explanatory to someone reading a script\nsix months later, where ",[48,1113,1114],{},"-n"," is a lookup. Short flags are for typing, long flags are for reading,\nand an example is something people read before they type it.",[1052,1117,1119],{"id":1118},"can-the-epilog-show-a-shell-function-or-alias","Can the epilog show a shell function or alias?",[10,1121,1122],{},"It can, and it is often the most useful line for a tool people run constantly — a two-line function\nwrapping a common invocation saves more typing than any flag. Keep it to one short definition, and\nmake sure it is valid in both bash and zsh, since a snippet that only works in one is a support\nquestion rather than a shortcut.",[1052,1124,1126],{"id":1125},"what-if-my-examples-need-a-fixture-directory","What if my examples need a fixture directory?",[10,1128,1129,1130,518,1133,1136,1137,1140],{},"Refer to something the reader plausibly has (",[48,1131,1132],{},".\u002Fdata",[48,1134,1135],{},".\u002Fconfig.toml",") rather than to a fixture\nthat only exists in your repository. In the test, create it in ",[48,1138,1139],{},"tmp_path"," first — the example stays\nrealistic for readers and runnable for CI, which is the whole reason to keep the strings in one\nmodule.",[14,1142,1144],{"id":1143},"related","Related",[19,1146,1147,1155,1162,1168,1175],{},[22,1148,1149,1150],{},"Up: ",[1151,1152,1154],"a",{"href":1153},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002F","CLI help output and documentation",[22,1156,1157,1158],{},"Sideways: ",[1151,1159,1161],{"href":1160},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fwriting-help-text-users-actually-read\u002F","Writing help text users actually read",[22,1163,1157,1164],{},[1151,1165,1167],{"href":1166},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fgenerating-man-pages-and-docs-from-a-cli\u002F","Generating man pages and docs from a CLI",[22,1169,1170,1171],{},"Related: ",[1151,1172,1174],{"href":1173},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fsnapshot-testing-cli-output\u002F","Snapshot testing CLI output",[22,1176,1170,1177],{},[1151,1178,1180],{"href":1179},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Femitting-json-output-for-scripting\u002F","Emitting JSON output for scripting",[1182,1183,1184],"style",{},"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 .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 pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}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 .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":86,"searchDepth":104,"depth":104,"links":1186},[1187,1188,1189,1190,1191,1192,1193,1194,1195,1206],{"id":16,"depth":104,"text":17},{"id":71,"depth":104,"text":72},{"id":78,"depth":104,"text":79},{"id":367,"depth":104,"text":368},{"id":409,"depth":104,"text":410},{"id":768,"depth":104,"text":769},{"id":800,"depth":104,"text":801},{"id":1042,"depth":104,"text":1043},{"id":1049,"depth":104,"text":1050,"children":1196},[1197,1198,1199,1200,1201,1202,1203,1204,1205],{"id":1054,"depth":111,"text":1055},{"id":1065,"depth":111,"text":1066},{"id":1072,"depth":111,"text":1073},{"id":1083,"depth":111,"text":1084},{"id":1090,"depth":111,"text":1091},{"id":1097,"depth":111,"text":1098},{"id":1104,"depth":111,"text":1105},{"id":1118,"depth":111,"text":1119},{"id":1125,"depth":111,"text":1126},{"id":1143,"depth":104,"text":1144},"2026-08-01","Put runnable examples in CLI help - epilogs in Typer, Click and argparse, choosing which invocations to show, and testing that they still work.","beginner",false,"md",{},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fadding-examples-and-epilogs-to-help-output",{"title":5,"description":1208},"advanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fadding-examples-and-epilogs-to-help-output\u002Findex",[743,1217,1218,1219,28],"examples","documentation","ux","SO8GcSySPKLihfNWT5SiXsJo8ix8AvQUFykcfDikljs",[1222,1225,1228,1231,1234,1235,1238,1241,1244,1247,1250,1253,1256,1259,1262,1265,1268,1271,1274,1277,1280,1283,1286,1289,1292,1295,1298,1301,1304,1307,1310,1313,1315,1318,1321,1324,1327,1330,1333,1336,1339,1342,1345,1348,1351,1354,1357,1360,1363,1366,1369,1372,1375,1378,1381,1384,1387,1390,1393,1396,1399,1402,1405,1408,1411,1414,1417,1420,1423,1426,1429,1432,1435,1438,1441,1444,1447,1450,1453,1456,1459,1462,1465],{"path":1223,"title":1224},"\u002Fabout","About Python CLI Toolcraft",{"path":1226,"title":1227},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies","Advanced Argument Validation Strategies",{"path":1229,"title":1230},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fparsing-nested-json-arguments-in-python-clis","Parsing Nested JSON Args in Python CLIs",{"path":1232,"title":1233},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fvalidating-file-and-directory-paths-in-clis","Validating File and Directory Paths in CLIs",{"path":1213,"title":5},{"path":1236,"title":1237},"\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":1239,"title":1240},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation","CLI Help Output and Documentation",{"path":1242,"title":1243},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fversioning-and-deprecating-cli-flags","Versioning and Deprecating CLI Flags",{"path":1245,"title":1246},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fwriting-help-text-users-actually-read","Writing Help Text Users Actually Read",{"path":1248,"title":1249},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fchoosing-exit-codes-for-cli-tools","Choosing Exit Codes for CLI Tools",{"path":1251,"title":1252},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Ffriendly-error-messages-and-tracebacks","Friendly Error Messages and Tracebacks",{"path":1254,"title":1255},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fhandling-keyboard-interrupt-cleanly","Handling Keyboard Interrupt Cleanly",{"path":1257,"title":1258},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes","Error Handling and Exit Codes for CLIs",{"path":1260,"title":1261},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Fconfig-precedence-flags-env-files-defaults","Config Precedence: Flags, Env, Files, Defaults",{"path":1263,"title":1264},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars","Handling Config Files and Env Vars in CLIs",{"path":1266,"title":1267},"\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":1269,"title":1270},"\u002Fadvanced-input-parsing-user-experience","Advanced Input Parsing for Python CLIs",{"path":1272,"title":1273},"\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":1275,"title":1276},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich","Interactive Terminal UI with Rich",{"path":1278,"title":1279},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Frendering-tables-and-json-with-rich","Rendering Tables and JSON with Rich",{"path":1281,"title":1282},"\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":1284,"title":1285},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis","Shell Completion for Python CLIs",{"path":1287,"title":1288},"\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":1290,"title":1291},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fadding-verbose-and-quiet-logging-flags","Adding Verbose and Quiet Logging Flags",{"path":1293,"title":1294},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps","Structured Logging for CLI Apps",{"path":1296,"title":1297},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fstructured-json-logging-in-python-clis","Structured JSON Logging in Python CLIs",{"path":1299,"title":1300},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Fdetecting-tty-and-adapting-output","Detecting a TTY and Adapting Output",{"path":1302,"title":1303},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Femitting-json-output-for-scripting","Emitting JSON Output for Scripting",{"path":1305,"title":1306},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Fhandling-broken-pipe-and-sigpipe","Handling Broken Pipe and SIGPIPE",{"path":1308,"title":1309},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes","Working with stdin, stdout and Pipes",{"path":1311,"title":1312},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Freading-piped-input-in-python-clis","Reading Piped Input in Python CLIs",{"path":929,"title":1314},"Python CLI Toolcraft",{"path":1316,"title":1317},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading","CLI Startup Performance and Lazy Loading",{"path":1319,"title":1320},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Flazy-loading-subcommands-for-faster-startup","Lazy Loading Subcommands for Faster Startup",{"path":1322,"title":1323},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Fprofiling-python-cli-startup-time","Profiling Python CLI Startup Time",{"path":1325,"title":1326},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Freducing-cli-dependency-weight","Reducing CLI Dependency Weight",{"path":1328,"title":1329},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargparse-subparsers-for-subcommands","argparse Subparsers for Subcommands",{"path":1331,"title":1332},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargparse-vs-click-vs-typer-comparison","argparse vs Click vs Typer Compared",{"path":1334,"title":1335},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse","Command-Line Parsing with argparse",{"path":1337,"title":1338},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fmigrating-from-argparse-to-typer","Migrating from argparse to Typer",{"path":1340,"title":1341},"\u002Fmodern-python-cli-frameworks-architecture","Python CLI Frameworks and Architecture",{"path":1343,"title":1344},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis","Plugin Architectures for Extensible CLIs",{"path":1346,"title":1347},"\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":1349,"title":1350},"\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":1352,"title":1353},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fdependency-injection-patterns-for-cli-commands","Dependency Injection Patterns for CLI Commands",{"path":1355,"title":1356},"\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":1358,"title":1359},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis","Structuring Multi-Command Python CLIs",{"path":1361,"title":1362},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fsharing-state-with-click-context-objects","Sharing State with Click Context Objects",{"path":1364,"title":1365},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications","Testing Python CLI Applications",{"path":1367,"title":1368},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fmeasuring-cli-test-coverage","Measuring CLI Test Coverage",{"path":1370,"title":1371},"\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":1373,"title":1374},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fsnapshot-testing-cli-output","Snapshot Testing CLI Output",{"path":1376,"title":1377},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftesting-click-commands-with-clirunner","Testing Click Commands with CliRunner",{"path":1379,"title":1380},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftesting-interactive-prompts-and-stdin","Testing Interactive Prompts and stdin",{"path":1382,"title":1383},"\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":1385,"title":1386},"\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":1388,"title":1389},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each","Typer vs Click: When to Use Each",{"path":1391,"title":1392},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Ftyper-callback-functions-explained","Typer callback functions explained",{"path":1394,"title":1395},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fcopier-vs-cookiecutter-for-cli-templates","Copier vs Cookiecutter for CLI Templates",{"path":1397,"title":1398},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter","CLI Project Scaffolding with Cookiecutter",{"path":1400,"title":1401},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fbuilding-cross-platform-release-binaries-in-ci","Building Cross-Platform Release Binaries in CI",{"path":1403,"title":1404},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fbundling-a-python-cli-with-pyinstaller","Bundling a Python CLI with PyInstaller",{"path":1406,"title":1407},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fhomebrew-and-scoop-packaging-for-python-clis","Homebrew and Scoop Packaging for Python CLIs",{"path":1409,"title":1410},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries","Distributing CLIs as Standalone Binaries",{"path":1412,"title":1413},"\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":1415,"title":1416},"\u002Fproject-setup-dependency-management","Project Setup & Dependency Management",{"path":1418,"title":1419},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fautomating-changelogs-with-conventional-commits","Automating Changelogs with Conventional Commits",{"path":1421,"title":1422},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fexposing-version-info-and-build-metadata","Exposing Version Info and Build Metadata",{"path":1424,"title":1425},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs","Managing CLI Versioning & Changelogs",{"path":1427,"title":1428},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fbuilding-wheels-and-sdists-for-python-clis","Building Wheels and sdists for Python CLIs",{"path":1430,"title":1431},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution","Packaging Python CLIs for Distribution",{"path":1433,"title":1434},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Finstalling-and-distributing-clis-with-pipx","Installing and Distributing CLIs with pipx",{"path":1436,"title":1437},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fpublishing-a-python-cli-to-pypi","Publishing a Python CLI to PyPI",{"path":1439,"title":1440},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development","Poetry Workflows for CLI Development",{"path":1442,"title":1443},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpoetry-entry-points-and-scripts-for-clis","Poetry Entry Points and Scripts for CLIs",{"path":1445,"title":1446},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects","Pre-commit Hooks for CLI Projects",{"path":1448,"title":1449},"\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":1451,"title":1452},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management","uv for Python CLI Dependency Management",{"path":1454,"title":1455},"\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":1457,"title":1458},"\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":1460,"title":1461},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices","Python CLI Env Isolation Best Practices",{"path":1463,"title":1464},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fmanaging-virtual-environments-for-cross-platform-clis","Managing Python CLI Virtual Environments",{"path":1466,"title":1467},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fpinning-the-python-version-for-a-cli","Pinning the Python Version for a CLI",1785614690027]