[{"data":1,"prerenderedAt":2530},["ShallowReactive",2],{"page-\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fisolating-cli-tools-from-project-dependencies\u002F":3,"content-directory":1687},{"id":4,"title":5,"body":6,"date":1673,"description":1674,"difficulty":1675,"draft":1676,"extension":1677,"meta":1678,"navigation":472,"path":1679,"seo":1680,"stem":1681,"tags":1682,"updated":1673,"__hash__":1686},"content\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fisolating-cli-tools-from-project-dependencies\u002Findex.md","Isolating CLI Tools from Your Project’s Dependencies",{"type":7,"value":8,"toc":1651},"minimark",[9,33,38,51,55,59,76,90,112,119,123,128,198,235,246,250,308,328,332,399,418,421,425,436,948,966,974,978,1034,1037,1041,1044,1488,1496,1500,1513,1517,1521,1527,1536,1554,1558,1568,1572,1581,1585,1611,1615,1647],[10,11,12,13,17,18,21,22,26,27,32],"p",{},"Every Python developer runs two kinds of software from the command line. There is the ",[14,15,16],"strong",{},"project"," — the CLI you are building, its dependencies and its tests — and there are ",[14,19,20],{},"tools",": Ruff, mypy, pre-commit, httpie, the AWS CLI, cookiecutter, your own team's deploy script. Mixing them causes a slow trickle of strange problems. A tool installed into the project's virtual environment pins a version of ",[23,24,25],"code",{},"rich"," that conflicts with yours. A globally installed tool upgrades and the project's formatting changes for one developer only. A command that \"works on my machine\" turns out to come from a different environment than everyone assumed. The fix is to decide, for each tool, which of three homes it belongs in — and to be able to check which home a command actually came from. This guide covers both. It belongs to the ",[28,29,31],"a",{"href":30},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002F","virtual environments topic",".",[34,35,37],"h2",{"id":36},"prerequisites","Prerequisites",[39,40,41,45],"ul",{},[42,43,44],"li",{},"uv (and optionally pipx) installed.",[42,46,47,48,32],{},"A CLI project with a virtual environment, for example created by ",[23,49,50],{},"uv sync",[34,52,54],{"id":53},"three-homes-for-a-tool","Three homes for a tool",[56,57],"inline-diagram",{"name":58},"iso-homes",[10,60,61,64,65,68,69,72,73,32],{},[14,62,63],{},"1. The project's dependency groups"," — for tools that must import your code or see your dependencies. pytest imports your package; mypy and Pyright need your dependencies to resolve types; a docs generator that renders your CLI's help imports your commands. These belong in ",[23,66,67],{},"[dependency-groups]"," (",[23,70,71],{},"uv add --dev pytest mypy","), locked with everything else, and run with ",[23,74,75],{},"uv run",[10,77,78,81,82,85,86,89],{},[14,79,80],{},"2. A pinned, ephemeral run"," — for tools that only read files and should match a version the project chooses. Ruff, a Markdown linter, ",[23,83,84],{},"pip-audit",", a YAML formatter: ",[23,87,88],{},"uvx ruff@0.16.10 check ."," runs that exact version from a cached, isolated environment without installing anything into the project. The version lives in the command (or in the pre-commit configuration), so every developer and CI use the same one.",[10,91,92,95,96,99,100,103,104,107,108,111],{},[14,93,94],{},"3. An isolated, installed tool"," — for tools you use across many projects and want on your ",[23,97,98],{},"PATH"," permanently: httpie, the cloud CLIs written in Python, cookiecutter, your team's internal tools. ",[23,101,102],{},"uv tool install httpie"," or ",[23,105,106],{},"pipx install httpie"," gives each its own virtual environment and links its executables into ",[23,109,110],{},"~\u002F.local\u002Fbin",". These are personal; the project should never depend on them being present.",[10,113,114,115,118],{},"The deciding question is simple: ",[14,116,117],{},"does the tool need to import the project?"," If yes, home 1. If no but the project cares about the exact version, home 2. Otherwise, home 3.",[34,120,122],{"id":121},"the-recipe","The recipe",[124,125,127],"h3",{"id":126},"project-coupled-tools-in-groups","Project-coupled tools in groups",[129,130,135],"pre",{"className":131,"code":132,"language":133,"meta":134,"style":134},"language-toml shiki shiki-themes github-light github-dark","[dependency-groups]\ndev = [\"pytest>=8\", \"pytest-cov>=5\"]\nlint = [\"mypy>=1.11\"]\ndocs = [\"mkdocs-material>=9\", \"mkdocs-click>=0.8\"]\n","toml","",[23,136,137,153,171,182],{"__ignoreMap":134},[138,139,142,146,150],"span",{"class":140,"line":141},"line",1,[138,143,145],{"class":144},"sVt8B","[",[138,147,149],{"class":148},"sScJk","dependency-groups",[138,151,152],{"class":144},"]\n",[138,154,156,159,163,166,169],{"class":140,"line":155},2,[138,157,158],{"class":144},"dev = [",[138,160,162],{"class":161},"sZZnC","\"pytest>=8\"",[138,164,165],{"class":144},", ",[138,167,168],{"class":161},"\"pytest-cov>=5\"",[138,170,152],{"class":144},[138,172,174,177,180],{"class":140,"line":173},3,[138,175,176],{"class":144},"lint = [",[138,178,179],{"class":161},"\"mypy>=1.11\"",[138,181,152],{"class":144},[138,183,185,188,191,193,196],{"class":140,"line":184},4,[138,186,187],{"class":144},"docs = [",[138,189,190],{"class":161},"\"mkdocs-material>=9\"",[138,192,165],{"class":144},[138,194,195],{"class":161},"\"mkdocs-click>=0.8\"",[138,197,152],{"class":144},[129,199,203],{"className":200,"code":201,"language":202,"meta":134,"style":134},"language-bash shiki shiki-themes github-light github-dark","uv run pytest\nuv run --group lint mypy src\n","bash",[23,204,205,216],{"__ignoreMap":134},[138,206,207,210,213],{"class":140,"line":141},[138,208,209],{"class":148},"uv",[138,211,212],{"class":161}," run",[138,214,215],{"class":161}," pytest\n",[138,217,218,220,222,226,229,232],{"class":140,"line":155},[138,219,209],{"class":148},[138,221,212],{"class":161},[138,223,225],{"class":224},"sj4cs"," --group",[138,227,228],{"class":161}," lint",[138,230,231],{"class":161}," mypy",[138,233,234],{"class":161}," src\n",[10,236,237,238,241,242,32],{},"Groups keep these tools out of the dependencies users install, while letting them see exactly the dependency versions in ",[23,239,240],{},"uv.lock"," — see ",[28,243,245],{"href":244},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002Flocking-and-syncing-cli-dependencies-with-uv\u002F","locking and syncing CLI dependencies with uv",[124,247,249],{"id":248},"version-pinned-runs-with-uvx","Version-pinned runs with uvx",[129,251,253],{"className":200,"code":252,"language":202,"meta":134,"style":134},"uvx ruff@0.16.10 check .\nuvx --from 'pip-audit==2.10.1' pip-audit -r requirements.txt\nuvx --python 3.12 --from cookiecutter cookiecutter gh:acme\u002Fcli-template\n",[23,254,255,269,288],{"__ignoreMap":134},[138,256,257,260,263,266],{"class":140,"line":141},[138,258,259],{"class":148},"uvx",[138,261,262],{"class":161}," ruff@0.16.10",[138,264,265],{"class":161}," check",[138,267,268],{"class":161}," .\n",[138,270,271,273,276,279,282,285],{"class":140,"line":155},[138,272,259],{"class":148},[138,274,275],{"class":224}," --from",[138,277,278],{"class":161}," 'pip-audit==2.10.1'",[138,280,281],{"class":161}," pip-audit",[138,283,284],{"class":224}," -r",[138,286,287],{"class":161}," requirements.txt\n",[138,289,290,292,295,298,300,303,305],{"class":140,"line":173},[138,291,259],{"class":148},[138,293,294],{"class":224}," --python",[138,296,297],{"class":224}," 3.12",[138,299,275],{"class":224},[138,301,302],{"class":161}," cookiecutter",[138,304,302],{"class":161},[138,306,307],{"class":161}," gh:acme\u002Fcli-template\n",[10,309,310,312,313,316,317,165,320,323,324,32],{},[23,311,259],{}," (an alias for ",[23,314,315],{},"uv tool run",") resolves the tool into a cached environment keyed by its requirements, so the first run downloads and later runs start instantly. Put the pinned commands in a ",[23,318,319],{},"Makefile",[23,321,322],{},"justfile"," or task runner so nobody types the version by hand — and keep them consistent with the pre-commit configuration, as discussed in ",[28,325,327],{"href":326},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects\u002Fkeeping-hook-versions-current-with-autoupdate\u002F","keeping hook versions current with autoupdate",[124,329,331],{"id":330},"personal-tools-installed-once","Personal tools installed once",[129,333,335],{"className":200,"code":334,"language":202,"meta":134,"style":134},"uv tool install httpie\nuv tool install 'mytool[yaml]' --with mytool-aws-plugin     # a tool plus a plugin\npipx install --suffix=@1 'mytool\u003C2'                          # pipx: keep an old major version alongside\nuv tool list --show-paths\n",[23,336,337,350,371,387],{"__ignoreMap":134},[138,338,339,341,344,347],{"class":140,"line":141},[138,340,209],{"class":148},[138,342,343],{"class":161}," tool",[138,345,346],{"class":161}," install",[138,348,349],{"class":161}," httpie\n",[138,351,352,354,356,358,361,364,367],{"class":140,"line":155},[138,353,209],{"class":148},[138,355,343],{"class":161},[138,357,346],{"class":161},[138,359,360],{"class":161}," 'mytool[yaml]'",[138,362,363],{"class":224}," --with",[138,365,366],{"class":161}," mytool-aws-plugin",[138,368,370],{"class":369},"sJ8bj","     # a tool plus a plugin\n",[138,372,373,376,378,381,384],{"class":140,"line":173},[138,374,375],{"class":148},"pipx",[138,377,346],{"class":161},[138,379,380],{"class":224}," --suffix=@1",[138,382,383],{"class":161}," 'mytool\u003C2'",[138,385,386],{"class":369},"                          # pipx: keep an old major version alongside\n",[138,388,389,391,393,396],{"class":140,"line":184},[138,390,209],{"class":148},[138,392,343],{"class":161},[138,394,395],{"class":161}," list",[138,397,398],{"class":224}," --show-paths\n",[10,400,401,404,405,409,410,413,414,417],{},[23,402,403],{},"--with"," adds packages into the tool's environment — the right way to add plugins to an isolated tool, as described in ",[28,406,408],{"href":407},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fwriting-a-plugin-for-an-existing-cli\u002F","writing a plugin for an existing CLI",". pipx's ",[23,411,412],{},"--suffix"," installs a second copy under a different command name (",[23,415,416],{},"mytool@1","), which is useful while migrating scripts between major versions.",[56,419],{"name":420},"iso-terminal",[34,422,424],{"id":423},"which-environment-did-that-command-come-from","Which environment did that command come from?",[10,426,427,428,431,432,435],{},"When behaviour differs between machines, the first question is which copy of a command ran. ",[23,429,430],{},"which"," gives a path, but not whether that path is a project environment, a pipx tool, a uv tool or the system. This small script answers it by looking for the ",[23,433,434],{},"pyvenv.cfg"," that every virtual environment has, plus the markers pipx and uv leave behind:",[129,437,441],{"className":438,"code":439,"language":440,"meta":134,"style":134},"language-python shiki shiki-themes github-light github-dark","# scripts\u002Fwhich_env.py\n\"\"\"Report which Python environment a command on PATH belongs to.\"\"\"\nfrom __future__ import annotations\n\nimport shutil\nimport sys\nfrom pathlib import Path\n\n\ndef environment_of(executable: Path) -> tuple[str, Path | None]:\n    # Check the path as found first (a venv's python is a symlink out of the venv),\n    # then the resolved target (tool managers symlink executables into ~\u002F.local\u002Fbin).\n    for parent in [*executable.absolute().parents[:2], *executable.resolve().parents]:\n        if (parent \u002F \"pyvenv.cfg\").is_file():\n            if (parent \u002F \"pipx_metadata.json\").is_file():\n                return \"pipx tool\", parent\n            if (parent \u002F \"uv-receipt.toml\").is_file():\n                return \"uv tool\", parent\n            return \"virtual environment\", parent\n    return \"system or other\", None\n\n\ndef main(names: list[str]) -> int:\n    status = 0\n    for name in names:\n        found = shutil.which(name)\n        if found is None:\n            print(f\"{name}: not on PATH\")\n            status = 1\n            continue\n        kind, env = environment_of(Path(found))\n        print(f\"{name}: {found} -> {kind}\" + (f\" ({env})\" if env else \"\"))\n    return status\n\n\nif __name__ == \"__main__\":\n    raise SystemExit(main(sys.argv[1:]))\n","python",[23,442,443,448,453,468,474,483,491,504,509,514,541,547,553,585,603,618,630,644,654,665,679,684,689,711,723,736,747,762,792,803,809,820,895,903,908,913,930],{"__ignoreMap":134},[138,444,445],{"class":140,"line":141},[138,446,447],{"class":369},"# scripts\u002Fwhich_env.py\n",[138,449,450],{"class":140,"line":155},[138,451,452],{"class":161},"\"\"\"Report which Python environment a command on PATH belongs to.\"\"\"\n",[138,454,455,459,462,465],{"class":140,"line":173},[138,456,458],{"class":457},"szBVR","from",[138,460,461],{"class":224}," __future__",[138,463,464],{"class":457}," import",[138,466,467],{"class":144}," annotations\n",[138,469,470],{"class":140,"line":184},[138,471,473],{"emptyLinePlaceholder":472},true,"\n",[138,475,477,480],{"class":140,"line":476},5,[138,478,479],{"class":457},"import",[138,481,482],{"class":144}," shutil\n",[138,484,486,488],{"class":140,"line":485},6,[138,487,479],{"class":457},[138,489,490],{"class":144}," sys\n",[138,492,494,496,499,501],{"class":140,"line":493},7,[138,495,458],{"class":457},[138,497,498],{"class":144}," pathlib ",[138,500,479],{"class":457},[138,502,503],{"class":144}," Path\n",[138,505,507],{"class":140,"line":506},8,[138,508,473],{"emptyLinePlaceholder":472},[138,510,512],{"class":140,"line":511},9,[138,513,473],{"emptyLinePlaceholder":472},[138,515,517,520,523,526,529,532,535,538],{"class":140,"line":516},10,[138,518,519],{"class":457},"def",[138,521,522],{"class":148}," environment_of",[138,524,525],{"class":144},"(executable: Path) -> tuple[",[138,527,528],{"class":224},"str",[138,530,531],{"class":144},", Path ",[138,533,534],{"class":457},"|",[138,536,537],{"class":224}," None",[138,539,540],{"class":144},"]:\n",[138,542,544],{"class":140,"line":543},11,[138,545,546],{"class":369},"    # Check the path as found first (a venv's python is a symlink out of the venv),\n",[138,548,550],{"class":140,"line":549},12,[138,551,552],{"class":369},"    # then the resolved target (tool managers symlink executables into ~\u002F.local\u002Fbin).\n",[138,554,556,559,562,565,568,571,574,577,580,582],{"class":140,"line":555},13,[138,557,558],{"class":457},"    for",[138,560,561],{"class":144}," parent ",[138,563,564],{"class":457},"in",[138,566,567],{"class":144}," [",[138,569,570],{"class":457},"*",[138,572,573],{"class":144},"executable.absolute().parents[:",[138,575,576],{"class":224},"2",[138,578,579],{"class":144},"], ",[138,581,570],{"class":457},[138,583,584],{"class":144},"executable.resolve().parents]:\n",[138,586,588,591,594,597,600],{"class":140,"line":587},14,[138,589,590],{"class":457},"        if",[138,592,593],{"class":144}," (parent ",[138,595,596],{"class":457},"\u002F",[138,598,599],{"class":161}," \"pyvenv.cfg\"",[138,601,602],{"class":144},").is_file():\n",[138,604,606,609,611,613,616],{"class":140,"line":605},15,[138,607,608],{"class":457},"            if",[138,610,593],{"class":144},[138,612,596],{"class":457},[138,614,615],{"class":161}," \"pipx_metadata.json\"",[138,617,602],{"class":144},[138,619,621,624,627],{"class":140,"line":620},16,[138,622,623],{"class":457},"                return",[138,625,626],{"class":161}," \"pipx tool\"",[138,628,629],{"class":144},", parent\n",[138,631,633,635,637,639,642],{"class":140,"line":632},17,[138,634,608],{"class":457},[138,636,593],{"class":144},[138,638,596],{"class":457},[138,640,641],{"class":161}," \"uv-receipt.toml\"",[138,643,602],{"class":144},[138,645,647,649,652],{"class":140,"line":646},18,[138,648,623],{"class":457},[138,650,651],{"class":161}," \"uv tool\"",[138,653,629],{"class":144},[138,655,657,660,663],{"class":140,"line":656},19,[138,658,659],{"class":457},"            return",[138,661,662],{"class":161}," \"virtual environment\"",[138,664,629],{"class":144},[138,666,668,671,674,676],{"class":140,"line":667},20,[138,669,670],{"class":457},"    return",[138,672,673],{"class":161}," \"system or other\"",[138,675,165],{"class":144},[138,677,678],{"class":224},"None\n",[138,680,682],{"class":140,"line":681},21,[138,683,473],{"emptyLinePlaceholder":472},[138,685,687],{"class":140,"line":686},22,[138,688,473],{"emptyLinePlaceholder":472},[138,690,692,694,697,700,702,705,708],{"class":140,"line":691},23,[138,693,519],{"class":457},[138,695,696],{"class":148}," main",[138,698,699],{"class":144},"(names: list[",[138,701,528],{"class":224},[138,703,704],{"class":144},"]) -> ",[138,706,707],{"class":224},"int",[138,709,710],{"class":144},":\n",[138,712,714,717,720],{"class":140,"line":713},24,[138,715,716],{"class":144},"    status ",[138,718,719],{"class":457},"=",[138,721,722],{"class":224}," 0\n",[138,724,726,728,731,733],{"class":140,"line":725},25,[138,727,558],{"class":457},[138,729,730],{"class":144}," name ",[138,732,564],{"class":457},[138,734,735],{"class":144}," names:\n",[138,737,739,742,744],{"class":140,"line":738},26,[138,740,741],{"class":144},"        found ",[138,743,719],{"class":457},[138,745,746],{"class":144}," shutil.which(name)\n",[138,748,750,752,755,758,760],{"class":140,"line":749},27,[138,751,590],{"class":457},[138,753,754],{"class":144}," found ",[138,756,757],{"class":457},"is",[138,759,537],{"class":224},[138,761,710],{"class":144},[138,763,765,768,771,774,777,780,783,786,789],{"class":140,"line":764},28,[138,766,767],{"class":224},"            print",[138,769,770],{"class":144},"(",[138,772,773],{"class":457},"f",[138,775,776],{"class":161},"\"",[138,778,779],{"class":224},"{",[138,781,782],{"class":144},"name",[138,784,785],{"class":224},"}",[138,787,788],{"class":161},": not on PATH\"",[138,790,791],{"class":144},")\n",[138,793,795,798,800],{"class":140,"line":794},29,[138,796,797],{"class":144},"            status ",[138,799,719],{"class":457},[138,801,802],{"class":224}," 1\n",[138,804,806],{"class":140,"line":805},30,[138,807,808],{"class":457},"            continue\n",[138,810,812,815,817],{"class":140,"line":811},31,[138,813,814],{"class":144},"        kind, env ",[138,816,719],{"class":457},[138,818,819],{"class":144}," environment_of(Path(found))\n",[138,821,823,826,828,830,832,834,836,838,841,843,846,848,851,853,856,858,860,863,865,867,870,872,875,877,880,883,886,889,892],{"class":140,"line":822},32,[138,824,825],{"class":224},"        print",[138,827,770],{"class":144},[138,829,773],{"class":457},[138,831,776],{"class":161},[138,833,779],{"class":224},[138,835,782],{"class":144},[138,837,785],{"class":224},[138,839,840],{"class":161},": ",[138,842,779],{"class":224},[138,844,845],{"class":144},"found",[138,847,785],{"class":224},[138,849,850],{"class":161}," -> ",[138,852,779],{"class":224},[138,854,855],{"class":144},"kind",[138,857,785],{"class":224},[138,859,776],{"class":161},[138,861,862],{"class":457}," +",[138,864,68],{"class":144},[138,866,773],{"class":457},[138,868,869],{"class":161},"\" (",[138,871,779],{"class":224},[138,873,874],{"class":144},"env",[138,876,785],{"class":224},[138,878,879],{"class":161},")\"",[138,881,882],{"class":457}," if",[138,884,885],{"class":144}," env ",[138,887,888],{"class":457},"else",[138,890,891],{"class":161}," \"\"",[138,893,894],{"class":144},"))\n",[138,896,898,900],{"class":140,"line":897},33,[138,899,670],{"class":457},[138,901,902],{"class":144}," status\n",[138,904,906],{"class":140,"line":905},34,[138,907,473],{"emptyLinePlaceholder":472},[138,909,911],{"class":140,"line":910},35,[138,912,473],{"emptyLinePlaceholder":472},[138,914,916,919,922,925,928],{"class":140,"line":915},36,[138,917,918],{"class":457},"if",[138,920,921],{"class":224}," __name__",[138,923,924],{"class":457}," ==",[138,926,927],{"class":161}," \"__main__\"",[138,929,710],{"class":144},[138,931,933,936,939,942,945],{"class":140,"line":932},37,[138,934,935],{"class":457},"    raise",[138,937,938],{"class":224}," SystemExit",[138,940,941],{"class":144},"(main(sys.argv[",[138,943,944],{"class":224},"1",[138,946,947],{"class":144},":]))\n",[10,949,950,951,953,954,958,959,962,963,965],{},"Checking both the path as found and its resolved target matters: a virtual environment's ",[23,952,440],{}," is a symlink ",[955,956,957],"em",{},"out of"," the environment to the base interpreter, while uv and pipx put symlinks ",[955,960,961],{},"into"," their tool environments on ",[23,964,98],{},". Run with an activated project environment, it reports something like:",[129,967,972],{"className":968,"code":970,"language":971,"meta":134},[969],"language-text","python3: ~\u002Fsrc\u002Fmytool\u002F.venv\u002Fbin\u002Fpython3 -> virtual environment (~\u002Fsrc\u002Fmytool\u002F.venv)\nmytool: ~\u002Fsrc\u002Fmytool\u002F.venv\u002Fbin\u002Fmytool -> virtual environment (~\u002Fsrc\u002Fmytool\u002F.venv)\nhttp: ~\u002F.local\u002Fbin\u002Fhttp -> uv tool (~\u002F.local\u002Fshare\u002Fuv\u002Ftools\u002Fhttpie)\n","text",[23,973,970],{"__ignoreMap":134},[34,975,977],{"id":976},"ux-considerations","UX considerations",[39,979,980,997,1010,1022],{},[42,981,982,985,986,989,990,992,993,996],{},[14,983,984],{},"Write the homes down."," A short table in ",[23,987,988],{},"CONTRIBUTING.md"," — \"pytest and mypy: ",[23,991,75],{},"; Ruff: ",[23,994,995],{},"uvx ruff@…"," or pre-commit; everything else: your choice\" — ends most \"which version?\" discussions.",[42,998,999,1002,1003,103,1006,1009],{},[14,1000,1001],{},"Never require personal tools."," If the project's scripts call ",[23,1004,1005],{},"http",[23,1007,1008],{},"aws",", either add them to a group or check for them and print an install hint; do not assume a developer installed them the same way you did.",[42,1011,1012,1015,1016,1018,1019,1021],{},[14,1013,1014],{},"Avoid activation-dependent behaviour."," ",[23,1017,75],{}," and ",[23,1020,259],{}," work the same whether or not a virtual environment is activated, which makes instructions copy-pasteable.",[42,1023,1024,1033],{},[14,1025,1026,1027,1029,1030,1032],{},"Keep ",[23,1028,110],{}," early on ",[23,1031,98],{},","," after any activated project environment, so tool installs work and project commands still win inside a project.",[56,1035],{"name":1036},"iso-decision",[34,1038,1040],{"id":1039},"testing-the-behaviour","Testing the behaviour",[10,1042,1043],{},"The diagnostic script is worth a test because path resolution is subtle:",[129,1045,1047],{"className":438,"code":1046,"language":440,"meta":134,"style":134},"# tests\u002Ftest_which_env.py\nfrom pathlib import Path\n\nfrom scripts.which_env import environment_of\n\n\ndef make_env(root: Path, marker: str | None = None) -> Path:\n    (root \u002F \"bin\").mkdir(parents=True)\n    (root \u002F \"pyvenv.cfg\").write_text(\"home = \u002Fusr\u002Fbin\\n\")\n    if marker:\n        (root \u002F marker).write_text(\"\")\n    exe = root \u002F \"bin\" \u002F \"tool\"\n    exe.write_text(\"#!\u002Fbin\u002Fsh\\n\")\n    return exe\n\n\ndef test_plain_venv(tmp_path):\n    exe = make_env(tmp_path \u002F \"venv\")\n    assert environment_of(exe)[0] == \"virtual environment\"\n\n\ndef test_uv_tool_through_symlink(tmp_path):\n    exe = make_env(tmp_path \u002F \"tools\" \u002F \"tool\", \"uv-receipt.toml\")\n    link_dir = tmp_path \u002F \"bin\"\n    link_dir.mkdir()\n    (link_dir \u002F \"tool\").symlink_to(exe)\n    assert environment_of(link_dir \u002F \"tool\")[0] == \"uv tool\"\n\n\ndef test_pipx_tool(tmp_path):\n    exe = make_env(tmp_path \u002F \"venvs\" \u002F \"tool\", \"pipx_metadata.json\")\n    assert environment_of(exe)[0] == \"pipx tool\"\n\n\ndef test_outside_any_env(tmp_path):\n    exe = tmp_path \u002F \"tool\"\n    exe.write_text(\"\")\n    assert environment_of(exe) == (\"system or other\", None)\n",[23,1048,1049,1054,1064,1068,1080,1084,1088,1113,1137,1158,1166,1181,1201,1215,1222,1226,1230,1240,1256,1276,1280,1284,1293,1318,1333,1338,1350,1373,1377,1381,1390,1414,1429,1433,1437,1446,1458,1466],{"__ignoreMap":134},[138,1050,1051],{"class":140,"line":141},[138,1052,1053],{"class":369},"# tests\u002Ftest_which_env.py\n",[138,1055,1056,1058,1060,1062],{"class":140,"line":155},[138,1057,458],{"class":457},[138,1059,498],{"class":144},[138,1061,479],{"class":457},[138,1063,503],{"class":144},[138,1065,1066],{"class":140,"line":173},[138,1067,473],{"emptyLinePlaceholder":472},[138,1069,1070,1072,1075,1077],{"class":140,"line":184},[138,1071,458],{"class":457},[138,1073,1074],{"class":144}," scripts.which_env ",[138,1076,479],{"class":457},[138,1078,1079],{"class":144}," environment_of\n",[138,1081,1082],{"class":140,"line":476},[138,1083,473],{"emptyLinePlaceholder":472},[138,1085,1086],{"class":140,"line":485},[138,1087,473],{"emptyLinePlaceholder":472},[138,1089,1090,1092,1095,1098,1100,1103,1105,1108,1110],{"class":140,"line":493},[138,1091,519],{"class":457},[138,1093,1094],{"class":148}," make_env",[138,1096,1097],{"class":144},"(root: Path, marker: ",[138,1099,528],{"class":224},[138,1101,1102],{"class":457}," |",[138,1104,537],{"class":224},[138,1106,1107],{"class":457}," =",[138,1109,537],{"class":224},[138,1111,1112],{"class":144},") -> Path:\n",[138,1114,1115,1118,1120,1123,1126,1130,1132,1135],{"class":140,"line":506},[138,1116,1117],{"class":144},"    (root ",[138,1119,596],{"class":457},[138,1121,1122],{"class":161}," \"bin\"",[138,1124,1125],{"class":144},").mkdir(",[138,1127,1129],{"class":1128},"s4XuR","parents",[138,1131,719],{"class":457},[138,1133,1134],{"class":224},"True",[138,1136,791],{"class":144},[138,1138,1139,1141,1143,1145,1148,1151,1154,1156],{"class":140,"line":511},[138,1140,1117],{"class":144},[138,1142,596],{"class":457},[138,1144,599],{"class":161},[138,1146,1147],{"class":144},").write_text(",[138,1149,1150],{"class":161},"\"home = \u002Fusr\u002Fbin",[138,1152,1153],{"class":224},"\\n",[138,1155,776],{"class":161},[138,1157,791],{"class":144},[138,1159,1160,1163],{"class":140,"line":516},[138,1161,1162],{"class":457},"    if",[138,1164,1165],{"class":144}," marker:\n",[138,1167,1168,1171,1173,1176,1179],{"class":140,"line":543},[138,1169,1170],{"class":144},"        (root ",[138,1172,596],{"class":457},[138,1174,1175],{"class":144}," marker).write_text(",[138,1177,1178],{"class":161},"\"\"",[138,1180,791],{"class":144},[138,1182,1183,1186,1188,1191,1193,1195,1198],{"class":140,"line":549},[138,1184,1185],{"class":144},"    exe ",[138,1187,719],{"class":457},[138,1189,1190],{"class":144}," root ",[138,1192,596],{"class":457},[138,1194,1122],{"class":161},[138,1196,1197],{"class":457}," \u002F",[138,1199,1200],{"class":161}," \"tool\"\n",[138,1202,1203,1206,1209,1211,1213],{"class":140,"line":555},[138,1204,1205],{"class":144},"    exe.write_text(",[138,1207,1208],{"class":161},"\"#!\u002Fbin\u002Fsh",[138,1210,1153],{"class":224},[138,1212,776],{"class":161},[138,1214,791],{"class":144},[138,1216,1217,1219],{"class":140,"line":587},[138,1218,670],{"class":457},[138,1220,1221],{"class":144}," exe\n",[138,1223,1224],{"class":140,"line":605},[138,1225,473],{"emptyLinePlaceholder":472},[138,1227,1228],{"class":140,"line":620},[138,1229,473],{"emptyLinePlaceholder":472},[138,1231,1232,1234,1237],{"class":140,"line":632},[138,1233,519],{"class":457},[138,1235,1236],{"class":148}," test_plain_venv",[138,1238,1239],{"class":144},"(tmp_path):\n",[138,1241,1242,1244,1246,1249,1251,1254],{"class":140,"line":646},[138,1243,1185],{"class":144},[138,1245,719],{"class":457},[138,1247,1248],{"class":144}," make_env(tmp_path ",[138,1250,596],{"class":457},[138,1252,1253],{"class":161}," \"venv\"",[138,1255,791],{"class":144},[138,1257,1258,1261,1264,1267,1270,1273],{"class":140,"line":656},[138,1259,1260],{"class":457},"    assert",[138,1262,1263],{"class":144}," environment_of(exe)[",[138,1265,1266],{"class":224},"0",[138,1268,1269],{"class":144},"] ",[138,1271,1272],{"class":457},"==",[138,1274,1275],{"class":161}," \"virtual environment\"\n",[138,1277,1278],{"class":140,"line":667},[138,1279,473],{"emptyLinePlaceholder":472},[138,1281,1282],{"class":140,"line":681},[138,1283,473],{"emptyLinePlaceholder":472},[138,1285,1286,1288,1291],{"class":140,"line":686},[138,1287,519],{"class":457},[138,1289,1290],{"class":148}," test_uv_tool_through_symlink",[138,1292,1239],{"class":144},[138,1294,1295,1297,1299,1301,1303,1306,1308,1311,1313,1316],{"class":140,"line":691},[138,1296,1185],{"class":144},[138,1298,719],{"class":457},[138,1300,1248],{"class":144},[138,1302,596],{"class":457},[138,1304,1305],{"class":161}," \"tools\"",[138,1307,1197],{"class":457},[138,1309,1310],{"class":161}," \"tool\"",[138,1312,165],{"class":144},[138,1314,1315],{"class":161},"\"uv-receipt.toml\"",[138,1317,791],{"class":144},[138,1319,1320,1323,1325,1328,1330],{"class":140,"line":713},[138,1321,1322],{"class":144},"    link_dir ",[138,1324,719],{"class":457},[138,1326,1327],{"class":144}," tmp_path ",[138,1329,596],{"class":457},[138,1331,1332],{"class":161}," \"bin\"\n",[138,1334,1335],{"class":140,"line":725},[138,1336,1337],{"class":144},"    link_dir.mkdir()\n",[138,1339,1340,1343,1345,1347],{"class":140,"line":738},[138,1341,1342],{"class":144},"    (link_dir ",[138,1344,596],{"class":457},[138,1346,1310],{"class":161},[138,1348,1349],{"class":144},").symlink_to(exe)\n",[138,1351,1352,1354,1357,1359,1361,1364,1366,1368,1370],{"class":140,"line":749},[138,1353,1260],{"class":457},[138,1355,1356],{"class":144}," environment_of(link_dir ",[138,1358,596],{"class":457},[138,1360,1310],{"class":161},[138,1362,1363],{"class":144},")[",[138,1365,1266],{"class":224},[138,1367,1269],{"class":144},[138,1369,1272],{"class":457},[138,1371,1372],{"class":161}," \"uv tool\"\n",[138,1374,1375],{"class":140,"line":764},[138,1376,473],{"emptyLinePlaceholder":472},[138,1378,1379],{"class":140,"line":794},[138,1380,473],{"emptyLinePlaceholder":472},[138,1382,1383,1385,1388],{"class":140,"line":805},[138,1384,519],{"class":457},[138,1386,1387],{"class":148}," test_pipx_tool",[138,1389,1239],{"class":144},[138,1391,1392,1394,1396,1398,1400,1403,1405,1407,1409,1412],{"class":140,"line":811},[138,1393,1185],{"class":144},[138,1395,719],{"class":457},[138,1397,1248],{"class":144},[138,1399,596],{"class":457},[138,1401,1402],{"class":161}," \"venvs\"",[138,1404,1197],{"class":457},[138,1406,1310],{"class":161},[138,1408,165],{"class":144},[138,1410,1411],{"class":161},"\"pipx_metadata.json\"",[138,1413,791],{"class":144},[138,1415,1416,1418,1420,1422,1424,1426],{"class":140,"line":822},[138,1417,1260],{"class":457},[138,1419,1263],{"class":144},[138,1421,1266],{"class":224},[138,1423,1269],{"class":144},[138,1425,1272],{"class":457},[138,1427,1428],{"class":161}," \"pipx tool\"\n",[138,1430,1431],{"class":140,"line":897},[138,1432,473],{"emptyLinePlaceholder":472},[138,1434,1435],{"class":140,"line":905},[138,1436,473],{"emptyLinePlaceholder":472},[138,1438,1439,1441,1444],{"class":140,"line":910},[138,1440,519],{"class":457},[138,1442,1443],{"class":148}," test_outside_any_env",[138,1445,1239],{"class":144},[138,1447,1448,1450,1452,1454,1456],{"class":140,"line":915},[138,1449,1185],{"class":144},[138,1451,719],{"class":457},[138,1453,1327],{"class":144},[138,1455,596],{"class":457},[138,1457,1200],{"class":161},[138,1459,1460,1462,1464],{"class":140,"line":932},[138,1461,1205],{"class":144},[138,1463,1178],{"class":161},[138,1465,791],{"class":144},[138,1467,1469,1471,1474,1476,1478,1481,1483,1486],{"class":140,"line":1468},38,[138,1470,1260],{"class":457},[138,1472,1473],{"class":144}," environment_of(exe) ",[138,1475,1272],{"class":457},[138,1477,68],{"class":144},[138,1479,1480],{"class":161},"\"system or other\"",[138,1482,165],{"class":144},[138,1484,1485],{"class":224},"None",[138,1487,791],{"class":144},[10,1489,1490,1491,1495],{},"Symlink creation needs extra privileges on some Windows setups; mark that test with a POSIX-only marker as in ",[28,1492,1494],{"href":1493},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis\u002Frunning-cli-tests-on-windows-and-macos-runners\u002F","running CLI tests on Windows and macOS runners"," if your CI includes Windows.",[34,1497,1499],{"id":1498},"conclusion","Conclusion",[10,1501,1502,1503,1505,1506,1508,1509,1512],{},"Tools and projects should not share an environment by accident. Put tools that import your code in dependency groups and run them with ",[23,1504,75],{},"; run file-only tools with a pinned ",[23,1507,259],{}," command; install personal, cross-project tools with ",[23,1510,1511],{},"uv tool install"," or pipx; and when something behaves differently on one machine, find out which environment the command came from before debugging anything else.",[34,1514,1516],{"id":1515},"frequently-asked-questions","Frequently asked questions",[124,1518,1520],{"id":1519},"should-ruff-be-a-dev-dependency-or-a-uvx-command","Should Ruff be a dev dependency or a uvx command?",[10,1522,1523,1524,1526],{},"Either works, as long as there is one version. A dev-group entry keeps it in the lock file and lets editors find it in the project environment; a pinned ",[23,1525,259],{}," command or pre-commit hook keeps the project environment smaller. Pick one and make CI use the same.",[124,1528,1530,1531,1018,1533,1535],{"id":1529},"what-is-the-difference-between-uvx-and-uv-run","What is the difference between ",[23,1532,259],{},[23,1534,75],{},"?",[10,1537,1538,1540,1541,1544,1545,1547,1548,1550,1551,1553],{},[23,1539,75],{}," runs a command inside the ",[955,1542,1543],{},"project's"," environment, syncing it first. ",[23,1546,259],{}," runs a tool in a separate, cached environment that has nothing to do with the project. Use ",[23,1549,75],{}," for pytest and mypy, ",[23,1552,259],{}," for standalone tools.",[124,1555,1557],{"id":1556},"can-i-install-a-tool-from-a-local-checkout","Can I install a tool from a local checkout?",[10,1559,1560,1561,103,1564,1567],{},"Yes: ",[23,1562,1563],{},"uv tool install --editable .",[23,1565,1566],{},"pipx install --editable ."," installs your own CLI as a tool that reflects source changes immediately — handy for dogfooding a CLI you are developing, without activating its project environment.",[124,1569,1571],{"id":1570},"why-does-a-tool-still-pick-up-my-projects-packages","Why does a tool still pick up my project's packages?",[10,1573,1574,1575,1578,1579,32],{},"Usually because the project environment is activated and the \"tool\" is actually the copy installed in it. Run the diagnostic script, or ",[23,1576,1577],{},"uv tool list --show-paths",", to see which one is first on ",[23,1580,98],{},[124,1582,1584],{"id":1583},"how-do-i-clean-up-tools-i-no-longer-use","How do I clean up tools I no longer use?",[10,1586,1587,1018,1590,1593,1594,1018,1597,1600,1601,1603,1604,1607,1608,1610],{},[23,1588,1589],{},"uv tool list",[23,1591,1592],{},"pipx list"," show what is installed; ",[23,1595,1596],{},"uv tool uninstall NAME",[23,1598,1599],{},"pipx uninstall NAME"," remove a tool and its whole environment, leaving nothing behind in system or project packages. For ephemeral ",[23,1602,259],{}," runs, ",[23,1605,1606],{},"uv cache prune"," removes cached environments that are no longer referenced — safe to run at any time, since ",[23,1609,259],{}," simply rebuilds what it needs.",[34,1612,1614],{"id":1613},"related","Related",[39,1616,1617,1623,1629,1635,1641],{},[42,1618,1619,1620],{},"Up: ",[28,1621,1622],{"href":30},"Virtual environments and isolation best practices",[42,1624,1625],{},[28,1626,1628],{"href":1627},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fexternally-managed-environments-and-pep-668\u002F","PEP 668 and the externally-managed-environment error",[42,1630,1631],{},[28,1632,1634],{"href":1633},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fdebugging-wrong-python-and-wrong-venv-problems\u002F","Debugging wrong-Python and wrong-venv problems",[42,1636,1637],{},[28,1638,1640],{"href":1639},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002Fuv-tool-install-vs-pipx-for-clis\u002F","uv tool install vs pipx for CLIs",[42,1642,1643],{},[28,1644,1646],{"href":1645},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002Frunning-one-off-cli-scripts-with-uv-run\u002F","Running one-off CLI scripts with uv run",[1648,1649,1650],"style",{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}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 .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":134,"searchDepth":155,"depth":155,"links":1652},[1653,1654,1655,1660,1661,1662,1663,1664,1672],{"id":36,"depth":155,"text":37},{"id":53,"depth":155,"text":54},{"id":121,"depth":155,"text":122,"children":1656},[1657,1658,1659],{"id":126,"depth":173,"text":127},{"id":248,"depth":173,"text":249},{"id":330,"depth":173,"text":331},{"id":423,"depth":155,"text":424},{"id":976,"depth":155,"text":977},{"id":1039,"depth":155,"text":1040},{"id":1498,"depth":155,"text":1499},{"id":1515,"depth":155,"text":1516,"children":1665},[1666,1667,1669,1670,1671],{"id":1519,"depth":173,"text":1520},{"id":1529,"depth":173,"text":1668},"What is the difference between uvx and uv run?",{"id":1556,"depth":173,"text":1557},{"id":1570,"depth":173,"text":1571},{"id":1583,"depth":173,"text":1584},{"id":1613,"depth":155,"text":1614},"2026-10-02","Decide where each Python tool belongs: project dependency groups, pinned uvx runs, or isolated pipx and uv tool installs — plus a script that shows which environment a command comes from.","intermediate",false,"md",{},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fisolating-cli-tools-from-project-dependencies",{"title":5,"description":1674},"project-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fisolating-cli-tools-from-project-dependencies\u002Findex",[1683,209,375,1684,1685],"virtual-environments","tooling","isolation","_cOOvFlcIaXYl-W5HYrq13Tkg2-P9DVnTXkDhuxYA7s",[1688,1691,1694,1697,1700,1703,1706,1709,1712,1715,1718,1721,1724,1727,1730,1733,1736,1739,1742,1745,1748,1751,1754,1757,1760,1763,1766,1769,1772,1775,1778,1781,1784,1787,1790,1793,1796,1799,1802,1805,1808,1811,1814,1817,1820,1823,1826,1829,1832,1835,1838,1841,1844,1847,1850,1853,1856,1859,1862,1865,1868,1871,1874,1877,1880,1883,1886,1889,1892,1895,1898,1901,1904,1907,1910,1913,1916,1919,1922,1925,1928,1931,1934,1937,1940,1943,1946,1949,1952,1955,1958,1961,1964,1967,1970,1973,1976,1979,1982,1985,1988,1991,1994,1997,2000,2003,2006,2009,2012,2015,2018,2021,2024,2027,2030,2033,2036,2039,2042,2045,2048,2051,2054,2057,2060,2063,2066,2069,2072,2075,2078,2081,2084,2087,2090,2093,2096,2099,2102,2105,2107,2110,2113,2116,2119,2122,2125,2128,2131,2134,2137,2140,2143,2146,2149,2152,2155,2158,2161,2164,2167,2170,2173,2176,2179,2182,2185,2188,2191,2194,2197,2200,2203,2206,2209,2212,2215,2218,2221,2224,2227,2230,2233,2236,2239,2242,2245,2248,2251,2254,2257,2260,2263,2266,2269,2272,2275,2278,2281,2284,2287,2290,2293,2296,2299,2302,2305,2308,2311,2314,2317,2320,2323,2326,2329,2332,2335,2338,2341,2344,2347,2350,2353,2356,2359,2362,2365,2368,2371,2374,2377,2380,2383,2386,2389,2392,2395,2398,2401,2404,2407,2410,2413,2416,2419,2422,2425,2428,2431,2434,2437,2440,2443,2446,2449,2452,2455,2458,2461,2464,2467,2470,2473,2476,2479,2482,2485,2488,2491,2494,2497,2500,2503,2506,2508,2511,2514,2517,2520,2521,2524,2527],{"path":1689,"title":1690},"\u002Fabout","About Python CLI Toolcraft",{"path":1692,"title":1693},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies","Advanced Argument Validation Strategies",{"path":1695,"title":1696},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fparsing-nested-json-arguments-in-python-clis","Parsing Nested JSON Args in Python CLIs",{"path":1698,"title":1699},"\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":1701,"title":1702},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fvalidating-dependent-and-conflicting-options","Validating Dependent and Conflicting CLI Options",{"path":1704,"title":1705},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fvalidating-file-and-directory-paths-in-clis","Validating File and Directory Paths in CLIs",{"path":1707,"title":1708},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fvalidating-urls-hosts-and-ports","Validating URLs, Hosts and Ports in Python CLI Arguments",{"path":1710,"title":1711},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fwriting-custom-click-parameter-types","Writing Custom Click Parameter Types",{"path":1713,"title":1714},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual\u002Fbrowsing-records-with-a-textual-datatable","Browsing Records with a Textual DataTable Picker",{"path":1716,"title":1717},"\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":1719,"title":1720},"\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":1722,"title":1723},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual","Building Terminal UIs with Textual for Python CLIs",{"path":1725,"title":1726},"\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":1728,"title":1729},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual\u002Fstyling-textual-apps-with-tcss","Styling Textual Apps with TCSS",{"path":1731,"title":1732},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual\u002Ftesting-textual-apps-with-pilot","Testing Textual Apps with Pilot",{"path":1734,"title":1735},"\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":1737,"title":1738},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fdocumenting-environment-variables-in-help","Documenting Environment Variables in CLI Help",{"path":1740,"title":1741},"\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":1743,"title":1744},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation","CLI Help Output and Documentation",{"path":1746,"title":1747},"\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":1749,"title":1750},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fversioning-and-deprecating-cli-flags","Versioning and Deprecating CLI Flags",{"path":1752,"title":1753},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fwriting-help-text-users-actually-read","Writing Help Text Users Actually Read",{"path":1755,"title":1756},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Fadapting-output-to-terminal-width","Adapting Python CLI Output to Terminal Width",{"path":1758,"title":1759},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Fdetecting-ci-environments-and-non-interactive-shells","Detecting CI Environments and Non-Interactive Shells",{"path":1761,"title":1762},"\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":1764,"title":1765},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Fhandling-ansi-escape-codes-on-windows-consoles","Handling ANSI Escape Codes on Windows Consoles",{"path":1767,"title":1768},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility","Cross-Platform Terminal Compatibility for Python CLIs",{"path":1770,"title":1771},"\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":1773,"title":1774},"\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":1776,"title":1777},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fchoosing-exit-codes-for-cli-tools","Choosing Exit Codes for CLI Tools",{"path":1779,"title":1780},"\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":1782,"title":1783},"\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":1785,"title":1786},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Ffriendly-error-messages-and-tracebacks","Friendly Error Messages and Tracebacks",{"path":1788,"title":1789},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fhandling-keyboard-interrupt-cleanly","Handling Keyboard Interrupt Cleanly",{"path":1791,"title":1792},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes","Error Handling and Exit Codes for CLIs",{"path":1794,"title":1795},"\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":1797,"title":1798},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fwriting-crash-reports-users-can-send","Writing Crash Reports Users Can Send",{"path":1800,"title":1801},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Fconfig-precedence-flags-env-files-defaults","Config Precedence: Flags, Env, Files, Defaults",{"path":1803,"title":1804},"\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":1806,"title":1807},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars","Handling Config Files and Env Vars in CLIs",{"path":1809,"title":1810},"\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":1812,"title":1813},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Freading-toml-config-with-tomllib","Reading TOML Config with tomllib in Python CLIs",{"path":1815,"title":1816},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Ftyped-settings-with-pydantic-settings","Typed Settings with pydantic-settings in Python CLIs",{"path":1818,"title":1819},"\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":1821,"title":1822},"\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":1824,"title":1825},"\u002Fadvanced-input-parsing-user-experience","Advanced Input Parsing for Python CLIs",{"path":1827,"title":1828},"\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":1830,"title":1831},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Fbuilding-interactive-prompts-and-menus","Building Interactive Prompts and Menus in Python CLIs",{"path":1833,"title":1834},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Fbuilding-tree-views-with-rich","Building Tree Views with Rich in a Python CLI",{"path":1836,"title":1837},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich","Interactive Terminal UI with Rich",{"path":1839,"title":1840},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Flive-dashboards-with-rich-live","Live Dashboards with Rich Live in Python CLIs",{"path":1842,"title":1843},"\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":1845,"title":1846},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Frendering-tables-and-json-with-rich","Rendering Tables and JSON with Rich",{"path":1848,"title":1849},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Ftheming-rich-output-consistently","Theming Rich Output Consistently in Python CLIs",{"path":1851,"title":1852},"\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":1854,"title":1855},"\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":1857,"title":1858},"\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":1860,"title":1861},"\u002Fadvanced-input-parsing-user-experience\u002Foutput-formats-for-data-clis","Output Formats for Data-Heavy Python CLIs",{"path":1863,"title":1864},"\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":1866,"title":1867},"\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":1869,"title":1870},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Fargcomplete-for-argparse-clis","Tab Completion for argparse CLIs with argcomplete",{"path":1872,"title":1873},"\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":1875,"title":1876},"\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":1878,"title":1879},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis","Shell Completion for Python CLIs",{"path":1881,"title":1882},"\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":1884,"title":1885},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Fshipping-completion-scripts-with-packages","Shipping Shell Completion Scripts with a Python CLI",{"path":1887,"title":1888},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Ftesting-shell-completion-in-python-clis","Testing Shell Completion in Python CLIs",{"path":1890,"title":1891},"\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":1893,"title":1894},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fadding-verbose-and-quiet-logging-flags","Adding Verbose and Quiet Logging Flags",{"path":1896,"title":1897},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps","Structured Logging for CLI Apps",{"path":1899,"title":1900},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Flogging-with-structlog-in-a-cli","Logging with structlog in a Python CLI",{"path":1902,"title":1903},"\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":1905,"title":1906},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fstructured-json-logging-in-python-clis","Structured JSON Logging in Python CLIs",{"path":1908,"title":1909},"\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":1911,"title":1912},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Fdetecting-tty-and-adapting-output","Detecting a TTY and Adapting Output",{"path":1914,"title":1915},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Femitting-json-output-for-scripting","Emitting JSON Output for Scripting",{"path":1917,"title":1918},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Fhandling-broken-pipe-and-sigpipe","Handling Broken Pipe and SIGPIPE",{"path":1920,"title":1921},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes","Working with stdin, stdout and Pipes",{"path":1923,"title":1924},"\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":1926,"title":1927},"\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":1929,"title":1930},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Freading-piped-input-in-python-clis","Reading Piped Input in Python CLIs",{"path":1932,"title":1933},"\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":1935,"title":1936},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Fdownloading-files-with-progress-in-python","Downloading Files with Progress in Python CLIs",{"path":1938,"title":1939},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis","Calling HTTP APIs from Python CLIs",{"path":1941,"title":1942},"\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":1944,"title":1945},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Foauth-device-flow-login-for-clis","OAuth Device Flow Login for Python CLIs",{"path":1947,"title":1948},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Fpaginating-api-results-in-a-cli","Paginating API Results in a Python CLI",{"path":1950,"title":1951},"\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":1953,"title":1954},"\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":1956,"title":1957},"\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":1959,"title":1960},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis","Concurrency and Async in Python CLIs",{"path":1962,"title":1963},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Fmultiprocessing-for-cpu-bound-cli-tasks","Multiprocessing for CPU-Bound CLI Tasks",{"path":1965,"title":1966},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Fparallelising-cli-work-with-thread-pools","Parallelising CLI Work with Thread Pools",{"path":1968,"title":1969},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Frate-limiting-concurrent-requests-in-clis","Rate-Limiting Concurrent Requests in Python CLIs",{"path":1971,"title":1972},"\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":1974,"title":1975},"\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":1977,"title":1978},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Fstructured-concurrency-with-taskgroups-in-clis","Structured Concurrency with TaskGroups in Python CLIs",{"path":1980,"title":1981},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fcross-platform-paths-with-pathlib","Cross-Platform Paths with pathlib in CLIs",{"path":1983,"title":1984},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Ffile-locking-for-concurrent-cli-runs","File Locking for Concurrent CLI Runs in Python",{"path":1986,"title":1987},"\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":1989,"title":1990},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes","Filesystem Paths and Atomic Writes for CLIs",{"path":1992,"title":1993},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fsafe-temporary-files-and-directories","Safe Temporary Files and Directories in CLIs",{"path":1995,"title":1996},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fstoring-app-data-with-platformdirs","Storing CLI App Data with platformdirs",{"path":1998,"title":1999},"\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":2001,"title":2002},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fwriting-files-atomically-in-python-clis","Writing Files Atomically in Python CLIs",{"path":2004,"title":2005},"\u002Fcli-runtime-systems-integration","CLI Runtime & Systems Integration for Python",{"path":2007,"title":2008},"\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":2010,"title":2011},"\u002Fcli-runtime-systems-integration\u002Flocal-state-and-sqlite-in-python-clis","Local State and SQLite in Python CLIs",{"path":2013,"title":2014},"\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":2016,"title":2017},"\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":2019,"title":2020},"\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":2022,"title":2023},"\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":2025,"title":2026},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis\u002Fhandling-sigterm-and-graceful-shutdown","Handling SIGTERM and Graceful Shutdown in CLIs",{"path":2028,"title":2029},"\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":2031,"title":2032},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis","Long-Running and Watch-Mode Python CLIs",{"path":2034,"title":2035},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis\u002Freloading-config-on-sighup","Reloading Configuration on SIGHUP in a Python CLI",{"path":2037,"title":2038},"\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":2040,"title":2041},"\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":2043,"title":2044},"\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":2046,"title":2047},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Favoiding-shell-injection-in-python-clis","Avoiding Shell Injection in Python CLIs",{"path":2049,"title":2050},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fcalling-external-commands-safely-with-subprocess","Calling External Commands Safely with subprocess",{"path":2052,"title":2053},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fhandling-subprocess-timeouts-and-exit-codes","Handling Subprocess Timeouts and Exit Codes",{"path":2055,"title":2056},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis","Running Subprocesses from Python CLIs",{"path":2058,"title":2059},"\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":2061,"title":2062},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fpiping-between-subprocesses-in-python","Piping Between Subprocesses in Python Without a Shell",{"path":2064,"title":2065},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fstreaming-subprocess-output-in-real-time","Streaming Subprocess Output in Real Time",{"path":2067,"title":2068},"\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":2070,"title":2071},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis","Secrets and Credentials in Python CLIs",{"path":2073,"title":2074},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Fprompting-for-passwords-securely","Prompting for Passwords Securely in Python CLIs",{"path":2076,"title":2077},"\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":2079,"title":2080},"\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":2082,"title":2083},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Frefreshing-expired-tokens-automatically","Refreshing Expired Tokens Automatically in a Python CLI",{"path":2085,"title":2086},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Fstoring-tokens-with-keyring","Storing CLI Tokens Securely with keyring",{"path":2088,"title":2089},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Fsupporting-multiple-profiles-and-accounts","Supporting Multiple Profiles and Accounts in CLIs",{"path":2091,"title":2092},"\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":2094,"title":2095},"\u002Fcli-runtime-systems-integration\u002Fupdate-checks-and-self-updating-clis","Update Checks, Upgrade Commands and Telemetry for Python CLIs",{"path":2097,"title":2098},"\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":2100,"title":2101},"\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":2103,"title":2104},"\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":596,"title":2106},"Python CLI Toolcraft",{"path":2108,"title":2109},"\u002Fmodern-python-cli-frameworks-architecture\u002Falternative-python-cli-frameworks\u002Fbuilding-a-cli-with-cleo","Building a Python CLI with Cleo Command Classes",{"path":2111,"title":2112},"\u002Fmodern-python-cli-frameworks-architecture\u002Falternative-python-cli-frameworks\u002Fbuilding-a-cli-with-cyclopts","Building a Type-Hinted Python CLI with Cyclopts",{"path":2114,"title":2115},"\u002Fmodern-python-cli-frameworks-architecture\u002Falternative-python-cli-frameworks","Beyond Click and Typer: Alternative Python CLI Frameworks",{"path":2117,"title":2118},"\u002Fmodern-python-cli-frameworks-architecture\u002Falternative-python-cli-frameworks\u002Fquick-clis-from-functions-with-python-fire","Quick CLIs from Functions with Python Fire",{"path":2120,"title":2121},"\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":2123,"title":2124},"\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":2126,"title":2127},"\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":2129,"title":2130},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Fguarding-startup-with-import-tests","Guarding CLI Startup with Import Tests",{"path":2132,"title":2133},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading","CLI Startup Performance and Lazy Loading",{"path":2135,"title":2136},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Flazy-loading-subcommands-for-faster-startup","Lazy Loading Subcommands for Faster Startup",{"path":2138,"title":2139},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Fprofiling-python-cli-startup-time","Profiling Python CLI Startup Time",{"path":2141,"title":2142},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Freducing-cli-dependency-weight","Reducing CLI Dependency Weight",{"path":2144,"title":2145},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargparse-subparsers-for-subcommands","argparse Subparsers for Subcommands",{"path":2147,"title":2148},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargparse-vs-click-vs-typer-comparison","argparse vs Click vs Typer Compared",{"path":2150,"title":2151},"\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":2153,"title":2154},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse","Command-Line Parsing with argparse",{"path":2156,"title":2157},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fmigrating-from-argparse-to-typer","Migrating from argparse to Typer",{"path":2159,"title":2160},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fmutually-exclusive-options-in-argparse","Mutually Exclusive Options in argparse",{"path":2162,"title":2163},"\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":2165,"title":2166},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fwriting-custom-argparse-actions","Writing Custom argparse Actions in Python",{"path":2168,"title":2169},"\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":2171,"title":2172},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Fdesigning-idempotent-commands","Designing Idempotent Commands in a Python CLI",{"path":2174,"title":2175},"\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":2177,"title":2178},"\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":2180,"title":2181},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions","Designing CLI Interfaces and Conventions in Python",{"path":2183,"title":2184},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Fnaming-commands-and-flags-consistently","Naming Commands and Flags Consistently in Python CLIs",{"path":2186,"title":2187},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Fpositional-arguments-vs-options","Positional Arguments vs Options: Designing a CLI Signature",{"path":2189,"title":2190},"\u002Fmodern-python-cli-frameworks-architecture","Python CLI Frameworks and Architecture",{"path":2192,"title":2193},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fdiscovering-plugins-with-entry-points","Discovering Plugins with Entry Points in Python CLIs",{"path":2195,"title":2196},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fhook-based-plugins-with-pluggy","Hook-Based Plugins for Python CLIs with pluggy",{"path":2198,"title":2199},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis","Plugin Architectures for Extensible CLIs",{"path":2201,"title":2202},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fisolating-plugin-failures","Isolating Plugin Failures in an Extensible Python CLI",{"path":2204,"title":2205},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Ftesting-plugins-against-the-host-cli","Testing Plugins Against the Host CLI",{"path":2207,"title":2208},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fversioning-a-plugin-api","Versioning a Plugin API for a Python CLI",{"path":2210,"title":2211},"\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":2213,"title":2214},"\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":2216,"title":2217},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fdependency-injection-patterns-for-cli-commands","Dependency Injection Patterns for CLI Commands",{"path":2219,"title":2220},"\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":2222,"title":2223},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis","Structuring Multi-Command Python CLIs",{"path":2225,"title":2226},"\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":2228,"title":2229},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fregistering-commands-from-modules-automatically","Registering CLI Commands from Modules Automatically",{"path":2231,"title":2232},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fsharing-common-options-across-commands","Sharing Common Options Across Python CLI Commands",{"path":2234,"title":2235},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fsharing-state-with-click-context-objects","Sharing State with Click Context Objects",{"path":2237,"title":2238},"\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":2240,"title":2241},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications","Testing Python CLI Applications",{"path":2243,"title":2244},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fmeasuring-cli-test-coverage","Measuring CLI Test Coverage",{"path":2246,"title":2247},"\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":2249,"title":2250},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fproperty-based-testing-cli-arguments-with-hypothesis","Property-Based Testing CLI Arguments with Hypothesis",{"path":2252,"title":2253},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fsnapshot-testing-cli-output","Snapshot Testing CLI Output",{"path":2255,"title":2256},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftesting-click-commands-with-clirunner","Testing Click Commands with CliRunner",{"path":2258,"title":2259},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftesting-commands-that-call-subprocesses","Testing Python CLI Commands That Call Subprocesses",{"path":2261,"title":2262},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftesting-interactive-prompts-and-stdin","Testing Interactive Prompts and stdin",{"path":2264,"title":2265},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftranscript-tests-for-cli-commands","Transcript Tests for Python CLI Commands",{"path":2267,"title":2268},"\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":2270,"title":2271},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Fbuilding-dynamic-commands-in-click","Building Dynamic Commands in Click",{"path":2273,"title":2274},"\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":2276,"title":2277},"\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":2279,"title":2280},"\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":2282,"title":2283},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each","Typer vs Click: When to Use Each",{"path":2285,"title":2286},"\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":2288,"title":2289},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Ftyper-callback-functions-explained","Typer callback functions explained",{"path":2291,"title":2292},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Fusing-annotated-options-in-typer","Using Annotated Options in Typer",{"path":2294,"title":2295},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis\u002Fautomating-releases-from-git-tags","Automating Python CLI Releases from Git Tags",{"path":2297,"title":2298},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis\u002Fcaching-uv-dependencies-in-ci","Caching uv Dependencies in CI for Python CLIs",{"path":2300,"title":2301},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis","CI\u002FCD Pipelines for Python CLIs",{"path":2303,"title":2304},"\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":2306,"title":2307},"\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":2309,"title":2310},"\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":2312,"title":2313},"\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":2315,"title":2316},"\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":2318,"title":2319},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fbuilding-a-cookiecutter-template-for-typer-clis","Building a Cookiecutter Template for Typer CLIs",{"path":2321,"title":2322},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fcopier-vs-cookiecutter-for-cli-templates","Copier vs Cookiecutter for CLI Templates",{"path":2324,"title":2325},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter","CLI Project Scaffolding with Cookiecutter",{"path":2327,"title":2328},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fpost-generation-hooks-in-cli-templates","Post-Generation Hooks in Python CLI Templates",{"path":2330,"title":2331},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Ftemplate-variables-and-conditional-files","Template Variables and Conditional Files in CLI Templates",{"path":2333,"title":2334},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Ftesting-a-project-template-with-pytest","Testing a CLI Project Template with pytest",{"path":2336,"title":2337},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fupdating-generated-projects-with-copier-update","Updating Generated CLI Projects with copier update",{"path":2339,"title":2340},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fbuilding-cross-platform-release-binaries-in-ci","Building Cross-Platform Release Binaries in CI",{"path":2342,"title":2343},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fbundling-a-python-cli-with-pyinstaller","Bundling a Python CLI with PyInstaller",{"path":2345,"title":2346},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fcode-signing-and-notarizing-cli-binaries","Code Signing and Notarizing Python CLI Binaries",{"path":2348,"title":2349},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fhomebrew-and-scoop-packaging-for-python-clis","Homebrew and Scoop Packaging for Python CLIs",{"path":2351,"title":2352},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries","Distributing CLIs as Standalone Binaries",{"path":2354,"title":2355},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fnuitka-vs-pyinstaller-for-python-clis","Nuitka vs PyInstaller for Python CLI Binaries",{"path":2357,"title":2358},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Freducing-pyinstaller-binary-size","Reducing the Size of a PyInstaller CLI Binary",{"path":2360,"title":2361},"\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":2363,"title":2364},"\u002Fproject-setup-dependency-management","Project Setup & Dependency Management",{"path":2366,"title":2367},"\u002Fproject-setup-dependency-management\u002Flinting-and-type-checking-cli-code\u002Fconfiguring-ruff-for-a-cli-project","Configuring Ruff for a Python CLI Project",{"path":2369,"title":2370},"\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":2372,"title":2373},"\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":2375,"title":2376},"\u002Fproject-setup-dependency-management\u002Flinting-and-type-checking-cli-code","Linting and Type-Checking Python CLI Code",{"path":2378,"title":2379},"\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":2381,"title":2382},"\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":2384,"title":2385},"\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":2387,"title":2388},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fautomating-changelogs-with-conventional-commits","Automating Changelogs with Conventional Commits",{"path":2390,"title":2391},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fbumping-versions-consistently-across-a-cli-project","Bumping Versions Consistently Across a Python CLI Project",{"path":2393,"title":2394},"\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":2396,"title":2397},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fexposing-version-info-and-build-metadata","Exposing Version Info and Build Metadata",{"path":2399,"title":2400},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs","Managing CLI Versioning & Changelogs",{"path":2402,"title":2403},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fsemantic-versioning-policy-for-cli-tools","A Semantic Versioning Policy for CLI Tools",{"path":2405,"title":2406},"\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":2408,"title":2409},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fbuilding-wheels-and-sdists-for-python-clis","Building Wheels and sdists for Python CLIs",{"path":2411,"title":2412},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fbundling-data-files-with-importlib-resources","Bundling Data Files with importlib.resources in CLIs",{"path":2414,"title":2415},"\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":2417,"title":2418},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution","Packaging Python CLIs for Distribution",{"path":2420,"title":2421},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Finstalling-and-distributing-clis-with-pipx","Installing and Distributing CLIs with pipx",{"path":2423,"title":2424},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Foptional-dependencies-and-extras-for-clis","Optional Dependencies and Extras for Python CLIs",{"path":2426,"title":2427},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fpublishing-a-python-cli-to-pypi","Publishing a Python CLI to PyPI",{"path":2429,"title":2430},"\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":2432,"title":2433},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fdynamic-versioning-with-poetry-plugins","Dynamic Versioning for a Poetry CLI from Git Tags",{"path":2435,"title":2436},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development","Poetry Workflows for CLI Development",{"path":2438,"title":2439},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fmanaging-poetry-lock-files-for-cli-tools","Managing Poetry Lock Files for CLI Tools",{"path":2441,"title":2442},"\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":2444,"title":2445},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpoetry-dependency-groups-for-cli-tooling","Poetry Dependency Groups for CLI Tooling",{"path":2447,"title":2448},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpoetry-entry-points-and-scripts-for-clis","Poetry Entry Points and Scripts for CLIs",{"path":2450,"title":2451},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpublishing-a-cli-with-poetry","Building and Publishing a Python CLI with Poetry",{"path":2453,"title":2454},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects","Pre-commit Hooks for CLI Projects",{"path":2456,"title":2457},"\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":2459,"title":2460},"\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":2462,"title":2463},"\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":2465,"title":2466},"\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":2468,"title":2469},"\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":2471,"title":2472},"\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":2474,"title":2475},"\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":2477,"title":2478},"\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":2480,"title":2481},"\u002Fproject-setup-dependency-management\u002Fsecuring-a-python-cli-supply-chain","Securing the Supply Chain of a Python CLI",{"path":2483,"title":2484},"\u002Fproject-setup-dependency-management\u002Fsecuring-a-python-cli-supply-chain\u002Fpinning-dependencies-with-hashes","Pinning a Python CLI’s Dependencies with Hashes",{"path":2486,"title":2487},"\u002Fproject-setup-dependency-management\u002Fsecuring-a-python-cli-supply-chain\u002Fpublishing-attestations-and-verifying-releases","Publishing Attestations and Verifying Python CLI Releases",{"path":2489,"title":2490},"\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":2492,"title":2493},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management","uv for Python CLI Dependency Management",{"path":2495,"title":2496},"\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":2498,"title":2499},"\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":2501,"title":2502},"\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":2504,"title":2505},"\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":2507,"title":1640},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002Fuv-tool-install-vs-pipx-for-clis",{"path":2509,"title":2510},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002Fuv-workspaces-for-multi-package-clis","uv Workspaces for Multi-Package Python CLIs",{"path":2512,"title":2513},"\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":2515,"title":2516},"\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":2518,"title":2519},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices","Python CLI Env Isolation Best Practices",{"path":1679,"title":5},{"path":2522,"title":2523},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fmanaging-virtual-environments-for-cross-platform-clis","Managing Python CLI Virtual Environments",{"path":2525,"title":2526},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fpinning-the-python-version-for-a-cli","Pinning the Python Version for a CLI",{"path":2528,"title":2529},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fsupporting-multiple-python-versions-with-nox","Supporting Multiple Python Versions with nox",1790967543684]