[{"data":1,"prerenderedAt":3251},["ShallowReactive",2],{"page-\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Fbuilding-tree-views-with-rich\u002F":3,"content-directory":2407},{"id":4,"title":5,"body":6,"date":2392,"description":2393,"difficulty":2394,"draft":2395,"extension":2396,"meta":2397,"navigation":137,"path":2398,"seo":2399,"stem":2400,"tags":2401,"updated":2392,"__hash__":2406},"content\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Fbuilding-tree-views-with-rich\u002Findex.md","Building Tree Views with Rich in a Python CLI",{"type":7,"value":8,"toc":2372},"minimark",[9,47,52,62,66,70,92,96,1052,1542,1557,1562,1584,1592,1596,1610,1613,1617,1624,1630,1659,1663,1719,1722,1726,1733,2243,2257,2261,2273,2277,2281,2292,2296,2307,2311,2319,2323,2326,2330,2333,2337,2369],[10,11,12,13,17,18,17,21,24,25,28,29,36,37,40,41,46],"p",{},"A lot of what CLIs show is hierarchical: environments containing services containing instances, a dependency graph, a directory, nested configuration, a test suite of modules, classes and tests. Flattened into a table, the structure disappears and the user has to rebuild it from repeated prefixes. Drawn as a tree — the way ",[14,15,16],"code",{},"tree",", ",[14,19,20],{},"pstree",[14,22,23],{},"cargo tree"," and ",[14,26,27],{},"pipdeptree"," do — the structure is the first thing the eye sees. ",[30,31,32,33],"strong",{},"Rich’s ",[14,34,35],{},"Tree"," draws the guide lines, handles indentation and wrapping, and accepts any Rich renderable as a label. This guide builds a ",[14,38,39],{},"status"," command that shows a fleet of services as a tree, colours guides by the worst status below them, limits depth and hides healthy branches on request, and — because trees are terrible input for scripts — emits one path per line in pipes and nested JSON on demand. It belongs to the ",[42,43,45],"a",{"href":44},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002F","interactive terminal UI with Rich topic",".",[48,49,51],"h2",{"id":50},"prerequisites","Prerequisites",[53,54,55],"ul",{},[56,57,58,61],"li",{},[14,59,60],{},"uv add rich typer"," (examples checked with Rich 15 and Typer 0.27).",[48,63,65],{"id":64},"anatomy-of-a-rich-tree","Anatomy of a Rich tree",[67,68],"inline-diagram",{"name":69},"richtree-anatomy",[10,71,72,73,75,76,79,80,83,84,87,88,91],{},"A ",[14,74,35],{}," is created with a label for the root; ",[14,77,78],{},"tree.add(label)"," returns a new subtree, so building a tree is a recursive walk over your data that adds a branch per child. Each subtree can have its own ",[14,81,82],{},"style"," for the label and ",[14,85,86],{},"guide_style"," for the lines that connect it, which is how colour can carry meaning: a red guide leads to a failure, a dim one to healthy nodes. Labels can be plain strings with markup, ",[14,89,90],{},"Text"," objects, or any renderable — even a small table.",[48,93,95],{"id":94},"the-recipe","The recipe",[97,98,103],"pre",{"className":99,"code":100,"language":101,"meta":102,"style":102},"language-python shiki shiki-themes github-light github-dark","# src\u002Fmytool\u002Ftree_view.py\nfrom __future__ import annotations\n\nfrom dataclasses import dataclass, field\n\nfrom rich.text import Text\nfrom rich.tree import Tree\n\n\n@dataclass\nclass Node:\n    name: str\n    status: str = \"ok\"\n    children: list[Node] = field(default_factory=list)\n\n\nSTATUS_STYLE = {\"ok\": \"green\", \"degraded\": \"yellow\", \"down\": \"bold red\"}\nGUIDE_STYLE = {\"ok\": \"dim\", \"degraded\": \"yellow\", \"down\": \"red\"}     # bold would draw heavy guides\n\n\ndef _label(node: Node) -> Text:\n    label = Text(node.name)\n    if node.status != \"ok\":\n        label.append(f\"  {node.status}\", style=STATUS_STYLE.get(node.status, \"\"))\n    return label\n\n\ndef _worst(node: Node) -> str:\n    order = list(STATUS_STYLE)\n    statuses = [node.status, *(_worst(c) for c in node.children)]\n    return max(statuses, key=lambda s: order.index(s) if s in order else 0)\n\n\ndef build_tree(root: Node, *, max_depth: int | None = None, only_problems: bool = False) -> Tree:\n    tree = Tree(_label(root), guide_style=\"dim\")\n\n    def add(parent: Tree, node: Node, depth: int) -> None:\n        children = [c for c in node.children if not only_problems or _worst(c) != \"ok\"]\n        if max_depth is not None and depth >= max_depth:\n            if children:\n                parent.add(Text(f\"… {len(children)} more\", style=\"dim\"))\n            return\n        for child in children:\n            branch = parent.add(_label(child), guide_style=GUIDE_STYLE.get(_worst(child), \"dim\"))\n            add(branch, child, depth + 1)\n\n    add(tree, root, 0)\n    return tree\n\n\ndef to_lines(node: Node, prefix: str = \"\") -> list[str]:\n    \"\"\"Plain, greppable form for pipes: one full path per line.\"\"\"\n    path = f\"{prefix}\u002F{node.name}\" if prefix else node.name\n    return [f\"{path}\\t{node.status}\"] + [line for c in node.children for line in to_lines(c, path)]\n\n\ndef to_dict(node: Node) -> dict:\n    return {\"name\": node.name, \"status\": node.status, \"children\": [to_dict(c) for c in node.children]}\n","python","",[14,104,105,114,132,139,153,158,171,184,189,194,201,213,222,238,262,267,272,315,355,360,365,377,388,405,446,455,460,465,480,498,527,566,571,576,619,638,643,665,706,734,743,775,781,794,818,832,837,848,856,861,866,892,898,941,993,998,1003,1018],{"__ignoreMap":102},[106,107,110],"span",{"class":108,"line":109},"line",1,[106,111,113],{"class":112},"sJ8bj","# src\u002Fmytool\u002Ftree_view.py\n",[106,115,117,121,125,128],{"class":108,"line":116},2,[106,118,120],{"class":119},"szBVR","from",[106,122,124],{"class":123},"sj4cs"," __future__",[106,126,127],{"class":119}," import",[106,129,131],{"class":130},"sVt8B"," annotations\n",[106,133,135],{"class":108,"line":134},3,[106,136,138],{"emptyLinePlaceholder":137},true,"\n",[106,140,142,144,147,150],{"class":108,"line":141},4,[106,143,120],{"class":119},[106,145,146],{"class":130}," dataclasses ",[106,148,149],{"class":119},"import",[106,151,152],{"class":130}," dataclass, field\n",[106,154,156],{"class":108,"line":155},5,[106,157,138],{"emptyLinePlaceholder":137},[106,159,161,163,166,168],{"class":108,"line":160},6,[106,162,120],{"class":119},[106,164,165],{"class":130}," rich.text ",[106,167,149],{"class":119},[106,169,170],{"class":130}," Text\n",[106,172,174,176,179,181],{"class":108,"line":173},7,[106,175,120],{"class":119},[106,177,178],{"class":130}," rich.tree ",[106,180,149],{"class":119},[106,182,183],{"class":130}," Tree\n",[106,185,187],{"class":108,"line":186},8,[106,188,138],{"emptyLinePlaceholder":137},[106,190,192],{"class":108,"line":191},9,[106,193,138],{"emptyLinePlaceholder":137},[106,195,197],{"class":108,"line":196},10,[106,198,200],{"class":199},"sScJk","@dataclass\n",[106,202,204,207,210],{"class":108,"line":203},11,[106,205,206],{"class":119},"class",[106,208,209],{"class":199}," Node",[106,211,212],{"class":130},":\n",[106,214,216,219],{"class":108,"line":215},12,[106,217,218],{"class":130},"    name: ",[106,220,221],{"class":123},"str\n",[106,223,225,228,231,234],{"class":108,"line":224},13,[106,226,227],{"class":130},"    status: ",[106,229,230],{"class":123},"str",[106,232,233],{"class":119}," =",[106,235,237],{"class":236},"sZZnC"," \"ok\"\n",[106,239,241,244,247,250,254,256,259],{"class":108,"line":240},14,[106,242,243],{"class":130},"    children: list[Node] ",[106,245,246],{"class":119},"=",[106,248,249],{"class":130}," field(",[106,251,253],{"class":252},"s4XuR","default_factory",[106,255,246],{"class":119},[106,257,258],{"class":123},"list",[106,260,261],{"class":130},")\n",[106,263,265],{"class":108,"line":264},15,[106,266,138],{"emptyLinePlaceholder":137},[106,268,270],{"class":108,"line":269},16,[106,271,138],{"emptyLinePlaceholder":137},[106,273,275,278,280,283,286,289,292,294,297,299,302,304,307,309,312],{"class":108,"line":274},17,[106,276,277],{"class":123},"STATUS_STYLE",[106,279,233],{"class":119},[106,281,282],{"class":130}," {",[106,284,285],{"class":236},"\"ok\"",[106,287,288],{"class":130},": ",[106,290,291],{"class":236},"\"green\"",[106,293,17],{"class":130},[106,295,296],{"class":236},"\"degraded\"",[106,298,288],{"class":130},[106,300,301],{"class":236},"\"yellow\"",[106,303,17],{"class":130},[106,305,306],{"class":236},"\"down\"",[106,308,288],{"class":130},[106,310,311],{"class":236},"\"bold red\"",[106,313,314],{"class":130},"}\n",[106,316,318,321,323,325,327,329,332,334,336,338,340,342,344,346,349,352],{"class":108,"line":317},18,[106,319,320],{"class":123},"GUIDE_STYLE",[106,322,233],{"class":119},[106,324,282],{"class":130},[106,326,285],{"class":236},[106,328,288],{"class":130},[106,330,331],{"class":236},"\"dim\"",[106,333,17],{"class":130},[106,335,296],{"class":236},[106,337,288],{"class":130},[106,339,301],{"class":236},[106,341,17],{"class":130},[106,343,306],{"class":236},[106,345,288],{"class":130},[106,347,348],{"class":236},"\"red\"",[106,350,351],{"class":130},"}     ",[106,353,354],{"class":112},"# bold would draw heavy guides\n",[106,356,358],{"class":108,"line":357},19,[106,359,138],{"emptyLinePlaceholder":137},[106,361,363],{"class":108,"line":362},20,[106,364,138],{"emptyLinePlaceholder":137},[106,366,368,371,374],{"class":108,"line":367},21,[106,369,370],{"class":119},"def",[106,372,373],{"class":199}," _label",[106,375,376],{"class":130},"(node: Node) -> Text:\n",[106,378,380,383,385],{"class":108,"line":379},22,[106,381,382],{"class":130},"    label ",[106,384,246],{"class":119},[106,386,387],{"class":130}," Text(node.name)\n",[106,389,391,394,397,400,403],{"class":108,"line":390},23,[106,392,393],{"class":119},"    if",[106,395,396],{"class":130}," node.status ",[106,398,399],{"class":119},"!=",[106,401,402],{"class":236}," \"ok\"",[106,404,212],{"class":130},[106,406,408,411,414,417,420,423,426,429,431,433,435,437,440,443],{"class":108,"line":407},24,[106,409,410],{"class":130},"        label.append(",[106,412,413],{"class":119},"f",[106,415,416],{"class":236},"\"  ",[106,418,419],{"class":123},"{",[106,421,422],{"class":130},"node.status",[106,424,425],{"class":123},"}",[106,427,428],{"class":236},"\"",[106,430,17],{"class":130},[106,432,82],{"class":252},[106,434,246],{"class":119},[106,436,277],{"class":123},[106,438,439],{"class":130},".get(node.status, ",[106,441,442],{"class":236},"\"\"",[106,444,445],{"class":130},"))\n",[106,447,449,452],{"class":108,"line":448},25,[106,450,451],{"class":119},"    return",[106,453,454],{"class":130}," label\n",[106,456,458],{"class":108,"line":457},26,[106,459,138],{"emptyLinePlaceholder":137},[106,461,463],{"class":108,"line":462},27,[106,464,138],{"emptyLinePlaceholder":137},[106,466,468,470,473,476,478],{"class":108,"line":467},28,[106,469,370],{"class":119},[106,471,472],{"class":199}," _worst",[106,474,475],{"class":130},"(node: Node) -> ",[106,477,230],{"class":123},[106,479,212],{"class":130},[106,481,483,486,488,491,494,496],{"class":108,"line":482},29,[106,484,485],{"class":130},"    order ",[106,487,246],{"class":119},[106,489,490],{"class":123}," list",[106,492,493],{"class":130},"(",[106,495,277],{"class":123},[106,497,261],{"class":130},[106,499,501,504,506,509,512,515,518,521,524],{"class":108,"line":500},30,[106,502,503],{"class":130},"    statuses ",[106,505,246],{"class":119},[106,507,508],{"class":130}," [node.status, ",[106,510,511],{"class":119},"*",[106,513,514],{"class":130},"(_worst(c) ",[106,516,517],{"class":119},"for",[106,519,520],{"class":130}," c ",[106,522,523],{"class":119},"in",[106,525,526],{"class":130}," node.children)]\n",[106,528,530,532,535,538,541,544,547,550,553,555,558,561,564],{"class":108,"line":529},31,[106,531,451],{"class":119},[106,533,534],{"class":123}," max",[106,536,537],{"class":130},"(statuses, ",[106,539,540],{"class":252},"key",[106,542,543],{"class":119},"=lambda",[106,545,546],{"class":130}," s: order.index(s) ",[106,548,549],{"class":119},"if",[106,551,552],{"class":130}," s ",[106,554,523],{"class":119},[106,556,557],{"class":130}," order ",[106,559,560],{"class":119},"else",[106,562,563],{"class":123}," 0",[106,565,261],{"class":130},[106,567,569],{"class":108,"line":568},32,[106,570,138],{"emptyLinePlaceholder":137},[106,572,574],{"class":108,"line":573},33,[106,575,138],{"emptyLinePlaceholder":137},[106,577,579,581,584,587,589,592,595,598,601,603,605,608,611,613,616],{"class":108,"line":578},34,[106,580,370],{"class":119},[106,582,583],{"class":199}," build_tree",[106,585,586],{"class":130},"(root: Node, ",[106,588,511],{"class":119},[106,590,591],{"class":130},", max_depth: ",[106,593,594],{"class":123},"int",[106,596,597],{"class":119}," |",[106,599,600],{"class":123}," None",[106,602,233],{"class":119},[106,604,600],{"class":123},[106,606,607],{"class":130},", only_problems: ",[106,609,610],{"class":123},"bool",[106,612,233],{"class":119},[106,614,615],{"class":123}," False",[106,617,618],{"class":130},") -> Tree:\n",[106,620,622,625,627,630,632,634,636],{"class":108,"line":621},35,[106,623,624],{"class":130},"    tree ",[106,626,246],{"class":119},[106,628,629],{"class":130}," Tree(_label(root), ",[106,631,86],{"class":252},[106,633,246],{"class":119},[106,635,331],{"class":236},[106,637,261],{"class":130},[106,639,641],{"class":108,"line":640},36,[106,642,138],{"emptyLinePlaceholder":137},[106,644,646,649,652,655,657,660,663],{"class":108,"line":645},37,[106,647,648],{"class":119},"    def",[106,650,651],{"class":199}," add",[106,653,654],{"class":130},"(parent: Tree, node: Node, depth: ",[106,656,594],{"class":123},[106,658,659],{"class":130},") -> ",[106,661,662],{"class":123},"None",[106,664,212],{"class":130},[106,666,668,671,673,676,678,680,682,685,687,690,693,696,699,701,703],{"class":108,"line":667},38,[106,669,670],{"class":130},"        children ",[106,672,246],{"class":119},[106,674,675],{"class":130}," [c ",[106,677,517],{"class":119},[106,679,520],{"class":130},[106,681,523],{"class":119},[106,683,684],{"class":130}," node.children ",[106,686,549],{"class":119},[106,688,689],{"class":119}," not",[106,691,692],{"class":130}," only_problems ",[106,694,695],{"class":119},"or",[106,697,698],{"class":130}," _worst(c) ",[106,700,399],{"class":119},[106,702,402],{"class":236},[106,704,705],{"class":130},"]\n",[106,707,709,712,715,718,720,722,725,728,731],{"class":108,"line":708},39,[106,710,711],{"class":119},"        if",[106,713,714],{"class":130}," max_depth ",[106,716,717],{"class":119},"is",[106,719,689],{"class":119},[106,721,600],{"class":123},[106,723,724],{"class":119}," and",[106,726,727],{"class":130}," depth ",[106,729,730],{"class":119},">=",[106,732,733],{"class":130}," max_depth:\n",[106,735,737,740],{"class":108,"line":736},40,[106,738,739],{"class":119},"            if",[106,741,742],{"class":130}," children:\n",[106,744,746,749,751,754,757,760,762,765,767,769,771,773],{"class":108,"line":745},41,[106,747,748],{"class":130},"                parent.add(Text(",[106,750,413],{"class":119},[106,752,753],{"class":236},"\"… ",[106,755,756],{"class":123},"{len",[106,758,759],{"class":130},"(children)",[106,761,425],{"class":123},[106,763,764],{"class":236}," more\"",[106,766,17],{"class":130},[106,768,82],{"class":252},[106,770,246],{"class":119},[106,772,331],{"class":236},[106,774,445],{"class":130},[106,776,778],{"class":108,"line":777},42,[106,779,780],{"class":119},"            return\n",[106,782,784,787,790,792],{"class":108,"line":783},43,[106,785,786],{"class":119},"        for",[106,788,789],{"class":130}," child ",[106,791,523],{"class":119},[106,793,742],{"class":130},[106,795,797,800,802,805,807,809,811,814,816],{"class":108,"line":796},44,[106,798,799],{"class":130},"            branch ",[106,801,246],{"class":119},[106,803,804],{"class":130}," parent.add(_label(child), ",[106,806,86],{"class":252},[106,808,246],{"class":119},[106,810,320],{"class":123},[106,812,813],{"class":130},".get(_worst(child), ",[106,815,331],{"class":236},[106,817,445],{"class":130},[106,819,821,824,827,830],{"class":108,"line":820},45,[106,822,823],{"class":130},"            add(branch, child, depth ",[106,825,826],{"class":119},"+",[106,828,829],{"class":123}," 1",[106,831,261],{"class":130},[106,833,835],{"class":108,"line":834},46,[106,836,138],{"emptyLinePlaceholder":137},[106,838,840,843,846],{"class":108,"line":839},47,[106,841,842],{"class":130},"    add(tree, root, ",[106,844,845],{"class":123},"0",[106,847,261],{"class":130},[106,849,851,853],{"class":108,"line":850},48,[106,852,451],{"class":119},[106,854,855],{"class":130}," tree\n",[106,857,859],{"class":108,"line":858},49,[106,860,138],{"emptyLinePlaceholder":137},[106,862,864],{"class":108,"line":863},50,[106,865,138],{"emptyLinePlaceholder":137},[106,867,869,871,874,877,879,881,884,887,889],{"class":108,"line":868},51,[106,870,370],{"class":119},[106,872,873],{"class":199}," to_lines",[106,875,876],{"class":130},"(node: Node, prefix: ",[106,878,230],{"class":123},[106,880,233],{"class":119},[106,882,883],{"class":236}," \"\"",[106,885,886],{"class":130},") -> list[",[106,888,230],{"class":123},[106,890,891],{"class":130},"]:\n",[106,893,895],{"class":108,"line":894},52,[106,896,897],{"class":236},"    \"\"\"Plain, greppable form for pipes: one full path per line.\"\"\"\n",[106,899,901,904,906,909,911,913,916,918,921,923,926,928,930,933,936,938],{"class":108,"line":900},53,[106,902,903],{"class":130},"    path ",[106,905,246],{"class":119},[106,907,908],{"class":119}," f",[106,910,428],{"class":236},[106,912,419],{"class":123},[106,914,915],{"class":130},"prefix",[106,917,425],{"class":123},[106,919,920],{"class":236},"\u002F",[106,922,419],{"class":123},[106,924,925],{"class":130},"node.name",[106,927,425],{"class":123},[106,929,428],{"class":236},[106,931,932],{"class":119}," if",[106,934,935],{"class":130}," prefix ",[106,937,560],{"class":119},[106,939,940],{"class":130}," node.name\n",[106,942,944,946,949,951,953,955,958,961,963,965,967,970,972,975,977,979,981,983,985,988,990],{"class":108,"line":943},54,[106,945,451],{"class":119},[106,947,948],{"class":130}," [",[106,950,413],{"class":119},[106,952,428],{"class":236},[106,954,419],{"class":123},[106,956,957],{"class":130},"path",[106,959,960],{"class":123},"}\\t{",[106,962,422],{"class":130},[106,964,425],{"class":123},[106,966,428],{"class":236},[106,968,969],{"class":130},"] ",[106,971,826],{"class":119},[106,973,974],{"class":130}," [line ",[106,976,517],{"class":119},[106,978,520],{"class":130},[106,980,523],{"class":119},[106,982,684],{"class":130},[106,984,517],{"class":119},[106,986,987],{"class":130}," line ",[106,989,523],{"class":119},[106,991,992],{"class":130}," to_lines(c, path)]\n",[106,994,996],{"class":108,"line":995},55,[106,997,138],{"emptyLinePlaceholder":137},[106,999,1001],{"class":108,"line":1000},56,[106,1002,138],{"emptyLinePlaceholder":137},[106,1004,1006,1008,1011,1013,1016],{"class":108,"line":1005},57,[106,1007,370],{"class":119},[106,1009,1010],{"class":199}," to_dict",[106,1012,475],{"class":130},[106,1014,1015],{"class":123},"dict",[106,1017,212],{"class":130},[106,1019,1021,1023,1025,1028,1031,1034,1037,1040,1043,1045,1047,1049],{"class":108,"line":1020},58,[106,1022,451],{"class":119},[106,1024,282],{"class":130},[106,1026,1027],{"class":236},"\"name\"",[106,1029,1030],{"class":130},": node.name, ",[106,1032,1033],{"class":236},"\"status\"",[106,1035,1036],{"class":130},": node.status, ",[106,1038,1039],{"class":236},"\"children\"",[106,1041,1042],{"class":130},": [to_dict(c) ",[106,1044,517],{"class":119},[106,1046,520],{"class":130},[106,1048,523],{"class":119},[106,1050,1051],{"class":130}," node.children]}\n",[97,1053,1055],{"className":99,"code":1054,"language":101,"meta":102,"style":102},"# src\u002Fmytool\u002Fcli.py\nimport json\nfrom typing import Annotated, Optional\n\nimport typer\nfrom rich.console import Console\n\nfrom mytool.tree_view import Node, build_tree, to_dict, to_lines\n\napp = typer.Typer()\n\nFLEET = Node(\"acme\", children=[\n    Node(\"production\", children=[\n        Node(\"api\", children=[Node(\"api-1\"), Node(\"api-2\", \"degraded\")]),\n        Node(\"worker\", children=[Node(\"worker-1\")]),\n    ]),\n    Node(\"staging\", children=[\n        Node(\"api\", children=[Node(\"api-1\", \"down\")]),\n    ]),\n])\n\n\n@app.callback()\ndef main() -> None:\n    \"\"\"Fleet tool.\"\"\"\n\n\n@app.command()\ndef status(\n    depth: Annotated[Optional[int], typer.Option(help=\"Limit how deep the tree goes.\")] = None,\n    problems: Annotated[bool, typer.Option(\"--problems\", help=\"Hide healthy branches.\")] = False,\n    as_json: Annotated[bool, typer.Option(\"--json\", help=\"Nested JSON for scripts.\")] = False,\n) -> None:\n    \"\"\"Show the fleet as a tree.\"\"\"\n    console = Console()\n    if as_json:\n        print(json.dumps(to_dict(FLEET), indent=2))\n    elif console.is_terminal:\n        console.print(build_tree(FLEET, max_depth=depth, only_problems=problems))\n    else:\n        print(\"\\n\".join(to_lines(FLEET)))\n",[14,1056,1057,1062,1069,1081,1085,1092,1104,1108,1120,1124,1134,1138,1161,1177,1210,1230,1235,1250,1272,1276,1281,1285,1289,1297,1311,1316,1320,1324,1331,1341,1369,1399,1428,1436,1441,1451,1458,1481,1489,1514,1521],{"__ignoreMap":102},[106,1058,1059],{"class":108,"line":109},[106,1060,1061],{"class":112},"# src\u002Fmytool\u002Fcli.py\n",[106,1063,1064,1066],{"class":108,"line":116},[106,1065,149],{"class":119},[106,1067,1068],{"class":130}," json\n",[106,1070,1071,1073,1076,1078],{"class":108,"line":134},[106,1072,120],{"class":119},[106,1074,1075],{"class":130}," typing ",[106,1077,149],{"class":119},[106,1079,1080],{"class":130}," Annotated, Optional\n",[106,1082,1083],{"class":108,"line":141},[106,1084,138],{"emptyLinePlaceholder":137},[106,1086,1087,1089],{"class":108,"line":155},[106,1088,149],{"class":119},[106,1090,1091],{"class":130}," typer\n",[106,1093,1094,1096,1099,1101],{"class":108,"line":160},[106,1095,120],{"class":119},[106,1097,1098],{"class":130}," rich.console ",[106,1100,149],{"class":119},[106,1102,1103],{"class":130}," Console\n",[106,1105,1106],{"class":108,"line":173},[106,1107,138],{"emptyLinePlaceholder":137},[106,1109,1110,1112,1115,1117],{"class":108,"line":186},[106,1111,120],{"class":119},[106,1113,1114],{"class":130}," mytool.tree_view ",[106,1116,149],{"class":119},[106,1118,1119],{"class":130}," Node, build_tree, to_dict, to_lines\n",[106,1121,1122],{"class":108,"line":191},[106,1123,138],{"emptyLinePlaceholder":137},[106,1125,1126,1129,1131],{"class":108,"line":196},[106,1127,1128],{"class":130},"app ",[106,1130,246],{"class":119},[106,1132,1133],{"class":130}," typer.Typer()\n",[106,1135,1136],{"class":108,"line":203},[106,1137,138],{"emptyLinePlaceholder":137},[106,1139,1140,1143,1145,1148,1151,1153,1156,1158],{"class":108,"line":215},[106,1141,1142],{"class":123},"FLEET",[106,1144,233],{"class":119},[106,1146,1147],{"class":130}," Node(",[106,1149,1150],{"class":236},"\"acme\"",[106,1152,17],{"class":130},[106,1154,1155],{"class":252},"children",[106,1157,246],{"class":119},[106,1159,1160],{"class":130},"[\n",[106,1162,1163,1166,1169,1171,1173,1175],{"class":108,"line":224},[106,1164,1165],{"class":130},"    Node(",[106,1167,1168],{"class":236},"\"production\"",[106,1170,17],{"class":130},[106,1172,1155],{"class":252},[106,1174,246],{"class":119},[106,1176,1160],{"class":130},[106,1178,1179,1182,1185,1187,1189,1191,1194,1197,1200,1203,1205,1207],{"class":108,"line":240},[106,1180,1181],{"class":130},"        Node(",[106,1183,1184],{"class":236},"\"api\"",[106,1186,17],{"class":130},[106,1188,1155],{"class":252},[106,1190,246],{"class":119},[106,1192,1193],{"class":130},"[Node(",[106,1195,1196],{"class":236},"\"api-1\"",[106,1198,1199],{"class":130},"), Node(",[106,1201,1202],{"class":236},"\"api-2\"",[106,1204,17],{"class":130},[106,1206,296],{"class":236},[106,1208,1209],{"class":130},")]),\n",[106,1211,1212,1214,1217,1219,1221,1223,1225,1228],{"class":108,"line":264},[106,1213,1181],{"class":130},[106,1215,1216],{"class":236},"\"worker\"",[106,1218,17],{"class":130},[106,1220,1155],{"class":252},[106,1222,246],{"class":119},[106,1224,1193],{"class":130},[106,1226,1227],{"class":236},"\"worker-1\"",[106,1229,1209],{"class":130},[106,1231,1232],{"class":108,"line":269},[106,1233,1234],{"class":130},"    ]),\n",[106,1236,1237,1239,1242,1244,1246,1248],{"class":108,"line":274},[106,1238,1165],{"class":130},[106,1240,1241],{"class":236},"\"staging\"",[106,1243,17],{"class":130},[106,1245,1155],{"class":252},[106,1247,246],{"class":119},[106,1249,1160],{"class":130},[106,1251,1252,1254,1256,1258,1260,1262,1264,1266,1268,1270],{"class":108,"line":317},[106,1253,1181],{"class":130},[106,1255,1184],{"class":236},[106,1257,17],{"class":130},[106,1259,1155],{"class":252},[106,1261,246],{"class":119},[106,1263,1193],{"class":130},[106,1265,1196],{"class":236},[106,1267,17],{"class":130},[106,1269,306],{"class":236},[106,1271,1209],{"class":130},[106,1273,1274],{"class":108,"line":357},[106,1275,1234],{"class":130},[106,1277,1278],{"class":108,"line":362},[106,1279,1280],{"class":130},"])\n",[106,1282,1283],{"class":108,"line":367},[106,1284,138],{"emptyLinePlaceholder":137},[106,1286,1287],{"class":108,"line":379},[106,1288,138],{"emptyLinePlaceholder":137},[106,1290,1291,1294],{"class":108,"line":390},[106,1292,1293],{"class":199},"@app.callback",[106,1295,1296],{"class":130},"()\n",[106,1298,1299,1301,1304,1307,1309],{"class":108,"line":407},[106,1300,370],{"class":119},[106,1302,1303],{"class":199}," main",[106,1305,1306],{"class":130},"() -> ",[106,1308,662],{"class":123},[106,1310,212],{"class":130},[106,1312,1313],{"class":108,"line":448},[106,1314,1315],{"class":236},"    \"\"\"Fleet tool.\"\"\"\n",[106,1317,1318],{"class":108,"line":457},[106,1319,138],{"emptyLinePlaceholder":137},[106,1321,1322],{"class":108,"line":462},[106,1323,138],{"emptyLinePlaceholder":137},[106,1325,1326,1329],{"class":108,"line":467},[106,1327,1328],{"class":199},"@app.command",[106,1330,1296],{"class":130},[106,1332,1333,1335,1338],{"class":108,"line":482},[106,1334,370],{"class":119},[106,1336,1337],{"class":199}," status",[106,1339,1340],{"class":130},"(\n",[106,1342,1343,1346,1348,1351,1354,1356,1359,1362,1364,1366],{"class":108,"line":500},[106,1344,1345],{"class":130},"    depth: Annotated[Optional[",[106,1347,594],{"class":123},[106,1349,1350],{"class":130},"], typer.Option(",[106,1352,1353],{"class":252},"help",[106,1355,246],{"class":119},[106,1357,1358],{"class":236},"\"Limit how deep the tree goes.\"",[106,1360,1361],{"class":130},")] ",[106,1363,246],{"class":119},[106,1365,600],{"class":123},[106,1367,1368],{"class":130},",\n",[106,1370,1371,1374,1376,1379,1382,1384,1386,1388,1391,1393,1395,1397],{"class":108,"line":529},[106,1372,1373],{"class":130},"    problems: Annotated[",[106,1375,610],{"class":123},[106,1377,1378],{"class":130},", typer.Option(",[106,1380,1381],{"class":236},"\"--problems\"",[106,1383,17],{"class":130},[106,1385,1353],{"class":252},[106,1387,246],{"class":119},[106,1389,1390],{"class":236},"\"Hide healthy branches.\"",[106,1392,1361],{"class":130},[106,1394,246],{"class":119},[106,1396,615],{"class":123},[106,1398,1368],{"class":130},[106,1400,1401,1404,1406,1408,1411,1413,1415,1417,1420,1422,1424,1426],{"class":108,"line":568},[106,1402,1403],{"class":130},"    as_json: Annotated[",[106,1405,610],{"class":123},[106,1407,1378],{"class":130},[106,1409,1410],{"class":236},"\"--json\"",[106,1412,17],{"class":130},[106,1414,1353],{"class":252},[106,1416,246],{"class":119},[106,1418,1419],{"class":236},"\"Nested JSON for scripts.\"",[106,1421,1361],{"class":130},[106,1423,246],{"class":119},[106,1425,615],{"class":123},[106,1427,1368],{"class":130},[106,1429,1430,1432,1434],{"class":108,"line":573},[106,1431,659],{"class":130},[106,1433,662],{"class":123},[106,1435,212],{"class":130},[106,1437,1438],{"class":108,"line":578},[106,1439,1440],{"class":236},"    \"\"\"Show the fleet as a tree.\"\"\"\n",[106,1442,1443,1446,1448],{"class":108,"line":621},[106,1444,1445],{"class":130},"    console ",[106,1447,246],{"class":119},[106,1449,1450],{"class":130}," Console()\n",[106,1452,1453,1455],{"class":108,"line":640},[106,1454,393],{"class":119},[106,1456,1457],{"class":130}," as_json:\n",[106,1459,1460,1463,1466,1468,1471,1474,1476,1479],{"class":108,"line":645},[106,1461,1462],{"class":123},"        print",[106,1464,1465],{"class":130},"(json.dumps(to_dict(",[106,1467,1142],{"class":123},[106,1469,1470],{"class":130},"), ",[106,1472,1473],{"class":252},"indent",[106,1475,246],{"class":119},[106,1477,1478],{"class":123},"2",[106,1480,445],{"class":130},[106,1482,1483,1486],{"class":108,"line":667},[106,1484,1485],{"class":119},"    elif",[106,1487,1488],{"class":130}," console.is_terminal:\n",[106,1490,1491,1494,1496,1498,1501,1503,1506,1509,1511],{"class":108,"line":708},[106,1492,1493],{"class":130},"        console.print(build_tree(",[106,1495,1142],{"class":123},[106,1497,17],{"class":130},[106,1499,1500],{"class":252},"max_depth",[106,1502,246],{"class":119},[106,1504,1505],{"class":130},"depth, ",[106,1507,1508],{"class":252},"only_problems",[106,1510,246],{"class":119},[106,1512,1513],{"class":130},"problems))\n",[106,1515,1516,1519],{"class":108,"line":736},[106,1517,1518],{"class":119},"    else",[106,1520,212],{"class":130},[106,1522,1523,1525,1527,1529,1532,1534,1537,1539],{"class":108,"line":745},[106,1524,1462],{"class":123},[106,1526,493],{"class":130},[106,1528,428],{"class":236},[106,1530,1531],{"class":123},"\\n",[106,1533,428],{"class":236},[106,1535,1536],{"class":130},".join(to_lines(",[106,1538,1142],{"class":123},[106,1540,1541],{"class":130},")))\n",[10,1543,1544,1545,1548,1549,1552,1553,1556],{},"The data model is deliberately independent of Rich: a ",[14,1546,1547],{},"Node"," with a name, a status and children. Your real data might be API responses, ",[14,1550,1551],{},"importlib.metadata"," requirements or ",[14,1554,1555],{},"Path.iterdir()","; converting it to a small tree type first keeps rendering, filtering and serialisation separate and testable.",[1558,1559,1561],"h3",{"id":1560},"colour-that-carries-information","Colour that carries information",[10,1563,1564,1565,1568,1569,1572,1573,1576,1577,17,1580,1583],{},"Labels show the status only when it is not ",[14,1566,1567],{},"ok",", so a healthy tree is quiet and problems stand out. Guides go further: each branch’s guide is coloured by the ",[30,1570,1571],{},"worst status anywhere beneath it",", computed by ",[14,1574,1575],{},"_worst",". On a large fleet the eye can follow a red line from the root down to the failing instance without reading every label. One detail matters here: Rich draws heavier guide characters (",[14,1578,1579],{},"┣━━",[14,1581,1582],{},"┗━━",") when the guide style is bold, so guide colours come from a separate, non-bold mapping to keep the lines consistent.",[97,1585,1590],{"className":1586,"code":1588,"language":1589,"meta":102},[1587],"language-text","acme\n├── production\n│   ├── api\n│   │   ├── api-1\n│   │   └── api-2  degraded\n│   └── worker\n│       └── worker-1\n└── staging\n    └── api\n        └── api-1  down\n","text",[14,1591,1588],{"__ignoreMap":102},[1558,1593,1595],{"id":1594},"depth-limits-and-problems-only-views","Depth limits and problems-only views",[10,1597,1598,1599,1602,1603,1606,1607,1609],{},"Real hierarchies get big. ",[14,1600,1601],{},"--depth N"," stops the walk at a level and replaces what is hidden with a dim “… 3 more” leaf, which tells the user there is more without drawing it. ",[14,1604,1605],{},"--problems"," hides every branch whose worst status is ",[14,1608,1567],{},", leaving only the paths that lead to something wrong — usually the view people actually want during an incident. Both are filters on the walk, not on the data, so they combine freely.",[67,1611],{"name":1612},"richtree-terminal",[1558,1614,1616],{"id":1615},"output-for-scripts","Output for scripts",[10,1618,1619,1620,1623],{},"Box-drawing characters and indentation are hard to parse. In a pipe, the command prints one line per node with the ",[30,1621,1622],{},"full path"," and status separated by a tab:",[97,1625,1628],{"className":1626,"code":1627,"language":1589,"meta":102},[1587],"acme\u002Fproduction\u002Fapi\u002Fapi-2   degraded\nacme\u002Fstaging\u002Fapi\u002Fapi-1  down\n",[14,1629,1627],{"__ignoreMap":102},[10,1631,1632,1633,17,1636,24,1639,1642,1643,1646,1647,1650,1651,24,1655,46],{},"That form works with ",[14,1634,1635],{},"grep",[14,1637,1638],{},"awk -F'\\t'",[14,1640,1641],{},"sort",", and each line stands on its own, which a tree line does not (",[14,1644,1645],{},"└── api-1"," is meaningless without its ancestors). ",[14,1648,1649],{},"--json"," emits the nested structure for programs that want the hierarchy itself. This is the same split described in ",[42,1652,1654],{"href":1653},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Fdetecting-tty-and-adapting-output\u002F","detecting a TTY and adapting output",[42,1656,1658],{"href":1657},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Femitting-json-output-for-scripting\u002F","emitting JSON output for scripting",[48,1660,1662],{"id":1661},"ux-considerations","UX considerations",[53,1664,1665,1671,1685,1700,1713],{},[56,1666,1667,1670],{},[30,1668,1669],{},"Sort children predictably."," Alphabetical, or by status with problems first; random API order makes two runs hard to compare.",[56,1672,1673,1676,1677,1680,1681,1684],{},[30,1674,1675],{},"Keep labels short."," Long labels wrap under the guides and break the visual structure; move detail to a ",[14,1678,1679],{},"show"," command or a ",[14,1682,1683],{},"--verbose"," table.",[56,1686,1687,1690,1691,1694,1695,1699],{},[30,1688,1689],{},"Mind the characters."," Guides use Unicode box drawing. On legacy Windows consoles or with ",[14,1692,1693],{},"TERM=dumb",", Rich falls back to ASCII guides when the encoding cannot display them; see ",[42,1696,1698],{"href":1697},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Fsupporting-dumb-terminals-and-screen-readers\u002F","supporting dumb terminals and screen readers"," for the accessibility side — screen readers read guide characters aloud, which is another reason to offer the path-per-line form.",[56,1701,1702,1705,1706,1708,1709,46],{},[30,1703,1704],{},"Expand on demand."," For trees too big to print, a Textual ",[14,1707,35],{}," widget offers collapsible nodes; see ",[42,1710,1712],{"href":1711},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual\u002F","building terminal UIs with Textual",[56,1714,1715,1718],{},[30,1716,1717],{},"Do not hide problems behind a depth limit."," If a hidden branch contains a failure, say so in the summary leaf (“… 3 more, 1 down”).",[67,1720],{"name":1721},"richtree-forms",[48,1723,1725],{"id":1724},"testing-the-behaviour","Testing the behaviour",[10,1727,1728,1729,1732],{},"Render trees into a ",[14,1730,1731],{},"StringIO"," console with colour disabled and a fixed width, then assert on the drawn structure. The CLI runner covers the piped and JSON forms:",[97,1734,1736],{"className":99,"code":1735,"language":101,"meta":102,"style":102},"# tests\u002Ftest_tree_view.py\nimport io\nimport json\n\nfrom rich.console import Console\nfrom typer.testing import CliRunner\n\nfrom mytool.cli import FLEET, app\nfrom mytool.tree_view import build_tree, to_lines\n\n\ndef render(tree) -> str:\n    console = Console(file=io.StringIO(), width=60, color_system=None)\n    console.print(tree)\n    return console.file.getvalue()\n\n\ndef test_tree_shows_hierarchy_with_guides():\n    out = render(build_tree(FLEET))\n    assert out.splitlines()[0] == \"acme\"\n    assert \"├── production\" in out and \"└── staging\" in out\n    assert \"api-2  degraded\" in out\n\n\ndef test_depth_limit_summarises_hidden_children():\n    out = render(build_tree(FLEET, max_depth=1))\n    assert \"production\" in out and \"api-1\" not in out\n    assert \"… 2 more\" in out\n\n\ndef test_problems_only_keeps_paths_to_failures():\n    out = render(build_tree(FLEET, only_problems=True))\n    assert \"api-2\" in out and \"api-1  down\" in out\n    assert \"worker\" not in out\n\n\ndef test_piped_output_is_one_path_per_line():\n    result = CliRunner().invoke(app, [\"status\"])\n    assert \"acme\u002Fstaging\u002Fapi\u002Fapi-1\\tdown\" in result.stdout.splitlines()\n    assert result.stdout.splitlines() == to_lines(FLEET)\n\n\ndef test_json_output_is_nested():\n    data = json.loads(CliRunner().invoke(app, [\"status\", \"--json\"]).stdout)\n    assert data[\"children\"][0][\"children\"][0][\"name\"] == \"api\"\n",[14,1737,1738,1743,1750,1756,1760,1770,1782,1786,1801,1812,1816,1820,1834,1870,1875,1882,1886,1890,1900,1914,1932,1956,1967,1971,1975,1984,2005,2027,2038,2042,2046,2055,2076,2096,2109,2113,2117,2126,2140,2158,2174,2178,2182,2191,2210],{"__ignoreMap":102},[106,1739,1740],{"class":108,"line":109},[106,1741,1742],{"class":112},"# tests\u002Ftest_tree_view.py\n",[106,1744,1745,1747],{"class":108,"line":116},[106,1746,149],{"class":119},[106,1748,1749],{"class":130}," io\n",[106,1751,1752,1754],{"class":108,"line":134},[106,1753,149],{"class":119},[106,1755,1068],{"class":130},[106,1757,1758],{"class":108,"line":141},[106,1759,138],{"emptyLinePlaceholder":137},[106,1761,1762,1764,1766,1768],{"class":108,"line":155},[106,1763,120],{"class":119},[106,1765,1098],{"class":130},[106,1767,149],{"class":119},[106,1769,1103],{"class":130},[106,1771,1772,1774,1777,1779],{"class":108,"line":160},[106,1773,120],{"class":119},[106,1775,1776],{"class":130}," typer.testing ",[106,1778,149],{"class":119},[106,1780,1781],{"class":130}," CliRunner\n",[106,1783,1784],{"class":108,"line":173},[106,1785,138],{"emptyLinePlaceholder":137},[106,1787,1788,1790,1793,1795,1798],{"class":108,"line":186},[106,1789,120],{"class":119},[106,1791,1792],{"class":130}," mytool.cli ",[106,1794,149],{"class":119},[106,1796,1797],{"class":123}," FLEET",[106,1799,1800],{"class":130},", app\n",[106,1802,1803,1805,1807,1809],{"class":108,"line":191},[106,1804,120],{"class":119},[106,1806,1114],{"class":130},[106,1808,149],{"class":119},[106,1810,1811],{"class":130}," build_tree, to_lines\n",[106,1813,1814],{"class":108,"line":196},[106,1815,138],{"emptyLinePlaceholder":137},[106,1817,1818],{"class":108,"line":203},[106,1819,138],{"emptyLinePlaceholder":137},[106,1821,1822,1824,1827,1830,1832],{"class":108,"line":215},[106,1823,370],{"class":119},[106,1825,1826],{"class":199}," render",[106,1828,1829],{"class":130},"(tree) -> ",[106,1831,230],{"class":123},[106,1833,212],{"class":130},[106,1835,1836,1838,1840,1843,1846,1848,1851,1854,1856,1859,1861,1864,1866,1868],{"class":108,"line":224},[106,1837,1445],{"class":130},[106,1839,246],{"class":119},[106,1841,1842],{"class":130}," Console(",[106,1844,1845],{"class":252},"file",[106,1847,246],{"class":119},[106,1849,1850],{"class":130},"io.StringIO(), ",[106,1852,1853],{"class":252},"width",[106,1855,246],{"class":119},[106,1857,1858],{"class":123},"60",[106,1860,17],{"class":130},[106,1862,1863],{"class":252},"color_system",[106,1865,246],{"class":119},[106,1867,662],{"class":123},[106,1869,261],{"class":130},[106,1871,1872],{"class":108,"line":240},[106,1873,1874],{"class":130},"    console.print(tree)\n",[106,1876,1877,1879],{"class":108,"line":264},[106,1878,451],{"class":119},[106,1880,1881],{"class":130}," console.file.getvalue()\n",[106,1883,1884],{"class":108,"line":269},[106,1885,138],{"emptyLinePlaceholder":137},[106,1887,1888],{"class":108,"line":274},[106,1889,138],{"emptyLinePlaceholder":137},[106,1891,1892,1894,1897],{"class":108,"line":317},[106,1893,370],{"class":119},[106,1895,1896],{"class":199}," test_tree_shows_hierarchy_with_guides",[106,1898,1899],{"class":130},"():\n",[106,1901,1902,1905,1907,1910,1912],{"class":108,"line":357},[106,1903,1904],{"class":130},"    out ",[106,1906,246],{"class":119},[106,1908,1909],{"class":130}," render(build_tree(",[106,1911,1142],{"class":123},[106,1913,445],{"class":130},[106,1915,1916,1919,1922,1924,1926,1929],{"class":108,"line":362},[106,1917,1918],{"class":119},"    assert",[106,1920,1921],{"class":130}," out.splitlines()[",[106,1923,845],{"class":123},[106,1925,969],{"class":130},[106,1927,1928],{"class":119},"==",[106,1930,1931],{"class":236}," \"acme\"\n",[106,1933,1934,1936,1939,1942,1945,1948,1951,1953],{"class":108,"line":367},[106,1935,1918],{"class":119},[106,1937,1938],{"class":236}," \"├── production\"",[106,1940,1941],{"class":119}," in",[106,1943,1944],{"class":130}," out ",[106,1946,1947],{"class":119},"and",[106,1949,1950],{"class":236}," \"└── staging\"",[106,1952,1941],{"class":119},[106,1954,1955],{"class":130}," out\n",[106,1957,1958,1960,1963,1965],{"class":108,"line":379},[106,1959,1918],{"class":119},[106,1961,1962],{"class":236}," \"api-2  degraded\"",[106,1964,1941],{"class":119},[106,1966,1955],{"class":130},[106,1968,1969],{"class":108,"line":390},[106,1970,138],{"emptyLinePlaceholder":137},[106,1972,1973],{"class":108,"line":407},[106,1974,138],{"emptyLinePlaceholder":137},[106,1976,1977,1979,1982],{"class":108,"line":448},[106,1978,370],{"class":119},[106,1980,1981],{"class":199}," test_depth_limit_summarises_hidden_children",[106,1983,1899],{"class":130},[106,1985,1986,1988,1990,1992,1994,1996,1998,2000,2003],{"class":108,"line":457},[106,1987,1904],{"class":130},[106,1989,246],{"class":119},[106,1991,1909],{"class":130},[106,1993,1142],{"class":123},[106,1995,17],{"class":130},[106,1997,1500],{"class":252},[106,1999,246],{"class":119},[106,2001,2002],{"class":123},"1",[106,2004,445],{"class":130},[106,2006,2007,2009,2012,2014,2016,2018,2021,2023,2025],{"class":108,"line":462},[106,2008,1918],{"class":119},[106,2010,2011],{"class":236}," \"production\"",[106,2013,1941],{"class":119},[106,2015,1944],{"class":130},[106,2017,1947],{"class":119},[106,2019,2020],{"class":236}," \"api-1\"",[106,2022,689],{"class":119},[106,2024,1941],{"class":119},[106,2026,1955],{"class":130},[106,2028,2029,2031,2034,2036],{"class":108,"line":467},[106,2030,1918],{"class":119},[106,2032,2033],{"class":236}," \"… 2 more\"",[106,2035,1941],{"class":119},[106,2037,1955],{"class":130},[106,2039,2040],{"class":108,"line":482},[106,2041,138],{"emptyLinePlaceholder":137},[106,2043,2044],{"class":108,"line":500},[106,2045,138],{"emptyLinePlaceholder":137},[106,2047,2048,2050,2053],{"class":108,"line":529},[106,2049,370],{"class":119},[106,2051,2052],{"class":199}," test_problems_only_keeps_paths_to_failures",[106,2054,1899],{"class":130},[106,2056,2057,2059,2061,2063,2065,2067,2069,2071,2074],{"class":108,"line":568},[106,2058,1904],{"class":130},[106,2060,246],{"class":119},[106,2062,1909],{"class":130},[106,2064,1142],{"class":123},[106,2066,17],{"class":130},[106,2068,1508],{"class":252},[106,2070,246],{"class":119},[106,2072,2073],{"class":123},"True",[106,2075,445],{"class":130},[106,2077,2078,2080,2083,2085,2087,2089,2092,2094],{"class":108,"line":573},[106,2079,1918],{"class":119},[106,2081,2082],{"class":236}," \"api-2\"",[106,2084,1941],{"class":119},[106,2086,1944],{"class":130},[106,2088,1947],{"class":119},[106,2090,2091],{"class":236}," \"api-1  down\"",[106,2093,1941],{"class":119},[106,2095,1955],{"class":130},[106,2097,2098,2100,2103,2105,2107],{"class":108,"line":578},[106,2099,1918],{"class":119},[106,2101,2102],{"class":236}," \"worker\"",[106,2104,689],{"class":119},[106,2106,1941],{"class":119},[106,2108,1955],{"class":130},[106,2110,2111],{"class":108,"line":621},[106,2112,138],{"emptyLinePlaceholder":137},[106,2114,2115],{"class":108,"line":640},[106,2116,138],{"emptyLinePlaceholder":137},[106,2118,2119,2121,2124],{"class":108,"line":645},[106,2120,370],{"class":119},[106,2122,2123],{"class":199}," test_piped_output_is_one_path_per_line",[106,2125,1899],{"class":130},[106,2127,2128,2131,2133,2136,2138],{"class":108,"line":667},[106,2129,2130],{"class":130},"    result ",[106,2132,246],{"class":119},[106,2134,2135],{"class":130}," CliRunner().invoke(app, [",[106,2137,1033],{"class":236},[106,2139,1280],{"class":130},[106,2141,2142,2144,2147,2150,2153,2155],{"class":108,"line":708},[106,2143,1918],{"class":119},[106,2145,2146],{"class":236}," \"acme\u002Fstaging\u002Fapi\u002Fapi-1",[106,2148,2149],{"class":123},"\\t",[106,2151,2152],{"class":236},"down\"",[106,2154,1941],{"class":119},[106,2156,2157],{"class":130}," result.stdout.splitlines()\n",[106,2159,2160,2162,2165,2167,2170,2172],{"class":108,"line":736},[106,2161,1918],{"class":119},[106,2163,2164],{"class":130}," result.stdout.splitlines() ",[106,2166,1928],{"class":119},[106,2168,2169],{"class":130}," to_lines(",[106,2171,1142],{"class":123},[106,2173,261],{"class":130},[106,2175,2176],{"class":108,"line":745},[106,2177,138],{"emptyLinePlaceholder":137},[106,2179,2180],{"class":108,"line":777},[106,2181,138],{"emptyLinePlaceholder":137},[106,2183,2184,2186,2189],{"class":108,"line":783},[106,2185,370],{"class":119},[106,2187,2188],{"class":199}," test_json_output_is_nested",[106,2190,1899],{"class":130},[106,2192,2193,2196,2198,2201,2203,2205,2207],{"class":108,"line":796},[106,2194,2195],{"class":130},"    data ",[106,2197,246],{"class":119},[106,2199,2200],{"class":130}," json.loads(CliRunner().invoke(app, [",[106,2202,1033],{"class":236},[106,2204,17],{"class":130},[106,2206,1410],{"class":236},[106,2208,2209],{"class":130},"]).stdout)\n",[106,2211,2212,2214,2217,2219,2222,2224,2226,2228,2230,2232,2234,2236,2238,2240],{"class":108,"line":820},[106,2213,1918],{"class":119},[106,2215,2216],{"class":130}," data[",[106,2218,1039],{"class":236},[106,2220,2221],{"class":130},"][",[106,2223,845],{"class":123},[106,2225,2221],{"class":130},[106,2227,1039],{"class":236},[106,2229,2221],{"class":130},[106,2231,845],{"class":123},[106,2233,2221],{"class":130},[106,2235,1027],{"class":236},[106,2237,969],{"class":130},[106,2239,1928],{"class":119},[106,2241,2242],{"class":236}," \"api\"\n",[10,2244,2245,2246,24,2249,2252,2253,2256],{},"Asserting on guide fragments such as ",[14,2247,2248],{},"├── production",[14,2250,2251],{},"└── staging"," checks ordering and nesting without depending on the exact width of every line. The piped test compares against ",[14,2254,2255],{},"to_lines"," directly, so the two output forms can never drift apart.",[48,2258,2260],{"id":2259},"conclusion","Conclusion",[10,2262,2263,2264,2266,2267,24,2270,2272],{},"Use Rich’s ",[14,2265,35],{}," whenever data is hierarchical: build it with a recursive walk over a small, Rich-independent node type, show status only when it is interesting, colour each guide by the worst status beneath it (without bold, to keep guide characters consistent), offer ",[14,2268,2269],{},"--depth",[14,2271,1605],{}," filters, print one full path per line in pipes and nested JSON on request, and test the rendered structure with a plain, fixed-width console.",[48,2274,2276],{"id":2275},"frequently-asked-questions","Frequently asked questions",[1558,2278,2280],{"id":2279},"how-do-i-show-a-directory-tree","How do I show a directory tree?",[10,2282,2283,2284,2286,2287,2291],{},"Walk it with ",[14,2285,1555],{},", sort directories first, and add a branch per entry; skip ignored files using the approach in ",[42,2288,2290],{"href":2289},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fwalking-directory-trees-with-ignore-rules\u002F","walking directory trees with ignore rules",". Add sizes as dim text in the label rather than a separate column.",[1558,2293,2295],{"id":2294},"can-a-tree-node-contain-a-table","Can a tree node contain a table?",[10,2297,2298,2299,2302,2303,2306],{},"Yes. Any renderable works as a label, so a node can hold a small ",[14,2300,2301],{},"Table"," or a ",[14,2304,2305],{},"Panel",". Use it sparingly — a tree of tables quickly becomes harder to read than either alone.",[1558,2308,2310],{"id":2309},"what-about-graphs-that-are-not-trees-like-dependencies-with-shared-children","What about graphs that are not trees, like dependencies with shared children?",[10,2312,2313,2314,24,2316,2318],{},"Draw each shared node in full the first time and as a dim reference (“requests (see above)”) afterwards, the way ",[14,2315,27],{},[14,2317,23],{}," do. Track visited nodes to avoid infinite loops on cycles.",[1558,2320,2322],{"id":2321},"how-large-can-a-rich-tree-get-before-it-is-too-slow","How large can a Rich tree get before it is too slow?",[10,2324,2325],{},"Rendering thousands of nodes is fast; reading them is not. Past a screenful, depth limits, filters or a pager serve users better than raw speed.",[1558,2327,2329],{"id":2328},"should-the-tree-go-to-stdout-or-stderr","Should the tree go to stdout or stderr?",[10,2331,2332],{},"To stdout — it is the command’s result. Only diagnostics, such as “fetched 3 environments in 1.2s”, belong on stderr.",[48,2334,2336],{"id":2335},"related","Related",[53,2338,2339,2345,2351,2357,2363],{},[56,2340,2341,2342],{},"Up: ",[42,2343,2344],{"href":44},"Interactive terminal UI with Rich",[56,2346,2347],{},[42,2348,2350],{"href":2349},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Frendering-tables-and-json-with-rich\u002F","Rendering tables and JSON with Rich",[56,2352,2353],{},[42,2354,2356],{"href":2355},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Frendering-markdown-and-syntax-highlighting-with-rich\u002F","Rendering Markdown and syntax highlighting with Rich",[56,2358,2359],{},[42,2360,2362],{"href":2361},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Flive-dashboards-with-rich-live\u002F","Live dashboards with Rich Live",[56,2364,2365],{},[42,2366,2368],{"href":2367},"\u002Fadvanced-input-parsing-user-experience\u002Foutput-formats-for-data-clis\u002Fselecting-fields-and-columns-from-cli-output\u002F","Selecting fields and columns from CLI output",[82,2370,2371],{},"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":102,"searchDepth":116,"depth":116,"links":2373},[2374,2375,2376,2381,2382,2383,2384,2391],{"id":50,"depth":116,"text":51},{"id":64,"depth":116,"text":65},{"id":94,"depth":116,"text":95,"children":2377},[2378,2379,2380],{"id":1560,"depth":134,"text":1561},{"id":1594,"depth":134,"text":1595},{"id":1615,"depth":134,"text":1616},{"id":1661,"depth":116,"text":1662},{"id":1724,"depth":116,"text":1725},{"id":2259,"depth":116,"text":2260},{"id":2275,"depth":116,"text":2276,"children":2385},[2386,2387,2388,2389,2390],{"id":2279,"depth":134,"text":2280},{"id":2294,"depth":134,"text":2295},{"id":2309,"depth":134,"text":2310},{"id":2321,"depth":134,"text":2322},{"id":2328,"depth":134,"text":2329},{"id":2335,"depth":116,"text":2336},"2026-10-02","Show hierarchical data as a Rich tree: colour-coded guides, depth limits, a problems-only view, greppable path lines in pipes, nested JSON, and tests.","beginner",false,"md",{},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Fbuilding-tree-views-with-rich",{"title":5,"description":2393},"advanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Fbuilding-tree-views-with-rich\u002Findex",[2402,16,2403,2404,2405],"rich","output","hierarchy","ux","aVKlHWvPssm8G5GVcVuG8LjGt-nSoJxi2LBTcktjBnQ",[2408,2411,2414,2417,2420,2423,2426,2429,2432,2435,2438,2441,2444,2447,2450,2453,2456,2459,2462,2465,2468,2471,2474,2477,2480,2483,2486,2489,2492,2495,2498,2501,2504,2507,2510,2513,2516,2519,2522,2525,2528,2531,2534,2537,2540,2543,2546,2549,2552,2553,2556,2559,2562,2565,2568,2571,2574,2577,2580,2583,2586,2589,2592,2595,2598,2601,2604,2607,2610,2613,2616,2619,2622,2625,2628,2631,2634,2637,2640,2643,2646,2649,2652,2655,2658,2661,2664,2667,2670,2673,2676,2679,2682,2685,2688,2691,2694,2697,2700,2703,2706,2709,2712,2715,2718,2721,2724,2727,2730,2733,2736,2739,2742,2745,2748,2751,2754,2757,2760,2763,2766,2769,2772,2775,2778,2781,2784,2787,2790,2793,2796,2799,2802,2805,2808,2811,2814,2817,2820,2823,2825,2828,2831,2834,2837,2840,2843,2846,2849,2852,2855,2858,2861,2864,2867,2870,2873,2876,2879,2882,2885,2888,2891,2894,2897,2900,2903,2906,2909,2912,2915,2918,2921,2924,2927,2930,2933,2936,2939,2942,2945,2948,2951,2954,2957,2960,2963,2966,2969,2972,2975,2978,2981,2984,2987,2990,2993,2996,2999,3002,3005,3008,3011,3014,3017,3020,3023,3026,3029,3032,3035,3038,3041,3044,3047,3050,3053,3056,3059,3062,3065,3068,3071,3074,3077,3080,3083,3086,3089,3092,3095,3098,3101,3104,3107,3110,3113,3116,3119,3122,3125,3128,3131,3134,3137,3140,3143,3146,3149,3152,3155,3158,3161,3164,3167,3170,3173,3176,3179,3182,3185,3188,3191,3194,3197,3200,3203,3206,3209,3212,3215,3218,3221,3224,3227,3230,3233,3236,3239,3242,3245,3248],{"path":2409,"title":2410},"\u002Fabout","About Python CLI Toolcraft",{"path":2412,"title":2413},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies","Advanced Argument Validation Strategies",{"path":2415,"title":2416},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fparsing-nested-json-arguments-in-python-clis","Parsing Nested JSON Args in Python CLIs",{"path":2418,"title":2419},"\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":2421,"title":2422},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fvalidating-dependent-and-conflicting-options","Validating Dependent and Conflicting CLI Options",{"path":2424,"title":2425},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fvalidating-file-and-directory-paths-in-clis","Validating File and Directory Paths in CLIs",{"path":2427,"title":2428},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fvalidating-urls-hosts-and-ports","Validating URLs, Hosts and Ports in Python CLI Arguments",{"path":2430,"title":2431},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fwriting-custom-click-parameter-types","Writing Custom Click Parameter Types",{"path":2433,"title":2434},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual\u002Fbrowsing-records-with-a-textual-datatable","Browsing Records with a Textual DataTable Picker",{"path":2436,"title":2437},"\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":2439,"title":2440},"\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":2442,"title":2443},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual","Building Terminal UIs with Textual for Python CLIs",{"path":2445,"title":2446},"\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":2448,"title":2449},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual\u002Fstyling-textual-apps-with-tcss","Styling Textual Apps with TCSS",{"path":2451,"title":2452},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual\u002Ftesting-textual-apps-with-pilot","Testing Textual Apps with Pilot",{"path":2454,"title":2455},"\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":2457,"title":2458},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fdocumenting-environment-variables-in-help","Documenting Environment Variables in CLI Help",{"path":2460,"title":2461},"\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":2463,"title":2464},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation","CLI Help Output and Documentation",{"path":2466,"title":2467},"\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":2469,"title":2470},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fversioning-and-deprecating-cli-flags","Versioning and Deprecating CLI Flags",{"path":2472,"title":2473},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fwriting-help-text-users-actually-read","Writing Help Text Users Actually Read",{"path":2475,"title":2476},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Fadapting-output-to-terminal-width","Adapting Python CLI Output to Terminal Width",{"path":2478,"title":2479},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Fdetecting-ci-environments-and-non-interactive-shells","Detecting CI Environments and Non-Interactive Shells",{"path":2481,"title":2482},"\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":2484,"title":2485},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Fhandling-ansi-escape-codes-on-windows-consoles","Handling ANSI Escape Codes on Windows Consoles",{"path":2487,"title":2488},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility","Cross-Platform Terminal Compatibility for Python CLIs",{"path":2490,"title":2491},"\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":2493,"title":2494},"\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":2496,"title":2497},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fchoosing-exit-codes-for-cli-tools","Choosing Exit Codes for CLI Tools",{"path":2499,"title":2500},"\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":2502,"title":2503},"\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":2505,"title":2506},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Ffriendly-error-messages-and-tracebacks","Friendly Error Messages and Tracebacks",{"path":2508,"title":2509},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fhandling-keyboard-interrupt-cleanly","Handling Keyboard Interrupt Cleanly",{"path":2511,"title":2512},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes","Error Handling and Exit Codes for CLIs",{"path":2514,"title":2515},"\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":2517,"title":2518},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fwriting-crash-reports-users-can-send","Writing Crash Reports Users Can Send",{"path":2520,"title":2521},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Fconfig-precedence-flags-env-files-defaults","Config Precedence: Flags, Env, Files, Defaults",{"path":2523,"title":2524},"\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":2526,"title":2527},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars","Handling Config Files and Env Vars in CLIs",{"path":2529,"title":2530},"\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":2532,"title":2533},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Freading-toml-config-with-tomllib","Reading TOML Config with tomllib in Python CLIs",{"path":2535,"title":2536},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Ftyped-settings-with-pydantic-settings","Typed Settings with pydantic-settings in Python CLIs",{"path":2538,"title":2539},"\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":2541,"title":2542},"\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":2544,"title":2545},"\u002Fadvanced-input-parsing-user-experience","Advanced Input Parsing for Python CLIs",{"path":2547,"title":2548},"\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":2550,"title":2551},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Fbuilding-interactive-prompts-and-menus","Building Interactive Prompts and Menus in Python CLIs",{"path":2398,"title":5},{"path":2554,"title":2555},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich","Interactive Terminal UI with Rich",{"path":2557,"title":2558},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Flive-dashboards-with-rich-live","Live Dashboards with Rich Live in Python CLIs",{"path":2560,"title":2561},"\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":2563,"title":2564},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Frendering-tables-and-json-with-rich","Rendering Tables and JSON with Rich",{"path":2566,"title":2567},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Ftheming-rich-output-consistently","Theming Rich Output Consistently in Python CLIs",{"path":2569,"title":2570},"\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":2572,"title":2573},"\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":2575,"title":2576},"\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":2578,"title":2579},"\u002Fadvanced-input-parsing-user-experience\u002Foutput-formats-for-data-clis","Output Formats for Data-Heavy Python CLIs",{"path":2581,"title":2582},"\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":2584,"title":2585},"\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":2587,"title":2588},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Fargcomplete-for-argparse-clis","Tab Completion for argparse CLIs with argcomplete",{"path":2590,"title":2591},"\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":2593,"title":2594},"\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":2596,"title":2597},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis","Shell Completion for Python CLIs",{"path":2599,"title":2600},"\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":2602,"title":2603},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Fshipping-completion-scripts-with-packages","Shipping Shell Completion Scripts with a Python CLI",{"path":2605,"title":2606},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Ftesting-shell-completion-in-python-clis","Testing Shell Completion in Python CLIs",{"path":2608,"title":2609},"\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":2611,"title":2612},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fadding-verbose-and-quiet-logging-flags","Adding Verbose and Quiet Logging Flags",{"path":2614,"title":2615},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps","Structured Logging for CLI Apps",{"path":2617,"title":2618},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Flogging-with-structlog-in-a-cli","Logging with structlog in a Python CLI",{"path":2620,"title":2621},"\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":2623,"title":2624},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fstructured-json-logging-in-python-clis","Structured JSON Logging in Python CLIs",{"path":2626,"title":2627},"\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":2629,"title":2630},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Fdetecting-tty-and-adapting-output","Detecting a TTY and Adapting Output",{"path":2632,"title":2633},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Femitting-json-output-for-scripting","Emitting JSON Output for Scripting",{"path":2635,"title":2636},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Fhandling-broken-pipe-and-sigpipe","Handling Broken Pipe and SIGPIPE",{"path":2638,"title":2639},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes","Working with stdin, stdout and Pipes",{"path":2641,"title":2642},"\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":2644,"title":2645},"\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":2647,"title":2648},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Freading-piped-input-in-python-clis","Reading Piped Input in Python CLIs",{"path":2650,"title":2651},"\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":2653,"title":2654},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Fdownloading-files-with-progress-in-python","Downloading Files with Progress in Python CLIs",{"path":2656,"title":2657},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis","Calling HTTP APIs from Python CLIs",{"path":2659,"title":2660},"\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":2662,"title":2663},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Foauth-device-flow-login-for-clis","OAuth Device Flow Login for Python CLIs",{"path":2665,"title":2666},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Fpaginating-api-results-in-a-cli","Paginating API Results in a Python CLI",{"path":2668,"title":2669},"\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":2671,"title":2672},"\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":2674,"title":2675},"\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":2677,"title":2678},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis","Concurrency and Async in Python CLIs",{"path":2680,"title":2681},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Fmultiprocessing-for-cpu-bound-cli-tasks","Multiprocessing for CPU-Bound CLI Tasks",{"path":2683,"title":2684},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Fparallelising-cli-work-with-thread-pools","Parallelising CLI Work with Thread Pools",{"path":2686,"title":2687},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Frate-limiting-concurrent-requests-in-clis","Rate-Limiting Concurrent Requests in Python CLIs",{"path":2689,"title":2690},"\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":2692,"title":2693},"\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":2695,"title":2696},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Fstructured-concurrency-with-taskgroups-in-clis","Structured Concurrency with TaskGroups in Python CLIs",{"path":2698,"title":2699},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fcross-platform-paths-with-pathlib","Cross-Platform Paths with pathlib in CLIs",{"path":2701,"title":2702},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Ffile-locking-for-concurrent-cli-runs","File Locking for Concurrent CLI Runs in Python",{"path":2704,"title":2705},"\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":2707,"title":2708},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes","Filesystem Paths and Atomic Writes for CLIs",{"path":2710,"title":2711},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fsafe-temporary-files-and-directories","Safe Temporary Files and Directories in CLIs",{"path":2713,"title":2714},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fstoring-app-data-with-platformdirs","Storing CLI App Data with platformdirs",{"path":2716,"title":2717},"\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":2719,"title":2720},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fwriting-files-atomically-in-python-clis","Writing Files Atomically in Python CLIs",{"path":2722,"title":2723},"\u002Fcli-runtime-systems-integration","CLI Runtime & Systems Integration for Python",{"path":2725,"title":2726},"\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":2728,"title":2729},"\u002Fcli-runtime-systems-integration\u002Flocal-state-and-sqlite-in-python-clis","Local State and SQLite in Python CLIs",{"path":2731,"title":2732},"\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":2734,"title":2735},"\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":2737,"title":2738},"\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":2740,"title":2741},"\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":2743,"title":2744},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis\u002Fhandling-sigterm-and-graceful-shutdown","Handling SIGTERM and Graceful Shutdown in CLIs",{"path":2746,"title":2747},"\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":2749,"title":2750},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis","Long-Running and Watch-Mode Python CLIs",{"path":2752,"title":2753},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis\u002Freloading-config-on-sighup","Reloading Configuration on SIGHUP in a Python CLI",{"path":2755,"title":2756},"\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":2758,"title":2759},"\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":2761,"title":2762},"\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":2764,"title":2765},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Favoiding-shell-injection-in-python-clis","Avoiding Shell Injection in Python CLIs",{"path":2767,"title":2768},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fcalling-external-commands-safely-with-subprocess","Calling External Commands Safely with subprocess",{"path":2770,"title":2771},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fhandling-subprocess-timeouts-and-exit-codes","Handling Subprocess Timeouts and Exit Codes",{"path":2773,"title":2774},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis","Running Subprocesses from Python CLIs",{"path":2776,"title":2777},"\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":2779,"title":2780},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fpiping-between-subprocesses-in-python","Piping Between Subprocesses in Python Without a Shell",{"path":2782,"title":2783},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fstreaming-subprocess-output-in-real-time","Streaming Subprocess Output in Real Time",{"path":2785,"title":2786},"\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":2788,"title":2789},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis","Secrets and Credentials in Python CLIs",{"path":2791,"title":2792},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Fprompting-for-passwords-securely","Prompting for Passwords Securely in Python CLIs",{"path":2794,"title":2795},"\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":2797,"title":2798},"\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":2800,"title":2801},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Frefreshing-expired-tokens-automatically","Refreshing Expired Tokens Automatically in a Python CLI",{"path":2803,"title":2804},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Fstoring-tokens-with-keyring","Storing CLI Tokens Securely with keyring",{"path":2806,"title":2807},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Fsupporting-multiple-profiles-and-accounts","Supporting Multiple Profiles and Accounts in CLIs",{"path":2809,"title":2810},"\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":2812,"title":2813},"\u002Fcli-runtime-systems-integration\u002Fupdate-checks-and-self-updating-clis","Update Checks, Upgrade Commands and Telemetry for Python CLIs",{"path":2815,"title":2816},"\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":2818,"title":2819},"\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":2821,"title":2822},"\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":920,"title":2824},"Python CLI Toolcraft",{"path":2826,"title":2827},"\u002Fmodern-python-cli-frameworks-architecture\u002Falternative-python-cli-frameworks\u002Fbuilding-a-cli-with-cleo","Building a Python CLI with Cleo Command Classes",{"path":2829,"title":2830},"\u002Fmodern-python-cli-frameworks-architecture\u002Falternative-python-cli-frameworks\u002Fbuilding-a-cli-with-cyclopts","Building a Type-Hinted Python CLI with Cyclopts",{"path":2832,"title":2833},"\u002Fmodern-python-cli-frameworks-architecture\u002Falternative-python-cli-frameworks","Beyond Click and Typer: Alternative Python CLI Frameworks",{"path":2835,"title":2836},"\u002Fmodern-python-cli-frameworks-architecture\u002Falternative-python-cli-frameworks\u002Fquick-clis-from-functions-with-python-fire","Quick CLIs from Functions with Python Fire",{"path":2838,"title":2839},"\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":2841,"title":2842},"\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":2844,"title":2845},"\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":2847,"title":2848},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Fguarding-startup-with-import-tests","Guarding CLI Startup with Import Tests",{"path":2850,"title":2851},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading","CLI Startup Performance and Lazy Loading",{"path":2853,"title":2854},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Flazy-loading-subcommands-for-faster-startup","Lazy Loading Subcommands for Faster Startup",{"path":2856,"title":2857},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Fprofiling-python-cli-startup-time","Profiling Python CLI Startup Time",{"path":2859,"title":2860},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Freducing-cli-dependency-weight","Reducing CLI Dependency Weight",{"path":2862,"title":2863},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargparse-subparsers-for-subcommands","argparse Subparsers for Subcommands",{"path":2865,"title":2866},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargparse-vs-click-vs-typer-comparison","argparse vs Click vs Typer Compared",{"path":2868,"title":2869},"\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":2871,"title":2872},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse","Command-Line Parsing with argparse",{"path":2874,"title":2875},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fmigrating-from-argparse-to-typer","Migrating from argparse to Typer",{"path":2877,"title":2878},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fmutually-exclusive-options-in-argparse","Mutually Exclusive Options in argparse",{"path":2880,"title":2881},"\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":2883,"title":2884},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fwriting-custom-argparse-actions","Writing Custom argparse Actions in Python",{"path":2886,"title":2887},"\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":2889,"title":2890},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Fdesigning-idempotent-commands","Designing Idempotent Commands in a Python CLI",{"path":2892,"title":2893},"\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":2895,"title":2896},"\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":2898,"title":2899},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions","Designing CLI Interfaces and Conventions in Python",{"path":2901,"title":2902},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Fnaming-commands-and-flags-consistently","Naming Commands and Flags Consistently in Python CLIs",{"path":2904,"title":2905},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Fpositional-arguments-vs-options","Positional Arguments vs Options: Designing a CLI Signature",{"path":2907,"title":2908},"\u002Fmodern-python-cli-frameworks-architecture","Python CLI Frameworks and Architecture",{"path":2910,"title":2911},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fdiscovering-plugins-with-entry-points","Discovering Plugins with Entry Points in Python CLIs",{"path":2913,"title":2914},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fhook-based-plugins-with-pluggy","Hook-Based Plugins for Python CLIs with pluggy",{"path":2916,"title":2917},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis","Plugin Architectures for Extensible CLIs",{"path":2919,"title":2920},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fisolating-plugin-failures","Isolating Plugin Failures in an Extensible Python CLI",{"path":2922,"title":2923},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Ftesting-plugins-against-the-host-cli","Testing Plugins Against the Host CLI",{"path":2925,"title":2926},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fversioning-a-plugin-api","Versioning a Plugin API for a Python CLI",{"path":2928,"title":2929},"\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":2931,"title":2932},"\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":2934,"title":2935},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fdependency-injection-patterns-for-cli-commands","Dependency Injection Patterns for CLI Commands",{"path":2937,"title":2938},"\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":2940,"title":2941},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis","Structuring Multi-Command Python CLIs",{"path":2943,"title":2944},"\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":2946,"title":2947},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fregistering-commands-from-modules-automatically","Registering CLI Commands from Modules Automatically",{"path":2949,"title":2950},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fsharing-common-options-across-commands","Sharing Common Options Across Python CLI Commands",{"path":2952,"title":2953},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fsharing-state-with-click-context-objects","Sharing State with Click Context Objects",{"path":2955,"title":2956},"\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":2958,"title":2959},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications","Testing Python CLI Applications",{"path":2961,"title":2962},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fmeasuring-cli-test-coverage","Measuring CLI Test Coverage",{"path":2964,"title":2965},"\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":2967,"title":2968},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fproperty-based-testing-cli-arguments-with-hypothesis","Property-Based Testing CLI Arguments with Hypothesis",{"path":2970,"title":2971},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fsnapshot-testing-cli-output","Snapshot Testing CLI Output",{"path":2973,"title":2974},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftesting-click-commands-with-clirunner","Testing Click Commands with CliRunner",{"path":2976,"title":2977},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftesting-commands-that-call-subprocesses","Testing Python CLI Commands That Call Subprocesses",{"path":2979,"title":2980},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftesting-interactive-prompts-and-stdin","Testing Interactive Prompts and stdin",{"path":2982,"title":2983},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftranscript-tests-for-cli-commands","Transcript Tests for Python CLI Commands",{"path":2985,"title":2986},"\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":2988,"title":2989},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Fbuilding-dynamic-commands-in-click","Building Dynamic Commands in Click",{"path":2991,"title":2992},"\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":2994,"title":2995},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Fclick-option-callbacks-and-eager-options","Click Option Callbacks and Eager Options Explained",{"path":2997,"title":2998},"\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":3000,"title":3001},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each","Typer vs Click: When to Use Each",{"path":3003,"title":3004},"\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":3006,"title":3007},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Ftyper-callback-functions-explained","Typer callback functions explained",{"path":3009,"title":3010},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Fusing-annotated-options-in-typer","Using Annotated Options in Typer",{"path":3012,"title":3013},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis\u002Fautomating-releases-from-git-tags","Automating Python CLI Releases from Git Tags",{"path":3015,"title":3016},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis\u002Fcaching-uv-dependencies-in-ci","Caching uv Dependencies in CI for Python CLIs",{"path":3018,"title":3019},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis","CI\u002FCD Pipelines for Python CLIs",{"path":3021,"title":3022},"\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":3024,"title":3025},"\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":3027,"title":3028},"\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":3030,"title":3031},"\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":3033,"title":3034},"\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":3036,"title":3037},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fbuilding-a-cookiecutter-template-for-typer-clis","Building a Cookiecutter Template for Typer CLIs",{"path":3039,"title":3040},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fcopier-vs-cookiecutter-for-cli-templates","Copier vs Cookiecutter for CLI Templates",{"path":3042,"title":3043},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter","CLI Project Scaffolding with Cookiecutter",{"path":3045,"title":3046},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fpost-generation-hooks-in-cli-templates","Post-Generation Hooks in Python CLI Templates",{"path":3048,"title":3049},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Ftemplate-variables-and-conditional-files","Template Variables and Conditional Files in CLI Templates",{"path":3051,"title":3052},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Ftesting-a-project-template-with-pytest","Testing a CLI Project Template with pytest",{"path":3054,"title":3055},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fupdating-generated-projects-with-copier-update","Updating Generated CLI Projects with copier update",{"path":3057,"title":3058},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fbuilding-cross-platform-release-binaries-in-ci","Building Cross-Platform Release Binaries in CI",{"path":3060,"title":3061},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fbundling-a-python-cli-with-pyinstaller","Bundling a Python CLI with PyInstaller",{"path":3063,"title":3064},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fcode-signing-and-notarizing-cli-binaries","Code Signing and Notarizing Python CLI Binaries",{"path":3066,"title":3067},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fhomebrew-and-scoop-packaging-for-python-clis","Homebrew and Scoop Packaging for Python CLIs",{"path":3069,"title":3070},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries","Distributing CLIs as Standalone Binaries",{"path":3072,"title":3073},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fnuitka-vs-pyinstaller-for-python-clis","Nuitka vs PyInstaller for Python CLI Binaries",{"path":3075,"title":3076},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Freducing-pyinstaller-binary-size","Reducing the Size of a PyInstaller CLI Binary",{"path":3078,"title":3079},"\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":3081,"title":3082},"\u002Fproject-setup-dependency-management","Project Setup & Dependency Management",{"path":3084,"title":3085},"\u002Fproject-setup-dependency-management\u002Flinting-and-type-checking-cli-code\u002Fconfiguring-ruff-for-a-cli-project","Configuring Ruff for a Python CLI Project",{"path":3087,"title":3088},"\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":3090,"title":3091},"\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":3093,"title":3094},"\u002Fproject-setup-dependency-management\u002Flinting-and-type-checking-cli-code","Linting and Type-Checking Python CLI Code",{"path":3096,"title":3097},"\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":3099,"title":3100},"\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":3102,"title":3103},"\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":3105,"title":3106},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fautomating-changelogs-with-conventional-commits","Automating Changelogs with Conventional Commits",{"path":3108,"title":3109},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fbumping-versions-consistently-across-a-cli-project","Bumping Versions Consistently Across a Python CLI Project",{"path":3111,"title":3112},"\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":3114,"title":3115},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fexposing-version-info-and-build-metadata","Exposing Version Info and Build Metadata",{"path":3117,"title":3118},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs","Managing CLI Versioning & Changelogs",{"path":3120,"title":3121},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fsemantic-versioning-policy-for-cli-tools","A Semantic Versioning Policy for CLI Tools",{"path":3123,"title":3124},"\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":3126,"title":3127},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fbuilding-wheels-and-sdists-for-python-clis","Building Wheels and sdists for Python CLIs",{"path":3129,"title":3130},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fbundling-data-files-with-importlib-resources","Bundling Data Files with importlib.resources in CLIs",{"path":3132,"title":3133},"\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":3135,"title":3136},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution","Packaging Python CLIs for Distribution",{"path":3138,"title":3139},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Finstalling-and-distributing-clis-with-pipx","Installing and Distributing CLIs with pipx",{"path":3141,"title":3142},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Foptional-dependencies-and-extras-for-clis","Optional Dependencies and Extras for Python CLIs",{"path":3144,"title":3145},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fpublishing-a-python-cli-to-pypi","Publishing a Python CLI to PyPI",{"path":3147,"title":3148},"\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":3150,"title":3151},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fdynamic-versioning-with-poetry-plugins","Dynamic Versioning for a Poetry CLI from Git Tags",{"path":3153,"title":3154},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development","Poetry Workflows for CLI Development",{"path":3156,"title":3157},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fmanaging-poetry-lock-files-for-cli-tools","Managing Poetry Lock Files for CLI Tools",{"path":3159,"title":3160},"\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":3162,"title":3163},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpoetry-dependency-groups-for-cli-tooling","Poetry Dependency Groups for CLI Tooling",{"path":3165,"title":3166},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpoetry-entry-points-and-scripts-for-clis","Poetry Entry Points and Scripts for CLIs",{"path":3168,"title":3169},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpublishing-a-cli-with-poetry","Building and Publishing a Python CLI with Poetry",{"path":3171,"title":3172},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects","Pre-commit Hooks for CLI Projects",{"path":3174,"title":3175},"\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":3177,"title":3178},"\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":3180,"title":3181},"\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":3183,"title":3184},"\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":3186,"title":3187},"\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":3189,"title":3190},"\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":3192,"title":3193},"\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":3195,"title":3196},"\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":3198,"title":3199},"\u002Fproject-setup-dependency-management\u002Fsecuring-a-python-cli-supply-chain","Securing the Supply Chain of a Python CLI",{"path":3201,"title":3202},"\u002Fproject-setup-dependency-management\u002Fsecuring-a-python-cli-supply-chain\u002Fpinning-dependencies-with-hashes","Pinning a Python CLI’s Dependencies with Hashes",{"path":3204,"title":3205},"\u002Fproject-setup-dependency-management\u002Fsecuring-a-python-cli-supply-chain\u002Fpublishing-attestations-and-verifying-releases","Publishing Attestations and Verifying Python CLI Releases",{"path":3207,"title":3208},"\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":3210,"title":3211},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management","uv for Python CLI Dependency Management",{"path":3213,"title":3214},"\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":3216,"title":3217},"\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":3219,"title":3220},"\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":3222,"title":3223},"\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":3225,"title":3226},"\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":3228,"title":3229},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002Fuv-workspaces-for-multi-package-clis","uv Workspaces for Multi-Package Python CLIs",{"path":3231,"title":3232},"\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":3234,"title":3235},"\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":3237,"title":3238},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices","Python CLI Env Isolation Best Practices",{"path":3240,"title":3241},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fisolating-cli-tools-from-project-dependencies","Isolating CLI Tools from Your Project’s Dependencies",{"path":3243,"title":3244},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fmanaging-virtual-environments-for-cross-platform-clis","Managing Python CLI Virtual Environments",{"path":3246,"title":3247},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fpinning-the-python-version-for-a-cli","Pinning the Python Version for a CLI",{"path":3249,"title":3250},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fsupporting-multiple-python-versions-with-nox","Supporting Multiple Python Versions with nox",1790967537211]