[{"data":1,"prerenderedAt":1882},["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,1837,1840,1843,1846,1849,1852,1855,1858,1861,1864,1867,1870,1873,1876,1879],{"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-dependent-and-conflicting-options","Validating Dependent and Conflicting CLI Options",{"path":1348,"title":1349},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fvalidating-file-and-directory-paths-in-clis","Validating File and Directory Paths in CLIs",{"path":1351,"title":1352},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fwriting-custom-click-parameter-types","Writing Custom Click Parameter Types",{"path":1354,"title":1355},"\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":1357,"title":1358},"\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":1360,"title":1361},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual","Building Terminal UIs with Textual for Python CLIs",{"path":1363,"title":1364},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual\u002Ftesting-textual-apps-with-pilot","Testing Textual Apps with Pilot",{"path":1366,"title":1367},"\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":1369,"title":1370},"\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":1372,"title":1373},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation","CLI Help Output and Documentation",{"path":1375,"title":1376},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fversioning-and-deprecating-cli-flags","Versioning and Deprecating CLI Flags",{"path":1378,"title":1379},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fwriting-help-text-users-actually-read","Writing Help Text Users Actually Read",{"path":1381,"title":1382},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Fadapting-output-to-terminal-width","Adapting Python CLI Output to Terminal Width",{"path":1384,"title":1385},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Fdetecting-ci-environments-and-non-interactive-shells","Detecting CI Environments and Non-Interactive Shells",{"path":1387,"title":1388},"\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":1390,"title":1391},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility","Cross-Platform Terminal Compatibility for Python CLIs",{"path":1393,"title":1394},"\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":1396,"title":1397},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fchoosing-exit-codes-for-cli-tools","Choosing Exit Codes for CLI Tools",{"path":1399,"title":1400},"\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":1402,"title":1403},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Ffriendly-error-messages-and-tracebacks","Friendly Error Messages and Tracebacks",{"path":1405,"title":1406},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fhandling-keyboard-interrupt-cleanly","Handling Keyboard Interrupt Cleanly",{"path":1408,"title":1409},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes","Error Handling and Exit Codes for CLIs",{"path":1411,"title":1412},"\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":1414,"title":1415},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Fconfig-precedence-flags-env-files-defaults","Config Precedence: Flags, Env, Files, Defaults",{"path":1417,"title":1418},"\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":1420,"title":1421},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars","Handling Config Files and Env Vars in CLIs",{"path":1423,"title":1424},"\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":1426,"title":1427},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Freading-toml-config-with-tomllib","Reading TOML Config with tomllib in Python CLIs",{"path":1429,"title":1430},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Ftyped-settings-with-pydantic-settings","Typed Settings with pydantic-settings in Python CLIs",{"path":1432,"title":1433},"\u002Fadvanced-input-parsing-user-experience","Advanced Input Parsing for Python CLIs",{"path":1435,"title":1436},"\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":1438,"title":1439},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Fbuilding-interactive-prompts-and-menus","Building Interactive Prompts and Menus in Python CLIs",{"path":1441,"title":1442},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich","Interactive Terminal UI with Rich",{"path":1444,"title":1445},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Flive-dashboards-with-rich-live","Live Dashboards with Rich Live in Python CLIs",{"path":1447,"title":1448},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Frendering-tables-and-json-with-rich","Rendering Tables and JSON with Rich",{"path":1450,"title":1451},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Ftheming-rich-output-consistently","Theming Rich Output Consistently in Python CLIs",{"path":1453,"title":1454},"\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":1456,"title":1457},"\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":1459,"title":1460},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis","Shell Completion for Python CLIs",{"path":1462,"title":1463},"\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":1465,"title":1466},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Ftesting-shell-completion-in-python-clis","Testing Shell Completion in Python CLIs",{"path":1468,"title":1469},"\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":1471,"title":1472},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fadding-verbose-and-quiet-logging-flags","Adding Verbose and Quiet Logging Flags",{"path":1474,"title":1475},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps","Structured Logging for CLI Apps",{"path":1477,"title":1478},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fstructured-json-logging-in-python-clis","Structured JSON Logging in Python CLIs",{"path":1480,"title":1481},"\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":1483,"title":1484},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Fdetecting-tty-and-adapting-output","Detecting a TTY and Adapting Output",{"path":1486,"title":1487},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Femitting-json-output-for-scripting","Emitting JSON Output for Scripting",{"path":1489,"title":1490},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Fhandling-broken-pipe-and-sigpipe","Handling Broken Pipe and SIGPIPE",{"path":1492,"title":1493},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes","Working with stdin, stdout and Pipes",{"path":1495,"title":1496},"\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":1498,"title":1499},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Freading-piped-input-in-python-clis","Reading Piped Input in Python CLIs",{"path":1501,"title":1502},"\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":1504,"title":1505},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Fdownloading-files-with-progress-in-python","Downloading Files with Progress in Python CLIs",{"path":1507,"title":1508},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis","Calling HTTP APIs from Python CLIs",{"path":1510,"title":1511},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Foauth-device-flow-login-for-clis","OAuth Device Flow Login for Python CLIs",{"path":1513,"title":1514},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Fpaginating-api-results-in-a-cli","Paginating API Results in a Python CLI",{"path":1516,"title":1517},"\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":1519,"title":1520},"\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":1522,"title":1523},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis","Concurrency and Async in Python CLIs",{"path":1525,"title":1526},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Fmultiprocessing-for-cpu-bound-cli-tasks","Multiprocessing for CPU-Bound CLI Tasks",{"path":1528,"title":1529},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Fparallelising-cli-work-with-thread-pools","Parallelising CLI Work with Thread Pools",{"path":1531,"title":1532},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Frate-limiting-concurrent-requests-in-clis","Rate-Limiting Concurrent Requests in Python CLIs",{"path":1534,"title":1535},"\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":1537,"title":1538},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fcross-platform-paths-with-pathlib","Cross-Platform Paths with pathlib in CLIs",{"path":1540,"title":1541},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Ffile-locking-for-concurrent-cli-runs","File Locking for Concurrent CLI Runs in Python",{"path":1543,"title":1544},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes","Filesystem Paths and Atomic Writes for CLIs",{"path":1546,"title":1547},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fsafe-temporary-files-and-directories","Safe Temporary Files and Directories in CLIs",{"path":1549,"title":1550},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fstoring-app-data-with-platformdirs","Storing CLI App Data with platformdirs",{"path":1552,"title":1553},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fwriting-files-atomically-in-python-clis","Writing Files Atomically in Python CLIs",{"path":1555,"title":1556},"\u002Fcli-runtime-systems-integration","CLI Runtime & Systems Integration for Python",{"path":1558,"title":1559},"\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":1561,"title":1562},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis\u002Fhandling-sigterm-and-graceful-shutdown","Handling SIGTERM and Graceful Shutdown in CLIs",{"path":1564,"title":1565},"\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":1567,"title":1568},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis","Long-Running and Watch-Mode Python CLIs",{"path":1570,"title":1571},"\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":1573,"title":1574},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Favoiding-shell-injection-in-python-clis","Avoiding Shell Injection in Python CLIs",{"path":1576,"title":1577},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fcalling-external-commands-safely-with-subprocess","Calling External Commands Safely with subprocess",{"path":1579,"title":1580},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fhandling-subprocess-timeouts-and-exit-codes","Handling Subprocess Timeouts and Exit Codes",{"path":1582,"title":1583},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis","Running Subprocesses from Python CLIs",{"path":1585,"title":1586},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fstreaming-subprocess-output-in-real-time","Streaming Subprocess Output in Real Time",{"path":1588,"title":1589},"\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":1591,"title":1592},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis","Secrets and Credentials in Python CLIs",{"path":1594,"title":1595},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Fprompting-for-passwords-securely","Prompting for Passwords Securely in Python CLIs",{"path":1597,"title":1598},"\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":1600,"title":1601},"\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":1603,"title":1604},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Fstoring-tokens-with-keyring","Storing CLI Tokens Securely with keyring",{"path":1606,"title":1607},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Fsupporting-multiple-profiles-and-accounts","Supporting Multiple Profiles and Accounts in CLIs",{"path":1609,"title":1610},"\u002F","Python CLI Toolcraft",{"path":1612,"title":1613},"\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":1615,"title":1616},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading","CLI Startup Performance and Lazy Loading",{"path":1618,"title":1619},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Flazy-loading-subcommands-for-faster-startup","Lazy Loading Subcommands for Faster Startup",{"path":1621,"title":1622},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Fprofiling-python-cli-startup-time","Profiling Python CLI Startup Time",{"path":1624,"title":1625},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Freducing-cli-dependency-weight","Reducing CLI Dependency Weight",{"path":1627,"title":1628},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargparse-subparsers-for-subcommands","argparse Subparsers for Subcommands",{"path":1630,"title":1631},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargparse-vs-click-vs-typer-comparison","argparse vs Click vs Typer Compared",{"path":1633,"title":1634},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse","Command-Line Parsing with argparse",{"path":1636,"title":1637},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fmigrating-from-argparse-to-typer","Migrating from argparse to Typer",{"path":1639,"title":1640},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fmutually-exclusive-options-in-argparse","Mutually Exclusive Options in argparse",{"path":1642,"title":1643},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fwriting-custom-argparse-actions","Writing Custom argparse Actions in Python",{"path":1645,"title":1646},"\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":1648,"title":1649},"\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":1651,"title":1652},"\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":1654,"title":1655},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions","Designing CLI Interfaces and Conventions in Python",{"path":1657,"title":1658},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Fnaming-commands-and-flags-consistently","Naming Commands and Flags Consistently in Python CLIs",{"path":1660,"title":1661},"\u002Fmodern-python-cli-frameworks-architecture","Python CLI Frameworks and Architecture",{"path":1663,"title":1664},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fdiscovering-plugins-with-entry-points","Discovering Plugins with Entry Points in Python CLIs",{"path":1666,"title":1667},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fhook-based-plugins-with-pluggy","Hook-Based Plugins for Python CLIs with pluggy",{"path":1669,"title":1670},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis","Plugin Architectures for Extensible CLIs",{"path":1672,"title":1673},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fversioning-a-plugin-api","Versioning a Plugin API for a Python CLI",{"path":1675,"title":1676},"\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":1678,"title":1679},"\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":1681,"title":1682},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fdependency-injection-patterns-for-cli-commands","Dependency Injection Patterns for CLI Commands",{"path":1684,"title":1685},"\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":1687,"title":1688},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis","Structuring Multi-Command Python CLIs",{"path":1690,"title":1691},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fsharing-common-options-across-commands","Sharing Common Options Across Python CLI Commands",{"path":1693,"title":1694},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fsharing-state-with-click-context-objects","Sharing State with Click Context Objects",{"path":1696,"title":1697},"\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":1699,"title":1700},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications","Testing Python CLI Applications",{"path":1702,"title":1703},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fmeasuring-cli-test-coverage","Measuring CLI Test Coverage",{"path":1705,"title":1706},"\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":1708,"title":1709},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fproperty-based-testing-cli-arguments-with-hypothesis","Property-Based Testing CLI Arguments with Hypothesis",{"path":1711,"title":1712},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fsnapshot-testing-cli-output","Snapshot Testing CLI Output",{"path":1714,"title":1715},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftesting-click-commands-with-clirunner","Testing Click Commands with CliRunner",{"path":1717,"title":1718},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftesting-interactive-prompts-and-stdin","Testing Interactive Prompts and stdin",{"path":1720,"title":1721},"\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":1723,"title":1724},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Fbuilding-dynamic-commands-in-click","Building Dynamic Commands in Click",{"path":1726,"title":1727},"\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":1729,"title":1730},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each","Typer vs Click: When to Use Each",{"path":1732,"title":1733},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Ftyper-callback-functions-explained","Typer callback functions explained",{"path":1735,"title":1736},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Fusing-annotated-options-in-typer","Using Annotated Options in Typer",{"path":1738,"title":1739},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis\u002Fautomating-releases-from-git-tags","Automating Python CLI Releases from Git Tags",{"path":1741,"title":1742},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis\u002Fcaching-uv-dependencies-in-ci","Caching uv Dependencies in CI for Python CLIs",{"path":1744,"title":1745},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis","CI\u002FCD Pipelines for Python CLIs",{"path":1747,"title":1748},"\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":1750,"title":1751},"\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":1753,"title":1754},"\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":1756,"title":1757},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fbuilding-a-cookiecutter-template-for-typer-clis","Building a Cookiecutter Template for Typer CLIs",{"path":1759,"title":1760},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fcopier-vs-cookiecutter-for-cli-templates","Copier vs Cookiecutter for CLI Templates",{"path":1762,"title":1763},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter","CLI Project Scaffolding with Cookiecutter",{"path":1765,"title":1766},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fpost-generation-hooks-in-cli-templates","Post-Generation Hooks in Python CLI Templates",{"path":1768,"title":1769},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fbuilding-cross-platform-release-binaries-in-ci","Building Cross-Platform Release Binaries in CI",{"path":1771,"title":1772},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fbundling-a-python-cli-with-pyinstaller","Bundling a Python CLI with PyInstaller",{"path":1774,"title":1775},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fhomebrew-and-scoop-packaging-for-python-clis","Homebrew and Scoop Packaging for Python CLIs",{"path":1777,"title":1778},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries","Distributing CLIs as Standalone Binaries",{"path":1780,"title":1781},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fnuitka-vs-pyinstaller-for-python-clis","Nuitka vs PyInstaller for Python CLI Binaries",{"path":1783,"title":1784},"\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":1786,"title":1787},"\u002Fproject-setup-dependency-management","Project Setup & Dependency Management",{"path":1789,"title":1790},"\u002Fproject-setup-dependency-management\u002Flinting-and-type-checking-cli-code\u002Fconfiguring-ruff-for-a-cli-project","Configuring Ruff for a Python CLI Project",{"path":1792,"title":1793},"\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":1795,"title":1796},"\u002Fproject-setup-dependency-management\u002Flinting-and-type-checking-cli-code","Linting and Type-Checking Python CLI Code",{"path":1798,"title":1799},"\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":1801,"title":1802},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fautomating-changelogs-with-conventional-commits","Automating Changelogs with Conventional Commits",{"path":1804,"title":1805},"\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":1807,"title":1808},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fexposing-version-info-and-build-metadata","Exposing Version Info and Build Metadata",{"path":1810,"title":1811},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs","Managing CLI Versioning & Changelogs",{"path":1813,"title":1814},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fsemantic-versioning-policy-for-cli-tools","A Semantic Versioning Policy for CLI Tools",{"path":1816,"title":1817},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fbuilding-wheels-and-sdists-for-python-clis","Building Wheels and sdists for Python CLIs",{"path":1819,"title":1820},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fbundling-data-files-with-importlib-resources","Bundling Data Files with importlib.resources in CLIs",{"path":1822,"title":1823},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution","Packaging Python CLIs for Distribution",{"path":1825,"title":1826},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Finstalling-and-distributing-clis-with-pipx","Installing and Distributing CLIs with pipx",{"path":1828,"title":1829},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fpublishing-a-python-cli-to-pypi","Publishing a Python CLI to PyPI",{"path":1831,"title":1832},"\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":1834,"title":1835},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development","Poetry Workflows for CLI Development",{"path":1327,"title":5},{"path":1838,"title":1839},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpoetry-dependency-groups-for-cli-tooling","Poetry Dependency Groups for CLI Tooling",{"path":1841,"title":1842},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpoetry-entry-points-and-scripts-for-clis","Poetry Entry Points and Scripts for CLIs",{"path":1844,"title":1845},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects","Pre-commit Hooks for CLI Projects",{"path":1847,"title":1848},"\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":1850,"title":1851},"\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":1853,"title":1854},"\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":1856,"title":1857},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management","uv for Python CLI Dependency Management",{"path":1859,"title":1860},"\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":1862,"title":1863},"\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":1865,"title":1866},"\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":1868,"title":1869},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002Fuv-workspaces-for-multi-package-clis","uv Workspaces for Multi-Package Python CLIs",{"path":1871,"title":1872},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices","Python CLI Env Isolation Best Practices",{"path":1874,"title":1875},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fmanaging-virtual-environments-for-cross-platform-clis","Managing Python CLI Virtual Environments",{"path":1877,"title":1878},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fpinning-the-python-version-for-a-cli","Pinning the Python Version for a CLI",{"path":1880,"title":1881},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fsupporting-multiple-python-versions-with-nox","Supporting Multiple Python Versions with nox",1789736907951]