[{"data":1,"prerenderedAt":1849},["ShallowReactive",2],{"page-\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fpinning-the-python-version-for-a-cli\u002F":3,"content-directory":1004},{"id":4,"title":5,"body":6,"date":990,"description":991,"difficulty":992,"draft":993,"extension":994,"meta":995,"navigation":748,"path":996,"seo":997,"stem":998,"tags":999,"updated":990,"__hash__":1003},"content\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fpinning-the-python-version-for-a-cli\u002Findex.md","Pinning the Python Version for a CLI",{"type":7,"value":8,"toc":967},"minimark",[9,13,18,58,62,68,72,76,120,140,155,166,170,173,176,192,218,257,282,296,300,303,306,323,329,332,344,348,474,477,492,495,566,570,577,580,603,606,669,673,679,694,704,708,711,850,856,862,866,869,873,878,881,888,891,895,898,905,908,912,918,922,925,929,963],[10,11,12],"p",{},"A CLI runs on whatever interpreter the user has, which is rarely the one you developed against.\nDeclaring which versions you support — and testing exactly those — is what turns \"it should work\"\ninto something an installer can enforce before anything breaks.",[14,15,17],"h2",{"id":16},"tldr","TL;DR",[19,20,21,34,42,48,55],"ul",{},[22,23,24,25,29,30,33],"li",{},"Declare ",[26,27,28],"code",{},"requires-python"," in ",[26,31,32],{},"pyproject.toml","; installers refuse unsupported versions up front.",[22,35,36,37,41],{},"Test the ",[38,39,40],"strong",{},"ends"," of that range in CI, not the middle — those are the versions that break.",[22,43,44,47],{},[26,45,46],{},".python-version"," sets the local default; it is not a support statement.",[22,49,50,51,54],{},"Raising the floor is a ",[38,52,53],{},"breaking change",": bump the major version and say so.",[22,56,57],{},"Follow upstream end-of-life dates for a policy nobody has to argue about.",[14,59,61],{"id":60},"prerequisites","Prerequisites",[10,63,64,65,67],{},"A packaged CLI with a ",[26,66,32],{},", and a CI workflow you can add a matrix to.",[14,69,71],{"id":70},"what-requires-python-actually-does","What requires-python actually does",[73,74],"inline-diagram",{"name":75},"requires-python-flow",[77,78,83],"pre",{"className":79,"code":80,"language":81,"meta":82,"style":82},"language-toml shiki shiki-themes github-light github-dark","[project]\nname = \"mytool\"\nrequires-python = \">=3.11\"\n","toml","",[26,84,85,101,111],{"__ignoreMap":82},[86,87,90,94,98],"span",{"class":88,"line":89},"line",1,[86,91,93],{"class":92},"sVt8B","[",[86,95,97],{"class":96},"sScJk","project",[86,99,100],{"class":92},"]\n",[86,102,104,107],{"class":88,"line":103},2,[86,105,106],{"class":92},"name = ",[86,108,110],{"class":109},"sZZnC","\"mytool\"\n",[86,112,114,117],{"class":88,"line":113},3,[86,115,116],{"class":92},"requires-python = ",[86,118,119],{"class":109},"\">=3.11\"\n",[10,121,122,123,127,128,131,132,135,136,139],{},"That line is metadata the installer reads ",[124,125,126],"em",{},"before"," downloading anything. On an unsupported\ninterpreter, ",[26,129,130],{},"pip install mytool"," or ",[26,133,134],{},"pipx install mytool"," fails with a message naming the required\nrange — instead of installing successfully and failing later with a ",[26,137,138],{},"SyntaxError"," on a match\nstatement, which is the failure people actually report.",[10,141,142,143,146,147,150,151,154],{},"Two details are worth getting right. Use ",[26,144,145],{},">="," with no upper bound unless you have a specific reason:\ncapping at ",[26,148,149],{},"\u003C4.0"," is conventional and harmless, but ",[26,152,153],{},"\u003C3.14"," means your tool stops installing the day\na new Python is released, which is rarely what you want and always discovered at the worst time.",[10,156,157,158,161,162,165],{},"And keep it honest. ",[26,159,160],{},">=3.9"," while your code uses ",[26,163,164],{},"X | None"," syntax means the metadata promises\nsomething the code cannot deliver, and the failure moves back to being a confusing runtime error.",[14,167,169],{"id":168},"the-four-places-a-version-is-decided","The four places a version is decided",[73,171],{"name":172},"python-version-matrix",[10,174,175],{},"They serve different purposes and are easy to conflate:",[77,177,179],{"className":79,"code":178,"language":81,"meta":82,"style":82},"requires-python = \">=3.11\"        # what may install this package — a promise to users\n",[26,180,181],{"__ignoreMap":82},[86,182,183,185,188],{"class":88,"line":89},[86,184,116],{"class":92},[86,186,187],{"class":109},"\">=3.11\"",[86,189,191],{"class":190},"sJ8bj","        # what may install this package — a promise to users\n",[77,193,197],{"className":194,"code":195,"language":196,"meta":82,"style":82},"language-bash shiki shiki-themes github-light github-dark","echo \"3.12\" > .python-version     # what uv or pyenv uses locally — a developer convenience\n","bash",[26,198,199],{"__ignoreMap":82},[86,200,201,205,208,212,215],{"class":88,"line":89},[86,202,204],{"class":203},"sj4cs","echo",[86,206,207],{"class":109}," \"3.12\"",[86,209,211],{"class":210},"szBVR"," >",[86,213,214],{"class":109}," .python-version",[86,216,217],{"class":190},"     # what uv or pyenv uses locally — a developer convenience\n",[77,219,223],{"className":220,"code":221,"language":222,"meta":82,"style":82},"language-yaml shiki shiki-themes github-light github-dark","matrix:\n  python: [\"3.11\", \"3.13\"]        # what is actually verified — the truth\n","yaml",[26,224,225,234],{"__ignoreMap":82},[86,226,227,231],{"class":88,"line":89},[86,228,230],{"class":229},"s9eBZ","matrix",[86,232,233],{"class":92},":\n",[86,235,236,239,242,245,248,251,254],{"class":88,"line":103},[86,237,238],{"class":229},"  python",[86,240,241],{"class":92},": [",[86,243,244],{"class":109},"\"3.11\"",[86,246,247],{"class":92},", ",[86,249,250],{"class":109},"\"3.13\"",[86,252,253],{"class":92},"]        ",[86,255,256],{"class":190},"# what is actually verified — the truth\n",[77,258,260],{"className":194,"code":259,"language":196,"meta":82,"style":82},"pipx install mytool --python 3.12 # what the user's install runs on — their choice\n",[26,261,262],{"__ignoreMap":82},[86,263,264,267,270,273,276,279],{"class":88,"line":89},[86,265,266],{"class":96},"pipx",[86,268,269],{"class":109}," install",[86,271,272],{"class":109}," mytool",[86,274,275],{"class":203}," --python",[86,277,278],{"class":203}," 3.12",[86,280,281],{"class":190}," # what the user's install runs on — their choice\n",[10,283,284,285,287,288,291,292,295],{},"The pair that must agree is the first and third. If ",[26,286,28],{}," says ",[26,289,290],{},">=3.10"," and CI tests\nonly 3.12, you are shipping support you have never verified; if CI tests 3.9 and the metadata says\n",[26,293,294],{},">=3.11",", you are testing something nobody can install.",[14,297,299],{"id":298},"choosing-the-floor","Choosing the floor",[73,301],{"name":302},"version-support-timeline",[10,304,305],{},"Two considerations, pulling in opposite directions.",[10,307,308,311,312,315,316,318,319,322],{},[38,309,310],{},"Language features you want."," Each release brings things worth having: ",[26,313,314],{},"tomllib"," in 3.11 removes a\ndependency for TOML configuration, and structural pattern matching, ",[26,317,164],{}," annotations and\n",[26,320,321],{},"ExceptionGroup"," all arrived recently enough to matter. Raising the floor makes code simpler.",[10,324,325,328],{},[38,326,327],{},"Where your users are."," Distribution Pythons lag. A tool aimed at developers can require a recent\nversion; one aimed at servers or CI images has to survive whatever the base image ships.",[10,330,331],{},"The defensible policy is to follow upstream: Python releases get five years of support, and dropping\na version once its security support ends is a rule nobody has to argue about. In practice that keeps\nyou supporting three or four versions at a time, which is a manageable matrix.",[10,333,334,335,337,338,340,341,343],{},"For a new CLI in 2026, ",[26,336,294],{}," is a reasonable default: it gets ",[26,339,314],{},", the improved error\nmessages, and ",[26,342,321],{},", while remaining widely available.",[14,345,347],{"id":346},"a-ci-matrix-that-matches","A CI matrix that matches",[77,349,351],{"className":220,"code":350,"language":222,"meta":82,"style":82},"strategy:\n  fail-fast: false\n  matrix:\n    python: [\"3.11\", \"3.13\"]        # the ends of requires-python\n    os: [ubuntu-latest, windows-latest]\nsteps:\n  - uses: actions\u002Fcheckout@v4\n  - uses: astral-sh\u002Fsetup-uv@v3\n  - run: uv sync --frozen --python ${{ matrix.python }}\n  - run: uv run pytest -q\n",[26,352,353,360,371,378,397,415,423,437,449,462],{"__ignoreMap":82},[86,354,355,358],{"class":88,"line":89},[86,356,357],{"class":229},"strategy",[86,359,233],{"class":92},[86,361,362,365,368],{"class":88,"line":103},[86,363,364],{"class":229},"  fail-fast",[86,366,367],{"class":92},": ",[86,369,370],{"class":203},"false\n",[86,372,373,376],{"class":88,"line":113},[86,374,375],{"class":229},"  matrix",[86,377,233],{"class":92},[86,379,381,384,386,388,390,392,394],{"class":88,"line":380},4,[86,382,383],{"class":229},"    python",[86,385,241],{"class":92},[86,387,244],{"class":109},[86,389,247],{"class":92},[86,391,250],{"class":109},[86,393,253],{"class":92},[86,395,396],{"class":190},"# the ends of requires-python\n",[86,398,400,403,405,408,410,413],{"class":88,"line":399},5,[86,401,402],{"class":229},"    os",[86,404,241],{"class":92},[86,406,407],{"class":109},"ubuntu-latest",[86,409,247],{"class":92},[86,411,412],{"class":109},"windows-latest",[86,414,100],{"class":92},[86,416,418,421],{"class":88,"line":417},6,[86,419,420],{"class":229},"steps",[86,422,233],{"class":92},[86,424,426,429,432,434],{"class":88,"line":425},7,[86,427,428],{"class":92},"  - ",[86,430,431],{"class":229},"uses",[86,433,367],{"class":92},[86,435,436],{"class":109},"actions\u002Fcheckout@v4\n",[86,438,440,442,444,446],{"class":88,"line":439},8,[86,441,428],{"class":92},[86,443,431],{"class":229},[86,445,367],{"class":92},[86,447,448],{"class":109},"astral-sh\u002Fsetup-uv@v3\n",[86,450,452,454,457,459],{"class":88,"line":451},9,[86,453,428],{"class":92},[86,455,456],{"class":229},"run",[86,458,367],{"class":92},[86,460,461],{"class":109},"uv sync --frozen --python ${{ matrix.python }}\n",[86,463,465,467,469,471],{"class":88,"line":464},10,[86,466,428],{"class":92},[86,468,456],{"class":229},[86,470,367],{"class":92},[86,472,473],{"class":109},"uv run pytest -q\n",[10,475,476],{},"Testing the ends rather than every version is a deliberate economy. The oldest supported version\ncatches syntax and library features you accidentally relied on; the newest catches deprecations and\nbehaviour changes. Versions in between rarely surface anything distinct, and each one is CI minutes\nspent for very little.",[10,478,479,480,483,484,487,488,491],{},"What genuinely justifies more entries is the ",[124,481,482],{},"platform"," axis. Windows path handling, console\nencoding and the ",[26,485,486],{},"bin"," versus ",[26,489,490],{},"Scripts"," split break tools that Linux CI declares healthy.",[10,493,494],{},"Add one job that verifies the metadata is enforceable:",[77,496,498],{"className":220,"code":497,"language":222,"meta":82,"style":82},"  refuses-old-python:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\u002Fsetup-python@v5\n        with: { python-version: \"3.10\" }\n      - run: pip install dist\u002F*.whl && exit 1 || echo \"correctly refused\"\n",[26,499,500,507,517,524,536,555],{"__ignoreMap":82},[86,501,502,505],{"class":88,"line":89},[86,503,504],{"class":229},"  refuses-old-python",[86,506,233],{"class":92},[86,508,509,512,514],{"class":88,"line":103},[86,510,511],{"class":229},"    runs-on",[86,513,367],{"class":92},[86,515,516],{"class":109},"ubuntu-latest\n",[86,518,519,522],{"class":88,"line":113},[86,520,521],{"class":229},"    steps",[86,523,233],{"class":92},[86,525,526,529,531,533],{"class":88,"line":380},[86,527,528],{"class":92},"      - ",[86,530,431],{"class":229},[86,532,367],{"class":92},[86,534,535],{"class":109},"actions\u002Fsetup-python@v5\n",[86,537,538,541,544,547,549,552],{"class":88,"line":399},[86,539,540],{"class":229},"        with",[86,542,543],{"class":92},": { ",[86,545,546],{"class":229},"python-version",[86,548,367],{"class":92},[86,550,551],{"class":109},"\"3.10\"",[86,553,554],{"class":92}," }\n",[86,556,557,559,561,563],{"class":88,"line":417},[86,558,528],{"class":92},[86,560,456],{"class":229},[86,562,367],{"class":92},[86,564,565],{"class":109},"pip install dist\u002F*.whl && exit 1 || echo \"correctly refused\"\n",[14,567,569],{"id":568},"raising-the-floor-later","Raising the floor later",[10,571,572,573,576],{},"Dropping a Python version is a breaking change for anyone still on it: their next ",[26,574,575],{},"pipx upgrade","\neither refuses or, worse, keeps them silently on an old release.",[10,578,579],{},"The sequence that treats users well:",[581,582,583,589,595],"ol",{},[22,584,585,588],{},[38,586,587],{},"Announce it"," in the release notes of a minor version, with the version and timeframe.",[22,590,591,594],{},[38,592,593],{},"Warn at run time"," for a release or two when running on the interpreter about to be dropped —\none line on stderr, naming the version and the deadline.",[22,596,597,602],{},[38,598,599,600],{},"Raise ",[26,601,28],{}," in a major release, with a changelog entry.",[10,604,605],{},"That last step matters: a user on the old interpreter will keep resolving to your last compatible\nversion, which is exactly the behaviour you want as long as that version was a real release rather\nthan an accident.",[77,607,611],{"className":608,"code":609,"language":610,"meta":82,"style":82},"language-python shiki shiki-themes github-light github-dark","if sys.version_info \u003C (3, 12):\n    err.print(\"[yellow]mytool 3.0 will require Python 3.12; you are on \"\n              f\"{platform.python_version()}[\u002F]\")\n","python",[26,612,613,638,646],{"__ignoreMap":82},[86,614,615,618,621,624,627,630,632,635],{"class":88,"line":89},[86,616,617],{"class":210},"if",[86,619,620],{"class":92}," sys.version_info ",[86,622,623],{"class":210},"\u003C",[86,625,626],{"class":92}," (",[86,628,629],{"class":203},"3",[86,631,247],{"class":92},[86,633,634],{"class":203},"12",[86,636,637],{"class":92},"):\n",[86,639,640,643],{"class":88,"line":103},[86,641,642],{"class":92},"    err.print(",[86,644,645],{"class":109},"\"[yellow]mytool 3.0 will require Python 3.12; you are on \"\n",[86,647,648,651,654,657,660,663,666],{"class":88,"line":113},[86,649,650],{"class":210},"              f",[86,652,653],{"class":109},"\"",[86,655,656],{"class":203},"{",[86,658,659],{"class":92},"platform.python_version()",[86,661,662],{"class":203},"}",[86,664,665],{"class":109},"[\u002F]\"",[86,667,668],{"class":92},")\n",[14,670,672],{"id":671},"ux-considerations","UX considerations",[10,674,675,678],{},[38,676,677],{},"Say the requirement in the README",", next to the install line. A user hitting a resolver error is\nlooking for that sentence, and finding it in the metadata means reading a traceback first.",[10,680,681,688,689,687],{},[38,682,683,684,687],{},"Report the interpreter in ",[26,685,686],{},"--version","."," Half of all \"it does not work\" reports resolve to the\nwrong Python, and printing the path answers it immediately — see\n",[690,691,693],"a",{"href":692},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fexposing-version-info-and-build-metadata\u002F","exposing version info",[10,695,696,699,700,703],{},[38,697,698],{},"Do not pin an exact version for users."," ",[26,701,702],{},"requires-python = \"==3.12.*\""," makes your tool\nuninstallable next to anything with a different constraint. Ranges, always.",[14,705,707],{"id":706},"testing-the-behaviour","Testing the behaviour",[10,709,710],{},"Beyond the matrix, one test keeps the declaration and the code honest:",[77,712,714],{"className":608,"code":713,"language":610,"meta":82,"style":82},"import sys\nimport tomllib\nfrom pathlib import Path\n\ndef test_requires_python_matches_the_ci_matrix():\n    project = tomllib.loads(Path(\"pyproject.toml\").read_text())\n    declared = project[\"project\"][\"requires-python\"]\n    workflow = Path(\".github\u002Fworkflows\u002Ftest.yml\").read_text()\n\n    assert declared == \">=3.11\"\n    assert '\"3.11\"' in workflow          # the floor is actually tested\n",[26,715,716,724,731,744,750,761,778,799,815,819,833],{"__ignoreMap":82},[86,717,718,721],{"class":88,"line":89},[86,719,720],{"class":210},"import",[86,722,723],{"class":92}," sys\n",[86,725,726,728],{"class":88,"line":103},[86,727,720],{"class":210},[86,729,730],{"class":92}," tomllib\n",[86,732,733,736,739,741],{"class":88,"line":113},[86,734,735],{"class":210},"from",[86,737,738],{"class":92}," pathlib ",[86,740,720],{"class":210},[86,742,743],{"class":92}," Path\n",[86,745,746],{"class":88,"line":380},[86,747,749],{"emptyLinePlaceholder":748},true,"\n",[86,751,752,755,758],{"class":88,"line":399},[86,753,754],{"class":210},"def",[86,756,757],{"class":96}," test_requires_python_matches_the_ci_matrix",[86,759,760],{"class":92},"():\n",[86,762,763,766,769,772,775],{"class":88,"line":417},[86,764,765],{"class":92},"    project ",[86,767,768],{"class":210},"=",[86,770,771],{"class":92}," tomllib.loads(Path(",[86,773,774],{"class":109},"\"pyproject.toml\"",[86,776,777],{"class":92},").read_text())\n",[86,779,780,783,785,788,791,794,797],{"class":88,"line":425},[86,781,782],{"class":92},"    declared ",[86,784,768],{"class":210},[86,786,787],{"class":92}," project[",[86,789,790],{"class":109},"\"project\"",[86,792,793],{"class":92},"][",[86,795,796],{"class":109},"\"requires-python\"",[86,798,100],{"class":92},[86,800,801,804,806,809,812],{"class":88,"line":439},[86,802,803],{"class":92},"    workflow ",[86,805,768],{"class":210},[86,807,808],{"class":92}," Path(",[86,810,811],{"class":109},"\".github\u002Fworkflows\u002Ftest.yml\"",[86,813,814],{"class":92},").read_text()\n",[86,816,817],{"class":88,"line":451},[86,818,749],{"emptyLinePlaceholder":748},[86,820,821,824,827,830],{"class":88,"line":464},[86,822,823],{"class":210},"    assert",[86,825,826],{"class":92}," declared ",[86,828,829],{"class":210},"==",[86,831,832],{"class":109}," \">=3.11\"\n",[86,834,836,838,841,844,847],{"class":88,"line":835},11,[86,837,823],{"class":210},[86,839,840],{"class":109}," '\"3.11\"'",[86,842,843],{"class":210}," in",[86,845,846],{"class":92}," workflow          ",[86,848,849],{"class":190},"# the floor is actually tested\n",[10,851,852,853,855],{},"It looks pedantic and it catches the common drift: someone raises the floor in ",[26,854,32],{},"\nwithout touching the matrix, and the oldest supported version silently stops being verified.",[10,857,858,859,687],{},"Running your test suite on the floor version locally is worth doing before a release:\n",[26,860,861],{},"uv run --python 3.11 pytest",[14,863,865],{"id":864},"conclusion","Conclusion",[10,867,868],{},"Declare the range, test its ends, keep the local default separate from the support statement, and\ntreat raising the floor as the breaking change it is. Following upstream end-of-life dates turns\nwhat is otherwise a recurring debate into a rule you can point at.",[14,870,872],{"id":871},"frequently-asked-questions","Frequently asked questions",[874,875,877],"h3",{"id":876},"should-i-support-the-newest-python-immediately","Should I support the newest Python immediately?",[10,879,880],{},"Support it as soon as your dependencies do, which is usually a few weeks after release. Adding it to\nthe matrix early is cheap and catches deprecations before users do; requiring it is a different\nquestion and usually premature.",[874,882,884,885,887],{"id":883},"what-is-python-version-for-then","What is ",[26,886,46],{}," for, then?",[10,889,890],{},"A local convenience: uv and pyenv read it to pick an interpreter for the project. It says nothing\nabout what you support, and it should generally name a version in the middle of your range rather\nthan the floor — you want the fast path locally and the boundaries in CI.",[874,892,894],{"id":893},"how-many-versions-should-the-matrix-cover","How many versions should the matrix cover?",[10,896,897],{},"Two Python versions and two platforms is a good default: the ends of your range on Linux and\nWindows. Add macOS if you ship binaries or touch filesystem semantics; add intermediate Pythons only\nwhen a specific bug justifies it.",[874,899,901,902,904],{"id":900},"does-requires-python-affect-an-already-installed-tool","Does ",[26,903,28],{}," affect an already-installed tool?",[10,906,907],{},"No. It is consulted at install time, so a tool installed before you raised the floor keeps working.\nThat is why the run-time warning is worth adding — it is the only way an existing user learns about\nthe upcoming change.",[874,909,911],{"id":910},"what-if-a-dependency-drops-a-version-before-i-do","What if a dependency drops a version before I do?",[10,913,914,915,917],{},"Then your effective floor has moved whether you like it or not: the resolver will refuse to install\nyour tool with that dependency on the old interpreter. Raise ",[26,916,28],{}," to match, and treat\nit as the breaking change it is — pinning the dependency to an old version instead is a short-term\nanswer with a security cost.",[874,919,921],{"id":920},"can-i-support-a-version-my-ci-cannot-install","Can I support a version my CI cannot install?",[10,923,924],{},"You can declare it, and you should not. Support you cannot verify is a guess, and the first bug\nreport from that version arrives with no way to reproduce it. If a version is genuinely hard to run\nin CI — an old distribution Python, say — either find a container that provides it or narrow the\ndeclared range to match what you test.",[14,926,928],{"id":927},"related","Related",[19,930,931,938,945,950,957],{},[22,932,933,934],{},"Up: ",[690,935,937],{"href":936},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002F","Python CLI environment isolation best practices",[22,939,940,941],{},"Sideways: ",[690,942,944],{"href":943},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fmanaging-virtual-environments-for-cross-platform-clis\u002F","Managing virtual environments for cross-platform CLIs",[22,946,940,947],{},[690,948,949],{"href":692},"Exposing version info and build metadata",[22,951,952,953],{},"Related: ",[690,954,956],{"href":955},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002F","uv for Python CLI dependency management",[22,958,952,959],{},[690,960,962],{"href":961},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fbuilding-wheels-and-sdists-for-python-clis\u002F","Building wheels and sdists for Python CLIs",[964,965,966],"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 .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":82,"searchDepth":103,"depth":103,"links":968},[969,970,971,972,973,974,975,976,977,978,979,989],{"id":16,"depth":103,"text":17},{"id":60,"depth":103,"text":61},{"id":70,"depth":103,"text":71},{"id":168,"depth":103,"text":169},{"id":298,"depth":103,"text":299},{"id":346,"depth":103,"text":347},{"id":568,"depth":103,"text":569},{"id":671,"depth":103,"text":672},{"id":706,"depth":103,"text":707},{"id":864,"depth":103,"text":865},{"id":871,"depth":103,"text":872,"children":980},[981,982,984,985,987,988],{"id":876,"depth":113,"text":877},{"id":883,"depth":113,"text":983},"What is .python-version for, then?",{"id":893,"depth":113,"text":894},{"id":900,"depth":113,"text":986},"Does requires-python affect an already-installed tool?",{"id":910,"depth":113,"text":911},{"id":920,"depth":113,"text":921},{"id":927,"depth":103,"text":928},"2026-08-01","Decide which Python versions a CLI supports - requires-python, .python-version, a CI matrix that matches, and when to drop an old interpreter.","intermediate",false,"md",{},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fpinning-the-python-version-for-a-cli",{"title":5,"description":991},"project-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fpinning-the-python-version-for-a-cli\u002Findex",[546,28,1000,1001,1002],"ci","compatibility","uv","g46EHN5d1DoXVgubczmDUrgHx3zh3Z4lTEScqjAdqXQ",[1005,1008,1011,1014,1017,1020,1023,1026,1029,1032,1035,1038,1041,1044,1047,1050,1053,1056,1059,1062,1065,1068,1071,1074,1077,1080,1083,1086,1089,1092,1095,1098,1101,1104,1107,1110,1113,1116,1119,1122,1125,1128,1131,1134,1137,1140,1143,1146,1149,1152,1155,1158,1161,1164,1167,1170,1173,1176,1179,1182,1185,1188,1191,1194,1197,1200,1203,1206,1209,1212,1215,1218,1221,1224,1227,1230,1233,1236,1239,1242,1245,1248,1251,1254,1257,1260,1263,1266,1269,1272,1275,1278,1281,1284,1287,1290,1293,1296,1299,1302,1305,1308,1311,1314,1317,1320,1323,1326,1329,1332,1335,1338,1341,1344,1347,1350,1353,1356,1359,1362,1365,1368,1371,1374,1377,1380,1383,1386,1389,1392,1395,1398,1401,1404,1407,1410,1413,1416,1419,1422,1425,1428,1431,1434,1437,1440,1443,1446,1449,1452,1455,1458,1461,1464,1467,1470,1473,1476,1479,1482,1485,1488,1491,1494,1497,1500,1503,1506,1509,1512,1515,1518,1521,1524,1527,1530,1533,1536,1539,1542,1545,1548,1551,1554,1557,1560,1563,1566,1569,1572,1575,1578,1581,1584,1587,1590,1593,1596,1599,1602,1605,1608,1611,1614,1617,1620,1623,1626,1629,1632,1635,1638,1641,1644,1647,1650,1653,1656,1659,1662,1665,1668,1671,1674,1677,1680,1683,1686,1689,1692,1695,1698,1701,1704,1707,1710,1713,1716,1719,1722,1725,1728,1731,1734,1737,1740,1743,1746,1749,1752,1755,1758,1761,1764,1767,1770,1773,1776,1779,1782,1785,1788,1791,1794,1797,1800,1803,1806,1809,1812,1815,1818,1821,1824,1827,1830,1833,1836,1839,1842,1845,1846],{"path":1006,"title":1007},"\u002Fabout","About Python CLI Toolcraft",{"path":1009,"title":1010},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies","Advanced Argument Validation Strategies",{"path":1012,"title":1013},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fparsing-nested-json-arguments-in-python-clis","Parsing Nested JSON Args in Python CLIs",{"path":1015,"title":1016},"\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":1018,"title":1019},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fvalidating-dependent-and-conflicting-options","Validating Dependent and Conflicting CLI Options",{"path":1021,"title":1022},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fvalidating-file-and-directory-paths-in-clis","Validating File and Directory Paths in CLIs",{"path":1024,"title":1025},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fvalidating-urls-hosts-and-ports","Validating URLs, Hosts and Ports in Python CLI Arguments",{"path":1027,"title":1028},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fwriting-custom-click-parameter-types","Writing Custom Click Parameter Types",{"path":1030,"title":1031},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual\u002Fbrowsing-records-with-a-textual-datatable","Browsing Records with a Textual DataTable Picker",{"path":1033,"title":1034},"\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":1036,"title":1037},"\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":1039,"title":1040},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual","Building Terminal UIs with Textual for Python CLIs",{"path":1042,"title":1043},"\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":1045,"title":1046},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual\u002Fstyling-textual-apps-with-tcss","Styling Textual Apps with TCSS",{"path":1048,"title":1049},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual\u002Ftesting-textual-apps-with-pilot","Testing Textual Apps with Pilot",{"path":1051,"title":1052},"\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":1054,"title":1055},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fdocumenting-environment-variables-in-help","Documenting Environment Variables in CLI Help",{"path":1057,"title":1058},"\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":1060,"title":1061},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation","CLI Help Output and Documentation",{"path":1063,"title":1064},"\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":1066,"title":1067},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fversioning-and-deprecating-cli-flags","Versioning and Deprecating CLI Flags",{"path":1069,"title":1070},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fwriting-help-text-users-actually-read","Writing Help Text Users Actually Read",{"path":1072,"title":1073},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Fadapting-output-to-terminal-width","Adapting Python CLI Output to Terminal Width",{"path":1075,"title":1076},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Fdetecting-ci-environments-and-non-interactive-shells","Detecting CI Environments and Non-Interactive Shells",{"path":1078,"title":1079},"\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":1081,"title":1082},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Fhandling-ansi-escape-codes-on-windows-consoles","Handling ANSI Escape Codes on Windows Consoles",{"path":1084,"title":1085},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility","Cross-Platform Terminal Compatibility for Python CLIs",{"path":1087,"title":1088},"\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":1090,"title":1091},"\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":1093,"title":1094},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fchoosing-exit-codes-for-cli-tools","Choosing Exit Codes for CLI Tools",{"path":1096,"title":1097},"\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":1099,"title":1100},"\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":1102,"title":1103},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Ffriendly-error-messages-and-tracebacks","Friendly Error Messages and Tracebacks",{"path":1105,"title":1106},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fhandling-keyboard-interrupt-cleanly","Handling Keyboard Interrupt Cleanly",{"path":1108,"title":1109},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes","Error Handling and Exit Codes for CLIs",{"path":1111,"title":1112},"\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":1114,"title":1115},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fwriting-crash-reports-users-can-send","Writing Crash Reports Users Can Send",{"path":1117,"title":1118},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Fconfig-precedence-flags-env-files-defaults","Config Precedence: Flags, Env, Files, Defaults",{"path":1120,"title":1121},"\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":1123,"title":1124},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars","Handling Config Files and Env Vars in CLIs",{"path":1126,"title":1127},"\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":1129,"title":1130},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Freading-toml-config-with-tomllib","Reading TOML Config with tomllib in Python CLIs",{"path":1132,"title":1133},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Ftyped-settings-with-pydantic-settings","Typed Settings with pydantic-settings in Python CLIs",{"path":1135,"title":1136},"\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":1138,"title":1139},"\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":1141,"title":1142},"\u002Fadvanced-input-parsing-user-experience","Advanced Input Parsing for Python CLIs",{"path":1144,"title":1145},"\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":1147,"title":1148},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Fbuilding-interactive-prompts-and-menus","Building Interactive Prompts and Menus in Python CLIs",{"path":1150,"title":1151},"\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":1153,"title":1154},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich","Interactive Terminal UI with Rich",{"path":1156,"title":1157},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Flive-dashboards-with-rich-live","Live Dashboards with Rich Live in Python CLIs",{"path":1159,"title":1160},"\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":1162,"title":1163},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Frendering-tables-and-json-with-rich","Rendering Tables and JSON with Rich",{"path":1165,"title":1166},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Ftheming-rich-output-consistently","Theming Rich Output Consistently in Python CLIs",{"path":1168,"title":1169},"\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":1171,"title":1172},"\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":1174,"title":1175},"\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":1177,"title":1178},"\u002Fadvanced-input-parsing-user-experience\u002Foutput-formats-for-data-clis","Output Formats for Data-Heavy Python CLIs",{"path":1180,"title":1181},"\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":1183,"title":1184},"\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":1186,"title":1187},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Fargcomplete-for-argparse-clis","Tab Completion for argparse CLIs with argcomplete",{"path":1189,"title":1190},"\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":1192,"title":1193},"\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":1195,"title":1196},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis","Shell Completion for Python CLIs",{"path":1198,"title":1199},"\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":1201,"title":1202},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Fshipping-completion-scripts-with-packages","Shipping Shell Completion Scripts with a Python CLI",{"path":1204,"title":1205},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Ftesting-shell-completion-in-python-clis","Testing Shell Completion in Python CLIs",{"path":1207,"title":1208},"\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":1210,"title":1211},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fadding-verbose-and-quiet-logging-flags","Adding Verbose and Quiet Logging Flags",{"path":1213,"title":1214},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps","Structured Logging for CLI Apps",{"path":1216,"title":1217},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Flogging-with-structlog-in-a-cli","Logging with structlog in a Python CLI",{"path":1219,"title":1220},"\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":1222,"title":1223},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fstructured-json-logging-in-python-clis","Structured JSON Logging in Python CLIs",{"path":1225,"title":1226},"\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":1228,"title":1229},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Fdetecting-tty-and-adapting-output","Detecting a TTY and Adapting Output",{"path":1231,"title":1232},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Femitting-json-output-for-scripting","Emitting JSON Output for Scripting",{"path":1234,"title":1235},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Fhandling-broken-pipe-and-sigpipe","Handling Broken Pipe and SIGPIPE",{"path":1237,"title":1238},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes","Working with stdin, stdout and Pipes",{"path":1240,"title":1241},"\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":1243,"title":1244},"\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":1246,"title":1247},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Freading-piped-input-in-python-clis","Reading Piped Input in Python CLIs",{"path":1249,"title":1250},"\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":1252,"title":1253},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Fdownloading-files-with-progress-in-python","Downloading Files with Progress in Python CLIs",{"path":1255,"title":1256},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis","Calling HTTP APIs from Python CLIs",{"path":1258,"title":1259},"\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":1261,"title":1262},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Foauth-device-flow-login-for-clis","OAuth Device Flow Login for Python CLIs",{"path":1264,"title":1265},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Fpaginating-api-results-in-a-cli","Paginating API Results in a Python CLI",{"path":1267,"title":1268},"\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":1270,"title":1271},"\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":1273,"title":1274},"\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":1276,"title":1277},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis","Concurrency and Async in Python CLIs",{"path":1279,"title":1280},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Fmultiprocessing-for-cpu-bound-cli-tasks","Multiprocessing for CPU-Bound CLI Tasks",{"path":1282,"title":1283},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Fparallelising-cli-work-with-thread-pools","Parallelising CLI Work with Thread Pools",{"path":1285,"title":1286},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Frate-limiting-concurrent-requests-in-clis","Rate-Limiting Concurrent Requests in Python CLIs",{"path":1288,"title":1289},"\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":1291,"title":1292},"\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":1294,"title":1295},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Fstructured-concurrency-with-taskgroups-in-clis","Structured Concurrency with TaskGroups in Python CLIs",{"path":1297,"title":1298},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fcross-platform-paths-with-pathlib","Cross-Platform Paths with pathlib in CLIs",{"path":1300,"title":1301},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Ffile-locking-for-concurrent-cli-runs","File Locking for Concurrent CLI Runs in Python",{"path":1303,"title":1304},"\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":1306,"title":1307},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes","Filesystem Paths and Atomic Writes for CLIs",{"path":1309,"title":1310},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fsafe-temporary-files-and-directories","Safe Temporary Files and Directories in CLIs",{"path":1312,"title":1313},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fstoring-app-data-with-platformdirs","Storing CLI App Data with platformdirs",{"path":1315,"title":1316},"\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":1318,"title":1319},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fwriting-files-atomically-in-python-clis","Writing Files Atomically in Python CLIs",{"path":1321,"title":1322},"\u002Fcli-runtime-systems-integration","CLI Runtime & Systems Integration for Python",{"path":1324,"title":1325},"\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":1327,"title":1328},"\u002Fcli-runtime-systems-integration\u002Flocal-state-and-sqlite-in-python-clis","Local State and SQLite in Python CLIs",{"path":1330,"title":1331},"\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":1333,"title":1334},"\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":1336,"title":1337},"\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":1339,"title":1340},"\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":1342,"title":1343},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis\u002Fhandling-sigterm-and-graceful-shutdown","Handling SIGTERM and Graceful Shutdown in CLIs",{"path":1345,"title":1346},"\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":1348,"title":1349},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis","Long-Running and Watch-Mode Python CLIs",{"path":1351,"title":1352},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis\u002Freloading-config-on-sighup","Reloading Configuration on SIGHUP in a Python CLI",{"path":1354,"title":1355},"\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":1357,"title":1358},"\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":1360,"title":1361},"\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":1363,"title":1364},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Favoiding-shell-injection-in-python-clis","Avoiding Shell Injection in Python CLIs",{"path":1366,"title":1367},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fcalling-external-commands-safely-with-subprocess","Calling External Commands Safely with subprocess",{"path":1369,"title":1370},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fhandling-subprocess-timeouts-and-exit-codes","Handling Subprocess Timeouts and Exit Codes",{"path":1372,"title":1373},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis","Running Subprocesses from Python CLIs",{"path":1375,"title":1376},"\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":1378,"title":1379},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fpiping-between-subprocesses-in-python","Piping Between Subprocesses in Python Without a Shell",{"path":1381,"title":1382},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fstreaming-subprocess-output-in-real-time","Streaming Subprocess Output in Real Time",{"path":1384,"title":1385},"\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":1387,"title":1388},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis","Secrets and Credentials in Python CLIs",{"path":1390,"title":1391},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Fprompting-for-passwords-securely","Prompting for Passwords Securely in Python CLIs",{"path":1393,"title":1394},"\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":1396,"title":1397},"\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":1399,"title":1400},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Frefreshing-expired-tokens-automatically","Refreshing Expired Tokens Automatically in a Python CLI",{"path":1402,"title":1403},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Fstoring-tokens-with-keyring","Storing CLI Tokens Securely with keyring",{"path":1405,"title":1406},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Fsupporting-multiple-profiles-and-accounts","Supporting Multiple Profiles and Accounts in CLIs",{"path":1408,"title":1409},"\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":1411,"title":1412},"\u002Fcli-runtime-systems-integration\u002Fupdate-checks-and-self-updating-clis","Update Checks, Upgrade Commands and Telemetry for Python CLIs",{"path":1414,"title":1415},"\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":1417,"title":1418},"\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":1420,"title":1421},"\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":1423,"title":1424},"\u002F","Python CLI Toolcraft",{"path":1426,"title":1427},"\u002Fmodern-python-cli-frameworks-architecture\u002Falternative-python-cli-frameworks\u002Fbuilding-a-cli-with-cleo","Building a Python CLI with Cleo Command Classes",{"path":1429,"title":1430},"\u002Fmodern-python-cli-frameworks-architecture\u002Falternative-python-cli-frameworks\u002Fbuilding-a-cli-with-cyclopts","Building a Type-Hinted Python CLI with Cyclopts",{"path":1432,"title":1433},"\u002Fmodern-python-cli-frameworks-architecture\u002Falternative-python-cli-frameworks","Beyond Click and Typer: Alternative Python CLI Frameworks",{"path":1435,"title":1436},"\u002Fmodern-python-cli-frameworks-architecture\u002Falternative-python-cli-frameworks\u002Fquick-clis-from-functions-with-python-fire","Quick CLIs from Functions with Python Fire",{"path":1438,"title":1439},"\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":1441,"title":1442},"\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":1444,"title":1445},"\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":1447,"title":1448},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Fguarding-startup-with-import-tests","Guarding CLI Startup with Import Tests",{"path":1450,"title":1451},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading","CLI Startup Performance and Lazy Loading",{"path":1453,"title":1454},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Flazy-loading-subcommands-for-faster-startup","Lazy Loading Subcommands for Faster Startup",{"path":1456,"title":1457},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Fprofiling-python-cli-startup-time","Profiling Python CLI Startup Time",{"path":1459,"title":1460},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Freducing-cli-dependency-weight","Reducing CLI Dependency Weight",{"path":1462,"title":1463},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargparse-subparsers-for-subcommands","argparse Subparsers for Subcommands",{"path":1465,"title":1466},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargparse-vs-click-vs-typer-comparison","argparse vs Click vs Typer Compared",{"path":1468,"title":1469},"\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":1471,"title":1472},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse","Command-Line Parsing with argparse",{"path":1474,"title":1475},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fmigrating-from-argparse-to-typer","Migrating from argparse to Typer",{"path":1477,"title":1478},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fmutually-exclusive-options-in-argparse","Mutually Exclusive Options in argparse",{"path":1480,"title":1481},"\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":1483,"title":1484},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fwriting-custom-argparse-actions","Writing Custom argparse Actions in Python",{"path":1486,"title":1487},"\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":1489,"title":1490},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Fdesigning-idempotent-commands","Designing Idempotent Commands in a Python CLI",{"path":1492,"title":1493},"\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":1495,"title":1496},"\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":1498,"title":1499},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions","Designing CLI Interfaces and Conventions in Python",{"path":1501,"title":1502},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Fnaming-commands-and-flags-consistently","Naming Commands and Flags Consistently in Python CLIs",{"path":1504,"title":1505},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Fpositional-arguments-vs-options","Positional Arguments vs Options: Designing a CLI Signature",{"path":1507,"title":1508},"\u002Fmodern-python-cli-frameworks-architecture","Python CLI Frameworks and Architecture",{"path":1510,"title":1511},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fdiscovering-plugins-with-entry-points","Discovering Plugins with Entry Points in Python CLIs",{"path":1513,"title":1514},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fhook-based-plugins-with-pluggy","Hook-Based Plugins for Python CLIs with pluggy",{"path":1516,"title":1517},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis","Plugin Architectures for Extensible CLIs",{"path":1519,"title":1520},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fisolating-plugin-failures","Isolating Plugin Failures in an Extensible Python CLI",{"path":1522,"title":1523},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Ftesting-plugins-against-the-host-cli","Testing Plugins Against the Host CLI",{"path":1525,"title":1526},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fversioning-a-plugin-api","Versioning a Plugin API for a Python CLI",{"path":1528,"title":1529},"\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":1531,"title":1532},"\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":1534,"title":1535},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fdependency-injection-patterns-for-cli-commands","Dependency Injection Patterns for CLI Commands",{"path":1537,"title":1538},"\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":1540,"title":1541},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis","Structuring Multi-Command Python CLIs",{"path":1543,"title":1544},"\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":1546,"title":1547},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fregistering-commands-from-modules-automatically","Registering CLI Commands from Modules Automatically",{"path":1549,"title":1550},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fsharing-common-options-across-commands","Sharing Common Options Across Python CLI Commands",{"path":1552,"title":1553},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fsharing-state-with-click-context-objects","Sharing State with Click Context Objects",{"path":1555,"title":1556},"\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":1558,"title":1559},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications","Testing Python CLI Applications",{"path":1561,"title":1562},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fmeasuring-cli-test-coverage","Measuring CLI Test Coverage",{"path":1564,"title":1565},"\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":1567,"title":1568},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fproperty-based-testing-cli-arguments-with-hypothesis","Property-Based Testing CLI Arguments with Hypothesis",{"path":1570,"title":1571},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fsnapshot-testing-cli-output","Snapshot Testing CLI Output",{"path":1573,"title":1574},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftesting-click-commands-with-clirunner","Testing Click Commands with CliRunner",{"path":1576,"title":1577},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftesting-commands-that-call-subprocesses","Testing Python CLI Commands That Call Subprocesses",{"path":1579,"title":1580},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftesting-interactive-prompts-and-stdin","Testing Interactive Prompts and stdin",{"path":1582,"title":1583},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftranscript-tests-for-cli-commands","Transcript Tests for Python CLI Commands",{"path":1585,"title":1586},"\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":1588,"title":1589},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Fbuilding-dynamic-commands-in-click","Building Dynamic Commands in Click",{"path":1591,"title":1592},"\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":1594,"title":1595},"\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":1597,"title":1598},"\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":1600,"title":1601},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each","Typer vs Click: When to Use Each",{"path":1603,"title":1604},"\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":1606,"title":1607},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Ftyper-callback-functions-explained","Typer callback functions explained",{"path":1609,"title":1610},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Fusing-annotated-options-in-typer","Using Annotated Options in Typer",{"path":1612,"title":1613},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis\u002Fautomating-releases-from-git-tags","Automating Python CLI Releases from Git Tags",{"path":1615,"title":1616},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis\u002Fcaching-uv-dependencies-in-ci","Caching uv Dependencies in CI for Python CLIs",{"path":1618,"title":1619},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis","CI\u002FCD Pipelines for Python CLIs",{"path":1621,"title":1622},"\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":1624,"title":1625},"\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":1627,"title":1628},"\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":1630,"title":1631},"\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":1633,"title":1634},"\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":1636,"title":1637},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fbuilding-a-cookiecutter-template-for-typer-clis","Building a Cookiecutter Template for Typer CLIs",{"path":1639,"title":1640},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fcopier-vs-cookiecutter-for-cli-templates","Copier vs Cookiecutter for CLI Templates",{"path":1642,"title":1643},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter","CLI Project Scaffolding with Cookiecutter",{"path":1645,"title":1646},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fpost-generation-hooks-in-cli-templates","Post-Generation Hooks in Python CLI Templates",{"path":1648,"title":1649},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Ftemplate-variables-and-conditional-files","Template Variables and Conditional Files in CLI Templates",{"path":1651,"title":1652},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Ftesting-a-project-template-with-pytest","Testing a CLI Project Template with pytest",{"path":1654,"title":1655},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fupdating-generated-projects-with-copier-update","Updating Generated CLI Projects with copier update",{"path":1657,"title":1658},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fbuilding-cross-platform-release-binaries-in-ci","Building Cross-Platform Release Binaries in CI",{"path":1660,"title":1661},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fbundling-a-python-cli-with-pyinstaller","Bundling a Python CLI with PyInstaller",{"path":1663,"title":1664},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fcode-signing-and-notarizing-cli-binaries","Code Signing and Notarizing Python CLI Binaries",{"path":1666,"title":1667},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fhomebrew-and-scoop-packaging-for-python-clis","Homebrew and Scoop Packaging for Python CLIs",{"path":1669,"title":1670},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries","Distributing CLIs as Standalone Binaries",{"path":1672,"title":1673},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fnuitka-vs-pyinstaller-for-python-clis","Nuitka vs PyInstaller for Python CLI Binaries",{"path":1675,"title":1676},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Freducing-pyinstaller-binary-size","Reducing the Size of a PyInstaller CLI Binary",{"path":1678,"title":1679},"\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":1681,"title":1682},"\u002Fproject-setup-dependency-management","Project Setup & Dependency Management",{"path":1684,"title":1685},"\u002Fproject-setup-dependency-management\u002Flinting-and-type-checking-cli-code\u002Fconfiguring-ruff-for-a-cli-project","Configuring Ruff for a Python CLI Project",{"path":1687,"title":1688},"\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":1690,"title":1691},"\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":1693,"title":1694},"\u002Fproject-setup-dependency-management\u002Flinting-and-type-checking-cli-code","Linting and Type-Checking Python CLI Code",{"path":1696,"title":1697},"\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":1699,"title":1700},"\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":1702,"title":1703},"\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":1705,"title":1706},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fautomating-changelogs-with-conventional-commits","Automating Changelogs with Conventional Commits",{"path":1708,"title":1709},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fbumping-versions-consistently-across-a-cli-project","Bumping Versions Consistently Across a Python CLI Project",{"path":1711,"title":1712},"\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":1714,"title":1715},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fexposing-version-info-and-build-metadata","Exposing Version Info and Build Metadata",{"path":1717,"title":1718},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs","Managing CLI Versioning & Changelogs",{"path":1720,"title":1721},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fsemantic-versioning-policy-for-cli-tools","A Semantic Versioning Policy for CLI Tools",{"path":1723,"title":1724},"\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":1726,"title":1727},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fbuilding-wheels-and-sdists-for-python-clis","Building Wheels and sdists for Python CLIs",{"path":1729,"title":1730},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fbundling-data-files-with-importlib-resources","Bundling Data Files with importlib.resources in CLIs",{"path":1732,"title":1733},"\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":1735,"title":1736},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution","Packaging Python CLIs for Distribution",{"path":1738,"title":1739},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Finstalling-and-distributing-clis-with-pipx","Installing and Distributing CLIs with pipx",{"path":1741,"title":1742},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Foptional-dependencies-and-extras-for-clis","Optional Dependencies and Extras for Python CLIs",{"path":1744,"title":1745},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fpublishing-a-python-cli-to-pypi","Publishing a Python CLI to PyPI",{"path":1747,"title":1748},"\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":1750,"title":1751},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fdynamic-versioning-with-poetry-plugins","Dynamic Versioning for a Poetry CLI from Git Tags",{"path":1753,"title":1754},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development","Poetry Workflows for CLI Development",{"path":1756,"title":1757},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fmanaging-poetry-lock-files-for-cli-tools","Managing Poetry Lock Files for CLI Tools",{"path":1759,"title":1760},"\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":1762,"title":1763},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpoetry-dependency-groups-for-cli-tooling","Poetry Dependency Groups for CLI Tooling",{"path":1765,"title":1766},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpoetry-entry-points-and-scripts-for-clis","Poetry Entry Points and Scripts for CLIs",{"path":1768,"title":1769},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpublishing-a-cli-with-poetry","Building and Publishing a Python CLI with Poetry",{"path":1771,"title":1772},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects","Pre-commit Hooks for CLI Projects",{"path":1774,"title":1775},"\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":1777,"title":1778},"\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":1780,"title":1781},"\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":1783,"title":1784},"\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":1786,"title":1787},"\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":1789,"title":1790},"\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":1792,"title":1793},"\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":1795,"title":1796},"\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":1798,"title":1799},"\u002Fproject-setup-dependency-management\u002Fsecuring-a-python-cli-supply-chain","Securing the Supply Chain of a Python CLI",{"path":1801,"title":1802},"\u002Fproject-setup-dependency-management\u002Fsecuring-a-python-cli-supply-chain\u002Fpinning-dependencies-with-hashes","Pinning a Python CLI’s Dependencies with Hashes",{"path":1804,"title":1805},"\u002Fproject-setup-dependency-management\u002Fsecuring-a-python-cli-supply-chain\u002Fpublishing-attestations-and-verifying-releases","Publishing Attestations and Verifying Python CLI Releases",{"path":1807,"title":1808},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002Fbuilding-and-publishing-a-cli-with-uv","Building and Publishing a Python CLI with uv",{"path":1810,"title":1811},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management","uv for Python CLI Dependency Management",{"path":1813,"title":1814},"\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":1816,"title":1817},"\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":1819,"title":1820},"\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":1822,"title":1823},"\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":1825,"title":1826},"\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":1828,"title":1829},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002Fuv-workspaces-for-multi-package-clis","uv Workspaces for Multi-Package Python CLIs",{"path":1831,"title":1832},"\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":1834,"title":1835},"\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":1837,"title":1838},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices","Python CLI Env Isolation Best Practices",{"path":1840,"title":1841},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fisolating-cli-tools-from-project-dependencies","Isolating CLI Tools from Your Project’s Dependencies",{"path":1843,"title":1844},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fmanaging-virtual-environments-for-cross-platform-clis","Managing Python CLI Virtual Environments",{"path":996,"title":5},{"path":1847,"title":1848},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fsupporting-multiple-python-versions-with-nox","Supporting Multiple Python Versions with nox",1790967543688]