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