[{"data":1,"prerenderedAt":1876},["ShallowReactive",2],{"page-\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects\u002Frunning-pre-commit-in-ci\u002F":3,"content-directory":1033},{"id":4,"title":5,"body":6,"date":1018,"description":1019,"difficulty":1020,"draft":1021,"extension":1022,"meta":1023,"navigation":155,"path":1024,"seo":1025,"stem":1026,"tags":1027,"updated":1018,"__hash__":1032},"content\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects\u002Frunning-pre-commit-in-ci\u002Findex.md","Running pre-commit in CI for a Python CLI Repository",{"type":7,"value":8,"toc":993},"minimark",[9,38,43,57,61,65,72,76,437,442,456,460,481,485,498,501,505,512,610,627,631,638,671,674,678,705,708,712,764,768,771,851,861,865,887,891,898,901,905,921,925,928,936,939,943,955,959,989],[10,11,12,13,17,18,21,22,26,27,32,33,37],"p",{},"pre-commit hooks protect a repository only for contributors who installed them. Someone commits from a fresh clone, from the GitHub web editor, or with ",[14,15,16],"code",{},"--no-verify",", and unformatted code, a stray debug print or an unsorted import lands on the branch anyway. Running the same hooks in CI closes that gap: the configuration that runs on every developer's commit also runs, unconditionally, on every push and pull request. Doing it well takes more than ",[14,19,20],{},"pre-commit run --all-files"," though — hook environments are slow to build and should be cached, pull requests should be checked for what ",[23,24,25],"em",{},"they"," changed, failures should show the fix, and some hooks are too slow for every commit but right for CI. This guide builds that job for a Python CLI repository. It belongs to the ",[28,29,31],"a",{"href":30},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects\u002F","pre-commit topic",", and extends the CI step sketched in ",[28,34,36],{"href":35},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects\u002Fsetting-up-pre-commit-for-python-cli-repos\u002F","setting up pre-commit for Python CLI repos",".",[39,40,42],"h2",{"id":41},"prerequisites","Prerequisites",[44,45,46,54],"ul",{},[47,48,49,50,53],"li",{},"A ",[14,51,52],{},".pre-commit-config.yaml"," with pinned hook revisions.",[47,55,56],{},"GitHub Actions (the ideas apply to any CI system).",[39,58,60],{"id":59},"what-ci-adds-to-local-hooks","What CI adds to local hooks",[62,63],"inline-diagram",{"name":64},"pcci-local-vs-ci",[10,66,67,68,71],{},"Locally, pre-commit runs on the files staged for a commit, in the developer's environment, and can be skipped. In CI it runs on a known set of files, in a clean environment, and cannot be skipped — which makes it the enforcement point, while local hooks remain the fast feedback loop. The job should therefore run ",[23,69,70],{},"the same hooks with the same versions",", so a commit that passed locally passes in CI and vice versa.",[39,73,75],{"id":74},"the-recipe","The recipe",[77,78,83],"pre",{"className":79,"code":80,"language":81,"meta":82,"style":82},"language-yaml shiki shiki-themes github-light github-dark","# .github\u002Fworkflows\u002Fpre-commit.yml\nname: pre-commit\non:\n  pull_request:\n  push:\n    branches: [main]\n\npermissions:\n  contents: read\n\njobs:\n  pre-commit:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\u002Fcheckout@v4\n        with:\n          fetch-depth: 0                       # needed for --from-ref on pull requests\n      - uses: actions\u002Fsetup-python@v5\n        with:\n          python-version: \"3.12\"\n      - uses: actions\u002Fcache@v4\n        with:\n          path: ~\u002F.cache\u002Fpre-commit\n          key: pre-commit-${{ runner.os }}-${{ hashFiles('.pre-commit-config.yaml') }}\n      - run: pipx install pre-commit==4.6.2\n      - name: Changed files (pull requests)\n        if: github.event_name == 'pull_request'\n        run: >\n          pre-commit run --show-diff-on-failure --color=always\n          --from-ref \"origin\u002F${{ github.base_ref }}\" --to-ref HEAD\n      - name: All files (main)\n        if: github.event_name == 'push'\n        run: pre-commit run --all-files --show-diff-on-failure --color=always\n      - name: CI-only hooks\n        run: pre-commit run --all-files --hook-stage manual\n","yaml","",[14,84,85,94,109,119,127,135,150,157,165,176,181,189,197,208,216,230,238,252,264,271,282,294,301,312,323,336,348,359,371,377,383,395,405,415,427],{"__ignoreMap":82},[86,87,90],"span",{"class":88,"line":89},"line",1,[86,91,93],{"class":92},"sJ8bj","# .github\u002Fworkflows\u002Fpre-commit.yml\n",[86,95,97,101,105],{"class":88,"line":96},2,[86,98,100],{"class":99},"s9eBZ","name",[86,102,104],{"class":103},"sVt8B",": ",[86,106,108],{"class":107},"sZZnC","pre-commit\n",[86,110,112,116],{"class":88,"line":111},3,[86,113,115],{"class":114},"sj4cs","on",[86,117,118],{"class":103},":\n",[86,120,122,125],{"class":88,"line":121},4,[86,123,124],{"class":99},"  pull_request",[86,126,118],{"class":103},[86,128,130,133],{"class":88,"line":129},5,[86,131,132],{"class":99},"  push",[86,134,118],{"class":103},[86,136,138,141,144,147],{"class":88,"line":137},6,[86,139,140],{"class":99},"    branches",[86,142,143],{"class":103},": [",[86,145,146],{"class":107},"main",[86,148,149],{"class":103},"]\n",[86,151,153],{"class":88,"line":152},7,[86,154,156],{"emptyLinePlaceholder":155},true,"\n",[86,158,160,163],{"class":88,"line":159},8,[86,161,162],{"class":99},"permissions",[86,164,118],{"class":103},[86,166,168,171,173],{"class":88,"line":167},9,[86,169,170],{"class":99},"  contents",[86,172,104],{"class":103},[86,174,175],{"class":107},"read\n",[86,177,179],{"class":88,"line":178},10,[86,180,156],{"emptyLinePlaceholder":155},[86,182,184,187],{"class":88,"line":183},11,[86,185,186],{"class":99},"jobs",[86,188,118],{"class":103},[86,190,192,195],{"class":88,"line":191},12,[86,193,194],{"class":99},"  pre-commit",[86,196,118],{"class":103},[86,198,200,203,205],{"class":88,"line":199},13,[86,201,202],{"class":99},"    runs-on",[86,204,104],{"class":103},[86,206,207],{"class":107},"ubuntu-latest\n",[86,209,211,214],{"class":88,"line":210},14,[86,212,213],{"class":99},"    steps",[86,215,118],{"class":103},[86,217,219,222,225,227],{"class":88,"line":218},15,[86,220,221],{"class":103},"      - ",[86,223,224],{"class":99},"uses",[86,226,104],{"class":103},[86,228,229],{"class":107},"actions\u002Fcheckout@v4\n",[86,231,233,236],{"class":88,"line":232},16,[86,234,235],{"class":99},"        with",[86,237,118],{"class":103},[86,239,241,244,246,249],{"class":88,"line":240},17,[86,242,243],{"class":99},"          fetch-depth",[86,245,104],{"class":103},[86,247,248],{"class":114},"0",[86,250,251],{"class":92},"                       # needed for --from-ref on pull requests\n",[86,253,255,257,259,261],{"class":88,"line":254},18,[86,256,221],{"class":103},[86,258,224],{"class":99},[86,260,104],{"class":103},[86,262,263],{"class":107},"actions\u002Fsetup-python@v5\n",[86,265,267,269],{"class":88,"line":266},19,[86,268,235],{"class":99},[86,270,118],{"class":103},[86,272,274,277,279],{"class":88,"line":273},20,[86,275,276],{"class":99},"          python-version",[86,278,104],{"class":103},[86,280,281],{"class":107},"\"3.12\"\n",[86,283,285,287,289,291],{"class":88,"line":284},21,[86,286,221],{"class":103},[86,288,224],{"class":99},[86,290,104],{"class":103},[86,292,293],{"class":107},"actions\u002Fcache@v4\n",[86,295,297,299],{"class":88,"line":296},22,[86,298,235],{"class":99},[86,300,118],{"class":103},[86,302,304,307,309],{"class":88,"line":303},23,[86,305,306],{"class":99},"          path",[86,308,104],{"class":103},[86,310,311],{"class":107},"~\u002F.cache\u002Fpre-commit\n",[86,313,315,318,320],{"class":88,"line":314},24,[86,316,317],{"class":99},"          key",[86,319,104],{"class":103},[86,321,322],{"class":107},"pre-commit-${{ runner.os }}-${{ hashFiles('.pre-commit-config.yaml') }}\n",[86,324,326,328,331,333],{"class":88,"line":325},25,[86,327,221],{"class":103},[86,329,330],{"class":99},"run",[86,332,104],{"class":103},[86,334,335],{"class":107},"pipx install pre-commit==4.6.2\n",[86,337,339,341,343,345],{"class":88,"line":338},26,[86,340,221],{"class":103},[86,342,100],{"class":99},[86,344,104],{"class":103},[86,346,347],{"class":107},"Changed files (pull requests)\n",[86,349,351,354,356],{"class":88,"line":350},27,[86,352,353],{"class":99},"        if",[86,355,104],{"class":103},[86,357,358],{"class":107},"github.event_name == 'pull_request'\n",[86,360,362,365,367],{"class":88,"line":361},28,[86,363,364],{"class":99},"        run",[86,366,104],{"class":103},[86,368,370],{"class":369},"szBVR",">\n",[86,372,374],{"class":88,"line":373},29,[86,375,376],{"class":107},"          pre-commit run --show-diff-on-failure --color=always\n",[86,378,380],{"class":88,"line":379},30,[86,381,382],{"class":107},"          --from-ref \"origin\u002F${{ github.base_ref }}\" --to-ref HEAD\n",[86,384,386,388,390,392],{"class":88,"line":385},31,[86,387,221],{"class":103},[86,389,100],{"class":99},[86,391,104],{"class":103},[86,393,394],{"class":107},"All files (main)\n",[86,396,398,400,402],{"class":88,"line":397},32,[86,399,353],{"class":99},[86,401,104],{"class":103},[86,403,404],{"class":107},"github.event_name == 'push'\n",[86,406,408,410,412],{"class":88,"line":407},33,[86,409,364],{"class":99},[86,411,104],{"class":103},[86,413,414],{"class":107},"pre-commit run --all-files --show-diff-on-failure --color=always\n",[86,416,418,420,422,424],{"class":88,"line":417},34,[86,419,221],{"class":103},[86,421,100],{"class":99},[86,423,104],{"class":103},[86,425,426],{"class":107},"CI-only hooks\n",[86,428,430,432,434],{"class":88,"line":429},35,[86,431,364],{"class":99},[86,433,104],{"class":103},[86,435,436],{"class":107},"pre-commit run --all-files --hook-stage manual\n",[438,439,441],"h3",{"id":440},"cache-hook-environments","Cache hook environments",[10,443,444,445,448,449,451,452,455],{},"pre-commit builds an isolated environment for each hook repository the first time it runs — cloning, creating a virtual environment, installing — which takes tens of seconds per hook. Those environments live in ",[14,446,447],{},"~\u002F.cache\u002Fpre-commit",". Keying the cache on a hash of ",[14,450,52],{}," means environments are rebuilt exactly when a hook's ",[14,453,454],{},"rev"," changes and reused otherwise, bringing a typical job from a minute or more down to a few seconds.",[438,457,459],{"id":458},"check-what-the-pull-request-changed","Check what the pull request changed",[10,461,462,465,466,469,470,473,474,476,477,480],{},[14,463,464],{},"--from-ref"," and ",[14,467,468],{},"--to-ref"," run hooks only on files that differ between the two refs, so a pull request is judged on its own changes rather than on every file in the repository. That matters when you add a new hook or tighten a rule: existing violations elsewhere do not block an unrelated pull request, and you can fix them in a dedicated clean-up. The full ",[14,471,472],{},"--all-files"," run on pushes to ",[14,475,146],{}," makes sure the default branch is entirely clean. ",[14,478,479],{},"fetch-depth: 0"," gives the job the base branch to compare against.",[438,482,484],{"id":483},"show-the-fix","Show the fix",[10,486,487,490,491,493,494,497],{},[14,488,489],{},"--show-diff-on-failure"," prints the changes that auto-fixing hooks (formatters, import sorters, end-of-file fixers) made, so the log says exactly what to change — or the contributor simply runs ",[14,492,20],{}," locally and commits the result. ",[14,495,496],{},"--color=always"," keeps pre-commit's colour output readable in the Actions log.",[62,499],{"name":500},"pcci-terminal",[438,502,504],{"id":503},"keep-slow-hooks-for-ci","Keep slow hooks for CI",[10,506,507,508,511],{},"Some checks are valuable but too slow for every commit: a full mypy run, building documentation, regenerating a CLI reference and checking it is up to date. Put them in the ",[14,509,510],{},"manual"," stage so local commits skip them and CI runs them explicitly:",[77,513,515],{"className":79,"code":514,"language":81,"meta":82,"style":82},"  - repo: local\n    hooks:\n      - id: cli-reference\n        name: CLI reference is up to date\n        entry: uv run python scripts\u002Fgen_cli_reference.py --check\n        language: system\n        pass_filenames: false\n        always_run: true\n        stages: [manual]\n",[14,516,517,530,537,549,559,569,579,589,599],{"__ignoreMap":82},[86,518,519,522,525,527],{"class":88,"line":89},[86,520,521],{"class":103},"  - ",[86,523,524],{"class":99},"repo",[86,526,104],{"class":103},[86,528,529],{"class":107},"local\n",[86,531,532,535],{"class":88,"line":96},[86,533,534],{"class":99},"    hooks",[86,536,118],{"class":103},[86,538,539,541,544,546],{"class":88,"line":111},[86,540,221],{"class":103},[86,542,543],{"class":99},"id",[86,545,104],{"class":103},[86,547,548],{"class":107},"cli-reference\n",[86,550,551,554,556],{"class":88,"line":121},[86,552,553],{"class":99},"        name",[86,555,104],{"class":103},[86,557,558],{"class":107},"CLI reference is up to date\n",[86,560,561,564,566],{"class":88,"line":129},[86,562,563],{"class":99},"        entry",[86,565,104],{"class":103},[86,567,568],{"class":107},"uv run python scripts\u002Fgen_cli_reference.py --check\n",[86,570,571,574,576],{"class":88,"line":137},[86,572,573],{"class":99},"        language",[86,575,104],{"class":103},[86,577,578],{"class":107},"system\n",[86,580,581,584,586],{"class":88,"line":152},[86,582,583],{"class":99},"        pass_filenames",[86,585,104],{"class":103},[86,587,588],{"class":114},"false\n",[86,590,591,594,596],{"class":88,"line":159},[86,592,593],{"class":99},"        always_run",[86,595,104],{"class":103},[86,597,598],{"class":114},"true\n",[86,600,601,604,606,608],{"class":88,"line":167},[86,602,603],{"class":99},"        stages",[86,605,143],{"class":103},[86,607,510],{"class":107},[86,609,149],{"class":103},[10,611,612,615,616,619,620,623,624,37],{},[14,613,614],{},"always_run: true"," matters for hooks that take no filenames: without it, pre-commit skips the hook with \"(no files to check)\" whenever none of its ",[14,617,618],{},"files"," patterns match — which, for a hook with no patterns and ",[14,621,622],{},"pass_filenames: false",", can surprise you. Developers can still run the stage on demand with ",[14,625,626],{},"pre-commit run --hook-stage manual --all-files",[438,628,630],{"id":629},"skip-what-cannot-run-in-ci","Skip what cannot run in CI",[10,632,633,634,637],{},"Occasionally a hook needs something CI lacks — a credential, a local service. Skip it by id with the ",[14,635,636],{},"SKIP"," variable rather than maintaining a second configuration:",[77,639,641],{"className":79,"code":640,"language":81,"meta":82,"style":82},"      - run: pre-commit run --all-files\n        env:\n          SKIP: check-license-server\n",[14,642,643,654,661],{"__ignoreMap":82},[86,644,645,647,649,651],{"class":88,"line":89},[86,646,221],{"class":103},[86,648,330],{"class":99},[86,650,104],{"class":103},[86,652,653],{"class":107},"pre-commit run --all-files\n",[86,655,656,659],{"class":88,"line":96},[86,657,658],{"class":99},"        env",[86,660,118],{"class":103},[86,662,663,666,668],{"class":88,"line":111},[86,664,665],{"class":99},"          SKIP",[86,667,104],{"class":103},[86,669,670],{"class":107},"check-license-server\n",[10,672,673],{},"Use this sparingly; every skipped hook is a check that only some contributors ever run.",[39,675,677],{"id":676},"or-let-pre-commitci-do-it","Or let pre-commit.ci do it",[10,679,680,686,687,690,691,694,695,697,698,701,702,704],{},[28,681,685],{"href":682,"rel":683},"https:\u002F\u002Fpre-commit.ci",[684],"nofollow","pre-commit.ci"," is a hosted service from the pre-commit maintainers that runs your configuration on every pull request with cached environments, pushes auto-fixes back to the branch as a commit, and opens weekly ",[14,688,689],{},"autoupdate"," pull requests. It needs no workflow file — only a ",[14,692,693],{},"ci:"," section in ",[14,696,52],{}," for options — and is free for open-source repositories. The trade-off: it cannot run hooks that need network access or ",[14,699,700],{},"language: system"," tools, which is exactly where the ",[14,703,510],{},"-stage job above still earns its place.",[62,706],{"name":707},"pcci-options",[39,709,711],{"id":710},"ux-considerations","UX considerations",[44,713,714,732,741,747,753],{},[47,715,716,720,721,724,725,727,728,37],{},[717,718,719],"strong",{},"Same versions everywhere."," Pin ",[14,722,723],{},"pre-commit"," itself in CI, and keep hook ",[14,726,454],{},"s as the single source of tool versions — see ",[28,729,731],{"href":730},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects\u002Fkeeping-hook-versions-current-with-autoupdate\u002F","keeping hook versions current with autoupdate",[47,733,734,737,738,740],{},[717,735,736],{},"Fail with instructions."," Add a step that runs on failure and prints \"Run ",[14,739,20],{}," locally and commit the changes\", because newcomers may not know pre-commit exists.",[47,742,743,746],{},[717,744,745],{},"Do not fight auto-fixes."," Letting CI commit fixes (as pre-commit.ci does) is convenient for formatting; for anything semantic, a failing check that a human fixes is clearer.",[47,748,749,752],{},[717,750,751],{},"Keep the job fast and separate."," A pre-commit job that finishes in under a minute, independent of the test matrix, gives contributors the cheapest possible feedback.",[47,754,755,758,759,763],{},[717,756,757],{},"Mind duplication."," If CI already runs Ruff and mypy with ",[28,760,762],{"href":761},"\u002Fproject-setup-dependency-management\u002Flinting-and-type-checking-cli-code\u002Frunning-ruff-and-mypy-in-ci-with-annotations\u002F","inline annotations",", either drop them from the pre-commit job or drop the separate job — running both doubles the noise for every finding.",[39,765,767],{"id":766},"testing-the-behaviour","Testing the behaviour",[10,769,770],{},"Prove the job does its job once, on a throwaway branch:",[77,772,776],{"className":773,"code":774,"language":775,"meta":82,"style":82},"language-bash shiki shiki-themes github-light github-dark","git switch -c check-pre-commit-ci\nprintf 'import os\\nx=1\\n' > src\u002Fmytool\u002Fscratch.py      # unused import, unformatted\ngit add src\u002Fmytool\u002Fscratch.py && git commit --no-verify -m \"deliberately bad\"\ngit push -u origin check-pre-commit-ci\n","bash",[14,777,778,793,810,836],{"__ignoreMap":82},[86,779,780,784,787,790],{"class":88,"line":89},[86,781,783],{"class":782},"sScJk","git",[86,785,786],{"class":107}," switch",[86,788,789],{"class":114}," -c",[86,791,792],{"class":107}," check-pre-commit-ci\n",[86,794,795,798,801,804,807],{"class":88,"line":96},[86,796,797],{"class":114},"printf",[86,799,800],{"class":107}," 'import os\\nx=1\\n'",[86,802,803],{"class":369}," >",[86,805,806],{"class":107}," src\u002Fmytool\u002Fscratch.py",[86,808,809],{"class":92},"      # unused import, unformatted\n",[86,811,812,814,817,819,822,824,827,830,833],{"class":88,"line":111},[86,813,783],{"class":782},[86,815,816],{"class":107}," add",[86,818,806],{"class":107},[86,820,821],{"class":103}," && ",[86,823,783],{"class":782},[86,825,826],{"class":107}," commit",[86,828,829],{"class":114}," --no-verify",[86,831,832],{"class":114}," -m",[86,834,835],{"class":107}," \"deliberately bad\"\n",[86,837,838,840,843,846,849],{"class":88,"line":121},[86,839,783],{"class":782},[86,841,842],{"class":107}," push",[86,844,845],{"class":114}," -u",[86,847,848],{"class":107}," origin",[86,850,792],{"class":107},[10,852,853,854,857,858,860],{},"Open a pull request and confirm the job fails with Ruff's finding and a formatting diff for ",[14,855,856],{},"scratch.py"," only. Then check the cache: the second run of the job should report a cache hit and skip the \"Installing environment\" lines entirely. Finally, change one hook's ",[14,859,454],{}," and confirm the cache key changes and the environments rebuild once.",[39,862,864],{"id":863},"conclusion","Conclusion",[10,866,867,868,870,871,873,874,876,877,879,880,882,883,886],{},"pre-commit in CI turns optional local checks into enforced ones without a second source of truth. Cache ",[14,869,447],{}," keyed on the config file, check pull requests with ",[14,872,464],{},"\u002F",[14,875,468],{}," and the default branch with ",[14,878,472],{},", show diffs on failure, run slow checks in a ",[14,881,510],{}," stage with ",[14,884,885],{},"always_run",", skip only what truly cannot run, and consider pre-commit.ci for automatic fixes and updates. Contributors then get the same answer locally and in CI — just faster locally.",[39,888,890],{"id":889},"frequently-asked-questions","Frequently asked questions",[438,892,894,895,897],{"id":893},"should-ci-run-all-files-on-pull-requests-too","Should CI run ",[14,896,472],{}," on pull requests too?",[10,899,900],{},"It is simpler and catches everything, but it makes every pull request responsible for the whole repository's state. Checking changed files on pull requests and all files on the default branch is a good balance; after adding a new hook, clean the repository in one dedicated pull request.",[438,902,904],{"id":903},"why-does-ci-find-problems-my-local-hooks-did-not","Why does CI find problems my local hooks did not?",[10,906,907,908,911,912,914,915,917,918,920],{},"Usually different versions (an unpinned local pre-commit or a stale hook environment — ",[14,909,910],{},"pre-commit clean"," fixes the latter), files committed with ",[14,913,16],{},", or hooks in the ",[14,916,510],{}," stage. Running ",[14,919,20],{}," locally reproduces most cases.",[438,922,924],{"id":923},"can-pre-commit-run-in-the-same-job-as-the-tests","Can pre-commit run in the same job as the tests?",[10,926,927],{},"It can, but a separate job reports faster and keeps the logs readable. Lint failures are the most common CI failure and should be the quickest to see.",[438,929,931,932,935],{"id":930},"does-the-github-action-pre-commitaction-still-make-sense","Does the GitHub Action ",[14,933,934],{},"pre-commit\u002Faction"," still make sense?",[10,937,938],{},"It wraps installation, caching and the run in one step and works well. It is in maintenance mode, with pre-commit.ci recommended instead; the explicit steps above give the same result with full control.",[438,940,942],{"id":941},"why-does-pre-commit-in-ci-complain-that-the-configuration-is-unstaged","Why does pre-commit in CI complain that the configuration is unstaged?",[10,944,945,946,948,949,951,952,954],{},"pre-commit refuses to run when ",[14,947,52],{}," has uncommitted changes, to avoid running a configuration nobody has reviewed. In CI this happens when an earlier step modifies the file — a templating step, or an ",[14,950,689],{}," run in the same job. Commit the change in that step, or run ",[14,953,689],{}," in a separate workflow that opens a pull request instead, so the checking job always runs exactly what is in the repository.",[39,956,958],{"id":957},"related","Related",[44,960,961,967,972,978,983],{},[47,962,963,964],{},"Up: ",[28,965,966],{"href":30},"Pre-commit hooks for CLI projects",[47,968,969],{},[28,970,971],{"href":35},"Setting up pre-commit for Python CLI repos",[47,973,974],{},[28,975,977],{"href":976},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects\u002Fspeeding-up-slow-pre-commit-hooks\u002F","Speeding up slow pre-commit hooks",[47,979,980],{},[28,981,982],{"href":730},"Keeping hook versions current with autoupdate",[47,984,985],{},[28,986,988],{"href":987},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis\u002Fcaching-uv-dependencies-in-ci\u002F","Caching uv dependencies in CI",[990,991,992],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}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 .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}",{"title":82,"searchDepth":96,"depth":96,"links":994},[995,996,997,1004,1005,1006,1007,1008,1017],{"id":41,"depth":96,"text":42},{"id":59,"depth":96,"text":60},{"id":74,"depth":96,"text":75,"children":998},[999,1000,1001,1002,1003],{"id":440,"depth":111,"text":441},{"id":458,"depth":111,"text":459},{"id":483,"depth":111,"text":484},{"id":503,"depth":111,"text":504},{"id":629,"depth":111,"text":630},{"id":676,"depth":96,"text":677},{"id":710,"depth":96,"text":711},{"id":766,"depth":96,"text":767},{"id":863,"depth":96,"text":864},{"id":889,"depth":96,"text":890,"children":1009},[1010,1012,1013,1014,1016],{"id":893,"depth":111,"text":1011},"Should CI run --all-files on pull requests too?",{"id":903,"depth":111,"text":904},{"id":923,"depth":111,"text":924},{"id":930,"depth":111,"text":1015},"Does the GitHub Action pre-commit\u002Faction still make sense?",{"id":941,"depth":111,"text":942},{"id":957,"depth":96,"text":958},"2026-10-02","Run pre-commit hooks in CI properly: cache hook environments, check only a pull request’s changes, show diffs, keep slow hooks for CI, skip network hooks and use pre-commit.ci.","intermediate",false,"md",{},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects\u002Frunning-pre-commit-in-ci",{"title":5,"description":1019},"project-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects\u002Frunning-pre-commit-in-ci\u002Findex",[723,1028,1029,1030,1031],"ci","github-actions","linting","automation","9LrtFfaFnXbCoYClE9bAcOxY8DeiTRm8aapZMGu2dAg",[1034,1037,1040,1043,1046,1049,1052,1055,1058,1061,1064,1067,1070,1073,1076,1079,1082,1085,1088,1091,1094,1097,1100,1103,1106,1109,1112,1115,1118,1121,1124,1127,1130,1133,1136,1139,1142,1145,1148,1151,1154,1157,1160,1163,1166,1169,1172,1175,1178,1181,1184,1187,1190,1193,1196,1199,1202,1205,1208,1211,1214,1217,1220,1223,1226,1229,1232,1235,1238,1241,1244,1247,1250,1253,1256,1259,1262,1265,1268,1271,1274,1277,1280,1283,1286,1289,1292,1295,1298,1301,1304,1307,1310,1313,1316,1319,1322,1325,1328,1331,1334,1337,1340,1343,1346,1349,1352,1355,1358,1361,1364,1367,1370,1373,1376,1379,1382,1385,1388,1391,1394,1397,1400,1403,1406,1409,1412,1415,1418,1421,1424,1427,1430,1433,1436,1439,1442,1445,1448,1451,1453,1456,1459,1462,1465,1468,1471,1474,1477,1480,1483,1486,1489,1492,1495,1498,1501,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,1805,1807,1810,1813,1816,1819,1822,1825,1828,1831,1834,1837,1840,1843,1846,1849,1852,1855,1858,1861,1864,1867,1870,1873],{"path":1035,"title":1036},"\u002Fabout","About Python CLI Toolcraft",{"path":1038,"title":1039},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies","Advanced Argument Validation Strategies",{"path":1041,"title":1042},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fparsing-nested-json-arguments-in-python-clis","Parsing Nested JSON Args in Python CLIs",{"path":1044,"title":1045},"\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":1047,"title":1048},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fvalidating-dependent-and-conflicting-options","Validating Dependent and Conflicting CLI Options",{"path":1050,"title":1051},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fvalidating-file-and-directory-paths-in-clis","Validating File and Directory Paths in CLIs",{"path":1053,"title":1054},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fvalidating-urls-hosts-and-ports","Validating URLs, Hosts and Ports in Python CLI Arguments",{"path":1056,"title":1057},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fwriting-custom-click-parameter-types","Writing Custom Click Parameter Types",{"path":1059,"title":1060},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual\u002Fbrowsing-records-with-a-textual-datatable","Browsing Records with a Textual DataTable Picker",{"path":1062,"title":1063},"\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":1065,"title":1066},"\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":1068,"title":1069},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual","Building Terminal UIs with Textual for Python CLIs",{"path":1071,"title":1072},"\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":1074,"title":1075},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual\u002Fstyling-textual-apps-with-tcss","Styling Textual Apps with TCSS",{"path":1077,"title":1078},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual\u002Ftesting-textual-apps-with-pilot","Testing Textual Apps with Pilot",{"path":1080,"title":1081},"\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":1083,"title":1084},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fdocumenting-environment-variables-in-help","Documenting Environment Variables in CLI Help",{"path":1086,"title":1087},"\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":1089,"title":1090},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation","CLI Help Output and Documentation",{"path":1092,"title":1093},"\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":1095,"title":1096},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fversioning-and-deprecating-cli-flags","Versioning and Deprecating CLI Flags",{"path":1098,"title":1099},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fwriting-help-text-users-actually-read","Writing Help Text Users Actually Read",{"path":1101,"title":1102},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Fadapting-output-to-terminal-width","Adapting Python CLI Output to Terminal Width",{"path":1104,"title":1105},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Fdetecting-ci-environments-and-non-interactive-shells","Detecting CI Environments and Non-Interactive Shells",{"path":1107,"title":1108},"\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":1110,"title":1111},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Fhandling-ansi-escape-codes-on-windows-consoles","Handling ANSI Escape Codes on Windows Consoles",{"path":1113,"title":1114},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility","Cross-Platform Terminal Compatibility for Python CLIs",{"path":1116,"title":1117},"\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":1119,"title":1120},"\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":1122,"title":1123},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fchoosing-exit-codes-for-cli-tools","Choosing Exit Codes for CLI Tools",{"path":1125,"title":1126},"\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":1128,"title":1129},"\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":1131,"title":1132},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Ffriendly-error-messages-and-tracebacks","Friendly Error Messages and Tracebacks",{"path":1134,"title":1135},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fhandling-keyboard-interrupt-cleanly","Handling Keyboard Interrupt Cleanly",{"path":1137,"title":1138},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes","Error Handling and Exit Codes for CLIs",{"path":1140,"title":1141},"\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":1143,"title":1144},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fwriting-crash-reports-users-can-send","Writing Crash Reports Users Can Send",{"path":1146,"title":1147},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Fconfig-precedence-flags-env-files-defaults","Config Precedence: Flags, Env, Files, Defaults",{"path":1149,"title":1150},"\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":1152,"title":1153},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars","Handling Config Files and Env Vars in CLIs",{"path":1155,"title":1156},"\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":1158,"title":1159},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Freading-toml-config-with-tomllib","Reading TOML Config with tomllib in Python CLIs",{"path":1161,"title":1162},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Ftyped-settings-with-pydantic-settings","Typed Settings with pydantic-settings in Python CLIs",{"path":1164,"title":1165},"\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":1167,"title":1168},"\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":1170,"title":1171},"\u002Fadvanced-input-parsing-user-experience","Advanced Input Parsing for Python CLIs",{"path":1173,"title":1174},"\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":1176,"title":1177},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Fbuilding-interactive-prompts-and-menus","Building Interactive Prompts and Menus in Python CLIs",{"path":1179,"title":1180},"\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":1182,"title":1183},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich","Interactive Terminal UI with Rich",{"path":1185,"title":1186},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Flive-dashboards-with-rich-live","Live Dashboards with Rich Live in Python CLIs",{"path":1188,"title":1189},"\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":1191,"title":1192},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Frendering-tables-and-json-with-rich","Rendering Tables and JSON with Rich",{"path":1194,"title":1195},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Ftheming-rich-output-consistently","Theming Rich Output Consistently in Python CLIs",{"path":1197,"title":1198},"\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":1200,"title":1201},"\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":1203,"title":1204},"\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":1206,"title":1207},"\u002Fadvanced-input-parsing-user-experience\u002Foutput-formats-for-data-clis","Output Formats for Data-Heavy Python CLIs",{"path":1209,"title":1210},"\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":1212,"title":1213},"\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":1215,"title":1216},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Fargcomplete-for-argparse-clis","Tab Completion for argparse CLIs with argcomplete",{"path":1218,"title":1219},"\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":1221,"title":1222},"\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":1224,"title":1225},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis","Shell Completion for Python CLIs",{"path":1227,"title":1228},"\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":1230,"title":1231},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Fshipping-completion-scripts-with-packages","Shipping Shell Completion Scripts with a Python CLI",{"path":1233,"title":1234},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Ftesting-shell-completion-in-python-clis","Testing Shell Completion in Python CLIs",{"path":1236,"title":1237},"\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":1239,"title":1240},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fadding-verbose-and-quiet-logging-flags","Adding Verbose and Quiet Logging Flags",{"path":1242,"title":1243},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps","Structured Logging for CLI Apps",{"path":1245,"title":1246},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Flogging-with-structlog-in-a-cli","Logging with structlog in a Python CLI",{"path":1248,"title":1249},"\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":1251,"title":1252},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fstructured-json-logging-in-python-clis","Structured JSON Logging in Python CLIs",{"path":1254,"title":1255},"\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":1257,"title":1258},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Fdetecting-tty-and-adapting-output","Detecting a TTY and Adapting Output",{"path":1260,"title":1261},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Femitting-json-output-for-scripting","Emitting JSON Output for Scripting",{"path":1263,"title":1264},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Fhandling-broken-pipe-and-sigpipe","Handling Broken Pipe and SIGPIPE",{"path":1266,"title":1267},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes","Working with stdin, stdout and Pipes",{"path":1269,"title":1270},"\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":1272,"title":1273},"\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":1275,"title":1276},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Freading-piped-input-in-python-clis","Reading Piped Input in Python CLIs",{"path":1278,"title":1279},"\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":1281,"title":1282},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Fdownloading-files-with-progress-in-python","Downloading Files with Progress in Python CLIs",{"path":1284,"title":1285},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis","Calling HTTP APIs from Python CLIs",{"path":1287,"title":1288},"\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":1290,"title":1291},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Foauth-device-flow-login-for-clis","OAuth Device Flow Login for Python CLIs",{"path":1293,"title":1294},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Fpaginating-api-results-in-a-cli","Paginating API Results in a Python CLI",{"path":1296,"title":1297},"\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":1299,"title":1300},"\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":1302,"title":1303},"\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":1305,"title":1306},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis","Concurrency and Async in Python CLIs",{"path":1308,"title":1309},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Fmultiprocessing-for-cpu-bound-cli-tasks","Multiprocessing for CPU-Bound CLI Tasks",{"path":1311,"title":1312},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Fparallelising-cli-work-with-thread-pools","Parallelising CLI Work with Thread Pools",{"path":1314,"title":1315},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Frate-limiting-concurrent-requests-in-clis","Rate-Limiting Concurrent Requests in Python CLIs",{"path":1317,"title":1318},"\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":1320,"title":1321},"\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":1323,"title":1324},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Fstructured-concurrency-with-taskgroups-in-clis","Structured Concurrency with TaskGroups in Python CLIs",{"path":1326,"title":1327},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fcross-platform-paths-with-pathlib","Cross-Platform Paths with pathlib in CLIs",{"path":1329,"title":1330},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Ffile-locking-for-concurrent-cli-runs","File Locking for Concurrent CLI Runs in Python",{"path":1332,"title":1333},"\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":1335,"title":1336},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes","Filesystem Paths and Atomic Writes for CLIs",{"path":1338,"title":1339},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fsafe-temporary-files-and-directories","Safe Temporary Files and Directories in CLIs",{"path":1341,"title":1342},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fstoring-app-data-with-platformdirs","Storing CLI App Data with platformdirs",{"path":1344,"title":1345},"\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":1347,"title":1348},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fwriting-files-atomically-in-python-clis","Writing Files Atomically in Python CLIs",{"path":1350,"title":1351},"\u002Fcli-runtime-systems-integration","CLI Runtime & Systems Integration for Python",{"path":1353,"title":1354},"\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":1356,"title":1357},"\u002Fcli-runtime-systems-integration\u002Flocal-state-and-sqlite-in-python-clis","Local State and SQLite in Python CLIs",{"path":1359,"title":1360},"\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":1362,"title":1363},"\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":1365,"title":1366},"\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":1368,"title":1369},"\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":1371,"title":1372},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis\u002Fhandling-sigterm-and-graceful-shutdown","Handling SIGTERM and Graceful Shutdown in CLIs",{"path":1374,"title":1375},"\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":1377,"title":1378},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis","Long-Running and Watch-Mode Python CLIs",{"path":1380,"title":1381},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis\u002Freloading-config-on-sighup","Reloading Configuration on SIGHUP in a Python CLI",{"path":1383,"title":1384},"\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":1386,"title":1387},"\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":1389,"title":1390},"\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":1392,"title":1393},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Favoiding-shell-injection-in-python-clis","Avoiding Shell Injection in Python CLIs",{"path":1395,"title":1396},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fcalling-external-commands-safely-with-subprocess","Calling External Commands Safely with subprocess",{"path":1398,"title":1399},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fhandling-subprocess-timeouts-and-exit-codes","Handling Subprocess Timeouts and Exit Codes",{"path":1401,"title":1402},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis","Running Subprocesses from Python CLIs",{"path":1404,"title":1405},"\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":1407,"title":1408},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fpiping-between-subprocesses-in-python","Piping Between Subprocesses in Python Without a Shell",{"path":1410,"title":1411},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fstreaming-subprocess-output-in-real-time","Streaming Subprocess Output in Real Time",{"path":1413,"title":1414},"\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":1416,"title":1417},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis","Secrets and Credentials in Python CLIs",{"path":1419,"title":1420},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Fprompting-for-passwords-securely","Prompting for Passwords Securely in Python CLIs",{"path":1422,"title":1423},"\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":1425,"title":1426},"\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":1428,"title":1429},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Frefreshing-expired-tokens-automatically","Refreshing Expired Tokens Automatically in a Python CLI",{"path":1431,"title":1432},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Fstoring-tokens-with-keyring","Storing CLI Tokens Securely with keyring",{"path":1434,"title":1435},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Fsupporting-multiple-profiles-and-accounts","Supporting Multiple Profiles and Accounts in CLIs",{"path":1437,"title":1438},"\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":1440,"title":1441},"\u002Fcli-runtime-systems-integration\u002Fupdate-checks-and-self-updating-clis","Update Checks, Upgrade Commands and Telemetry for Python CLIs",{"path":1443,"title":1444},"\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":1446,"title":1447},"\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":1449,"title":1450},"\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":873,"title":1452},"Python CLI Toolcraft",{"path":1454,"title":1455},"\u002Fmodern-python-cli-frameworks-architecture\u002Falternative-python-cli-frameworks\u002Fbuilding-a-cli-with-cleo","Building a Python CLI with Cleo Command Classes",{"path":1457,"title":1458},"\u002Fmodern-python-cli-frameworks-architecture\u002Falternative-python-cli-frameworks\u002Fbuilding-a-cli-with-cyclopts","Building a Type-Hinted Python CLI with Cyclopts",{"path":1460,"title":1461},"\u002Fmodern-python-cli-frameworks-architecture\u002Falternative-python-cli-frameworks","Beyond Click and Typer: Alternative Python CLI Frameworks",{"path":1463,"title":1464},"\u002Fmodern-python-cli-frameworks-architecture\u002Falternative-python-cli-frameworks\u002Fquick-clis-from-functions-with-python-fire","Quick CLIs from Functions with Python Fire",{"path":1466,"title":1467},"\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":1469,"title":1470},"\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":1472,"title":1473},"\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":1475,"title":1476},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Fguarding-startup-with-import-tests","Guarding CLI Startup with Import Tests",{"path":1478,"title":1479},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading","CLI Startup Performance and Lazy Loading",{"path":1481,"title":1482},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Flazy-loading-subcommands-for-faster-startup","Lazy Loading Subcommands for Faster Startup",{"path":1484,"title":1485},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Fprofiling-python-cli-startup-time","Profiling Python CLI Startup Time",{"path":1487,"title":1488},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Freducing-cli-dependency-weight","Reducing CLI Dependency Weight",{"path":1490,"title":1491},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargparse-subparsers-for-subcommands","argparse Subparsers for Subcommands",{"path":1493,"title":1494},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargparse-vs-click-vs-typer-comparison","argparse vs Click vs Typer Compared",{"path":1496,"title":1497},"\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":1499,"title":1500},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse","Command-Line Parsing with argparse",{"path":1502,"title":1503},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fmigrating-from-argparse-to-typer","Migrating from argparse to Typer",{"path":1505,"title":1506},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fmutually-exclusive-options-in-argparse","Mutually Exclusive Options in argparse",{"path":1508,"title":1509},"\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":1511,"title":1512},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fwriting-custom-argparse-actions","Writing Custom argparse Actions in Python",{"path":1514,"title":1515},"\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":1517,"title":1518},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Fdesigning-idempotent-commands","Designing Idempotent Commands in a Python CLI",{"path":1520,"title":1521},"\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":1523,"title":1524},"\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":1526,"title":1527},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions","Designing CLI Interfaces and Conventions in Python",{"path":1529,"title":1530},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Fnaming-commands-and-flags-consistently","Naming Commands and Flags Consistently in Python CLIs",{"path":1532,"title":1533},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Fpositional-arguments-vs-options","Positional Arguments vs Options: Designing a CLI Signature",{"path":1535,"title":1536},"\u002Fmodern-python-cli-frameworks-architecture","Python CLI Frameworks and Architecture",{"path":1538,"title":1539},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fdiscovering-plugins-with-entry-points","Discovering Plugins with Entry Points in Python CLIs",{"path":1541,"title":1542},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fhook-based-plugins-with-pluggy","Hook-Based Plugins for Python CLIs with pluggy",{"path":1544,"title":1545},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis","Plugin Architectures for Extensible CLIs",{"path":1547,"title":1548},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fisolating-plugin-failures","Isolating Plugin Failures in an Extensible Python CLI",{"path":1550,"title":1551},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Ftesting-plugins-against-the-host-cli","Testing Plugins Against the Host CLI",{"path":1553,"title":1554},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fversioning-a-plugin-api","Versioning a Plugin API for a Python CLI",{"path":1556,"title":1557},"\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":1559,"title":1560},"\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":1562,"title":1563},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fdependency-injection-patterns-for-cli-commands","Dependency Injection Patterns for CLI Commands",{"path":1565,"title":1566},"\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":1568,"title":1569},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis","Structuring Multi-Command Python CLIs",{"path":1571,"title":1572},"\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":1574,"title":1575},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fregistering-commands-from-modules-automatically","Registering CLI Commands from Modules Automatically",{"path":1577,"title":1578},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fsharing-common-options-across-commands","Sharing Common Options Across Python CLI Commands",{"path":1580,"title":1581},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fsharing-state-with-click-context-objects","Sharing State with Click Context Objects",{"path":1583,"title":1584},"\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":1586,"title":1587},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications","Testing Python CLI Applications",{"path":1589,"title":1590},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fmeasuring-cli-test-coverage","Measuring CLI Test Coverage",{"path":1592,"title":1593},"\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":1595,"title":1596},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fproperty-based-testing-cli-arguments-with-hypothesis","Property-Based Testing CLI Arguments with Hypothesis",{"path":1598,"title":1599},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fsnapshot-testing-cli-output","Snapshot Testing CLI Output",{"path":1601,"title":1602},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftesting-click-commands-with-clirunner","Testing Click Commands with CliRunner",{"path":1604,"title":1605},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftesting-commands-that-call-subprocesses","Testing Python CLI Commands That Call Subprocesses",{"path":1607,"title":1608},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftesting-interactive-prompts-and-stdin","Testing Interactive Prompts and stdin",{"path":1610,"title":1611},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftranscript-tests-for-cli-commands","Transcript Tests for Python CLI Commands",{"path":1613,"title":1614},"\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":1616,"title":1617},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Fbuilding-dynamic-commands-in-click","Building Dynamic Commands in Click",{"path":1619,"title":1620},"\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":1622,"title":1623},"\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":1625,"title":1626},"\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":1628,"title":1629},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each","Typer vs Click: When to Use Each",{"path":1631,"title":1632},"\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":1634,"title":1635},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Ftyper-callback-functions-explained","Typer callback functions explained",{"path":1637,"title":1638},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Fusing-annotated-options-in-typer","Using Annotated Options in Typer",{"path":1640,"title":1641},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis\u002Fautomating-releases-from-git-tags","Automating Python CLI Releases from Git Tags",{"path":1643,"title":1644},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis\u002Fcaching-uv-dependencies-in-ci","Caching uv Dependencies in CI for Python CLIs",{"path":1646,"title":1647},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis","CI\u002FCD Pipelines for Python CLIs",{"path":1649,"title":1650},"\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":1652,"title":1653},"\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":1655,"title":1656},"\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":1658,"title":1659},"\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":1661,"title":1662},"\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":1664,"title":1665},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fbuilding-a-cookiecutter-template-for-typer-clis","Building a Cookiecutter Template for Typer CLIs",{"path":1667,"title":1668},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fcopier-vs-cookiecutter-for-cli-templates","Copier vs Cookiecutter for CLI Templates",{"path":1670,"title":1671},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter","CLI Project Scaffolding with Cookiecutter",{"path":1673,"title":1674},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fpost-generation-hooks-in-cli-templates","Post-Generation Hooks in Python CLI Templates",{"path":1676,"title":1677},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Ftemplate-variables-and-conditional-files","Template Variables and Conditional Files in CLI Templates",{"path":1679,"title":1680},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Ftesting-a-project-template-with-pytest","Testing a CLI Project Template with pytest",{"path":1682,"title":1683},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fupdating-generated-projects-with-copier-update","Updating Generated CLI Projects with copier update",{"path":1685,"title":1686},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fbuilding-cross-platform-release-binaries-in-ci","Building Cross-Platform Release Binaries in CI",{"path":1688,"title":1689},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fbundling-a-python-cli-with-pyinstaller","Bundling a Python CLI with PyInstaller",{"path":1691,"title":1692},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fcode-signing-and-notarizing-cli-binaries","Code Signing and Notarizing Python CLI Binaries",{"path":1694,"title":1695},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fhomebrew-and-scoop-packaging-for-python-clis","Homebrew and Scoop Packaging for Python CLIs",{"path":1697,"title":1698},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries","Distributing CLIs as Standalone Binaries",{"path":1700,"title":1701},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fnuitka-vs-pyinstaller-for-python-clis","Nuitka vs PyInstaller for Python CLI Binaries",{"path":1703,"title":1704},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Freducing-pyinstaller-binary-size","Reducing the Size of a PyInstaller CLI Binary",{"path":1706,"title":1707},"\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":1709,"title":1710},"\u002Fproject-setup-dependency-management","Project Setup & Dependency Management",{"path":1712,"title":1713},"\u002Fproject-setup-dependency-management\u002Flinting-and-type-checking-cli-code\u002Fconfiguring-ruff-for-a-cli-project","Configuring Ruff for a Python CLI Project",{"path":1715,"title":1716},"\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":1718,"title":1719},"\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":1721,"title":1722},"\u002Fproject-setup-dependency-management\u002Flinting-and-type-checking-cli-code","Linting and Type-Checking Python CLI Code",{"path":1724,"title":1725},"\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":1727,"title":1728},"\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":1730,"title":1731},"\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":1733,"title":1734},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fautomating-changelogs-with-conventional-commits","Automating Changelogs with Conventional Commits",{"path":1736,"title":1737},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fbumping-versions-consistently-across-a-cli-project","Bumping Versions Consistently Across a Python CLI Project",{"path":1739,"title":1740},"\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":1742,"title":1743},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fexposing-version-info-and-build-metadata","Exposing Version Info and Build Metadata",{"path":1745,"title":1746},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs","Managing CLI Versioning & Changelogs",{"path":1748,"title":1749},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fsemantic-versioning-policy-for-cli-tools","A Semantic Versioning Policy for CLI Tools",{"path":1751,"title":1752},"\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":1754,"title":1755},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fbuilding-wheels-and-sdists-for-python-clis","Building Wheels and sdists for Python CLIs",{"path":1757,"title":1758},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fbundling-data-files-with-importlib-resources","Bundling Data Files with importlib.resources in CLIs",{"path":1760,"title":1761},"\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":1763,"title":1764},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution","Packaging Python CLIs for Distribution",{"path":1766,"title":1767},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Finstalling-and-distributing-clis-with-pipx","Installing and Distributing CLIs with pipx",{"path":1769,"title":1770},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Foptional-dependencies-and-extras-for-clis","Optional Dependencies and Extras for Python CLIs",{"path":1772,"title":1773},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fpublishing-a-python-cli-to-pypi","Publishing a Python CLI to PyPI",{"path":1775,"title":1776},"\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":1778,"title":1779},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fdynamic-versioning-with-poetry-plugins","Dynamic Versioning for a Poetry CLI from Git Tags",{"path":1781,"title":1782},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development","Poetry Workflows for CLI Development",{"path":1784,"title":1785},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fmanaging-poetry-lock-files-for-cli-tools","Managing Poetry Lock Files for CLI Tools",{"path":1787,"title":1788},"\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":1790,"title":1791},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpoetry-dependency-groups-for-cli-tooling","Poetry Dependency Groups for CLI Tooling",{"path":1793,"title":1794},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpoetry-entry-points-and-scripts-for-clis","Poetry Entry Points and Scripts for CLIs",{"path":1796,"title":1797},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpublishing-a-cli-with-poetry","Building and Publishing a Python CLI with Poetry",{"path":1799,"title":1800},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects","Pre-commit Hooks for CLI Projects",{"path":1802,"title":1803},"\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":1024,"title":5},{"path":1806,"title":971},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects\u002Fsetting-up-pre-commit-for-python-cli-repos",{"path":1808,"title":1809},"\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":1811,"title":1812},"\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":1814,"title":1815},"\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":1817,"title":1818},"\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":1820,"title":1821},"\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":1823,"title":1824},"\u002Fproject-setup-dependency-management\u002Fsecuring-a-python-cli-supply-chain","Securing the Supply Chain of a Python CLI",{"path":1826,"title":1827},"\u002Fproject-setup-dependency-management\u002Fsecuring-a-python-cli-supply-chain\u002Fpinning-dependencies-with-hashes","Pinning a Python CLI’s Dependencies with Hashes",{"path":1829,"title":1830},"\u002Fproject-setup-dependency-management\u002Fsecuring-a-python-cli-supply-chain\u002Fpublishing-attestations-and-verifying-releases","Publishing Attestations and Verifying Python CLI Releases",{"path":1832,"title":1833},"\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":1835,"title":1836},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management","uv for Python CLI Dependency Management",{"path":1838,"title":1839},"\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":1841,"title":1842},"\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":1844,"title":1845},"\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":1847,"title":1848},"\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":1850,"title":1851},"\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":1853,"title":1854},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002Fuv-workspaces-for-multi-package-clis","uv Workspaces for Multi-Package Python CLIs",{"path":1856,"title":1857},"\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":1859,"title":1860},"\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":1862,"title":1863},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices","Python CLI Env Isolation Best Practices",{"path":1865,"title":1866},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fisolating-cli-tools-from-project-dependencies","Isolating CLI Tools from Your Project’s Dependencies",{"path":1868,"title":1869},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fmanaging-virtual-environments-for-cross-platform-clis","Managing Python CLI Virtual Environments",{"path":1871,"title":1872},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fpinning-the-python-version-for-a-cli","Pinning the Python Version for a CLI",{"path":1874,"title":1875},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fsupporting-multiple-python-versions-with-nox","Supporting Multiple Python Versions with nox",1790967542987]