[{"data":1,"prerenderedAt":2179},["ShallowReactive",2],{"page-\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fmigrating-a-cli-from-poetry-to-uv\u002F":3,"content-directory":1334},{"id":4,"title":5,"body":6,"date":1321,"description":1322,"difficulty":1323,"draft":1324,"extension":1325,"meta":1326,"navigation":253,"path":1327,"seo":1328,"stem":1329,"tags":1330,"updated":1321,"__hash__":1333},"content\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fmigrating-a-cli-from-poetry-to-uv\u002Findex.md","Migrating a Python CLI from Poetry to uv",{"type":7,"value":8,"toc":1299},"minimark",[9,24,29,59,63,72,76,84,88,93,117,172,175,373,521,525,548,552,581,585,588,733,740,743,747,861,880,884,887,973,977,980,1009,1012,1016,1019,1177,1193,1197,1203,1207,1211,1220,1224,1239,1243,1256,1260,1263,1267,1295],[10,11,12,13,17,18,23],"p",{},"Your CLI has been managed with Poetry for years and it works. The reasons to move to uv are usually practical: much faster installs in CI, one tool that also manages Python versions and globally installed tools, standard ",[14,15,16],"code",{},"[project]"," metadata that every other tool understands, and a team where most other projects already use it. The risk is that a migration quietly changes something users depend on — a dependency resolving to a different version, a console script that disappears, a constraint that becomes looser or tighter than intended. This guide walks through a migration that can be reviewed like any other change: converting metadata to the standard format, translating Poetry-specific constraints deliberately, regenerating the lockfile, comparing what actually resolves, and switching CI. It belongs to the ",[19,20,22],"a",{"href":21},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002F","Poetry workflows for CLI development topic",".",[25,26,28],"h2",{"id":27},"prerequisites","Prerequisites",[30,31,32,43,56],"ul",{},[33,34,35,36,39,40,23],"li",{},"A CLI project using Poetry, with ",[14,37,38],{},"pyproject.toml"," and a committed ",[14,41,42],{},"poetry.lock",[33,44,45,46,50,51,55],{},"uv installed. The ",[19,47,49],{"href":48},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002F","uv for Python CLI dependency management"," overview covers what changes day to day, and ",[19,52,54],{"href":53},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002Fuv-init-vs-poetry-init-for-cli-tools\u002F","uv init vs Poetry init for CLI tools"," compares the two starting points.",[33,57,58],{},"A branch, so the old and new configurations can be compared side by side.",[25,60,62],{"id":61},"what-changes-and-what-does-not","What changes and what does not",[10,64,65,66,68,69,23],{},"Most of a Poetry project maps directly onto standards that uv reads. Recent Poetry releases (2.x) already support the standard ",[14,67,16],{}," table, so some projects are halfway there; older projects keep everything under ",[14,70,71],{},"[tool.poetry]",[73,74],"inline-diagram",{"name":75},"mig-field-map",[10,77,78,79,83],{},"Your ",[80,81,82],"strong",{},"package code, tests and entry point functions do not change at all",". What changes is metadata syntax, the lockfile format, the commands developers and CI run, and — optionally — the build backend.",[25,85,87],{"id":86},"the-recipe","The recipe",[89,90,92],"h3",{"id":91},"step-1-convert-the-metadata","Step 1: convert the metadata",[10,94,95,96,99,100,102,103,105,106,109,110,113,114,23],{},"The ",[14,97,98],{},"migrate-to-uv"," tool automates the mechanical part: it rewrites ",[14,101,71],{}," sections into ",[14,104,16],{},", ",[14,107,108],{},"[project.scripts]"," and ",[14,111,112],{},"[dependency-groups]",", converts constraints, and runs ",[14,115,116],{},"uv lock",[118,119,124],"pre",{"className":120,"code":121,"language":122,"meta":123,"style":123},"language-bash shiki shiki-themes github-light github-dark","git switch -c migrate-to-uv\nuvx migrate-to-uv --dry-run        # print the proposed pyproject.toml, change nothing\nuvx migrate-to-uv                  # rewrite pyproject.toml and create uv.lock\n","bash","",[14,125,126,146,162],{"__ignoreMap":123},[127,128,131,135,139,143],"span",{"class":129,"line":130},"line",1,[127,132,134],{"class":133},"sScJk","git",[127,136,138],{"class":137},"sZZnC"," switch",[127,140,142],{"class":141},"sj4cs"," -c",[127,144,145],{"class":137}," migrate-to-uv\n",[127,147,149,152,155,158],{"class":129,"line":148},2,[127,150,151],{"class":133},"uvx",[127,153,154],{"class":137}," migrate-to-uv",[127,156,157],{"class":141}," --dry-run",[127,159,161],{"class":160},"sJ8bj","        # print the proposed pyproject.toml, change nothing\n",[127,163,165,167,169],{"class":129,"line":164},3,[127,166,151],{"class":133},[127,168,154],{"class":137},[127,170,171],{"class":160},"                  # rewrite pyproject.toml and create uv.lock\n",[10,173,174],{},"Always read the dry-run output. A before-and-after for a typical CLI looks like this:",[118,176,180],{"className":177,"code":178,"language":179,"meta":123,"style":123},"language-toml shiki shiki-themes github-light github-dark","# Before: Poetry 1.x style\n[tool.poetry]\nname = \"mytool\"\nversion = \"2.3.1\"\ndescription = \"Deploy helper for the platform team\"\npackages = [{ include = \"mytool\", from = \"src\" }]\n\n[tool.poetry.dependencies]\npython = \"^3.10\"\ntyper = \"^0.12\"\nhttpx = \"~0.27\"\n\n[tool.poetry.group.dev.dependencies]\npytest = \"^8.0\"\n\n[tool.poetry.scripts]\nmytool = \"mytool.cli:app\"\n","toml",[14,181,182,187,204,212,221,230,248,255,273,282,291,300,305,332,341,346,364],{"__ignoreMap":123},[127,183,184],{"class":129,"line":130},[127,185,186],{"class":160},"# Before: Poetry 1.x style\n",[127,188,189,193,196,198,201],{"class":129,"line":148},[127,190,192],{"class":191},"sVt8B","[",[127,194,195],{"class":133},"tool",[127,197,23],{"class":191},[127,199,200],{"class":133},"poetry",[127,202,203],{"class":191},"]\n",[127,205,206,209],{"class":129,"line":164},[127,207,208],{"class":191},"name = ",[127,210,211],{"class":137},"\"mytool\"\n",[127,213,215,218],{"class":129,"line":214},4,[127,216,217],{"class":191},"version = ",[127,219,220],{"class":137},"\"2.3.1\"\n",[127,222,224,227],{"class":129,"line":223},5,[127,225,226],{"class":191},"description = ",[127,228,229],{"class":137},"\"Deploy helper for the platform team\"\n",[127,231,233,236,239,242,245],{"class":129,"line":232},6,[127,234,235],{"class":191},"packages = [{ include = ",[127,237,238],{"class":137},"\"mytool\"",[127,240,241],{"class":191},", from = ",[127,243,244],{"class":137},"\"src\"",[127,246,247],{"class":191}," }]\n",[127,249,251],{"class":129,"line":250},7,[127,252,254],{"emptyLinePlaceholder":253},true,"\n",[127,256,258,260,262,264,266,268,271],{"class":129,"line":257},8,[127,259,192],{"class":191},[127,261,195],{"class":133},[127,263,23],{"class":191},[127,265,200],{"class":133},[127,267,23],{"class":191},[127,269,270],{"class":133},"dependencies",[127,272,203],{"class":191},[127,274,276,279],{"class":129,"line":275},9,[127,277,278],{"class":191},"python = ",[127,280,281],{"class":137},"\"^3.10\"\n",[127,283,285,288],{"class":129,"line":284},10,[127,286,287],{"class":191},"typer = ",[127,289,290],{"class":137},"\"^0.12\"\n",[127,292,294,297],{"class":129,"line":293},11,[127,295,296],{"class":191},"httpx = ",[127,298,299],{"class":137},"\"~0.27\"\n",[127,301,303],{"class":129,"line":302},12,[127,304,254],{"emptyLinePlaceholder":253},[127,306,308,310,312,314,316,318,321,323,326,328,330],{"class":129,"line":307},13,[127,309,192],{"class":191},[127,311,195],{"class":133},[127,313,23],{"class":191},[127,315,200],{"class":133},[127,317,23],{"class":191},[127,319,320],{"class":133},"group",[127,322,23],{"class":191},[127,324,325],{"class":133},"dev",[127,327,23],{"class":191},[127,329,270],{"class":133},[127,331,203],{"class":191},[127,333,335,338],{"class":129,"line":334},14,[127,336,337],{"class":191},"pytest = ",[127,339,340],{"class":137},"\"^8.0\"\n",[127,342,344],{"class":129,"line":343},15,[127,345,254],{"emptyLinePlaceholder":253},[127,347,349,351,353,355,357,359,362],{"class":129,"line":348},16,[127,350,192],{"class":191},[127,352,195],{"class":133},[127,354,23],{"class":191},[127,356,200],{"class":133},[127,358,23],{"class":191},[127,360,361],{"class":133},"scripts",[127,363,203],{"class":191},[127,365,367,370],{"class":129,"line":366},17,[127,368,369],{"class":191},"mytool = ",[127,371,372],{"class":137},"\"mytool.cli:app\"\n",[118,374,376],{"className":177,"code":375,"language":179,"meta":123,"style":123},"# After: standard metadata that uv (and every other tool) reads\n[project]\nname = \"mytool\"\nversion = \"2.3.1\"\ndescription = \"Deploy helper for the platform team\"\nrequires-python = \">=3.10,\u003C4\"\ndependencies = [\n  \"typer>=0.12,\u003C0.13\",\n  \"httpx>=0.27,\u003C0.28\",\n]\n\n[project.scripts]\nmytool = \"mytool.cli:app\"\n\n[dependency-groups]\ndev = [\"pytest>=8.0,\u003C9\"]\n\n[build-system]\nrequires = [\"hatchling\"]\nbuild-backend = \"hatchling.build\"\n",[14,377,378,383,392,398,404,410,418,423,431,438,442,446,458,464,468,477,487,491,501,512],{"__ignoreMap":123},[127,379,380],{"class":129,"line":130},[127,381,382],{"class":160},"# After: standard metadata that uv (and every other tool) reads\n",[127,384,385,387,390],{"class":129,"line":148},[127,386,192],{"class":191},[127,388,389],{"class":133},"project",[127,391,203],{"class":191},[127,393,394,396],{"class":129,"line":164},[127,395,208],{"class":191},[127,397,211],{"class":137},[127,399,400,402],{"class":129,"line":214},[127,401,217],{"class":191},[127,403,220],{"class":137},[127,405,406,408],{"class":129,"line":223},[127,407,226],{"class":191},[127,409,229],{"class":137},[127,411,412,415],{"class":129,"line":232},[127,413,414],{"class":191},"requires-python = ",[127,416,417],{"class":137},"\">=3.10,\u003C4\"\n",[127,419,420],{"class":129,"line":250},[127,421,422],{"class":191},"dependencies = [\n",[127,424,425,428],{"class":129,"line":257},[127,426,427],{"class":137},"  \"typer>=0.12,\u003C0.13\"",[127,429,430],{"class":191},",\n",[127,432,433,436],{"class":129,"line":275},[127,434,435],{"class":137},"  \"httpx>=0.27,\u003C0.28\"",[127,437,430],{"class":191},[127,439,440],{"class":129,"line":284},[127,441,203],{"class":191},[127,443,444],{"class":129,"line":293},[127,445,254],{"emptyLinePlaceholder":253},[127,447,448,450,452,454,456],{"class":129,"line":302},[127,449,192],{"class":191},[127,451,389],{"class":133},[127,453,23],{"class":191},[127,455,361],{"class":133},[127,457,203],{"class":191},[127,459,460,462],{"class":129,"line":307},[127,461,369],{"class":191},[127,463,372],{"class":137},[127,465,466],{"class":129,"line":334},[127,467,254],{"emptyLinePlaceholder":253},[127,469,470,472,475],{"class":129,"line":343},[127,471,192],{"class":191},[127,473,474],{"class":133},"dependency-groups",[127,476,203],{"class":191},[127,478,479,482,485],{"class":129,"line":348},[127,480,481],{"class":191},"dev = [",[127,483,484],{"class":137},"\"pytest>=8.0,\u003C9\"",[127,486,203],{"class":191},[127,488,489],{"class":129,"line":366},[127,490,254],{"emptyLinePlaceholder":253},[127,492,494,496,499],{"class":129,"line":493},18,[127,495,192],{"class":191},[127,497,498],{"class":133},"build-system",[127,500,203],{"class":191},[127,502,504,507,510],{"class":129,"line":503},19,[127,505,506],{"class":191},"requires = [",[127,508,509],{"class":137},"\"hatchling\"",[127,511,203],{"class":191},[127,513,515,518],{"class":129,"line":514},20,[127,516,517],{"class":191},"build-backend = ",[127,519,520],{"class":137},"\"hatchling.build\"\n",[89,522,524],{"id":523},"step-2-decide-what-the-constraints-should-be","Step 2: decide what the constraints should be",[10,526,527,528,531,532,535,536,539,540,543,544,23],{},"Poetry's caret (",[14,529,530],{},"^",") is a compact way to write an upper bound, and a faithful translation preserves it: ",[14,533,534],{},"^0.12"," becomes ",[14,537,538],{},">=0.12,\u003C0.13",", because for 0.x versions the caret pins the minor version. Keep the faithful translation for the migration itself — changing resolution and syntax in one step makes problems hard to attribute — but review the result afterwards. Many libraries' guidance for CLIs and libraries alike is to avoid upper bounds unless a known incompatibility exists, and ",[14,541,542],{},"requires-python = \">=3.10,\u003C4\""," in particular is an upper bound worth removing in a follow-up. The trade-offs are covered in ",[19,545,547],{"href":546},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fsemantic-versioning-policy-for-cli-tools\u002F","semantic versioning policy for CLI tools",[89,549,551],{"id":550},"step-3-choose-the-build-backend","Step 3: choose the build backend",[10,553,554,555,558,559,561,562,564,565,568,569,572,573,576,577,23],{},"Poetry projects build with ",[14,556,557],{},"poetry-core",". You can keep it — uv builds any PEP 517 backend — but ",[14,560,557],{}," reads package locations from ",[14,563,71],{}," settings you may have just removed. Switching to ",[14,566,567],{},"hatchling"," or ",[14,570,571],{},"uv_build"," is usually simpler for a ",[14,574,575],{},"src\u002F"," layout CLI. If you switch, check that data files are still included; see ",[19,578,580],{"href":579},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fbundling-data-files-with-importlib-resources\u002F","bundling data files with importlib.resources",[89,582,584],{"id":583},"step-4-compare-what-resolves","Step 4: compare what resolves",[10,586,587],{},"This is the step that makes the migration safe. Export both lockfiles to a flat list of pinned versions and diff them:",[118,589,591],{"className":120,"code":590,"language":122,"meta":123,"style":123},"git show main:poetry.lock > \u002Ftmp\u002Fpoetry.lock\n# Poetry side: list what the old lock pinned (runtime + dev)\npython - \u003C\u003C'EOF' > \u002Ftmp\u002Fbefore.txt\nimport tomllib\nlock = tomllib.load(open(\"\u002Ftmp\u002Fpoetry.lock\", \"rb\"))\nfor p in sorted(lock[\"package\"], key=lambda p: p[\"name\"].lower()):\n    print(f'{p[\"name\"].lower()}=={p[\"version\"]}')\nEOF\n# uv side\nuv export --all-groups --no-hashes --no-emit-project --format requirements-txt \\\n  | grep \"==\" | sed 's\u002F ;.*\u002F\u002F' | sort -f > \u002Ftmp\u002Fafter.txt\ndiff \u002Ftmp\u002Fbefore.txt \u002Ftmp\u002Fafter.txt\n",[14,592,593,610,615,634,639,644,649,654,659,664,690,723],{"__ignoreMap":123},[127,594,595,597,600,603,607],{"class":129,"line":130},[127,596,134],{"class":133},[127,598,599],{"class":137}," show",[127,601,602],{"class":137}," main:poetry.lock",[127,604,606],{"class":605},"szBVR"," >",[127,608,609],{"class":137}," \u002Ftmp\u002Fpoetry.lock\n",[127,611,612],{"class":129,"line":148},[127,613,614],{"class":160},"# Poetry side: list what the old lock pinned (runtime + dev)\n",[127,616,617,620,623,626,629,631],{"class":129,"line":164},[127,618,619],{"class":133},"python",[127,621,622],{"class":137}," -",[127,624,625],{"class":605}," \u003C\u003C",[127,627,628],{"class":137},"'EOF'",[127,630,606],{"class":605},[127,632,633],{"class":137}," \u002Ftmp\u002Fbefore.txt\n",[127,635,636],{"class":129,"line":214},[127,637,638],{"class":137},"import tomllib\n",[127,640,641],{"class":129,"line":223},[127,642,643],{"class":137},"lock = tomllib.load(open(\"\u002Ftmp\u002Fpoetry.lock\", \"rb\"))\n",[127,645,646],{"class":129,"line":232},[127,647,648],{"class":137},"for p in sorted(lock[\"package\"], key=lambda p: p[\"name\"].lower()):\n",[127,650,651],{"class":129,"line":250},[127,652,653],{"class":137},"    print(f'{p[\"name\"].lower()}=={p[\"version\"]}')\n",[127,655,656],{"class":129,"line":257},[127,657,658],{"class":137},"EOF\n",[127,660,661],{"class":129,"line":275},[127,662,663],{"class":160},"# uv side\n",[127,665,666,669,672,675,678,681,684,687],{"class":129,"line":284},[127,667,668],{"class":133},"uv",[127,670,671],{"class":137}," export",[127,673,674],{"class":141}," --all-groups",[127,676,677],{"class":141}," --no-hashes",[127,679,680],{"class":141}," --no-emit-project",[127,682,683],{"class":141}," --format",[127,685,686],{"class":137}," requirements-txt",[127,688,689],{"class":141}," \\\n",[127,691,692,695,698,701,704,707,710,712,715,718,720],{"class":129,"line":293},[127,693,694],{"class":605},"  |",[127,696,697],{"class":133}," grep",[127,699,700],{"class":137}," \"==\"",[127,702,703],{"class":605}," |",[127,705,706],{"class":133}," sed",[127,708,709],{"class":137}," 's\u002F ;.*\u002F\u002F'",[127,711,703],{"class":605},[127,713,714],{"class":133}," sort",[127,716,717],{"class":141}," -f",[127,719,606],{"class":605},[127,721,722],{"class":137}," \u002Ftmp\u002Fafter.txt\n",[127,724,725,728,731],{"class":129,"line":302},[127,726,727],{"class":133},"diff",[127,729,730],{"class":137}," \u002Ftmp\u002Fbefore.txt",[127,732,722],{"class":137},[10,734,735,736,739],{},"An empty diff is the ideal. Small differences are normal — a newer patch release of a transitive dependency, a platform marker resolved differently — but each one should be understood. If something major moved, pin it temporarily with ",[14,737,738],{},"uv lock --upgrade-package name==old"," or a constraint, migrate, and upgrade it in a separate change.",[73,741],{"name":742},"mig-steps",[89,744,746],{"id":745},"step-5-switch-the-commands","Step 5: switch the commands",[748,749,750,762],"table",{},[751,752,753],"thead",{},[754,755,756,760],"tr",{},[757,758,759],"th",{},"Poetry",[757,761,668],{},[763,764,765,778,790,802,814,826,837,849],"tbody",{},[754,766,767,773],{},[768,769,770],"td",{},[14,771,772],{},"poetry install",[768,774,775],{},[14,776,777],{},"uv sync",[754,779,780,785],{},[768,781,782],{},[14,783,784],{},"poetry install --only main",[768,786,787],{},[14,788,789],{},"uv sync --no-dev",[754,791,792,797],{},[768,793,794],{},[14,795,796],{},"poetry add httpx",[768,798,799],{},[14,800,801],{},"uv add httpx",[754,803,804,809],{},[768,805,806],{},[14,807,808],{},"poetry add --group dev pytest",[768,810,811],{},[14,812,813],{},"uv add --dev pytest",[754,815,816,821],{},[768,817,818],{},[14,819,820],{},"poetry run mytool",[768,822,823],{},[14,824,825],{},"uv run mytool",[754,827,828,833],{},[768,829,830],{},[14,831,832],{},"poetry lock",[768,834,835],{},[14,836,116],{},[754,838,839,844],{},[768,840,841],{},[14,842,843],{},"poetry build",[768,845,846],{},[14,847,848],{},"uv build",[754,850,851,856],{},[768,852,853],{},[14,854,855],{},"poetry publish",[768,857,858],{},[14,859,860],{},"uv publish",[10,862,863,864,867,868,872,873,876,877,879],{},"Update CI to install with ",[14,865,866],{},"uv sync --locked",", which fails if the lockfile is stale — see ",[19,869,871],{"href":870},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis\u002Fcaching-uv-dependencies-in-ci\u002F","caching uv dependencies in CI"," — and update the README, ",[14,874,875],{},"CONTRIBUTING.md",", pre-commit hooks and any Dockerfiles. Delete ",[14,878,42],{}," once the comparison is done and the branch is ready to merge.",[89,881,883],{"id":882},"features-without-a-one-to-one-equivalent","Features without a one-to-one equivalent",[10,885,886],{},"A few Poetry features need a decision rather than a translation:",[30,888,889,911,936,961],{},[33,890,891,894,895,898,899,902,903,906,907,910],{},[80,892,893],{},"Extras defined through optional dependencies."," Poetry marks dependencies ",[14,896,897],{},"optional = true"," and lists them under ",[14,900,901],{},"[tool.poetry.extras]",". The standard form is ",[14,904,905],{},"[project.optional-dependencies]",", keyed by extra name — the same ",[14,908,909],{},"pip install mytool[s3]"," syntax works for users afterwards.",[33,912,913,916,917,920,921,924,925,928,929,931,932,23],{},[80,914,915],{},"Path and git dependencies."," Poetry's ",[14,918,919],{},"{ path = \"..\u002Flib\", develop = true }"," becomes an entry in ",[14,922,923],{},"[tool.uv.sources]"," with ",[14,926,927],{},"path = \"..\u002Flib\", editable = true",", while the dependency itself is listed normally in ",[14,930,16],{},". If several local packages depend on each other, a workspace is usually the better model; see ",[19,933,935],{"href":934},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002Fuv-workspaces-for-multi-package-clis\u002F","uv workspaces for multi-package CLIs",[33,937,938,941,942,945,946,105,949,952,953,956,957,960],{},[80,939,940],{},"Scripts that are not console scripts."," Some projects abuse ",[14,943,944],{},"[tool.poetry.scripts]"," to run maintenance tasks. Those belong in a task runner (a ",[14,947,948],{},"justfile",[14,950,951],{},"nox"," sessions, or a small ",[14,954,955],{},"scripts\u002F"," directory run with ",[14,958,959],{},"uv run","), not in the package's public entry points.",[33,962,963,968,969,972],{},[80,964,965,23],{},[14,966,967],{},"poetry version patch"," uv provides ",[14,970,971],{},"uv version --bump patch"," for static versions; projects that derive versions from git tags do not need either.",[25,974,976],{"id":975},"ux-considerations","UX considerations",[10,978,979],{},"The people affected by the migration are your contributors and your users:",[30,981,982,991,1003],{},[33,983,984,987,988,23],{},[80,985,986],{},"Users should notice nothing."," The package name, version, console script and runtime dependencies must be identical after the migration. Verify by building and installing the wheel: ",[14,989,990],{},"uv build && uv tool install dist\u002F*.whl && mytool --version",[33,992,993,996,997,999,1000,1002],{},[80,994,995],{},"Contributors need one clear message."," A short note in the pull request and the changelog — \"we now use uv; run ",[14,998,777],{}," instead of ",[14,1001,772],{},"\" — plus updated docs prevents a week of confused questions.",[33,1004,1005,1008],{},[80,1006,1007],{},"Keep the migration separate from upgrades."," One pull request converts; later pull requests change versions or loosen constraints. Reviewers can then reason about each.",[73,1010],{"name":1011},"mig-terminal",[25,1013,1015],{"id":1014},"testing-the-behaviour","Testing the behaviour",[10,1017,1018],{},"Beyond the dependency diff, three checks confirm nothing user-facing changed:",[118,1020,1022],{"className":120,"code":1021,"language":122,"meta":123,"style":123},"# 1. The test suite passes in the new environment.\nuv sync --locked && uv run pytest -q\n\n# 2. The built wheel has the same metadata and entry points as before.\nuv build\nunzip -p dist\u002Fmytool-*.whl '*.dist-info\u002Fentry_points.txt'\nunzip -p dist\u002Fmytool-*.whl '*.dist-info\u002FMETADATA' | grep -E '^(Name|Version|Requires-Python|Requires-Dist):'\n\n# 3. The installed command works from outside the repository.\nuv tool install --force dist\u002Fmytool-*.whl\n(cd \u002Ftmp && mytool --version && mytool --help >\u002Fdev\u002Fnull)\n",[14,1023,1024,1029,1053,1057,1062,1069,1089,1114,1118,1123,1143],{"__ignoreMap":123},[127,1025,1026],{"class":129,"line":130},[127,1027,1028],{"class":160},"# 1. The test suite passes in the new environment.\n",[127,1030,1031,1033,1036,1039,1042,1044,1047,1050],{"class":129,"line":148},[127,1032,668],{"class":133},[127,1034,1035],{"class":137}," sync",[127,1037,1038],{"class":141}," --locked",[127,1040,1041],{"class":191}," && ",[127,1043,668],{"class":133},[127,1045,1046],{"class":137}," run",[127,1048,1049],{"class":137}," pytest",[127,1051,1052],{"class":141}," -q\n",[127,1054,1055],{"class":129,"line":164},[127,1056,254],{"emptyLinePlaceholder":253},[127,1058,1059],{"class":129,"line":214},[127,1060,1061],{"class":160},"# 2. The built wheel has the same metadata and entry points as before.\n",[127,1063,1064,1066],{"class":129,"line":223},[127,1065,668],{"class":133},[127,1067,1068],{"class":137}," build\n",[127,1070,1071,1074,1077,1080,1083,1086],{"class":129,"line":232},[127,1072,1073],{"class":133},"unzip",[127,1075,1076],{"class":141}," -p",[127,1078,1079],{"class":137}," dist\u002Fmytool-",[127,1081,1082],{"class":141},"*",[127,1084,1085],{"class":137},".whl",[127,1087,1088],{"class":137}," '*.dist-info\u002Fentry_points.txt'\n",[127,1090,1091,1093,1095,1097,1099,1101,1104,1106,1108,1111],{"class":129,"line":250},[127,1092,1073],{"class":133},[127,1094,1076],{"class":141},[127,1096,1079],{"class":137},[127,1098,1082],{"class":141},[127,1100,1085],{"class":137},[127,1102,1103],{"class":137}," '*.dist-info\u002FMETADATA'",[127,1105,703],{"class":605},[127,1107,697],{"class":133},[127,1109,1110],{"class":141}," -E",[127,1112,1113],{"class":137}," '^(Name|Version|Requires-Python|Requires-Dist):'\n",[127,1115,1116],{"class":129,"line":257},[127,1117,254],{"emptyLinePlaceholder":253},[127,1119,1120],{"class":129,"line":275},[127,1121,1122],{"class":160},"# 3. The installed command works from outside the repository.\n",[127,1124,1125,1127,1130,1133,1136,1138,1140],{"class":129,"line":284},[127,1126,668],{"class":133},[127,1128,1129],{"class":137}," tool",[127,1131,1132],{"class":137}," install",[127,1134,1135],{"class":141}," --force",[127,1137,1079],{"class":137},[127,1139,1082],{"class":141},[127,1141,1142],{"class":137},".whl\n",[127,1144,1145,1148,1151,1154,1156,1159,1162,1164,1166,1169,1171,1174],{"class":129,"line":293},[127,1146,1147],{"class":191},"(",[127,1149,1150],{"class":141},"cd",[127,1152,1153],{"class":137}," \u002Ftmp",[127,1155,1041],{"class":191},[127,1157,1158],{"class":133},"mytool",[127,1160,1161],{"class":141}," --version",[127,1163,1041],{"class":191},[127,1165,1158],{"class":133},[127,1167,1168],{"class":141}," --help",[127,1170,606],{"class":605},[127,1172,1173],{"class":137},"\u002Fdev\u002Fnull",[127,1175,1176],{"class":191},")\n",[10,1178,1179,1180,1183,1184,1187,1188,1192],{},"Compare the ",[14,1181,1182],{},"METADATA"," output with the same command run against a wheel built from ",[14,1185,1186],{},"main"," with Poetry; the dependency list may be written differently but should express the same constraints. The third check is the ",[19,1189,1191],{"href":1190},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis\u002Fsmoke-testing-the-built-wheel-in-ci\u002F","wheel smoke test"," in miniature.",[25,1194,1196],{"id":1195},"conclusion","Conclusion",[10,1198,1199,1200,1202],{},"Migrating a CLI from Poetry to uv is mostly mechanical, and the mechanical part is automated by ",[14,1201,98],{},". What makes it safe is treating it as a reviewed change: read the converted metadata, translate constraints faithfully first and relax them later, choose a build backend deliberately, diff the resolved versions against the old lockfile, and prove with a built wheel that users will see no difference. Then switch CI and the docs in the same pull request, and the team's next install is simply faster.",[25,1204,1206],{"id":1205},"frequently-asked-questions","Frequently asked questions",[89,1208,1210],{"id":1209},"can-i-keep-using-poetry-for-some-things","Can I keep using Poetry for some things?",[10,1212,1213,1214,1216,1217,1219],{},"Yes. With standard ",[14,1215,16],{}," metadata, Poetry 2.x and uv can both read the same ",[14,1218,38],{},", and some teams keep Poetry for publishing during a transition. Maintaining two lockfiles is the part to avoid; pick one tool to own resolution.",[89,1221,1223],{"id":1222},"what-about-poetry-plugins-such-as-poetry-dynamic-versioning","What about Poetry plugins such as poetry-dynamic-versioning?",[10,1225,1226,1227,1230,1231,1235,1236,23],{},"Replace them with build-backend features. Dynamic versions from git tags move to ",[14,1228,1229],{},"hatch-vcs",", as in ",[19,1232,1234],{"href":1233},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fderiving-versions-from-git-tags-with-hatch-vcs\u002F","deriving versions from git tags with hatch-vcs","; export plugins are replaced by ",[14,1237,1238],{},"uv export",[89,1240,1242],{"id":1241},"does-uv-support-private-package-indexes-like-poetry-sources","Does uv support private package indexes like Poetry sources?",[10,1244,1245,1246,1249,1250,1253,1254,23],{},"Yes, via ",[14,1247,1248],{},"[[tool.uv.index]]"," entries with names, URLs and optional ",[14,1251,1252],{},"explicit = true"," to limit a package to that index. Credentials come from environment variables or keyring, never from ",[14,1255,38],{},[89,1257,1259],{"id":1258},"how-long-does-a-migration-take","How long does a migration take?",[10,1261,1262],{},"For a typical CLI, the conversion and comparison take under an hour; most of the time goes into updating CI configuration and documentation. Projects with many Poetry plugins or unusual packaging settings take longer.",[25,1264,1266],{"id":1265},"related","Related",[30,1268,1269,1275,1281,1287,1291],{},[33,1270,1271,1272],{},"Up: ",[19,1273,1274],{"href":21},"Poetry workflows for CLI development",[33,1276,1277],{},[19,1278,1280],{"href":1279},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpoetry-dependency-groups-for-cli-tooling\u002F","Poetry dependency groups for CLI tooling",[33,1282,1283],{},[19,1284,1286],{"href":1285},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpoetry-entry-points-and-scripts-for-clis\u002F","Poetry entry points and scripts for CLIs",[33,1288,1289],{},[19,1290,54],{"href":53},[33,1292,1293],{},[19,1294,935],{"href":934},[1296,1297,1298],"style",{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .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 .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 .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}",{"title":123,"searchDepth":148,"depth":148,"links":1300},[1301,1302,1303,1311,1312,1313,1314,1320],{"id":27,"depth":148,"text":28},{"id":61,"depth":148,"text":62},{"id":86,"depth":148,"text":87,"children":1304},[1305,1306,1307,1308,1309,1310],{"id":91,"depth":164,"text":92},{"id":523,"depth":164,"text":524},{"id":550,"depth":164,"text":551},{"id":583,"depth":164,"text":584},{"id":745,"depth":164,"text":746},{"id":882,"depth":164,"text":883},{"id":975,"depth":148,"text":976},{"id":1014,"depth":148,"text":1015},{"id":1195,"depth":148,"text":1196},{"id":1205,"depth":148,"text":1206,"children":1315},[1316,1317,1318,1319],{"id":1209,"depth":164,"text":1210},{"id":1222,"depth":164,"text":1223},{"id":1241,"depth":164,"text":1242},{"id":1258,"depth":164,"text":1259},{"id":1265,"depth":148,"text":1266},"2026-09-18","Move a Poetry-managed Python CLI to uv safely: convert to PEP 621 metadata, translate caret constraints, keep entry points, compare locked versions and update CI.","intermediate",false,"md",{},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fmigrating-a-cli-from-poetry-to-uv",{"title":5,"description":1322},"project-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fmigrating-a-cli-from-poetry-to-uv\u002Findex",[668,200,1331,1332],"migration","packaging","-w_TVT-9iTBw2sJVzQxZGS8hC8PZzBSMxkUV-DPgEdM",[1335,1338,1341,1344,1347,1350,1353,1356,1359,1362,1365,1368,1371,1374,1377,1380,1383,1386,1389,1392,1395,1398,1401,1404,1407,1410,1413,1416,1419,1422,1425,1428,1431,1434,1437,1440,1443,1446,1449,1452,1455,1458,1461,1464,1467,1470,1473,1476,1479,1482,1485,1488,1491,1494,1497,1500,1503,1506,1509,1512,1515,1518,1521,1524,1527,1530,1533,1536,1539,1542,1545,1548,1551,1554,1557,1560,1563,1566,1569,1572,1575,1578,1581,1584,1587,1590,1593,1596,1599,1602,1605,1608,1611,1614,1617,1620,1623,1626,1629,1632,1635,1638,1641,1644,1647,1650,1653,1656,1659,1662,1665,1668,1671,1674,1677,1680,1683,1686,1689,1692,1695,1698,1701,1704,1707,1710,1713,1716,1719,1722,1725,1728,1731,1734,1737,1740,1743,1746,1749,1752,1755,1758,1761,1764,1767,1770,1773,1776,1779,1782,1785,1788,1791,1794,1797,1800,1803,1806,1809,1812,1815,1818,1821,1824,1827,1830,1833,1836,1839,1842,1845,1848,1851,1854,1857,1860,1863,1866,1869,1872,1875,1878,1881,1884,1887,1890,1893,1896,1899,1902,1905,1908,1911,1914,1917,1920,1923,1926,1929,1932,1935,1938,1941,1944,1947,1950,1953,1956,1959,1962,1965,1968,1971,1974,1977,1980,1983,1986,1989,1992,1995,1998,2001,2004,2007,2010,2013,2016,2019,2022,2025,2028,2031,2034,2037,2040,2043,2046,2049,2052,2055,2058,2061,2064,2067,2070,2073,2076,2079,2082,2085,2088,2089,2092,2095,2098,2101,2104,2107,2110,2113,2116,2119,2122,2125,2128,2131,2134,2137,2140,2143,2146,2149,2152,2155,2158,2161,2164,2167,2170,2173,2176],{"path":1336,"title":1337},"\u002Fabout","About Python CLI Toolcraft",{"path":1339,"title":1340},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies","Advanced Argument Validation Strategies",{"path":1342,"title":1343},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fparsing-nested-json-arguments-in-python-clis","Parsing Nested JSON Args in Python CLIs",{"path":1345,"title":1346},"\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":1348,"title":1349},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fvalidating-dependent-and-conflicting-options","Validating Dependent and Conflicting CLI Options",{"path":1351,"title":1352},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fvalidating-file-and-directory-paths-in-clis","Validating File and Directory Paths in CLIs",{"path":1354,"title":1355},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fvalidating-urls-hosts-and-ports","Validating URLs, Hosts and Ports in Python CLI Arguments",{"path":1357,"title":1358},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fwriting-custom-click-parameter-types","Writing Custom Click Parameter Types",{"path":1360,"title":1361},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual\u002Fbrowsing-records-with-a-textual-datatable","Browsing Records with a Textual DataTable Picker",{"path":1363,"title":1364},"\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":1366,"title":1367},"\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":1369,"title":1370},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual","Building Terminal UIs with Textual for Python CLIs",{"path":1372,"title":1373},"\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":1375,"title":1376},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual\u002Fstyling-textual-apps-with-tcss","Styling Textual Apps with TCSS",{"path":1378,"title":1379},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual\u002Ftesting-textual-apps-with-pilot","Testing Textual Apps with Pilot",{"path":1381,"title":1382},"\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":1384,"title":1385},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fdocumenting-environment-variables-in-help","Documenting Environment Variables in CLI Help",{"path":1387,"title":1388},"\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":1390,"title":1391},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation","CLI Help Output and Documentation",{"path":1393,"title":1394},"\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":1396,"title":1397},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fversioning-and-deprecating-cli-flags","Versioning and Deprecating CLI Flags",{"path":1399,"title":1400},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fwriting-help-text-users-actually-read","Writing Help Text Users Actually Read",{"path":1402,"title":1403},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Fadapting-output-to-terminal-width","Adapting Python CLI Output to Terminal Width",{"path":1405,"title":1406},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Fdetecting-ci-environments-and-non-interactive-shells","Detecting CI Environments and Non-Interactive Shells",{"path":1408,"title":1409},"\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":1411,"title":1412},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Fhandling-ansi-escape-codes-on-windows-consoles","Handling ANSI Escape Codes on Windows Consoles",{"path":1414,"title":1415},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility","Cross-Platform Terminal Compatibility for Python CLIs",{"path":1417,"title":1418},"\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":1420,"title":1421},"\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":1423,"title":1424},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fchoosing-exit-codes-for-cli-tools","Choosing Exit Codes for CLI Tools",{"path":1426,"title":1427},"\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":1429,"title":1430},"\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":1432,"title":1433},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Ffriendly-error-messages-and-tracebacks","Friendly Error Messages and Tracebacks",{"path":1435,"title":1436},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fhandling-keyboard-interrupt-cleanly","Handling Keyboard Interrupt Cleanly",{"path":1438,"title":1439},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes","Error Handling and Exit Codes for CLIs",{"path":1441,"title":1442},"\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":1444,"title":1445},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fwriting-crash-reports-users-can-send","Writing Crash Reports Users Can Send",{"path":1447,"title":1448},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Fconfig-precedence-flags-env-files-defaults","Config Precedence: Flags, Env, Files, Defaults",{"path":1450,"title":1451},"\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":1453,"title":1454},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars","Handling Config Files and Env Vars in CLIs",{"path":1456,"title":1457},"\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":1459,"title":1460},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Freading-toml-config-with-tomllib","Reading TOML Config with tomllib in Python CLIs",{"path":1462,"title":1463},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Ftyped-settings-with-pydantic-settings","Typed Settings with pydantic-settings in Python CLIs",{"path":1465,"title":1466},"\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":1468,"title":1469},"\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":1471,"title":1472},"\u002Fadvanced-input-parsing-user-experience","Advanced Input Parsing for Python CLIs",{"path":1474,"title":1475},"\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":1477,"title":1478},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Fbuilding-interactive-prompts-and-menus","Building Interactive Prompts and Menus in Python CLIs",{"path":1480,"title":1481},"\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":1483,"title":1484},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich","Interactive Terminal UI with Rich",{"path":1486,"title":1487},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Flive-dashboards-with-rich-live","Live Dashboards with Rich Live in Python CLIs",{"path":1489,"title":1490},"\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":1492,"title":1493},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Frendering-tables-and-json-with-rich","Rendering Tables and JSON with Rich",{"path":1495,"title":1496},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Ftheming-rich-output-consistently","Theming Rich Output Consistently in Python CLIs",{"path":1498,"title":1499},"\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":1501,"title":1502},"\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":1504,"title":1505},"\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":1507,"title":1508},"\u002Fadvanced-input-parsing-user-experience\u002Foutput-formats-for-data-clis","Output Formats for Data-Heavy Python CLIs",{"path":1510,"title":1511},"\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":1513,"title":1514},"\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":1516,"title":1517},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Fargcomplete-for-argparse-clis","Tab Completion for argparse CLIs with argcomplete",{"path":1519,"title":1520},"\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":1522,"title":1523},"\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":1525,"title":1526},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis","Shell Completion for Python CLIs",{"path":1528,"title":1529},"\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":1531,"title":1532},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Fshipping-completion-scripts-with-packages","Shipping Shell Completion Scripts with a Python CLI",{"path":1534,"title":1535},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Ftesting-shell-completion-in-python-clis","Testing Shell Completion in Python CLIs",{"path":1537,"title":1538},"\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":1540,"title":1541},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fadding-verbose-and-quiet-logging-flags","Adding Verbose and Quiet Logging Flags",{"path":1543,"title":1544},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps","Structured Logging for CLI Apps",{"path":1546,"title":1547},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Flogging-with-structlog-in-a-cli","Logging with structlog in a Python CLI",{"path":1549,"title":1550},"\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":1552,"title":1553},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fstructured-json-logging-in-python-clis","Structured JSON Logging in Python CLIs",{"path":1555,"title":1556},"\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":1558,"title":1559},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Fdetecting-tty-and-adapting-output","Detecting a TTY and Adapting Output",{"path":1561,"title":1562},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Femitting-json-output-for-scripting","Emitting JSON Output for Scripting",{"path":1564,"title":1565},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Fhandling-broken-pipe-and-sigpipe","Handling Broken Pipe and SIGPIPE",{"path":1567,"title":1568},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes","Working with stdin, stdout and Pipes",{"path":1570,"title":1571},"\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":1573,"title":1574},"\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":1576,"title":1577},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Freading-piped-input-in-python-clis","Reading Piped Input in Python CLIs",{"path":1579,"title":1580},"\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":1582,"title":1583},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Fdownloading-files-with-progress-in-python","Downloading Files with Progress in Python CLIs",{"path":1585,"title":1586},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis","Calling HTTP APIs from Python CLIs",{"path":1588,"title":1589},"\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":1591,"title":1592},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Foauth-device-flow-login-for-clis","OAuth Device Flow Login for Python CLIs",{"path":1594,"title":1595},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Fpaginating-api-results-in-a-cli","Paginating API Results in a Python CLI",{"path":1597,"title":1598},"\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":1600,"title":1601},"\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":1603,"title":1604},"\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":1606,"title":1607},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis","Concurrency and Async in Python CLIs",{"path":1609,"title":1610},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Fmultiprocessing-for-cpu-bound-cli-tasks","Multiprocessing for CPU-Bound CLI Tasks",{"path":1612,"title":1613},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Fparallelising-cli-work-with-thread-pools","Parallelising CLI Work with Thread Pools",{"path":1615,"title":1616},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Frate-limiting-concurrent-requests-in-clis","Rate-Limiting Concurrent Requests in Python CLIs",{"path":1618,"title":1619},"\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":1621,"title":1622},"\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":1624,"title":1625},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Fstructured-concurrency-with-taskgroups-in-clis","Structured Concurrency with TaskGroups in Python CLIs",{"path":1627,"title":1628},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fcross-platform-paths-with-pathlib","Cross-Platform Paths with pathlib in CLIs",{"path":1630,"title":1631},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Ffile-locking-for-concurrent-cli-runs","File Locking for Concurrent CLI Runs in Python",{"path":1633,"title":1634},"\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":1636,"title":1637},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes","Filesystem Paths and Atomic Writes for CLIs",{"path":1639,"title":1640},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fsafe-temporary-files-and-directories","Safe Temporary Files and Directories in CLIs",{"path":1642,"title":1643},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fstoring-app-data-with-platformdirs","Storing CLI App Data with platformdirs",{"path":1645,"title":1646},"\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":1648,"title":1649},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fwriting-files-atomically-in-python-clis","Writing Files Atomically in Python CLIs",{"path":1651,"title":1652},"\u002Fcli-runtime-systems-integration","CLI Runtime & Systems Integration for Python",{"path":1654,"title":1655},"\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":1657,"title":1658},"\u002Fcli-runtime-systems-integration\u002Flocal-state-and-sqlite-in-python-clis","Local State and SQLite in Python CLIs",{"path":1660,"title":1661},"\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":1663,"title":1664},"\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":1666,"title":1667},"\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":1669,"title":1670},"\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":1672,"title":1673},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis\u002Fhandling-sigterm-and-graceful-shutdown","Handling SIGTERM and Graceful Shutdown in CLIs",{"path":1675,"title":1676},"\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":1678,"title":1679},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis","Long-Running and Watch-Mode Python CLIs",{"path":1681,"title":1682},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis\u002Freloading-config-on-sighup","Reloading Configuration on SIGHUP in a Python CLI",{"path":1684,"title":1685},"\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":1687,"title":1688},"\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":1690,"title":1691},"\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":1693,"title":1694},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Favoiding-shell-injection-in-python-clis","Avoiding Shell Injection in Python CLIs",{"path":1696,"title":1697},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fcalling-external-commands-safely-with-subprocess","Calling External Commands Safely with subprocess",{"path":1699,"title":1700},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fhandling-subprocess-timeouts-and-exit-codes","Handling Subprocess Timeouts and Exit Codes",{"path":1702,"title":1703},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis","Running Subprocesses from Python CLIs",{"path":1705,"title":1706},"\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":1708,"title":1709},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fpiping-between-subprocesses-in-python","Piping Between Subprocesses in Python Without a Shell",{"path":1711,"title":1712},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fstreaming-subprocess-output-in-real-time","Streaming Subprocess Output in Real Time",{"path":1714,"title":1715},"\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":1717,"title":1718},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis","Secrets and Credentials in Python CLIs",{"path":1720,"title":1721},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Fprompting-for-passwords-securely","Prompting for Passwords Securely in Python CLIs",{"path":1723,"title":1724},"\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":1726,"title":1727},"\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":1729,"title":1730},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Frefreshing-expired-tokens-automatically","Refreshing Expired Tokens Automatically in a Python CLI",{"path":1732,"title":1733},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Fstoring-tokens-with-keyring","Storing CLI Tokens Securely with keyring",{"path":1735,"title":1736},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Fsupporting-multiple-profiles-and-accounts","Supporting Multiple Profiles and Accounts in CLIs",{"path":1738,"title":1739},"\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":1741,"title":1742},"\u002Fcli-runtime-systems-integration\u002Fupdate-checks-and-self-updating-clis","Update Checks, Upgrade Commands and Telemetry for Python CLIs",{"path":1744,"title":1745},"\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":1747,"title":1748},"\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":1750,"title":1751},"\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":1753,"title":1754},"\u002F","Python CLI Toolcraft",{"path":1756,"title":1757},"\u002Fmodern-python-cli-frameworks-architecture\u002Falternative-python-cli-frameworks\u002Fbuilding-a-cli-with-cleo","Building a Python CLI with Cleo Command Classes",{"path":1759,"title":1760},"\u002Fmodern-python-cli-frameworks-architecture\u002Falternative-python-cli-frameworks\u002Fbuilding-a-cli-with-cyclopts","Building a Type-Hinted Python CLI with Cyclopts",{"path":1762,"title":1763},"\u002Fmodern-python-cli-frameworks-architecture\u002Falternative-python-cli-frameworks","Beyond Click and Typer: Alternative Python CLI Frameworks",{"path":1765,"title":1766},"\u002Fmodern-python-cli-frameworks-architecture\u002Falternative-python-cli-frameworks\u002Fquick-clis-from-functions-with-python-fire","Quick CLIs from Functions with Python Fire",{"path":1768,"title":1769},"\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":1771,"title":1772},"\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":1774,"title":1775},"\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":1777,"title":1778},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Fguarding-startup-with-import-tests","Guarding CLI Startup with Import Tests",{"path":1780,"title":1781},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading","CLI Startup Performance and Lazy Loading",{"path":1783,"title":1784},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Flazy-loading-subcommands-for-faster-startup","Lazy Loading Subcommands for Faster Startup",{"path":1786,"title":1787},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Fprofiling-python-cli-startup-time","Profiling Python CLI Startup Time",{"path":1789,"title":1790},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Freducing-cli-dependency-weight","Reducing CLI Dependency Weight",{"path":1792,"title":1793},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargparse-subparsers-for-subcommands","argparse Subparsers for Subcommands",{"path":1795,"title":1796},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargparse-vs-click-vs-typer-comparison","argparse vs Click vs Typer Compared",{"path":1798,"title":1799},"\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":1801,"title":1802},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse","Command-Line Parsing with argparse",{"path":1804,"title":1805},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fmigrating-from-argparse-to-typer","Migrating from argparse to Typer",{"path":1807,"title":1808},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fmutually-exclusive-options-in-argparse","Mutually Exclusive Options in argparse",{"path":1810,"title":1811},"\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":1813,"title":1814},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fwriting-custom-argparse-actions","Writing Custom argparse Actions in Python",{"path":1816,"title":1817},"\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":1819,"title":1820},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Fdesigning-idempotent-commands","Designing Idempotent Commands in a Python CLI",{"path":1822,"title":1823},"\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":1825,"title":1826},"\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":1828,"title":1829},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions","Designing CLI Interfaces and Conventions in Python",{"path":1831,"title":1832},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Fnaming-commands-and-flags-consistently","Naming Commands and Flags Consistently in Python CLIs",{"path":1834,"title":1835},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Fpositional-arguments-vs-options","Positional Arguments vs Options: Designing a CLI Signature",{"path":1837,"title":1838},"\u002Fmodern-python-cli-frameworks-architecture","Python CLI Frameworks and Architecture",{"path":1840,"title":1841},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fdiscovering-plugins-with-entry-points","Discovering Plugins with Entry Points in Python CLIs",{"path":1843,"title":1844},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fhook-based-plugins-with-pluggy","Hook-Based Plugins for Python CLIs with pluggy",{"path":1846,"title":1847},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis","Plugin Architectures for Extensible CLIs",{"path":1849,"title":1850},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fisolating-plugin-failures","Isolating Plugin Failures in an Extensible Python CLI",{"path":1852,"title":1853},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Ftesting-plugins-against-the-host-cli","Testing Plugins Against the Host CLI",{"path":1855,"title":1856},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fversioning-a-plugin-api","Versioning a Plugin API for a Python CLI",{"path":1858,"title":1859},"\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":1861,"title":1862},"\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":1864,"title":1865},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fdependency-injection-patterns-for-cli-commands","Dependency Injection Patterns for CLI Commands",{"path":1867,"title":1868},"\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":1870,"title":1871},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis","Structuring Multi-Command Python CLIs",{"path":1873,"title":1874},"\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":1876,"title":1877},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fregistering-commands-from-modules-automatically","Registering CLI Commands from Modules Automatically",{"path":1879,"title":1880},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fsharing-common-options-across-commands","Sharing Common Options Across Python CLI Commands",{"path":1882,"title":1883},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fsharing-state-with-click-context-objects","Sharing State with Click Context Objects",{"path":1885,"title":1886},"\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":1888,"title":1889},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications","Testing Python CLI Applications",{"path":1891,"title":1892},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fmeasuring-cli-test-coverage","Measuring CLI Test Coverage",{"path":1894,"title":1895},"\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":1897,"title":1898},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fproperty-based-testing-cli-arguments-with-hypothesis","Property-Based Testing CLI Arguments with Hypothesis",{"path":1900,"title":1901},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fsnapshot-testing-cli-output","Snapshot Testing CLI Output",{"path":1903,"title":1904},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftesting-click-commands-with-clirunner","Testing Click Commands with CliRunner",{"path":1906,"title":1907},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftesting-commands-that-call-subprocesses","Testing Python CLI Commands That Call Subprocesses",{"path":1909,"title":1910},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftesting-interactive-prompts-and-stdin","Testing Interactive Prompts and stdin",{"path":1912,"title":1913},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftranscript-tests-for-cli-commands","Transcript Tests for Python CLI Commands",{"path":1915,"title":1916},"\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":1918,"title":1919},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Fbuilding-dynamic-commands-in-click","Building Dynamic Commands in Click",{"path":1921,"title":1922},"\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":1924,"title":1925},"\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":1927,"title":1928},"\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":1930,"title":1931},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each","Typer vs Click: When to Use Each",{"path":1933,"title":1934},"\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":1936,"title":1937},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Ftyper-callback-functions-explained","Typer callback functions explained",{"path":1939,"title":1940},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Fusing-annotated-options-in-typer","Using Annotated Options in Typer",{"path":1942,"title":1943},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis\u002Fautomating-releases-from-git-tags","Automating Python CLI Releases from Git Tags",{"path":1945,"title":1946},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis\u002Fcaching-uv-dependencies-in-ci","Caching uv Dependencies in CI for Python CLIs",{"path":1948,"title":1949},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis","CI\u002FCD Pipelines for Python CLIs",{"path":1951,"title":1952},"\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":1954,"title":1955},"\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":1957,"title":1958},"\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":1960,"title":1961},"\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":1963,"title":1964},"\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":1966,"title":1967},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fbuilding-a-cookiecutter-template-for-typer-clis","Building a Cookiecutter Template for Typer CLIs",{"path":1969,"title":1970},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fcopier-vs-cookiecutter-for-cli-templates","Copier vs Cookiecutter for CLI Templates",{"path":1972,"title":1973},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter","CLI Project Scaffolding with Cookiecutter",{"path":1975,"title":1976},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fpost-generation-hooks-in-cli-templates","Post-Generation Hooks in Python CLI Templates",{"path":1978,"title":1979},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Ftemplate-variables-and-conditional-files","Template Variables and Conditional Files in CLI Templates",{"path":1981,"title":1982},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Ftesting-a-project-template-with-pytest","Testing a CLI Project Template with pytest",{"path":1984,"title":1985},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fupdating-generated-projects-with-copier-update","Updating Generated CLI Projects with copier update",{"path":1987,"title":1988},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fbuilding-cross-platform-release-binaries-in-ci","Building Cross-Platform Release Binaries in CI",{"path":1990,"title":1991},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fbundling-a-python-cli-with-pyinstaller","Bundling a Python CLI with PyInstaller",{"path":1993,"title":1994},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fcode-signing-and-notarizing-cli-binaries","Code Signing and Notarizing Python CLI Binaries",{"path":1996,"title":1997},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fhomebrew-and-scoop-packaging-for-python-clis","Homebrew and Scoop Packaging for Python CLIs",{"path":1999,"title":2000},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries","Distributing CLIs as Standalone Binaries",{"path":2002,"title":2003},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fnuitka-vs-pyinstaller-for-python-clis","Nuitka vs PyInstaller for Python CLI Binaries",{"path":2005,"title":2006},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Freducing-pyinstaller-binary-size","Reducing the Size of a PyInstaller CLI Binary",{"path":2008,"title":2009},"\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":2011,"title":2012},"\u002Fproject-setup-dependency-management","Project Setup & Dependency Management",{"path":2014,"title":2015},"\u002Fproject-setup-dependency-management\u002Flinting-and-type-checking-cli-code\u002Fconfiguring-ruff-for-a-cli-project","Configuring Ruff for a Python CLI Project",{"path":2017,"title":2018},"\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":2020,"title":2021},"\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":2023,"title":2024},"\u002Fproject-setup-dependency-management\u002Flinting-and-type-checking-cli-code","Linting and Type-Checking Python CLI Code",{"path":2026,"title":2027},"\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":2029,"title":2030},"\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":2032,"title":2033},"\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":2035,"title":2036},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fautomating-changelogs-with-conventional-commits","Automating Changelogs with Conventional Commits",{"path":2038,"title":2039},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fbumping-versions-consistently-across-a-cli-project","Bumping Versions Consistently Across a Python CLI Project",{"path":2041,"title":2042},"\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":2044,"title":2045},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fexposing-version-info-and-build-metadata","Exposing Version Info and Build Metadata",{"path":2047,"title":2048},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs","Managing CLI Versioning & Changelogs",{"path":2050,"title":2051},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fsemantic-versioning-policy-for-cli-tools","A Semantic Versioning Policy for CLI Tools",{"path":2053,"title":2054},"\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":2056,"title":2057},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fbuilding-wheels-and-sdists-for-python-clis","Building Wheels and sdists for Python CLIs",{"path":2059,"title":2060},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fbundling-data-files-with-importlib-resources","Bundling Data Files with importlib.resources in CLIs",{"path":2062,"title":2063},"\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":2065,"title":2066},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution","Packaging Python CLIs for Distribution",{"path":2068,"title":2069},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Finstalling-and-distributing-clis-with-pipx","Installing and Distributing CLIs with pipx",{"path":2071,"title":2072},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Foptional-dependencies-and-extras-for-clis","Optional Dependencies and Extras for Python CLIs",{"path":2074,"title":2075},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fpublishing-a-python-cli-to-pypi","Publishing a Python CLI to PyPI",{"path":2077,"title":2078},"\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":2080,"title":2081},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fdynamic-versioning-with-poetry-plugins","Dynamic Versioning for a Poetry CLI from Git Tags",{"path":2083,"title":2084},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development","Poetry Workflows for CLI Development",{"path":2086,"title":2087},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fmanaging-poetry-lock-files-for-cli-tools","Managing Poetry Lock Files for CLI Tools",{"path":1327,"title":5},{"path":2090,"title":2091},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpoetry-dependency-groups-for-cli-tooling","Poetry Dependency Groups for CLI Tooling",{"path":2093,"title":2094},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpoetry-entry-points-and-scripts-for-clis","Poetry Entry Points and Scripts for CLIs",{"path":2096,"title":2097},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpublishing-a-cli-with-poetry","Building and Publishing a Python CLI with Poetry",{"path":2099,"title":2100},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects","Pre-commit Hooks for CLI Projects",{"path":2102,"title":2103},"\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":2105,"title":2106},"\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":2108,"title":2109},"\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":2111,"title":2112},"\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":2114,"title":2115},"\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":2117,"title":2118},"\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":2120,"title":2121},"\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":2123,"title":2124},"\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":2126,"title":2127},"\u002Fproject-setup-dependency-management\u002Fsecuring-a-python-cli-supply-chain","Securing the Supply Chain of a Python CLI",{"path":2129,"title":2130},"\u002Fproject-setup-dependency-management\u002Fsecuring-a-python-cli-supply-chain\u002Fpinning-dependencies-with-hashes","Pinning a Python CLI’s Dependencies with Hashes",{"path":2132,"title":2133},"\u002Fproject-setup-dependency-management\u002Fsecuring-a-python-cli-supply-chain\u002Fpublishing-attestations-and-verifying-releases","Publishing Attestations and Verifying Python CLI Releases",{"path":2135,"title":2136},"\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":2138,"title":2139},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management","uv for Python CLI Dependency Management",{"path":2141,"title":2142},"\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":2144,"title":2145},"\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":2147,"title":2148},"\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":2150,"title":2151},"\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":2153,"title":2154},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002Fuv-tool-install-vs-pipx-for-clis","uv tool install vs pipx for CLIs",{"path":2156,"title":2157},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002Fuv-workspaces-for-multi-package-clis","uv Workspaces for Multi-Package Python CLIs",{"path":2159,"title":2160},"\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":2162,"title":2163},"\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":2165,"title":2166},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices","Python CLI Env Isolation Best Practices",{"path":2168,"title":2169},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fisolating-cli-tools-from-project-dependencies","Isolating CLI Tools from Your Project’s Dependencies",{"path":2171,"title":2172},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fmanaging-virtual-environments-for-cross-platform-clis","Managing Python CLI Virtual Environments",{"path":2174,"title":2175},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fpinning-the-python-version-for-a-cli","Pinning the Python Version for a CLI",{"path":2177,"title":2178},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fsupporting-multiple-python-versions-with-nox","Supporting Multiple Python Versions with nox",1790967542956]