[{"data":1,"prerenderedAt":2348},["ShallowReactive",2],{"page-\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002Fbuilding-and-publishing-a-cli-with-uv\u002F":3,"content-directory":1503},{"id":4,"title":5,"body":6,"date":1488,"description":1489,"difficulty":1490,"draft":1491,"extension":1492,"meta":1493,"navigation":581,"path":1494,"seo":1495,"stem":1496,"tags":1497,"updated":1488,"__hash__":1502},"content\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002Fbuilding-and-publishing-a-cli-with-uv\u002Findex.md","Building and Publishing a Python CLI with uv",{"type":7,"value":8,"toc":1464},"minimark",[9,53,58,76,80,84,91,95,100,106,152,173,177,247,300,304,342,360,363,367,370,411,414,461,469,473,476,491,509,516,520,523,798,806,810,813,878,890,894,897,945,948,952,955,1320,1327,1331,1350,1354,1358,1377,1384,1394,1398,1417,1421,1424,1428,1460],[10,11,12,13,17,18,21,22,25,26,29,30,33,34,37,38,41,42,47,48,52],"p",{},"A release used to need four tools: something to bump the version, ",[14,15,16],"code",{},"build"," to make the wheel and sdist, ",[14,19,20],{},"twine"," to check and upload them, and a virtual environment manager to hold it all together. uv now covers the whole path itself: ",[14,23,24],{},"uv version"," edits the version, ",[14,27,28],{},"uv build"," produces distributions with uv's own fast build backend, and ",[14,31,32],{},"uv publish"," uploads them — with trusted publishing support in CI. For a CLI project that already uses uv for dependencies, that means one tool, one lock file and one set of commands from ",[14,35,36],{},"git tag"," to ",[14,39,40],{},"pipx install",". This guide walks through each step, the checks worth running between them, and a small release script that strings them together. It belongs to the ",[43,44,46],"a",{"href":45},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002F","uv topic","; the general PyPI process is in ",[43,49,51],{"href":50},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fpublishing-a-python-cli-to-pypi\u002F","publishing a Python CLI to PyPI",".",[54,55,57],"h2",{"id":56},"prerequisites","Prerequisites",[59,60,61,73],"ul",{},[62,63,64,65,68,69,52],"li",{},"uv 0.8 or later, and a CLI project with ",[14,66,67],{},"[project.scripts]"," defined — see ",[43,70,72],{"href":71},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fwriting-pyproject-toml-metadata-for-a-cli\u002F","writing pyproject.toml metadata for a CLI",[62,74,75],{},"A PyPI account with a trusted publisher configured for the project, or an API token for a first manual upload.",[54,77,79],{"id":78},"the-release-path","The release path",[81,82],"inline-diagram",{"name":83},"uvrel-path",[10,85,86,87,90],{},"Each step has one job and one command, and each produces something you can inspect before moving on: a changed version line, two files in ",[14,88,89],{},"dist\u002F",", a successful install in a clean environment, and finally a release on PyPI.",[54,92,94],{"id":93},"the-recipe","The recipe",[96,97,99],"h3",{"id":98},"_1-choose-a-build-backend","1. Choose a build backend",[10,101,102,105],{},[14,103,104],{},"uv init --package"," writes this by default:",[107,108,113],"pre",{"className":109,"code":110,"language":111,"meta":112,"style":112},"language-toml shiki shiki-themes github-light github-dark","[build-system]\nrequires = [\"uv_build>=0.8,\u003C0.9\"]\nbuild-backend = \"uv_build\"\n","toml","",[14,114,115,131,143],{"__ignoreMap":112},[116,117,120,124,128],"span",{"class":118,"line":119},"line",1,[116,121,123],{"class":122},"sVt8B","[",[116,125,127],{"class":126},"sScJk","build-system",[116,129,130],{"class":122},"]\n",[116,132,134,137,141],{"class":118,"line":133},2,[116,135,136],{"class":122},"requires = [",[116,138,140],{"class":139},"sZZnC","\"uv_build>=0.8,\u003C0.9\"",[116,142,130],{"class":122},[116,144,146,149],{"class":118,"line":145},3,[116,147,148],{"class":122},"build-backend = ",[116,150,151],{"class":139},"\"uv_build\"\n",[10,153,154,157,158,161,162,164,165,169,170,172],{},[14,155,156],{},"uv_build"," is fast and strict: it expects the conventional ",[14,159,160],{},"src\u002F\u003Cpackage_name>\u002F"," layout, includes package data inside the package directory, and needs almost no configuration. If your project needs build-time customisation — version from git tags, compiled extensions, custom file selection — Hatchling or setuptools remain good choices, and ",[14,163,28],{}," drives them just as well. ",[43,166,168],{"href":167},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fchoosing-a-build-backend-for-a-python-cli\u002F","Choosing a build backend for a Python CLI"," compares the options. The upper bound on ",[14,171,156],{}," is deliberate: build behaviour should change when you choose, not when a new release appears.",[96,174,176],{"id":175},"_2-bump-the-version","2. Bump the version",[107,178,182],{"className":179,"code":180,"language":181,"meta":112,"style":112},"language-bash shiki shiki-themes github-light github-dark","uv version                      # print: mytool 1.5.2\nuv version --bump minor         # 1.5.2 -> 1.6.0, rewrites pyproject.toml, re-locks\nuv version --bump patch --bump rc --dry-run   # preview 1.5.3rc1 without writing\nuv version 2.0.0                # set an explicit version\n","bash",[14,183,184,196,212,234],{"__ignoreMap":112},[116,185,186,189,192],{"class":118,"line":119},[116,187,188],{"class":126},"uv",[116,190,191],{"class":139}," version",[116,193,195],{"class":194},"sJ8bj","                      # print: mytool 1.5.2\n",[116,197,198,200,202,206,209],{"class":118,"line":133},[116,199,188],{"class":126},[116,201,191],{"class":139},[116,203,205],{"class":204},"sj4cs"," --bump",[116,207,208],{"class":139}," minor",[116,210,211],{"class":194},"         # 1.5.2 -> 1.6.0, rewrites pyproject.toml, re-locks\n",[116,213,214,216,218,220,223,225,228,231],{"class":118,"line":145},[116,215,188],{"class":126},[116,217,191],{"class":139},[116,219,205],{"class":204},[116,221,222],{"class":139}," patch",[116,224,205],{"class":204},[116,226,227],{"class":139}," rc",[116,229,230],{"class":204}," --dry-run",[116,232,233],{"class":194},"   # preview 1.5.3rc1 without writing\n",[116,235,237,239,241,244],{"class":118,"line":236},4,[116,238,188],{"class":126},[116,240,191],{"class":139},[116,242,243],{"class":204}," 2.0.0",[116,245,246],{"class":194},"                # set an explicit version\n",[10,248,249,252,253,256,257,256,260,263,264,256,267,256,270,256,273,256,276,279,280,283,284,287,288,291,292,295,296,52],{},[14,250,251],{},"uv version --bump"," understands PEP 440 segments — ",[14,254,255],{},"major",", ",[14,258,259],{},"minor",[14,261,262],{},"patch",", plus ",[14,265,266],{},"alpha",[14,268,269],{},"beta",[14,271,272],{},"rc",[14,274,275],{},"post",[14,277,278],{},"dev"," and ",[14,281,282],{},"stable"," to drop a pre-release suffix — and can combine them, so pre-releases are one command. It updates ",[14,285,286],{},"[project] version"," in ",[14,289,290],{},"pyproject.toml"," and the project's entry in ",[14,293,294],{},"uv.lock"," together. If you derive versions from git tags instead, skip this step; the backend reads the tag at build time, as in ",[43,297,299],{"href":298},"\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",[96,301,303],{"id":302},"_3-build","3. Build",[107,305,307],{"className":179,"code":306,"language":181,"meta":112,"style":112},"rm -rf dist\nuv build                         # dist\u002Fmytool-1.6.0.tar.gz and dist\u002Fmytool-1.6.0-py3-none-any.whl\nuv build --no-sources            # build as a downstream user would, ignoring [tool.uv.sources]\n",[14,308,309,320,330],{"__ignoreMap":112},[116,310,311,314,317],{"class":118,"line":119},[116,312,313],{"class":126},"rm",[116,315,316],{"class":204}," -rf",[116,318,319],{"class":139}," dist\n",[116,321,322,324,327],{"class":118,"line":133},[116,323,188],{"class":126},[116,325,326],{"class":139}," build",[116,328,329],{"class":194},"                         # dist\u002Fmytool-1.6.0.tar.gz and dist\u002Fmytool-1.6.0-py3-none-any.whl\n",[116,331,332,334,336,339],{"class":118,"line":145},[116,333,188],{"class":126},[116,335,326],{"class":139},[116,337,338],{"class":204}," --no-sources",[116,340,341],{"class":194},"            # build as a downstream user would, ignoring [tool.uv.sources]\n",[10,343,344,346,347,351,352,355,356,359],{},[14,345,28],{}," creates an isolated build environment, builds the sdist, then builds the wheel ",[348,349,350],"em",{},"from the sdist"," — which proves the sdist is complete. ",[14,353,354],{},"--no-sources"," matters if you use ",[14,357,358],{},"[tool.uv.sources]"," to point a dependency at a local path or git branch during development: published metadata must not depend on those overrides, and building without them catches that before users do.",[81,361],{"name":362},"uvrel-dist",[96,364,366],{"id":365},"_4-check-the-wheel-before-it-leaves","4. Check the wheel before it leaves",[10,368,369],{},"Two quick checks catch most packaging mistakes. First, look inside the wheel:",[107,371,373],{"className":179,"code":372,"language":181,"meta":112,"style":112},"unzip -l dist\u002Fmytool-1.6.0-py3-none-any.whl | grep -v dist-info\nunzip -p dist\u002Fmytool-1.6.0-py3-none-any.whl 'mytool-1.6.0.dist-info\u002Fentry_points.txt'\n",[14,374,375,399],{"__ignoreMap":112},[116,376,377,380,383,386,390,393,396],{"class":118,"line":119},[116,378,379],{"class":126},"unzip",[116,381,382],{"class":204}," -l",[116,384,385],{"class":139}," dist\u002Fmytool-1.6.0-py3-none-any.whl",[116,387,389],{"class":388},"szBVR"," |",[116,391,392],{"class":126}," grep",[116,394,395],{"class":204}," -v",[116,397,398],{"class":139}," dist-info\n",[116,400,401,403,406,408],{"class":118,"line":133},[116,402,379],{"class":126},[116,404,405],{"class":204}," -p",[116,407,385],{"class":139},[116,409,410],{"class":139}," 'mytool-1.6.0.dist-info\u002Fentry_points.txt'\n",[10,412,413],{},"Second, install it into a throwaway environment, exactly as a user would, and run the command:",[107,415,417],{"className":179,"code":416,"language":181,"meta":112,"style":112},"uv run --isolated --no-project --with dist\u002Fmytool-1.6.0-py3-none-any.whl -- mytool --version\nuvx --from dist\u002Fmytool-1.6.0-py3-none-any.whl mytool --help\n",[14,418,419,446],{"__ignoreMap":112},[116,420,421,423,426,429,432,435,437,440,443],{"class":118,"line":119},[116,422,188],{"class":126},[116,424,425],{"class":139}," run",[116,427,428],{"class":204}," --isolated",[116,430,431],{"class":204}," --no-project",[116,433,434],{"class":204}," --with",[116,436,385],{"class":139},[116,438,439],{"class":204}," --",[116,441,442],{"class":139}," mytool",[116,444,445],{"class":204}," --version\n",[116,447,448,451,454,456,458],{"class":118,"line":133},[116,449,450],{"class":126},"uvx",[116,452,453],{"class":204}," --from",[116,455,385],{"class":139},[116,457,442],{"class":139},[116,459,460],{"class":204}," --help\n",[10,462,463,464,468],{},"Both commands create a temporary environment containing only the wheel and its declared dependencies. A missing dependency, a broken entry point or a data file left out of the package fails here, minutes before it would have failed for every user. ",[43,465,467],{"href":466},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis\u002Fsmoke-testing-the-built-wheel-in-ci\u002F","Smoke-testing the built wheel in CI"," automates the same idea.",[96,470,472],{"id":471},"_5-publish","5. Publish",[10,474,475],{},"From CI with trusted publishing, no credentials are needed:",[107,477,479],{"className":179,"code":478,"language":181,"meta":112,"style":112},"uv publish                       # uploads everything in dist\u002F\n",[14,480,481],{"__ignoreMap":112},[116,482,483,485,488],{"class":118,"line":119},[116,484,188],{"class":126},[116,486,487],{"class":139}," publish",[116,489,490],{"class":194},"                       # uploads everything in dist\u002F\n",[10,492,493,494,497,498,500,501,504,505,508],{},"In GitHub Actions the job needs ",[14,495,496],{},"permissions: id-token: write","; ",[14,499,32],{}," detects the environment and exchanges the OIDC token for a short-lived upload credential. For a first manual upload, before a trusted publisher exists, pass a token through the environment rather than the command line: ",[14,502,503],{},"UV_PUBLISH_TOKEN=pypi-… uv publish",". To rehearse, publish to TestPyPI with ",[14,506,507],{},"uv publish --publish-url https:\u002F\u002Ftest.pypi.org\u002Flegacy\u002F"," and install from there.",[10,510,511,512,515],{},"If a publish is interrupted halfway, run it again with ",[14,513,514],{},"--check-url https:\u002F\u002Fpypi.org\u002Fsimple\u002F"," so uv skips files that already exist instead of failing on them.",[96,517,519],{"id":518},"a-release-script","A release script",[10,521,522],{},"Wrapping the steps in a script makes releases repeatable and keeps the order right:",[107,524,526],{"className":179,"code":525,"language":181,"meta":112,"style":112},"#!\u002Fusr\u002Fbin\u002Fenv bash\n# scripts\u002Frelease.sh — usage: scripts\u002Frelease.sh minor|patch|major|rc\nset -euo pipefail\nbump=${1:?usage: release.sh minor|patch|major|rc}\n\ngit diff --quiet || { echo \"working tree is dirty\" >&2; exit 1; }\nuv lock --check\nuv run --locked pytest -q\n\nuv version --bump \"$bump\"\nversion=$(uv version --short)\nrm -rf dist && uv build --no-sources\nuvx --from \"dist\u002Fmytool-${version}-py3-none-any.whl\" mytool --version | grep -q \"$version\"\n\ngit commit -am \"Release ${version}\" && git tag \"v${version}\"\necho \"Tagged v${version}. Push with: git push --follow-tags\"\n",[14,527,528,533,538,549,576,583,621,632,648,653,671,692,712,746,751,785],{"__ignoreMap":112},[116,529,530],{"class":118,"line":119},[116,531,532],{"class":194},"#!\u002Fusr\u002Fbin\u002Fenv bash\n",[116,534,535],{"class":118,"line":133},[116,536,537],{"class":194},"# scripts\u002Frelease.sh — usage: scripts\u002Frelease.sh minor|patch|major|rc\n",[116,539,540,543,546],{"class":118,"line":145},[116,541,542],{"class":204},"set",[116,544,545],{"class":204}," -euo",[116,547,548],{"class":139}," pipefail\n",[116,550,551,554,557,561,564,567,570,573],{"class":118,"line":236},[116,552,553],{"class":122},"bump",[116,555,556],{"class":388},"=",[116,558,560],{"class":559},"s4XuR","${1",[116,562,563],{"class":388},":?",[116,565,566],{"class":122},"usage",[116,568,569],{"class":388},":",[116,571,572],{"class":122}," release.sh minor|patch|major|rc",[116,574,575],{"class":559},"}\n",[116,577,579],{"class":118,"line":578},5,[116,580,582],{"emptyLinePlaceholder":581},true,"\n",[116,584,586,589,592,595,598,601,604,607,610,612,615,618],{"class":118,"line":585},6,[116,587,588],{"class":126},"git",[116,590,591],{"class":139}," diff",[116,593,594],{"class":204}," --quiet",[116,596,597],{"class":388}," ||",[116,599,600],{"class":122}," { ",[116,602,603],{"class":204},"echo",[116,605,606],{"class":139}," \"working tree is dirty\"",[116,608,609],{"class":388}," >&2",[116,611,497],{"class":122},[116,613,614],{"class":204},"exit",[116,616,617],{"class":204}," 1",[116,619,620],{"class":122},"; }\n",[116,622,624,626,629],{"class":118,"line":623},7,[116,625,188],{"class":126},[116,627,628],{"class":139}," lock",[116,630,631],{"class":204}," --check\n",[116,633,635,637,639,642,645],{"class":118,"line":634},8,[116,636,188],{"class":126},[116,638,425],{"class":139},[116,640,641],{"class":204}," --locked",[116,643,644],{"class":139}," pytest",[116,646,647],{"class":204}," -q\n",[116,649,651],{"class":118,"line":650},9,[116,652,582],{"emptyLinePlaceholder":581},[116,654,656,658,660,662,665,668],{"class":118,"line":655},10,[116,657,188],{"class":126},[116,659,191],{"class":139},[116,661,205],{"class":204},[116,663,664],{"class":139}," \"",[116,666,667],{"class":122},"$bump",[116,669,670],{"class":139},"\"\n",[116,672,674,677,679,682,684,686,689],{"class":118,"line":673},11,[116,675,676],{"class":122},"version",[116,678,556],{"class":388},[116,680,681],{"class":122},"$(",[116,683,188],{"class":126},[116,685,191],{"class":139},[116,687,688],{"class":204}," --short",[116,690,691],{"class":122},")\n",[116,693,695,697,699,702,705,707,709],{"class":118,"line":694},12,[116,696,313],{"class":126},[116,698,316],{"class":204},[116,700,701],{"class":139}," dist",[116,703,704],{"class":122}," && ",[116,706,188],{"class":126},[116,708,326],{"class":139},[116,710,711],{"class":204}," --no-sources\n",[116,713,715,717,719,722,724,727,729,732,734,736,739,741,744],{"class":118,"line":714},13,[116,716,450],{"class":126},[116,718,453],{"class":204},[116,720,721],{"class":139}," \"dist\u002Fmytool-${",[116,723,676],{"class":122},[116,725,726],{"class":139},"}-py3-none-any.whl\"",[116,728,442],{"class":139},[116,730,731],{"class":204}," --version",[116,733,389],{"class":388},[116,735,392],{"class":126},[116,737,738],{"class":204}," -q",[116,740,664],{"class":139},[116,742,743],{"class":122},"$version",[116,745,670],{"class":139},[116,747,749],{"class":118,"line":748},14,[116,750,582],{"emptyLinePlaceholder":581},[116,752,754,756,759,762,765,767,770,772,774,777,780,782],{"class":118,"line":753},15,[116,755,588],{"class":126},[116,757,758],{"class":139}," commit",[116,760,761],{"class":204}," -am",[116,763,764],{"class":139}," \"Release ${",[116,766,676],{"class":122},[116,768,769],{"class":139},"}\"",[116,771,704],{"class":122},[116,773,588],{"class":126},[116,775,776],{"class":139}," tag",[116,778,779],{"class":139}," \"v${",[116,781,676],{"class":122},[116,783,784],{"class":139},"}\"\n",[116,786,788,790,793,795],{"class":118,"line":787},16,[116,789,603],{"class":204},[116,791,792],{"class":139}," \"Tagged v${",[116,794,676],{"class":122},[116,796,797],{"class":139},"}. Push with: git push --follow-tags\"\n",[10,799,800,801,805],{},"The script stops at tagging on purpose: pushing the tag triggers the CI workflow that publishes with trusted publishing, so no upload credential ever lives on a laptop. ",[43,802,804],{"href":803},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis\u002Fautomating-releases-from-git-tags\u002F","Automating releases from git tags"," builds that workflow.",[54,807,809],{"id":808},"when-something-goes-wrong","When something goes wrong",[10,811,812],{},"Releases fail in a small number of recurring ways, and each has a quick fix:",[59,814,815,830,839,852,865],{},[62,816,817,821,822,825,826,829],{},[818,819,820],"strong",{},"\"File already exists\" on upload."," PyPI never lets you replace a file, even after deleting it. Bump to a new version (a ",[14,823,824],{},".post1"," for a packaging-only fix) and publish again; if the upload was interrupted, re-run with ",[14,827,828],{},"--check-url"," so existing files are skipped.",[62,831,832,835,836,838],{},[818,833,834],{},"The wheel lacks a data file."," ",[14,837,156],{}," includes files inside the package directory; files elsewhere need moving into the package or backend configuration. The packaging test below catches this before a release.",[62,840,841,844,845,847,848,851],{},[818,842,843],{},"The installed command is not found."," The ",[14,846,67],{}," entry is missing, misspelled, or points at a function that does not exist; ",[14,849,850],{},"unzip -p … entry_points.txt"," shows what was actually declared.",[62,853,854,857,858,860,861,864],{},[818,855,856],{},"A dependency resolves locally but not for users."," A ",[14,859,358],{}," override was masking a missing or wrong requirement; ",[14,862,863],{},"uv build --no-sources"," and the isolated smoke test reproduce what users see.",[62,866,867,870,871,873,874,877],{},[818,868,869],{},"Wrong version in the wheel."," The tree was not clean, or a stale ",[14,872,89],{}," was published. Always ",[14,875,876],{},"rm -rf dist"," before building, and check the version in the smoke test.",[10,879,880,881,884,885,889],{},"If a broken release does reach PyPI, ",[348,882,883],{},"yank"," it from the project's management page rather than deleting it: yanked releases stay downloadable for anyone who pinned them but are skipped by resolvers, which is what ",[43,886,888],{"href":887},"\u002Fcli-runtime-systems-integration\u002Fupdate-checks-and-self-updating-clis\u002Fchecking-pypi-for-a-newer-version\u002F","the update checks guide"," also relies on.",[54,891,893],{"id":892},"ux-considerations","UX considerations",[10,895,896],{},"The \"users\" of a release process are maintainers, often months apart:",[59,898,899,909,919,929,935],{},[62,900,901,904,905,908],{},[818,902,903],{},"One command per release."," A script or ",[14,906,907],{},"just release minor"," beats a checklist in a wiki page nobody reads.",[62,910,911,914,915,918],{},[818,912,913],{},"Fail before changing anything."," Check for a clean tree, a current lock and passing tests ",[348,916,917],{},"before"," bumping the version, so a failed release leaves nothing to undo.",[62,920,921,924,925,928],{},[818,922,923],{},"Show the version in the smoke test."," Checking that ",[14,926,927],{},"mytool --version"," prints the new number catches the classic mistake of building from stale files.",[62,930,931,934],{},[818,932,933],{},"Keep credentials off laptops."," Tags trigger CI; CI publishes. A token on a developer machine is the credential most likely to leak.",[62,936,937,940,941,52],{},[818,938,939],{},"Write the changelog before tagging",", ideally generated as in ",[43,942,944],{"href":943},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fautomating-changelogs-with-conventional-commits\u002F","automating changelogs with conventional commits",[81,946],{"name":947},"uvrel-terminal",[54,949,951],{"id":950},"testing-the-behaviour","Testing the behaviour",[10,953,954],{},"A test can guard the packaging itself: build the wheel and assert on its contents. This runs in a second or two and catches missing data files and wrong entry points in ordinary test runs:",[107,956,960],{"className":957,"code":958,"language":959,"meta":112,"style":112},"language-python shiki shiki-themes github-light github-dark","# tests\u002Ftest_packaging.py\nimport subprocess\nimport zipfile\nfrom pathlib import Path\n\nimport pytest\n\n\n@pytest.fixture(scope=\"session\")\ndef wheel(tmp_path_factory) -> Path:\n    out = tmp_path_factory.mktemp(\"dist\")\n    subprocess.run([\"uv\", \"build\", \"--wheel\", \"--out-dir\", str(out)], check=True,\n                   capture_output=True)\n    return next(out.glob(\"*.whl\"))\n\n\ndef test_entry_point_is_declared(wheel):\n    with zipfile.ZipFile(wheel) as zf:\n        name = next(n for n in zf.namelist() if n.endswith(\"entry_points.txt\"))\n        entry_points = zf.read(name).decode()\n    assert \"[console_scripts]\" in entry_points\n    assert \"mytool = mytool\" in entry_points\n\n\ndef test_no_tests_or_caches_in_wheel(wheel):\n    with zipfile.ZipFile(wheel) as zf:\n        names = zf.namelist()\n    assert not [n for n in names if n.startswith(\"tests\u002F\") or \"__pycache__\" in n]\n","python",[14,961,962,967,975,982,995,999,1006,1010,1014,1032,1043,1058,1100,1111,1128,1132,1136,1147,1162,1198,1209,1224,1236,1241,1246,1256,1267,1278],{"__ignoreMap":112},[116,963,964],{"class":118,"line":119},[116,965,966],{"class":194},"# tests\u002Ftest_packaging.py\n",[116,968,969,972],{"class":118,"line":133},[116,970,971],{"class":388},"import",[116,973,974],{"class":122}," subprocess\n",[116,976,977,979],{"class":118,"line":145},[116,978,971],{"class":388},[116,980,981],{"class":122}," zipfile\n",[116,983,984,987,990,992],{"class":118,"line":236},[116,985,986],{"class":388},"from",[116,988,989],{"class":122}," pathlib ",[116,991,971],{"class":388},[116,993,994],{"class":122}," Path\n",[116,996,997],{"class":118,"line":578},[116,998,582],{"emptyLinePlaceholder":581},[116,1000,1001,1003],{"class":118,"line":585},[116,1002,971],{"class":388},[116,1004,1005],{"class":122}," pytest\n",[116,1007,1008],{"class":118,"line":623},[116,1009,582],{"emptyLinePlaceholder":581},[116,1011,1012],{"class":118,"line":634},[116,1013,582],{"emptyLinePlaceholder":581},[116,1015,1016,1019,1022,1025,1027,1030],{"class":118,"line":650},[116,1017,1018],{"class":126},"@pytest.fixture",[116,1020,1021],{"class":122},"(",[116,1023,1024],{"class":559},"scope",[116,1026,556],{"class":388},[116,1028,1029],{"class":139},"\"session\"",[116,1031,691],{"class":122},[116,1033,1034,1037,1040],{"class":118,"line":655},[116,1035,1036],{"class":388},"def",[116,1038,1039],{"class":126}," wheel",[116,1041,1042],{"class":122},"(tmp_path_factory) -> Path:\n",[116,1044,1045,1048,1050,1053,1056],{"class":118,"line":673},[116,1046,1047],{"class":122},"    out ",[116,1049,556],{"class":388},[116,1051,1052],{"class":122}," tmp_path_factory.mktemp(",[116,1054,1055],{"class":139},"\"dist\"",[116,1057,691],{"class":122},[116,1059,1060,1063,1066,1068,1071,1073,1076,1078,1081,1083,1086,1089,1092,1094,1097],{"class":118,"line":694},[116,1061,1062],{"class":122},"    subprocess.run([",[116,1064,1065],{"class":139},"\"uv\"",[116,1067,256],{"class":122},[116,1069,1070],{"class":139},"\"build\"",[116,1072,256],{"class":122},[116,1074,1075],{"class":139},"\"--wheel\"",[116,1077,256],{"class":122},[116,1079,1080],{"class":139},"\"--out-dir\"",[116,1082,256],{"class":122},[116,1084,1085],{"class":204},"str",[116,1087,1088],{"class":122},"(out)], ",[116,1090,1091],{"class":559},"check",[116,1093,556],{"class":388},[116,1095,1096],{"class":204},"True",[116,1098,1099],{"class":122},",\n",[116,1101,1102,1105,1107,1109],{"class":118,"line":714},[116,1103,1104],{"class":559},"                   capture_output",[116,1106,556],{"class":388},[116,1108,1096],{"class":204},[116,1110,691],{"class":122},[116,1112,1113,1116,1119,1122,1125],{"class":118,"line":748},[116,1114,1115],{"class":388},"    return",[116,1117,1118],{"class":204}," next",[116,1120,1121],{"class":122},"(out.glob(",[116,1123,1124],{"class":139},"\"*.whl\"",[116,1126,1127],{"class":122},"))\n",[116,1129,1130],{"class":118,"line":753},[116,1131,582],{"emptyLinePlaceholder":581},[116,1133,1134],{"class":118,"line":787},[116,1135,582],{"emptyLinePlaceholder":581},[116,1137,1139,1141,1144],{"class":118,"line":1138},17,[116,1140,1036],{"class":388},[116,1142,1143],{"class":126}," test_entry_point_is_declared",[116,1145,1146],{"class":122},"(wheel):\n",[116,1148,1150,1153,1156,1159],{"class":118,"line":1149},18,[116,1151,1152],{"class":388},"    with",[116,1154,1155],{"class":122}," zipfile.ZipFile(wheel) ",[116,1157,1158],{"class":388},"as",[116,1160,1161],{"class":122}," zf:\n",[116,1163,1165,1168,1170,1172,1175,1178,1181,1184,1187,1190,1193,1196],{"class":118,"line":1164},19,[116,1166,1167],{"class":122},"        name ",[116,1169,556],{"class":388},[116,1171,1118],{"class":204},[116,1173,1174],{"class":122},"(n ",[116,1176,1177],{"class":388},"for",[116,1179,1180],{"class":122}," n ",[116,1182,1183],{"class":388},"in",[116,1185,1186],{"class":122}," zf.namelist() ",[116,1188,1189],{"class":388},"if",[116,1191,1192],{"class":122}," n.endswith(",[116,1194,1195],{"class":139},"\"entry_points.txt\"",[116,1197,1127],{"class":122},[116,1199,1201,1204,1206],{"class":118,"line":1200},20,[116,1202,1203],{"class":122},"        entry_points ",[116,1205,556],{"class":388},[116,1207,1208],{"class":122}," zf.read(name).decode()\n",[116,1210,1212,1215,1218,1221],{"class":118,"line":1211},21,[116,1213,1214],{"class":388},"    assert",[116,1216,1217],{"class":139}," \"[console_scripts]\"",[116,1219,1220],{"class":388}," in",[116,1222,1223],{"class":122}," entry_points\n",[116,1225,1227,1229,1232,1234],{"class":118,"line":1226},22,[116,1228,1214],{"class":388},[116,1230,1231],{"class":139}," \"mytool = mytool\"",[116,1233,1220],{"class":388},[116,1235,1223],{"class":122},[116,1237,1239],{"class":118,"line":1238},23,[116,1240,582],{"emptyLinePlaceholder":581},[116,1242,1244],{"class":118,"line":1243},24,[116,1245,582],{"emptyLinePlaceholder":581},[116,1247,1249,1251,1254],{"class":118,"line":1248},25,[116,1250,1036],{"class":388},[116,1252,1253],{"class":126}," test_no_tests_or_caches_in_wheel",[116,1255,1146],{"class":122},[116,1257,1259,1261,1263,1265],{"class":118,"line":1258},26,[116,1260,1152],{"class":388},[116,1262,1155],{"class":122},[116,1264,1158],{"class":388},[116,1266,1161],{"class":122},[116,1268,1270,1273,1275],{"class":118,"line":1269},27,[116,1271,1272],{"class":122},"        names ",[116,1274,556],{"class":388},[116,1276,1277],{"class":122}," zf.namelist()\n",[116,1279,1281,1283,1286,1289,1291,1293,1295,1298,1300,1303,1306,1309,1312,1315,1317],{"class":118,"line":1280},28,[116,1282,1214],{"class":388},[116,1284,1285],{"class":388}," not",[116,1287,1288],{"class":122}," [n ",[116,1290,1177],{"class":388},[116,1292,1180],{"class":122},[116,1294,1183],{"class":388},[116,1296,1297],{"class":122}," names ",[116,1299,1189],{"class":388},[116,1301,1302],{"class":122}," n.startswith(",[116,1304,1305],{"class":139},"\"tests\u002F\"",[116,1307,1308],{"class":122},") ",[116,1310,1311],{"class":388},"or",[116,1313,1314],{"class":139}," \"__pycache__\"",[116,1316,1220],{"class":388},[116,1318,1319],{"class":122}," n]\n",[10,1321,1322,1323,1326],{},"Mark it with a custom marker (for example ",[14,1324,1325],{},"@pytest.mark.packaging",") if you want to run it only in CI. The installed-command smoke test from step 4 belongs in CI as well, on every platform you support.",[54,1328,1330],{"id":1329},"conclusion","Conclusion",[10,1332,1333,1334,1336,1337,1339,1340,1342,1343,1346,1347,1349],{},"With uv, a release is five commands: choose ",[14,1335,156],{}," (or another backend), bump with ",[14,1338,251],{},", build with ",[14,1341,863],{},", smoke-test the wheel with ",[14,1344,1345],{},"uvx --from",", and publish with ",[14,1348,32],{}," from CI using trusted publishing. Wrap the local part in a script that refuses to start from a dirty tree, let a tag push trigger the publish, and test the wheel's contents in your suite so packaging mistakes surface long before a release.",[54,1351,1353],{"id":1352},"frequently-asked-questions","Frequently asked questions",[96,1355,1357],{"id":1356},"can-i-still-use-twine","Can I still use twine?",[10,1359,1360,1361,1363,1364,279,1367,1370,1371,1373,1374,1376],{},"Yes — ",[14,1362,28],{}," produces standard files that ",[14,1365,1366],{},"twine check",[14,1368,1369],{},"twine upload"," accept. ",[14,1372,32],{}," simply removes the extra tool. ",[14,1375,1366],{}," remains a useful extra validation of the README rendering on PyPI if you like it.",[96,1378,1380,1381,1383],{"id":1379},"does-uv-build-work-for-projects-that-are-not-managed-by-uv","Does ",[14,1382,28],{}," work for projects that are not managed by uv?",[10,1385,1386,1387,1389,1390,1393],{},"It does. ",[14,1388,28],{}," builds any project with a ",[14,1391,1392],{},"[build-system]"," table, using whatever backend it declares, so you can adopt it for builds before moving dependency management.",[96,1395,1397],{"id":1396},"how-do-i-publish-to-a-private-index","How do I publish to a private index?",[10,1399,1400,1401,1403,1404,1407,1408,1411,1412,1416],{},"Declare the index in ",[14,1402,290],{}," with a ",[14,1405,1406],{},"publish-url",", then run ",[14,1409,1410],{},"uv publish --index internal",", with credentials in environment variables. ",[43,1413,1415],{"href":1414},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002Fusing-private-package-indexes-with-uv\u002F","Using private package indexes with uv"," covers the configuration and the dependency-confusion protections that come with it.",[96,1418,1420],{"id":1419},"why-build-the-wheel-from-the-sdist","Why build the wheel from the sdist?",[10,1422,1423],{},"Because users who install from the sdist — on platforms without a matching wheel, or in distributions that rebuild from source — get whatever the sdist contains. Building the wheel from it proves nothing was left out of the source archive.",[54,1425,1427],{"id":1426},"related","Related",[59,1429,1430,1436,1442,1448,1454],{},[62,1431,1432,1433],{},"Up: ",[43,1434,1435],{"href":45},"uv for Python CLI dependency management",[62,1437,1438],{},[43,1439,1441],{"href":1440},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002Flocking-and-syncing-cli-dependencies-with-uv\u002F","Locking and syncing CLI dependencies with uv",[62,1443,1444],{},[43,1445,1447],{"href":1446},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fbuilding-wheels-and-sdists-for-python-clis\u002F","Building wheels and sdists for Python CLIs",[62,1449,1450],{},[43,1451,1453],{"href":1452},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis\u002Fpublishing-to-pypi-with-trusted-publishing\u002F","Publishing to PyPI with trusted publishing",[62,1455,1456],{},[43,1457,1459],{"href":1458},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fshipping-pre-releases-and-release-candidates\u002F","Shipping pre-releases and release candidates",[1461,1462,1463],"style",{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":112,"searchDepth":133,"depth":133,"links":1465},[1466,1467,1468,1476,1477,1478,1479,1480,1487],{"id":56,"depth":133,"text":57},{"id":78,"depth":133,"text":79},{"id":93,"depth":133,"text":94,"children":1469},[1470,1471,1472,1473,1474,1475],{"id":98,"depth":145,"text":99},{"id":175,"depth":145,"text":176},{"id":302,"depth":145,"text":303},{"id":365,"depth":145,"text":366},{"id":471,"depth":145,"text":472},{"id":518,"depth":145,"text":519},{"id":808,"depth":133,"text":809},{"id":892,"depth":133,"text":893},{"id":950,"depth":133,"text":951},{"id":1329,"depth":133,"text":1330},{"id":1352,"depth":133,"text":1353,"children":1481},[1482,1483,1485,1486],{"id":1356,"depth":145,"text":1357},{"id":1379,"depth":145,"text":1484},"Does uv build work for projects that are not managed by uv?",{"id":1396,"depth":145,"text":1397},{"id":1419,"depth":145,"text":1420},{"id":1426,"depth":133,"text":1427},"2026-10-02","Release a Python CLI with only uv: the uv_build backend, uv version --bump, uv build, checking the wheel, uv publish with trusted publishing, and a script.","intermediate",false,"md",{},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002Fbuilding-and-publishing-a-cli-with-uv",{"title":5,"description":1489},"project-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002Fbuilding-and-publishing-a-cli-with-uv\u002Findex",[188,1498,1499,1500,1501],"packaging","pypi","releases","uv-build","prWrcyx_N07VRGjO3p1x_mlyQrD14Goga_e9zVtzNCA",[1504,1507,1510,1513,1516,1519,1522,1525,1528,1531,1534,1537,1540,1543,1546,1549,1552,1555,1558,1561,1564,1567,1570,1573,1576,1579,1582,1585,1588,1591,1594,1597,1600,1603,1606,1609,1612,1615,1618,1621,1624,1627,1630,1633,1636,1639,1642,1645,1648,1651,1654,1657,1660,1663,1666,1669,1672,1675,1678,1681,1684,1687,1690,1693,1696,1699,1702,1705,1708,1711,1714,1717,1720,1723,1726,1729,1732,1735,1738,1741,1744,1747,1750,1753,1756,1759,1762,1765,1768,1771,1774,1777,1780,1783,1786,1789,1792,1795,1798,1801,1804,1807,1810,1813,1816,1819,1822,1825,1828,1831,1834,1837,1840,1843,1846,1849,1852,1855,1858,1861,1864,1867,1870,1873,1876,1879,1882,1885,1888,1891,1894,1897,1900,1903,1906,1909,1912,1915,1918,1921,1924,1927,1930,1933,1936,1939,1942,1945,1948,1951,1954,1957,1960,1963,1966,1969,1972,1975,1978,1981,1984,1987,1990,1993,1996,1999,2002,2005,2008,2011,2014,2017,2020,2023,2026,2029,2032,2035,2038,2041,2044,2047,2050,2053,2056,2059,2062,2065,2068,2071,2074,2077,2080,2083,2086,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,2179,2182,2185,2188,2191,2194,2197,2200,2203,2206,2209,2212,2215,2218,2221,2224,2227,2230,2233,2236,2239,2242,2245,2248,2251,2254,2257,2260,2263,2266,2269,2272,2275,2278,2281,2284,2287,2290,2293,2296,2299,2302,2305,2306,2309,2312,2315,2318,2321,2324,2327,2330,2333,2336,2339,2342,2345],{"path":1505,"title":1506},"\u002Fabout","About Python CLI Toolcraft",{"path":1508,"title":1509},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies","Advanced Argument Validation Strategies",{"path":1511,"title":1512},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fparsing-nested-json-arguments-in-python-clis","Parsing Nested JSON Args in Python CLIs",{"path":1514,"title":1515},"\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":1517,"title":1518},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fvalidating-dependent-and-conflicting-options","Validating Dependent and Conflicting CLI Options",{"path":1520,"title":1521},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fvalidating-file-and-directory-paths-in-clis","Validating File and Directory Paths in CLIs",{"path":1523,"title":1524},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fvalidating-urls-hosts-and-ports","Validating URLs, Hosts and Ports in Python CLI Arguments",{"path":1526,"title":1527},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fwriting-custom-click-parameter-types","Writing Custom Click Parameter Types",{"path":1529,"title":1530},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual\u002Fbrowsing-records-with-a-textual-datatable","Browsing Records with a Textual DataTable Picker",{"path":1532,"title":1533},"\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":1535,"title":1536},"\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":1538,"title":1539},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual","Building Terminal UIs with Textual for Python CLIs",{"path":1541,"title":1542},"\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":1544,"title":1545},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual\u002Fstyling-textual-apps-with-tcss","Styling Textual Apps with TCSS",{"path":1547,"title":1548},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual\u002Ftesting-textual-apps-with-pilot","Testing Textual Apps with Pilot",{"path":1550,"title":1551},"\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":1553,"title":1554},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fdocumenting-environment-variables-in-help","Documenting Environment Variables in CLI Help",{"path":1556,"title":1557},"\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":1559,"title":1560},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation","CLI Help Output and Documentation",{"path":1562,"title":1563},"\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":1565,"title":1566},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fversioning-and-deprecating-cli-flags","Versioning and Deprecating CLI Flags",{"path":1568,"title":1569},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fwriting-help-text-users-actually-read","Writing Help Text Users Actually Read",{"path":1571,"title":1572},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Fadapting-output-to-terminal-width","Adapting Python CLI Output to Terminal Width",{"path":1574,"title":1575},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Fdetecting-ci-environments-and-non-interactive-shells","Detecting CI Environments and Non-Interactive Shells",{"path":1577,"title":1578},"\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":1580,"title":1581},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Fhandling-ansi-escape-codes-on-windows-consoles","Handling ANSI Escape Codes on Windows Consoles",{"path":1583,"title":1584},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility","Cross-Platform Terminal Compatibility for Python CLIs",{"path":1586,"title":1587},"\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":1589,"title":1590},"\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":1592,"title":1593},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fchoosing-exit-codes-for-cli-tools","Choosing Exit Codes for CLI Tools",{"path":1595,"title":1596},"\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":1598,"title":1599},"\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":1601,"title":1602},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Ffriendly-error-messages-and-tracebacks","Friendly Error Messages and Tracebacks",{"path":1604,"title":1605},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fhandling-keyboard-interrupt-cleanly","Handling Keyboard Interrupt Cleanly",{"path":1607,"title":1608},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes","Error Handling and Exit Codes for CLIs",{"path":1610,"title":1611},"\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":1613,"title":1614},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fwriting-crash-reports-users-can-send","Writing Crash Reports Users Can Send",{"path":1616,"title":1617},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Fconfig-precedence-flags-env-files-defaults","Config Precedence: Flags, Env, Files, Defaults",{"path":1619,"title":1620},"\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":1622,"title":1623},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars","Handling Config Files and Env Vars in CLIs",{"path":1625,"title":1626},"\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":1628,"title":1629},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Freading-toml-config-with-tomllib","Reading TOML Config with tomllib in Python CLIs",{"path":1631,"title":1632},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Ftyped-settings-with-pydantic-settings","Typed Settings with pydantic-settings in Python CLIs",{"path":1634,"title":1635},"\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":1637,"title":1638},"\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":1640,"title":1641},"\u002Fadvanced-input-parsing-user-experience","Advanced Input Parsing for Python CLIs",{"path":1643,"title":1644},"\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":1646,"title":1647},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Fbuilding-interactive-prompts-and-menus","Building Interactive Prompts and Menus in Python CLIs",{"path":1649,"title":1650},"\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":1652,"title":1653},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich","Interactive Terminal UI with Rich",{"path":1655,"title":1656},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Flive-dashboards-with-rich-live","Live Dashboards with Rich Live in Python CLIs",{"path":1658,"title":1659},"\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":1661,"title":1662},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Frendering-tables-and-json-with-rich","Rendering Tables and JSON with Rich",{"path":1664,"title":1665},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Ftheming-rich-output-consistently","Theming Rich Output Consistently in Python CLIs",{"path":1667,"title":1668},"\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":1670,"title":1671},"\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":1673,"title":1674},"\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":1676,"title":1677},"\u002Fadvanced-input-parsing-user-experience\u002Foutput-formats-for-data-clis","Output Formats for Data-Heavy Python CLIs",{"path":1679,"title":1680},"\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":1682,"title":1683},"\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":1685,"title":1686},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Fargcomplete-for-argparse-clis","Tab Completion for argparse CLIs with argcomplete",{"path":1688,"title":1689},"\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":1691,"title":1692},"\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":1694,"title":1695},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis","Shell Completion for Python CLIs",{"path":1697,"title":1698},"\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":1700,"title":1701},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Fshipping-completion-scripts-with-packages","Shipping Shell Completion Scripts with a Python CLI",{"path":1703,"title":1704},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Ftesting-shell-completion-in-python-clis","Testing Shell Completion in Python CLIs",{"path":1706,"title":1707},"\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":1709,"title":1710},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fadding-verbose-and-quiet-logging-flags","Adding Verbose and Quiet Logging Flags",{"path":1712,"title":1713},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps","Structured Logging for CLI Apps",{"path":1715,"title":1716},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Flogging-with-structlog-in-a-cli","Logging with structlog in a Python CLI",{"path":1718,"title":1719},"\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":1721,"title":1722},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fstructured-json-logging-in-python-clis","Structured JSON Logging in Python CLIs",{"path":1724,"title":1725},"\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":1727,"title":1728},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Fdetecting-tty-and-adapting-output","Detecting a TTY and Adapting Output",{"path":1730,"title":1731},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Femitting-json-output-for-scripting","Emitting JSON Output for Scripting",{"path":1733,"title":1734},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Fhandling-broken-pipe-and-sigpipe","Handling Broken Pipe and SIGPIPE",{"path":1736,"title":1737},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes","Working with stdin, stdout and Pipes",{"path":1739,"title":1740},"\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":1742,"title":1743},"\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":1745,"title":1746},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Freading-piped-input-in-python-clis","Reading Piped Input in Python CLIs",{"path":1748,"title":1749},"\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":1751,"title":1752},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Fdownloading-files-with-progress-in-python","Downloading Files with Progress in Python CLIs",{"path":1754,"title":1755},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis","Calling HTTP APIs from Python CLIs",{"path":1757,"title":1758},"\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":1760,"title":1761},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Foauth-device-flow-login-for-clis","OAuth Device Flow Login for Python CLIs",{"path":1763,"title":1764},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Fpaginating-api-results-in-a-cli","Paginating API Results in a Python CLI",{"path":1766,"title":1767},"\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":1769,"title":1770},"\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":1772,"title":1773},"\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":1775,"title":1776},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis","Concurrency and Async in Python CLIs",{"path":1778,"title":1779},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Fmultiprocessing-for-cpu-bound-cli-tasks","Multiprocessing for CPU-Bound CLI Tasks",{"path":1781,"title":1782},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Fparallelising-cli-work-with-thread-pools","Parallelising CLI Work with Thread Pools",{"path":1784,"title":1785},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Frate-limiting-concurrent-requests-in-clis","Rate-Limiting Concurrent Requests in Python CLIs",{"path":1787,"title":1788},"\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":1790,"title":1791},"\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":1793,"title":1794},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Fstructured-concurrency-with-taskgroups-in-clis","Structured Concurrency with TaskGroups in Python CLIs",{"path":1796,"title":1797},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fcross-platform-paths-with-pathlib","Cross-Platform Paths with pathlib in CLIs",{"path":1799,"title":1800},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Ffile-locking-for-concurrent-cli-runs","File Locking for Concurrent CLI Runs in Python",{"path":1802,"title":1803},"\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":1805,"title":1806},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes","Filesystem Paths and Atomic Writes for CLIs",{"path":1808,"title":1809},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fsafe-temporary-files-and-directories","Safe Temporary Files and Directories in CLIs",{"path":1811,"title":1812},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fstoring-app-data-with-platformdirs","Storing CLI App Data with platformdirs",{"path":1814,"title":1815},"\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":1817,"title":1818},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fwriting-files-atomically-in-python-clis","Writing Files Atomically in Python CLIs",{"path":1820,"title":1821},"\u002Fcli-runtime-systems-integration","CLI Runtime & Systems Integration for Python",{"path":1823,"title":1824},"\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":1826,"title":1827},"\u002Fcli-runtime-systems-integration\u002Flocal-state-and-sqlite-in-python-clis","Local State and SQLite in Python CLIs",{"path":1829,"title":1830},"\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":1832,"title":1833},"\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":1835,"title":1836},"\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":1838,"title":1839},"\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":1841,"title":1842},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis\u002Fhandling-sigterm-and-graceful-shutdown","Handling SIGTERM and Graceful Shutdown in CLIs",{"path":1844,"title":1845},"\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":1847,"title":1848},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis","Long-Running and Watch-Mode Python CLIs",{"path":1850,"title":1851},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis\u002Freloading-config-on-sighup","Reloading Configuration on SIGHUP in a Python CLI",{"path":1853,"title":1854},"\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":1856,"title":1857},"\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":1859,"title":1860},"\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":1862,"title":1863},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Favoiding-shell-injection-in-python-clis","Avoiding Shell Injection in Python CLIs",{"path":1865,"title":1866},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fcalling-external-commands-safely-with-subprocess","Calling External Commands Safely with subprocess",{"path":1868,"title":1869},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fhandling-subprocess-timeouts-and-exit-codes","Handling Subprocess Timeouts and Exit Codes",{"path":1871,"title":1872},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis","Running Subprocesses from Python CLIs",{"path":1874,"title":1875},"\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":1877,"title":1878},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fpiping-between-subprocesses-in-python","Piping Between Subprocesses in Python Without a Shell",{"path":1880,"title":1881},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fstreaming-subprocess-output-in-real-time","Streaming Subprocess Output in Real Time",{"path":1883,"title":1884},"\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":1886,"title":1887},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis","Secrets and Credentials in Python CLIs",{"path":1889,"title":1890},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Fprompting-for-passwords-securely","Prompting for Passwords Securely in Python CLIs",{"path":1892,"title":1893},"\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":1895,"title":1896},"\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":1898,"title":1899},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Frefreshing-expired-tokens-automatically","Refreshing Expired Tokens Automatically in a Python CLI",{"path":1901,"title":1902},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Fstoring-tokens-with-keyring","Storing CLI Tokens Securely with keyring",{"path":1904,"title":1905},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Fsupporting-multiple-profiles-and-accounts","Supporting Multiple Profiles and Accounts in CLIs",{"path":1907,"title":1908},"\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":1910,"title":1911},"\u002Fcli-runtime-systems-integration\u002Fupdate-checks-and-self-updating-clis","Update Checks, Upgrade Commands and Telemetry for Python CLIs",{"path":1913,"title":1914},"\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":1916,"title":1917},"\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":1919,"title":1920},"\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":1922,"title":1923},"\u002F","Python CLI Toolcraft",{"path":1925,"title":1926},"\u002Fmodern-python-cli-frameworks-architecture\u002Falternative-python-cli-frameworks\u002Fbuilding-a-cli-with-cleo","Building a Python CLI with Cleo Command Classes",{"path":1928,"title":1929},"\u002Fmodern-python-cli-frameworks-architecture\u002Falternative-python-cli-frameworks\u002Fbuilding-a-cli-with-cyclopts","Building a Type-Hinted Python CLI with Cyclopts",{"path":1931,"title":1932},"\u002Fmodern-python-cli-frameworks-architecture\u002Falternative-python-cli-frameworks","Beyond Click and Typer: Alternative Python CLI Frameworks",{"path":1934,"title":1935},"\u002Fmodern-python-cli-frameworks-architecture\u002Falternative-python-cli-frameworks\u002Fquick-clis-from-functions-with-python-fire","Quick CLIs from Functions with Python Fire",{"path":1937,"title":1938},"\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":1940,"title":1941},"\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":1943,"title":1944},"\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":1946,"title":1947},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Fguarding-startup-with-import-tests","Guarding CLI Startup with Import Tests",{"path":1949,"title":1950},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading","CLI Startup Performance and Lazy Loading",{"path":1952,"title":1953},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Flazy-loading-subcommands-for-faster-startup","Lazy Loading Subcommands for Faster Startup",{"path":1955,"title":1956},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Fprofiling-python-cli-startup-time","Profiling Python CLI Startup Time",{"path":1958,"title":1959},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Freducing-cli-dependency-weight","Reducing CLI Dependency Weight",{"path":1961,"title":1962},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargparse-subparsers-for-subcommands","argparse Subparsers for Subcommands",{"path":1964,"title":1965},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargparse-vs-click-vs-typer-comparison","argparse vs Click vs Typer Compared",{"path":1967,"title":1968},"\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":1970,"title":1971},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse","Command-Line Parsing with argparse",{"path":1973,"title":1974},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fmigrating-from-argparse-to-typer","Migrating from argparse to Typer",{"path":1976,"title":1977},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fmutually-exclusive-options-in-argparse","Mutually Exclusive Options in argparse",{"path":1979,"title":1980},"\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":1982,"title":1983},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fwriting-custom-argparse-actions","Writing Custom argparse Actions in Python",{"path":1985,"title":1986},"\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":1988,"title":1989},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Fdesigning-idempotent-commands","Designing Idempotent Commands in a Python CLI",{"path":1991,"title":1992},"\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":1994,"title":1995},"\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":1997,"title":1998},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions","Designing CLI Interfaces and Conventions in Python",{"path":2000,"title":2001},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Fnaming-commands-and-flags-consistently","Naming Commands and Flags Consistently in Python CLIs",{"path":2003,"title":2004},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Fpositional-arguments-vs-options","Positional Arguments vs Options: Designing a CLI Signature",{"path":2006,"title":2007},"\u002Fmodern-python-cli-frameworks-architecture","Python CLI Frameworks and Architecture",{"path":2009,"title":2010},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fdiscovering-plugins-with-entry-points","Discovering Plugins with Entry Points in Python CLIs",{"path":2012,"title":2013},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fhook-based-plugins-with-pluggy","Hook-Based Plugins for Python CLIs with pluggy",{"path":2015,"title":2016},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis","Plugin Architectures for Extensible CLIs",{"path":2018,"title":2019},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fisolating-plugin-failures","Isolating Plugin Failures in an Extensible Python CLI",{"path":2021,"title":2022},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Ftesting-plugins-against-the-host-cli","Testing Plugins Against the Host CLI",{"path":2024,"title":2025},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fversioning-a-plugin-api","Versioning a Plugin API for a Python CLI",{"path":2027,"title":2028},"\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":2030,"title":2031},"\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":2033,"title":2034},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fdependency-injection-patterns-for-cli-commands","Dependency Injection Patterns for CLI Commands",{"path":2036,"title":2037},"\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":2039,"title":2040},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis","Structuring Multi-Command Python CLIs",{"path":2042,"title":2043},"\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":2045,"title":2046},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fregistering-commands-from-modules-automatically","Registering CLI Commands from Modules Automatically",{"path":2048,"title":2049},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fsharing-common-options-across-commands","Sharing Common Options Across Python CLI Commands",{"path":2051,"title":2052},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fsharing-state-with-click-context-objects","Sharing State with Click Context Objects",{"path":2054,"title":2055},"\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":2057,"title":2058},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications","Testing Python CLI Applications",{"path":2060,"title":2061},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fmeasuring-cli-test-coverage","Measuring CLI Test Coverage",{"path":2063,"title":2064},"\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":2066,"title":2067},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fproperty-based-testing-cli-arguments-with-hypothesis","Property-Based Testing CLI Arguments with Hypothesis",{"path":2069,"title":2070},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fsnapshot-testing-cli-output","Snapshot Testing CLI Output",{"path":2072,"title":2073},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftesting-click-commands-with-clirunner","Testing Click Commands with CliRunner",{"path":2075,"title":2076},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftesting-commands-that-call-subprocesses","Testing Python CLI Commands That Call Subprocesses",{"path":2078,"title":2079},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftesting-interactive-prompts-and-stdin","Testing Interactive Prompts and stdin",{"path":2081,"title":2082},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftranscript-tests-for-cli-commands","Transcript Tests for Python CLI Commands",{"path":2084,"title":2085},"\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":2087,"title":2088},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Fbuilding-dynamic-commands-in-click","Building Dynamic Commands in Click",{"path":2090,"title":2091},"\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":2093,"title":2094},"\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":2096,"title":2097},"\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":2099,"title":2100},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each","Typer vs Click: When to Use Each",{"path":2102,"title":2103},"\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":2105,"title":2106},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Ftyper-callback-functions-explained","Typer callback functions explained",{"path":2108,"title":2109},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Fusing-annotated-options-in-typer","Using Annotated Options in Typer",{"path":2111,"title":2112},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis\u002Fautomating-releases-from-git-tags","Automating Python CLI Releases from Git Tags",{"path":2114,"title":2115},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis\u002Fcaching-uv-dependencies-in-ci","Caching uv Dependencies in CI for Python CLIs",{"path":2117,"title":2118},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis","CI\u002FCD Pipelines for Python CLIs",{"path":2120,"title":2121},"\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":2123,"title":2124},"\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":2126,"title":2127},"\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":2129,"title":2130},"\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":2132,"title":2133},"\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":2135,"title":2136},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fbuilding-a-cookiecutter-template-for-typer-clis","Building a Cookiecutter Template for Typer CLIs",{"path":2138,"title":2139},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fcopier-vs-cookiecutter-for-cli-templates","Copier vs Cookiecutter for CLI Templates",{"path":2141,"title":2142},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter","CLI Project Scaffolding with Cookiecutter",{"path":2144,"title":2145},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fpost-generation-hooks-in-cli-templates","Post-Generation Hooks in Python CLI Templates",{"path":2147,"title":2148},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Ftemplate-variables-and-conditional-files","Template Variables and Conditional Files in CLI Templates",{"path":2150,"title":2151},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Ftesting-a-project-template-with-pytest","Testing a CLI Project Template with pytest",{"path":2153,"title":2154},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fupdating-generated-projects-with-copier-update","Updating Generated CLI Projects with copier update",{"path":2156,"title":2157},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fbuilding-cross-platform-release-binaries-in-ci","Building Cross-Platform Release Binaries in CI",{"path":2159,"title":2160},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fbundling-a-python-cli-with-pyinstaller","Bundling a Python CLI with PyInstaller",{"path":2162,"title":2163},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fcode-signing-and-notarizing-cli-binaries","Code Signing and Notarizing Python CLI Binaries",{"path":2165,"title":2166},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fhomebrew-and-scoop-packaging-for-python-clis","Homebrew and Scoop Packaging for Python CLIs",{"path":2168,"title":2169},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries","Distributing CLIs as Standalone Binaries",{"path":2171,"title":2172},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fnuitka-vs-pyinstaller-for-python-clis","Nuitka vs PyInstaller for Python CLI Binaries",{"path":2174,"title":2175},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Freducing-pyinstaller-binary-size","Reducing the Size of a PyInstaller CLI Binary",{"path":2177,"title":2178},"\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":2180,"title":2181},"\u002Fproject-setup-dependency-management","Project Setup & Dependency Management",{"path":2183,"title":2184},"\u002Fproject-setup-dependency-management\u002Flinting-and-type-checking-cli-code\u002Fconfiguring-ruff-for-a-cli-project","Configuring Ruff for a Python CLI Project",{"path":2186,"title":2187},"\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":2189,"title":2190},"\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":2192,"title":2193},"\u002Fproject-setup-dependency-management\u002Flinting-and-type-checking-cli-code","Linting and Type-Checking Python CLI Code",{"path":2195,"title":2196},"\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":2198,"title":2199},"\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":2201,"title":2202},"\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":2204,"title":2205},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fautomating-changelogs-with-conventional-commits","Automating Changelogs with Conventional Commits",{"path":2207,"title":2208},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fbumping-versions-consistently-across-a-cli-project","Bumping Versions Consistently Across a Python CLI Project",{"path":2210,"title":2211},"\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":2213,"title":2214},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fexposing-version-info-and-build-metadata","Exposing Version Info and Build Metadata",{"path":2216,"title":2217},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs","Managing CLI Versioning & Changelogs",{"path":2219,"title":2220},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fsemantic-versioning-policy-for-cli-tools","A Semantic Versioning Policy for CLI Tools",{"path":2222,"title":2223},"\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":2225,"title":2226},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fbuilding-wheels-and-sdists-for-python-clis","Building Wheels and sdists for Python CLIs",{"path":2228,"title":2229},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fbundling-data-files-with-importlib-resources","Bundling Data Files with importlib.resources in CLIs",{"path":2231,"title":2232},"\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":2234,"title":2235},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution","Packaging Python CLIs for Distribution",{"path":2237,"title":2238},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Finstalling-and-distributing-clis-with-pipx","Installing and Distributing CLIs with pipx",{"path":2240,"title":2241},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Foptional-dependencies-and-extras-for-clis","Optional Dependencies and Extras for Python CLIs",{"path":2243,"title":2244},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fpublishing-a-python-cli-to-pypi","Publishing a Python CLI to PyPI",{"path":2246,"title":2247},"\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":2249,"title":2250},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fdynamic-versioning-with-poetry-plugins","Dynamic Versioning for a Poetry CLI from Git Tags",{"path":2252,"title":2253},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development","Poetry Workflows for CLI Development",{"path":2255,"title":2256},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fmanaging-poetry-lock-files-for-cli-tools","Managing Poetry Lock Files for CLI Tools",{"path":2258,"title":2259},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fmigrating-a-cli-from-poetry-to-uv","Migrating a Python CLI from Poetry to uv",{"path":2261,"title":2262},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpoetry-dependency-groups-for-cli-tooling","Poetry Dependency Groups for CLI Tooling",{"path":2264,"title":2265},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpoetry-entry-points-and-scripts-for-clis","Poetry Entry Points and Scripts for CLIs",{"path":2267,"title":2268},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpublishing-a-cli-with-poetry","Building and Publishing a Python CLI with Poetry",{"path":2270,"title":2271},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects","Pre-commit Hooks for CLI Projects",{"path":2273,"title":2274},"\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":2276,"title":2277},"\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":2279,"title":2280},"\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":2282,"title":2283},"\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":2285,"title":2286},"\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":2288,"title":2289},"\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":2291,"title":2292},"\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":2294,"title":2295},"\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":2297,"title":2298},"\u002Fproject-setup-dependency-management\u002Fsecuring-a-python-cli-supply-chain","Securing the Supply Chain of a Python CLI",{"path":2300,"title":2301},"\u002Fproject-setup-dependency-management\u002Fsecuring-a-python-cli-supply-chain\u002Fpinning-dependencies-with-hashes","Pinning a Python CLI’s Dependencies with Hashes",{"path":2303,"title":2304},"\u002Fproject-setup-dependency-management\u002Fsecuring-a-python-cli-supply-chain\u002Fpublishing-attestations-and-verifying-releases","Publishing Attestations and Verifying Python CLI Releases",{"path":1494,"title":5},{"path":2307,"title":2308},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management","uv for Python CLI Dependency Management",{"path":2310,"title":2311},"\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":2313,"title":2314},"\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":2316,"title":2317},"\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":2319,"title":2320},"\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":2322,"title":2323},"\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":2325,"title":2326},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002Fuv-workspaces-for-multi-package-clis","uv Workspaces for Multi-Package Python CLIs",{"path":2328,"title":2329},"\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":2331,"title":2332},"\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":2334,"title":2335},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices","Python CLI Env Isolation Best Practices",{"path":2337,"title":2338},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fisolating-cli-tools-from-project-dependencies","Isolating CLI Tools from Your Project’s Dependencies",{"path":2340,"title":2341},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fmanaging-virtual-environments-for-cross-platform-clis","Managing Python CLI Virtual Environments",{"path":2343,"title":2344},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fpinning-the-python-version-for-a-cli","Pinning the Python Version for a CLI",{"path":2346,"title":2347},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fsupporting-multiple-python-versions-with-nox","Supporting Multiple Python Versions with nox",1790967543050]