[{"data":1,"prerenderedAt":2540},["ShallowReactive",2],{"page-\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects\u002Fkeeping-hook-versions-current-with-autoupdate\u002F":3,"content-directory":1695},{"id":4,"title":5,"body":6,"date":1680,"description":1681,"difficulty":1682,"draft":1683,"extension":1684,"meta":1685,"navigation":376,"path":1686,"seo":1687,"stem":1688,"tags":1689,"updated":1680,"__hash__":1694},"content\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects\u002Fkeeping-hook-versions-current-with-autoupdate\u002Findex.md","Keeping pre-commit Hook Versions Current with autoupdate",{"type":7,"value":8,"toc":1658},"minimark",[9,40,45,62,66,75,79,147,153,157,162,173,242,253,257,279,314,322,325,329,340,962,990,994,997,1212,1221,1225,1259,1262,1266,1269,1529,1539,1543,1549,1553,1561,1574,1578,1581,1585,1595,1599,1609,1613,1619,1623,1654],[10,11,12,13,17,18,21,22,25,26,29,30,33,34,39],"p",{},"Every hook in ",[14,15,16],"code",{},".pre-commit-config.yaml"," is pinned to a ",[14,19,20],{},"rev",", and that pin is what makes hooks reproducible: everyone runs the same Ruff, the same formatter, the same checks. It is also a dependency that ages. Left alone for a year, the pins drift far behind the versions your editor and CI use; new lint rules, bug fixes and Python-version support never arrive; and the eventual catch-up update changes so much at once that nobody wants to review it. ",[14,23,24],{},"pre-commit autoupdate"," moves pins forward, but running it well involves a few decisions — tags or commit hashes, everything at once or one repository at a time, by hand or by bot — and one trap that catches most projects: the same tool pinned at different versions in ",[14,27,28],{},"pre-commit"," and in ",[14,31,32],{},"pyproject.toml",". This guide covers all of it for a Python CLI repository. It belongs to the ",[35,36,38],"a",{"href":37},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects\u002F","pre-commit topic",".",[41,42,44],"h2",{"id":43},"prerequisites","Prerequisites",[46,47,48,59],"ul",{},[49,50,51,52,54,55,39],"li",{},"A ",[14,53,16],{}," with remote hook repositories, as in ",[35,56,58],{"href":57},"\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",[49,60,61],{},"pre-commit 3.x or 4.x; uv for the version-sync check.",[41,63,65],{"id":64},"what-autoupdate-does","What autoupdate does",[10,67,68,69,71,72,74],{},"For each remote repository in the configuration, ",[14,70,24],{}," fetches its tags, picks the newest, and rewrites the ",[14,73,20],{},". It does not change hook ids, arguments or any other settings — which is exactly why an update occasionally needs a human.",[76,77],"inline-diagram",{"name":78},"pcau-what",[80,81,86],"pre",{"className":82,"code":83,"language":84,"meta":85,"style":85},"language-bash shiki shiki-themes github-light github-dark","pre-commit autoupdate                                   # every repository\npre-commit autoupdate --repo https:\u002F\u002Fgithub.com\u002Fastral-sh\u002Fruff-pre-commit   # one repository\npre-commit autoupdate --freeze                          # pin to commit SHAs\npre-commit autoupdate --bleeding-edge                   # newest commit on the default branch\n","bash","",[14,87,88,104,121,134],{"__ignoreMap":85},[89,90,93,96,100],"span",{"class":91,"line":92},"line",1,[89,94,28],{"class":95},"sScJk",[89,97,99],{"class":98},"sZZnC"," autoupdate",[89,101,103],{"class":102},"sJ8bj","                                   # every repository\n",[89,105,107,109,111,115,118],{"class":91,"line":106},2,[89,108,28],{"class":95},[89,110,99],{"class":98},[89,112,114],{"class":113},"sj4cs"," --repo",[89,116,117],{"class":98}," https:\u002F\u002Fgithub.com\u002Fastral-sh\u002Fruff-pre-commit",[89,119,120],{"class":102},"   # one repository\n",[89,122,124,126,128,131],{"class":91,"line":123},3,[89,125,28],{"class":95},[89,127,99],{"class":98},[89,129,130],{"class":113}," --freeze",[89,132,133],{"class":102},"                          # pin to commit SHAs\n",[89,135,137,139,141,144],{"class":91,"line":136},4,[89,138,28],{"class":95},[89,140,99],{"class":98},[89,142,143],{"class":113}," --bleeding-edge",[89,145,146],{"class":102},"                   # newest commit on the default branch\n",[10,148,149,152],{},[14,150,151],{},"--bleeding-edge"," tracks unreleased code and has no place in a project others depend on. The other three are everyday tools.",[41,154,156],{"id":155},"the-recipe","The recipe",[158,159,161],"h3",{"id":160},"choose-tags-or-frozen-hashes","Choose tags or frozen hashes",[10,163,164,165,168,169,172],{},"A tag such as ",[14,166,167],{},"v0.16.10"," is readable but mutable: whoever controls the hook repository could move it. ",[14,170,171],{},"--freeze"," pins the exact commit instead and keeps the tag as a comment:",[80,174,178],{"className":175,"code":176,"language":177,"meta":85,"style":85},"language-yaml shiki shiki-themes github-light github-dark","  - repo: https:\u002F\u002Fgithub.com\u002Fastral-sh\u002Fruff-pre-commit\n    rev: f12be1ebaa5351c1fc76472de98db2c3446c8253  # frozen: v0.16.10\n    hooks:\n      - id: ruff-check\n      - id: ruff-format\n","yaml",[14,179,180,196,209,217,230],{"__ignoreMap":85},[89,181,182,186,190,193],{"class":91,"line":92},[89,183,185],{"class":184},"sVt8B","  - ",[89,187,189],{"class":188},"s9eBZ","repo",[89,191,192],{"class":184},": ",[89,194,195],{"class":98},"https:\u002F\u002Fgithub.com\u002Fastral-sh\u002Fruff-pre-commit\n",[89,197,198,201,203,206],{"class":91,"line":106},[89,199,200],{"class":188},"    rev",[89,202,192],{"class":184},[89,204,205],{"class":98},"f12be1ebaa5351c1fc76472de98db2c3446c8253",[89,207,208],{"class":102},"  # frozen: v0.16.10\n",[89,210,211,214],{"class":91,"line":123},[89,212,213],{"class":188},"    hooks",[89,215,216],{"class":184},":\n",[89,218,219,222,225,227],{"class":91,"line":136},[89,220,221],{"class":184},"      - ",[89,223,224],{"class":188},"id",[89,226,192],{"class":184},[89,228,229],{"class":98},"ruff-check\n",[89,231,233,235,237,239],{"class":91,"line":232},5,[89,234,221],{"class":184},[89,236,224],{"class":188},[89,238,192],{"class":184},[89,240,241],{"class":98},"ruff-format\n",[10,243,244,245,249,250,252],{},"Frozen pins are the same idea as pinning GitHub Actions to commit SHAs, discussed in ",[35,246,248],{"href":247},"\u002Fproject-setup-dependency-management\u002Fsecuring-a-python-cli-supply-chain\u002F","securing the supply chain of a Python CLI",": hooks run code on every developer's machine, so an immutable reference is the safer default for anything outside your own organisation. Use ",[14,251,171],{}," consistently, so the file has one convention.",[158,254,256],{"id":255},"review-renamed-and-removed-hooks","Review renamed and removed hooks",[10,258,259,260,262,263,266,267,270,271,274,275,278],{},"Because autoupdate only changes ",[14,261,20],{},", a hook repository that renames a hook leaves your configuration pointing at the old id. Sometimes the old id survives as an alias — after updating ",[14,264,265],{},"ruff-pre-commit"," across a major rename, the output shows ",[14,268,269],{},"ruff (legacy alias)"," for a configuration still using ",[14,272,273],{},"id: ruff",", where current releases call it ",[14,276,277],{},"ruff-check",". Sometimes it disappears and the next run fails with \"hook id not present in repository\". After every update, run the hooks once and read the names:",[80,280,282],{"className":82,"code":281,"language":84,"meta":85,"style":85},"pre-commit autoupdate --freeze\npre-commit run --all-files\ngit diff .pre-commit-config.yaml\n",[14,283,284,293,303],{"__ignoreMap":85},[89,285,286,288,290],{"class":91,"line":92},[89,287,28],{"class":95},[89,289,99],{"class":98},[89,291,292],{"class":113}," --freeze\n",[89,294,295,297,300],{"class":91,"line":106},[89,296,28],{"class":95},[89,298,299],{"class":98}," run",[89,301,302],{"class":113}," --all-files\n",[89,304,305,308,311],{"class":91,"line":123},[89,306,307],{"class":95},"git",[89,309,310],{"class":98}," diff",[89,312,313],{"class":98}," .pre-commit-config.yaml\n",[10,315,316,317,321],{},"New releases of linters also add rules or change formatting, so the first run after an update may fix or flag code. Commit those changes ",[318,319,320],"em",{},"with"," the update, in the same pull request, so the history explains why fifty files were reformatted.",[76,323],{"name":324},"pcau-terminal",[158,326,328],{"id":327},"keep-hook-and-project-versions-in-sync","Keep hook and project versions in sync",[10,330,331,332,335,336,339],{},"The same tool often appears twice: Ruff as a pre-commit hook ",[318,333,334],{},"and"," in the project's lint dependency group, used by editors and CI. If the hook says 0.16.10 and ",[14,337,338],{},"uv.lock"," says 0.15.2, formatting flips back and forth between the two, and CI disagrees with the commit hook. A small check makes the drift visible:",[80,341,345],{"className":342,"code":343,"language":344,"meta":85,"style":85},"language-python shiki shiki-themes github-light github-dark","# scripts\u002Fcheck_tool_versions.py\n\"\"\"Fail if a tool's pre-commit rev differs from its version in uv.lock.\"\"\"\nfrom __future__ import annotations\n\nimport re\nimport sys\nimport tomllib\nfrom pathlib import Path\n\nHOOK_REPOS = {\n    \"https:\u002F\u002Fgithub.com\u002Fastral-sh\u002Fruff-pre-commit\": \"ruff\",\n    \"https:\u002F\u002Fgithub.com\u002Fpre-commit\u002Fmirrors-mypy\": \"mypy\",\n}\n\n\ndef hook_versions(config_text: str) -> dict[str, str]:\n    found = {}\n    for repo, package in HOOK_REPOS.items():\n        m = re.search(rf\"repo:\\s*{re.escape(repo)}\\s*\\n\\s*rev:\\s*(\\S+)(?:\\s*#\\s*frozen:\\s*(\\S+))?\",\n                      config_text)\n        if m:\n            tag = m.group(2) or m.group(1)       # frozen pins keep the tag in a comment\n            found[package] = tag.lstrip(\"v\")\n    return found\n\n\ndef locked_versions(lock_text: str) -> dict[str, str]:\n    lock = tomllib.loads(lock_text)\n    return {p[\"name\"]: p[\"version\"] for p in lock.get(\"package\", [])}\n\n\ndef main() -> int:\n    hooks = hook_versions(Path(\".pre-commit-config.yaml\").read_text())\n    locked = locked_versions(Path(\"uv.lock\").read_text())\n    problems = [f\"{name}: pre-commit {hv} vs uv.lock {locked[name]}\"\n                for name, hv in hooks.items() if name in locked and locked[name] != hv]\n    for p in problems:\n        print(f\"version mismatch {p}\", file=sys.stderr)\n    return 1 if problems else 0\n\n\nif __name__ == \"__main__\":\n    raise SystemExit(main())\n","python",[14,346,347,352,357,372,378,386,394,402,415,420,432,446,459,465,470,475,503,515,533,564,570,579,610,627,636,641,646,669,680,717,722,727,743,760,776,823,859,871,904,924,929,934,950],{"__ignoreMap":85},[89,348,349],{"class":91,"line":92},[89,350,351],{"class":102},"# scripts\u002Fcheck_tool_versions.py\n",[89,353,354],{"class":91,"line":106},[89,355,356],{"class":98},"\"\"\"Fail if a tool's pre-commit rev differs from its version in uv.lock.\"\"\"\n",[89,358,359,363,366,369],{"class":91,"line":123},[89,360,362],{"class":361},"szBVR","from",[89,364,365],{"class":113}," __future__",[89,367,368],{"class":361}," import",[89,370,371],{"class":184}," annotations\n",[89,373,374],{"class":91,"line":136},[89,375,377],{"emptyLinePlaceholder":376},true,"\n",[89,379,380,383],{"class":91,"line":232},[89,381,382],{"class":361},"import",[89,384,385],{"class":184}," re\n",[89,387,389,391],{"class":91,"line":388},6,[89,390,382],{"class":361},[89,392,393],{"class":184}," sys\n",[89,395,397,399],{"class":91,"line":396},7,[89,398,382],{"class":361},[89,400,401],{"class":184}," tomllib\n",[89,403,405,407,410,412],{"class":91,"line":404},8,[89,406,362],{"class":361},[89,408,409],{"class":184}," pathlib ",[89,411,382],{"class":361},[89,413,414],{"class":184}," Path\n",[89,416,418],{"class":91,"line":417},9,[89,419,377],{"emptyLinePlaceholder":376},[89,421,423,426,429],{"class":91,"line":422},10,[89,424,425],{"class":113},"HOOK_REPOS",[89,427,428],{"class":361}," =",[89,430,431],{"class":184}," {\n",[89,433,435,438,440,443],{"class":91,"line":434},11,[89,436,437],{"class":98},"    \"https:\u002F\u002Fgithub.com\u002Fastral-sh\u002Fruff-pre-commit\"",[89,439,192],{"class":184},[89,441,442],{"class":98},"\"ruff\"",[89,444,445],{"class":184},",\n",[89,447,449,452,454,457],{"class":91,"line":448},12,[89,450,451],{"class":98},"    \"https:\u002F\u002Fgithub.com\u002Fpre-commit\u002Fmirrors-mypy\"",[89,453,192],{"class":184},[89,455,456],{"class":98},"\"mypy\"",[89,458,445],{"class":184},[89,460,462],{"class":91,"line":461},13,[89,463,464],{"class":184},"}\n",[89,466,468],{"class":91,"line":467},14,[89,469,377],{"emptyLinePlaceholder":376},[89,471,473],{"class":91,"line":472},15,[89,474,377],{"emptyLinePlaceholder":376},[89,476,478,481,484,487,490,493,495,498,500],{"class":91,"line":477},16,[89,479,480],{"class":361},"def",[89,482,483],{"class":95}," hook_versions",[89,485,486],{"class":184},"(config_text: ",[89,488,489],{"class":113},"str",[89,491,492],{"class":184},") -> dict[",[89,494,489],{"class":113},[89,496,497],{"class":184},", ",[89,499,489],{"class":113},[89,501,502],{"class":184},"]:\n",[89,504,506,509,512],{"class":91,"line":505},17,[89,507,508],{"class":184},"    found ",[89,510,511],{"class":361},"=",[89,513,514],{"class":184}," {}\n",[89,516,518,521,524,527,530],{"class":91,"line":517},18,[89,519,520],{"class":361},"    for",[89,522,523],{"class":184}," repo, package ",[89,525,526],{"class":361},"in",[89,528,529],{"class":113}," HOOK_REPOS",[89,531,532],{"class":184},".items():\n",[89,534,536,539,541,544,547,550,553,556,559,562],{"class":91,"line":535},19,[89,537,538],{"class":184},"        m ",[89,540,511],{"class":361},[89,542,543],{"class":184}," re.search(",[89,545,546],{"class":361},"rf",[89,548,549],{"class":98},"\"repo:\\s*",[89,551,552],{"class":113},"{",[89,554,555],{"class":184},"re.escape(repo)",[89,557,558],{"class":113},"}",[89,560,561],{"class":98},"\\s*\\n\\s*rev:\\s*(\\S+)(?:\\s*#\\s*frozen:\\s*(\\S+))?\"",[89,563,445],{"class":184},[89,565,567],{"class":91,"line":566},20,[89,568,569],{"class":184},"                      config_text)\n",[89,571,573,576],{"class":91,"line":572},21,[89,574,575],{"class":361},"        if",[89,577,578],{"class":184}," m:\n",[89,580,582,585,587,590,593,596,599,601,604,607],{"class":91,"line":581},22,[89,583,584],{"class":184},"            tag ",[89,586,511],{"class":361},[89,588,589],{"class":184}," m.group(",[89,591,592],{"class":113},"2",[89,594,595],{"class":184},") ",[89,597,598],{"class":361},"or",[89,600,589],{"class":184},[89,602,603],{"class":113},"1",[89,605,606],{"class":184},")       ",[89,608,609],{"class":102},"# frozen pins keep the tag in a comment\n",[89,611,613,616,618,621,624],{"class":91,"line":612},23,[89,614,615],{"class":184},"            found[package] ",[89,617,511],{"class":361},[89,619,620],{"class":184}," tag.lstrip(",[89,622,623],{"class":98},"\"v\"",[89,625,626],{"class":184},")\n",[89,628,630,633],{"class":91,"line":629},24,[89,631,632],{"class":361},"    return",[89,634,635],{"class":184}," found\n",[89,637,639],{"class":91,"line":638},25,[89,640,377],{"emptyLinePlaceholder":376},[89,642,644],{"class":91,"line":643},26,[89,645,377],{"emptyLinePlaceholder":376},[89,647,649,651,654,657,659,661,663,665,667],{"class":91,"line":648},27,[89,650,480],{"class":361},[89,652,653],{"class":95}," locked_versions",[89,655,656],{"class":184},"(lock_text: ",[89,658,489],{"class":113},[89,660,492],{"class":184},[89,662,489],{"class":113},[89,664,497],{"class":184},[89,666,489],{"class":113},[89,668,502],{"class":184},[89,670,672,675,677],{"class":91,"line":671},28,[89,673,674],{"class":184},"    lock ",[89,676,511],{"class":361},[89,678,679],{"class":184}," tomllib.loads(lock_text)\n",[89,681,683,685,688,691,694,697,700,703,706,708,711,714],{"class":91,"line":682},29,[89,684,632],{"class":361},[89,686,687],{"class":184}," {p[",[89,689,690],{"class":98},"\"name\"",[89,692,693],{"class":184},"]: p[",[89,695,696],{"class":98},"\"version\"",[89,698,699],{"class":184},"] ",[89,701,702],{"class":361},"for",[89,704,705],{"class":184}," p ",[89,707,526],{"class":361},[89,709,710],{"class":184}," lock.get(",[89,712,713],{"class":98},"\"package\"",[89,715,716],{"class":184},", [])}\n",[89,718,720],{"class":91,"line":719},30,[89,721,377],{"emptyLinePlaceholder":376},[89,723,725],{"class":91,"line":724},31,[89,726,377],{"emptyLinePlaceholder":376},[89,728,730,732,735,738,741],{"class":91,"line":729},32,[89,731,480],{"class":361},[89,733,734],{"class":95}," main",[89,736,737],{"class":184},"() -> ",[89,739,740],{"class":113},"int",[89,742,216],{"class":184},[89,744,746,749,751,754,757],{"class":91,"line":745},33,[89,747,748],{"class":184},"    hooks ",[89,750,511],{"class":361},[89,752,753],{"class":184}," hook_versions(Path(",[89,755,756],{"class":98},"\".pre-commit-config.yaml\"",[89,758,759],{"class":184},").read_text())\n",[89,761,763,766,768,771,774],{"class":91,"line":762},34,[89,764,765],{"class":184},"    locked ",[89,767,511],{"class":361},[89,769,770],{"class":184}," locked_versions(Path(",[89,772,773],{"class":98},"\"uv.lock\"",[89,775,759],{"class":184},[89,777,779,782,784,787,790,793,795,798,800,803,805,808,810,813,815,818,820],{"class":91,"line":778},35,[89,780,781],{"class":184},"    problems ",[89,783,511],{"class":361},[89,785,786],{"class":184}," [",[89,788,789],{"class":361},"f",[89,791,792],{"class":98},"\"",[89,794,552],{"class":113},[89,796,797],{"class":184},"name",[89,799,558],{"class":113},[89,801,802],{"class":98},": pre-commit ",[89,804,552],{"class":113},[89,806,807],{"class":184},"hv",[89,809,558],{"class":113},[89,811,812],{"class":98}," vs uv.lock ",[89,814,552],{"class":113},[89,816,817],{"class":184},"locked[name]",[89,819,558],{"class":113},[89,821,822],{"class":98},"\"\n",[89,824,826,829,832,834,837,840,843,845,848,850,853,856],{"class":91,"line":825},36,[89,827,828],{"class":361},"                for",[89,830,831],{"class":184}," name, hv ",[89,833,526],{"class":361},[89,835,836],{"class":184}," hooks.items() ",[89,838,839],{"class":361},"if",[89,841,842],{"class":184}," name ",[89,844,526],{"class":361},[89,846,847],{"class":184}," locked ",[89,849,334],{"class":361},[89,851,852],{"class":184}," locked[name] ",[89,854,855],{"class":361},"!=",[89,857,858],{"class":184}," hv]\n",[89,860,862,864,866,868],{"class":91,"line":861},37,[89,863,520],{"class":361},[89,865,705],{"class":184},[89,867,526],{"class":361},[89,869,870],{"class":184}," problems:\n",[89,872,874,877,880,882,885,887,889,891,893,895,899,901],{"class":91,"line":873},38,[89,875,876],{"class":113},"        print",[89,878,879],{"class":184},"(",[89,881,789],{"class":361},[89,883,884],{"class":98},"\"version mismatch ",[89,886,552],{"class":113},[89,888,10],{"class":184},[89,890,558],{"class":113},[89,892,792],{"class":98},[89,894,497],{"class":184},[89,896,898],{"class":897},"s4XuR","file",[89,900,511],{"class":361},[89,902,903],{"class":184},"sys.stderr)\n",[89,905,907,909,912,915,918,921],{"class":91,"line":906},39,[89,908,632],{"class":361},[89,910,911],{"class":113}," 1",[89,913,914],{"class":361}," if",[89,916,917],{"class":184}," problems ",[89,919,920],{"class":361},"else",[89,922,923],{"class":113}," 0\n",[89,925,927],{"class":91,"line":926},40,[89,928,377],{"emptyLinePlaceholder":376},[89,930,932],{"class":91,"line":931},41,[89,933,377],{"emptyLinePlaceholder":376},[89,935,937,939,942,945,948],{"class":91,"line":936},42,[89,938,839],{"class":361},[89,940,941],{"class":113}," __name__",[89,943,944],{"class":361}," ==",[89,946,947],{"class":98}," \"__main__\"",[89,949,216],{"class":184},[89,951,953,956,959],{"class":91,"line":952},43,[89,954,955],{"class":361},"    raise",[89,957,958],{"class":113}," SystemExit",[89,960,961],{"class":184},"(main())\n",[10,963,964,965,968,969,972,973,976,977,980,981,984,985,989],{},"Run it in CI and as a local hook. When it fails, update whichever side is behind — ",[14,966,967],{},"uv lock --upgrade-package ruff"," or ",[14,970,971],{},"pre-commit autoupdate --repo …"," — in the same change. An alternative that removes the duplication entirely is a ",[14,974,975],{},"local"," hook with ",[14,978,979],{},"language: system"," and ",[14,982,983],{},"entry: uv run ruff check",", so the hook always uses the locked version; ",[35,986,988],{"href":987},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects\u002Fspeeding-up-slow-pre-commit-hooks\u002F","speeding up slow pre-commit hooks"," uses that pattern for mypy. The trade-off is that the hook then needs the project environment to exist.",[158,991,993],{"id":992},"automate-the-update","Automate the update",[10,995,996],{},"A weekly workflow that runs autoupdate and opens a pull request turns maintenance into a five-minute review:",[80,998,1000],{"className":175,"code":999,"language":177,"meta":85,"style":85},"# .github\u002Fworkflows\u002Fpre-commit-autoupdate.yml\nname: pre-commit autoupdate\non:\n  schedule:\n    - cron: \"30 5 * * 1\"\n  workflow_dispatch:\n\npermissions:\n  contents: write\n  pull-requests: write\n\njobs:\n  autoupdate:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\u002Fcheckout@v4\n      - run: pipx install pre-commit==4.6.2\n      - run: pre-commit autoupdate --freeze\n      - run: pre-commit run --all-files || true          # apply formatter changes to the PR\n      - uses: peter-evans\u002Fcreate-pull-request@v7\n        with:\n          branch: pre-commit-autoupdate\n          title: \"Update pre-commit hooks\"\n          body: \"Automated `pre-commit autoupdate --freeze`. CI runs the hooks on this branch.\"\n",[14,1001,1002,1007,1016,1023,1030,1043,1050,1054,1061,1071,1080,1084,1091,1098,1108,1115,1127,1139,1150,1164,1175,1182,1192,1202],{"__ignoreMap":85},[89,1003,1004],{"class":91,"line":92},[89,1005,1006],{"class":102},"# .github\u002Fworkflows\u002Fpre-commit-autoupdate.yml\n",[89,1008,1009,1011,1013],{"class":91,"line":106},[89,1010,797],{"class":188},[89,1012,192],{"class":184},[89,1014,1015],{"class":98},"pre-commit autoupdate\n",[89,1017,1018,1021],{"class":91,"line":123},[89,1019,1020],{"class":113},"on",[89,1022,216],{"class":184},[89,1024,1025,1028],{"class":91,"line":136},[89,1026,1027],{"class":188},"  schedule",[89,1029,216],{"class":184},[89,1031,1032,1035,1038,1040],{"class":91,"line":232},[89,1033,1034],{"class":184},"    - ",[89,1036,1037],{"class":188},"cron",[89,1039,192],{"class":184},[89,1041,1042],{"class":98},"\"30 5 * * 1\"\n",[89,1044,1045,1048],{"class":91,"line":388},[89,1046,1047],{"class":188},"  workflow_dispatch",[89,1049,216],{"class":184},[89,1051,1052],{"class":91,"line":396},[89,1053,377],{"emptyLinePlaceholder":376},[89,1055,1056,1059],{"class":91,"line":404},[89,1057,1058],{"class":188},"permissions",[89,1060,216],{"class":184},[89,1062,1063,1066,1068],{"class":91,"line":417},[89,1064,1065],{"class":188},"  contents",[89,1067,192],{"class":184},[89,1069,1070],{"class":98},"write\n",[89,1072,1073,1076,1078],{"class":91,"line":422},[89,1074,1075],{"class":188},"  pull-requests",[89,1077,192],{"class":184},[89,1079,1070],{"class":98},[89,1081,1082],{"class":91,"line":434},[89,1083,377],{"emptyLinePlaceholder":376},[89,1085,1086,1089],{"class":91,"line":448},[89,1087,1088],{"class":188},"jobs",[89,1090,216],{"class":184},[89,1092,1093,1096],{"class":91,"line":461},[89,1094,1095],{"class":188},"  autoupdate",[89,1097,216],{"class":184},[89,1099,1100,1103,1105],{"class":91,"line":467},[89,1101,1102],{"class":188},"    runs-on",[89,1104,192],{"class":184},[89,1106,1107],{"class":98},"ubuntu-latest\n",[89,1109,1110,1113],{"class":91,"line":472},[89,1111,1112],{"class":188},"    steps",[89,1114,216],{"class":184},[89,1116,1117,1119,1122,1124],{"class":91,"line":477},[89,1118,221],{"class":184},[89,1120,1121],{"class":188},"uses",[89,1123,192],{"class":184},[89,1125,1126],{"class":98},"actions\u002Fcheckout@v4\n",[89,1128,1129,1131,1134,1136],{"class":91,"line":505},[89,1130,221],{"class":184},[89,1132,1133],{"class":188},"run",[89,1135,192],{"class":184},[89,1137,1138],{"class":98},"pipx install pre-commit==4.6.2\n",[89,1140,1141,1143,1145,1147],{"class":91,"line":517},[89,1142,221],{"class":184},[89,1144,1133],{"class":188},[89,1146,192],{"class":184},[89,1148,1149],{"class":98},"pre-commit autoupdate --freeze\n",[89,1151,1152,1154,1156,1158,1161],{"class":91,"line":535},[89,1153,221],{"class":184},[89,1155,1133],{"class":188},[89,1157,192],{"class":184},[89,1159,1160],{"class":98},"pre-commit run --all-files || true",[89,1162,1163],{"class":102},"          # apply formatter changes to the PR\n",[89,1165,1166,1168,1170,1172],{"class":91,"line":566},[89,1167,221],{"class":184},[89,1169,1121],{"class":188},[89,1171,192],{"class":184},[89,1173,1174],{"class":98},"peter-evans\u002Fcreate-pull-request@v7\n",[89,1176,1177,1180],{"class":91,"line":572},[89,1178,1179],{"class":188},"        with",[89,1181,216],{"class":184},[89,1183,1184,1187,1189],{"class":91,"line":581},[89,1185,1186],{"class":188},"          branch",[89,1188,192],{"class":184},[89,1190,1191],{"class":98},"pre-commit-autoupdate\n",[89,1193,1194,1197,1199],{"class":91,"line":612},[89,1195,1196],{"class":188},"          title",[89,1198,192],{"class":184},[89,1200,1201],{"class":98},"\"Update pre-commit hooks\"\n",[89,1203,1204,1207,1209],{"class":91,"line":629},[89,1205,1206],{"class":188},"          body",[89,1208,192],{"class":184},[89,1210,1211],{"class":98},"\"Automated `pre-commit autoupdate --freeze`. CI runs the hooks on this branch.\"\n",[10,1213,1214,1220],{},[35,1215,1219],{"href":1216,"rel":1217},"https:\u002F\u002Fpre-commit.ci",[1218],"nofollow","pre-commit.ci"," offers the same weekly pull requests without a workflow, and Renovate has a pre-commit manager that can group hook updates with other dependency updates. Whichever you choose, let the normal CI — including the version-sync check — run on the update branch.",[41,1222,1224],{"id":1223},"ux-considerations","UX considerations",[46,1226,1227,1234,1247,1253],{},[49,1228,1229,1233],{},[1230,1231,1232],"strong",{},"Small, regular updates."," Weekly or monthly, one pull request at a time. A year's worth of updates in one change is unreviewable.",[49,1235,1236,1239,1240,1243,1244,39],{},[1230,1237,1238],{},"Update reformatting separately from features."," Keep the \"hooks changed the code\" commit out of feature branches, so blame and review stay readable; ",[14,1241,1242],{},".git-blame-ignore-revs"," can hide large reformat commits from ",[14,1245,1246],{},"git blame",[49,1248,1249,1252],{},[1230,1250,1251],{},"Pin pre-commit itself"," in CI and document the version for contributors, so the tool reading the configuration is as reproducible as the hooks.",[49,1254,1255,1258],{},[1230,1256,1257],{},"Read the changelogs of major bumps."," Linters announce rule removals and renames there; autoupdate will not.",[76,1260],{"name":1261},"pcau-cadence",[41,1263,1265],{"id":1264},"testing-the-behaviour","Testing the behaviour",[10,1267,1268],{},"The sync check is pure parsing, so it is easy to test with inline fixtures:",[80,1270,1272],{"className":342,"code":1271,"language":344,"meta":85,"style":85},"# tests\u002Ftest_check_tool_versions.py\nfrom scripts.check_tool_versions import hook_versions, locked_versions\n\nCONFIG = \"\"\"\nrepos:\n  - repo: https:\u002F\u002Fgithub.com\u002Fastral-sh\u002Fruff-pre-commit\n    rev: f12be1ebaa5351c1fc76472de98db2c3446c8253  # frozen: v0.16.10\n    hooks:\n      - id: ruff-check\n  - repo: https:\u002F\u002Fgithub.com\u002Fpre-commit\u002Fmirrors-mypy\n    rev: v1.11.2\n    hooks:\n      - id: mypy\n\"\"\"\n\nLOCK = \"\"\"\nversion = 1\n[[package]]\nname = \"ruff\"\nversion = \"0.16.10\"\n[[package]]\nname = \"mypy\"\nversion = \"1.10.0\"\n\"\"\"\n\n\ndef test_frozen_pins_use_the_tag_comment():\n    assert hook_versions(CONFIG) == {\"ruff\": \"0.16.10\", \"mypy\": \"1.11.2\"}\n\n\ndef test_mismatch_is_detected():\n    hooks, locked = hook_versions(CONFIG), locked_versions(LOCK)\n    assert [n for n, v in hooks.items() if locked.get(n) != v] == [\"mypy\"]\n",[14,1273,1274,1279,1291,1295,1305,1310,1315,1320,1325,1330,1335,1340,1344,1349,1354,1358,1367,1372,1377,1382,1387,1391,1396,1401,1405,1409,1413,1423,1459,1463,1467,1476,1494],{"__ignoreMap":85},[89,1275,1276],{"class":91,"line":92},[89,1277,1278],{"class":102},"# tests\u002Ftest_check_tool_versions.py\n",[89,1280,1281,1283,1286,1288],{"class":91,"line":106},[89,1282,362],{"class":361},[89,1284,1285],{"class":184}," scripts.check_tool_versions ",[89,1287,382],{"class":361},[89,1289,1290],{"class":184}," hook_versions, locked_versions\n",[89,1292,1293],{"class":91,"line":123},[89,1294,377],{"emptyLinePlaceholder":376},[89,1296,1297,1300,1302],{"class":91,"line":136},[89,1298,1299],{"class":113},"CONFIG",[89,1301,428],{"class":361},[89,1303,1304],{"class":98}," \"\"\"\n",[89,1306,1307],{"class":91,"line":232},[89,1308,1309],{"class":98},"repos:\n",[89,1311,1312],{"class":91,"line":388},[89,1313,1314],{"class":98},"  - repo: https:\u002F\u002Fgithub.com\u002Fastral-sh\u002Fruff-pre-commit\n",[89,1316,1317],{"class":91,"line":396},[89,1318,1319],{"class":98},"    rev: f12be1ebaa5351c1fc76472de98db2c3446c8253  # frozen: v0.16.10\n",[89,1321,1322],{"class":91,"line":404},[89,1323,1324],{"class":98},"    hooks:\n",[89,1326,1327],{"class":91,"line":417},[89,1328,1329],{"class":98},"      - id: ruff-check\n",[89,1331,1332],{"class":91,"line":422},[89,1333,1334],{"class":98},"  - repo: https:\u002F\u002Fgithub.com\u002Fpre-commit\u002Fmirrors-mypy\n",[89,1336,1337],{"class":91,"line":434},[89,1338,1339],{"class":98},"    rev: v1.11.2\n",[89,1341,1342],{"class":91,"line":448},[89,1343,1324],{"class":98},[89,1345,1346],{"class":91,"line":461},[89,1347,1348],{"class":98},"      - id: mypy\n",[89,1350,1351],{"class":91,"line":467},[89,1352,1353],{"class":98},"\"\"\"\n",[89,1355,1356],{"class":91,"line":472},[89,1357,377],{"emptyLinePlaceholder":376},[89,1359,1360,1363,1365],{"class":91,"line":477},[89,1361,1362],{"class":113},"LOCK",[89,1364,428],{"class":361},[89,1366,1304],{"class":98},[89,1368,1369],{"class":91,"line":505},[89,1370,1371],{"class":98},"version = 1\n",[89,1373,1374],{"class":91,"line":517},[89,1375,1376],{"class":98},"[[package]]\n",[89,1378,1379],{"class":91,"line":535},[89,1380,1381],{"class":98},"name = \"ruff\"\n",[89,1383,1384],{"class":91,"line":566},[89,1385,1386],{"class":98},"version = \"0.16.10\"\n",[89,1388,1389],{"class":91,"line":572},[89,1390,1376],{"class":98},[89,1392,1393],{"class":91,"line":581},[89,1394,1395],{"class":98},"name = \"mypy\"\n",[89,1397,1398],{"class":91,"line":612},[89,1399,1400],{"class":98},"version = \"1.10.0\"\n",[89,1402,1403],{"class":91,"line":629},[89,1404,1353],{"class":98},[89,1406,1407],{"class":91,"line":638},[89,1408,377],{"emptyLinePlaceholder":376},[89,1410,1411],{"class":91,"line":643},[89,1412,377],{"emptyLinePlaceholder":376},[89,1414,1415,1417,1420],{"class":91,"line":648},[89,1416,480],{"class":361},[89,1418,1419],{"class":95}," test_frozen_pins_use_the_tag_comment",[89,1421,1422],{"class":184},"():\n",[89,1424,1425,1428,1431,1433,1435,1438,1441,1443,1445,1448,1450,1452,1454,1457],{"class":91,"line":671},[89,1426,1427],{"class":361},"    assert",[89,1429,1430],{"class":184}," hook_versions(",[89,1432,1299],{"class":113},[89,1434,595],{"class":184},[89,1436,1437],{"class":361},"==",[89,1439,1440],{"class":184}," {",[89,1442,442],{"class":98},[89,1444,192],{"class":184},[89,1446,1447],{"class":98},"\"0.16.10\"",[89,1449,497],{"class":184},[89,1451,456],{"class":98},[89,1453,192],{"class":184},[89,1455,1456],{"class":98},"\"1.11.2\"",[89,1458,464],{"class":184},[89,1460,1461],{"class":91,"line":682},[89,1462,377],{"emptyLinePlaceholder":376},[89,1464,1465],{"class":91,"line":719},[89,1466,377],{"emptyLinePlaceholder":376},[89,1468,1469,1471,1474],{"class":91,"line":724},[89,1470,480],{"class":361},[89,1472,1473],{"class":95}," test_mismatch_is_detected",[89,1475,1422],{"class":184},[89,1477,1478,1481,1483,1485,1487,1490,1492],{"class":91,"line":729},[89,1479,1480],{"class":184},"    hooks, locked ",[89,1482,511],{"class":361},[89,1484,1430],{"class":184},[89,1486,1299],{"class":113},[89,1488,1489],{"class":184},"), locked_versions(",[89,1491,1362],{"class":113},[89,1493,626],{"class":184},[89,1495,1496,1498,1501,1503,1506,1508,1510,1512,1515,1517,1520,1522,1524,1526],{"class":91,"line":745},[89,1497,1427],{"class":361},[89,1499,1500],{"class":184}," [n ",[89,1502,702],{"class":361},[89,1504,1505],{"class":184}," n, v ",[89,1507,526],{"class":361},[89,1509,836],{"class":184},[89,1511,839],{"class":361},[89,1513,1514],{"class":184}," locked.get(n) ",[89,1516,855],{"class":361},[89,1518,1519],{"class":184}," v] ",[89,1521,1437],{"class":361},[89,1523,786],{"class":184},[89,1525,456],{"class":98},[89,1527,1528],{"class":184},"]\n",[10,1530,1531,1532,1534,1535,1538],{},"Then run the whole loop once by hand: downgrade one hook's ",[14,1533,20],{},", run ",[14,1536,1537],{},"pre-commit autoupdate --freeze",", run the hooks, and check that the version-sync script points at the remaining mismatch.",[41,1540,1542],{"id":1541},"conclusion","Conclusion",[10,1544,1545,1546,1548],{},"Hook pins should move forward regularly and deliberately. Use ",[14,1547,1537],{}," for immutable pins with readable comments, update one repository at a time when a change needs attention, run the hooks right after updating to catch renamed ids and new formatting, keep hook and project tool versions in sync with a small check, and let a weekly bot open the pull request. Maintenance then costs minutes a month instead of an afternoon a year.",[41,1550,1552],{"id":1551},"frequently-asked-questions","Frequently asked questions",[158,1554,1556,1557,1560],{"id":1555},"does-autoupdate-update-additional_dependencies","Does autoupdate update ",[14,1558,1559],{},"additional_dependencies","?",[10,1562,1563,1564,1566,1567,1570,1571,1573],{},"No. Versions listed in ",[14,1565,1559],{}," (common with ",[14,1568,1569],{},"mirrors-mypy",") stay where they are. That is one more reason to prefer local hooks that use the project environment, where ",[14,1572,338],{}," controls every version.",[158,1575,1577],{"id":1576},"can-autoupdate-pick-pre-release-tags","Can autoupdate pick pre-release tags?",[10,1579,1580],{},"It picks the newest tag according to the repository's tag list, which is normally the newest release. If a hook repository publishes pre-release tags you do not want, pin that repository by hand and exclude it from automated updates.",[158,1582,1584],{"id":1583},"how-do-i-roll-back-a-bad-hook-update","How do I roll back a bad hook update?",[10,1586,1587,1588,1590,1591,1594],{},"Revert the update commit, or set the old ",[14,1589,20],{}," again. pre-commit keeps old environments in its cache until ",[14,1592,1593],{},"pre-commit gc",", so switching back is instant.",[158,1596,1598],{"id":1597},"should-a-cli-that-ships-its-own-hook-test-against-autoupdate","Should a CLI that ships its own hook test against autoupdate?",[10,1600,1601,1602,1604,1605,39],{},"Yes — users will run ",[14,1603,24],{}," to get your new releases, so tag releases properly and test the hook as described in ",[35,1606,1608],{"href":1607},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects\u002Fshipping-your-cli-as-a-pre-commit-hook\u002F","shipping your CLI as a pre-commit hook",[158,1610,1612],{"id":1611},"what-about-hooks-from-the-same-organisation-as-the-project","What about hooks from the same organisation as the project?",[10,1614,1615,1616,1618],{},"Internal hook repositories — a company's shared license-header or secret-scanning hooks — deserve the same treatment, with one addition: release them with real tags. A hook repository that is only ever referenced by branch name forces every consumer onto ",[14,1617,151],{}," behaviour, so a broken commit to the hook breaks every repository at once. Tagged releases plus a weekly autoupdate give each project a review step before the change reaches it.",[41,1620,1622],{"id":1621},"related","Related",[46,1624,1625,1631,1637,1642,1648],{},[49,1626,1627,1628],{},"Up: ",[35,1629,1630],{"href":37},"Pre-commit hooks for CLI projects",[49,1632,1633],{},[35,1634,1636],{"href":1635},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects\u002Frunning-pre-commit-in-ci\u002F","Running pre-commit in CI",[49,1638,1639],{},[35,1640,1641],{"href":987},"Speeding up slow pre-commit hooks",[49,1643,1644],{},[35,1645,1647],{"href":1646},"\u002Fproject-setup-dependency-management\u002Flinting-and-type-checking-cli-code\u002Fconfiguring-ruff-for-a-cli-project\u002F","Configuring Ruff for a CLI project",[49,1649,1650],{},[35,1651,1653],{"href":1652},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002Flocking-and-syncing-cli-dependencies-with-uv\u002F","Locking and syncing CLI dependencies with uv",[1655,1656,1657],"style",{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .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 .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":85,"searchDepth":106,"depth":106,"links":1659},[1660,1661,1662,1668,1669,1670,1671,1679],{"id":43,"depth":106,"text":44},{"id":64,"depth":106,"text":65},{"id":155,"depth":106,"text":156,"children":1663},[1664,1665,1666,1667],{"id":160,"depth":123,"text":161},{"id":255,"depth":123,"text":256},{"id":327,"depth":123,"text":328},{"id":992,"depth":123,"text":993},{"id":1223,"depth":106,"text":1224},{"id":1264,"depth":106,"text":1265},{"id":1541,"depth":106,"text":1542},{"id":1551,"depth":106,"text":1552,"children":1672},[1673,1675,1676,1677,1678],{"id":1555,"depth":123,"text":1674},"Does autoupdate update additional_dependencies?",{"id":1576,"depth":123,"text":1577},{"id":1583,"depth":123,"text":1584},{"id":1597,"depth":123,"text":1598},{"id":1611,"depth":123,"text":1612},{"id":1621,"depth":106,"text":1622},"2026-10-02","Update pre-commit hooks safely: autoupdate options, frozen SHA pins, renamed hook ids, scheduled update pull requests, and a check that hook and project tool versions match.","intermediate",false,"md",{},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects\u002Fkeeping-hook-versions-current-with-autoupdate",{"title":5,"description":1681},"project-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects\u002Fkeeping-hook-versions-current-with-autoupdate\u002Findex",[28,1690,1691,1692,1693],"dependencies","automation","ruff","maintenance","1nlDL_0o3H5mb5KcKmbeXDDL1NSOuX2pBI0pPnZnbLM",[1696,1699,1702,1705,1708,1711,1714,1717,1720,1723,1726,1729,1732,1735,1738,1741,1744,1747,1750,1753,1756,1759,1762,1765,1768,1771,1774,1777,1780,1783,1786,1789,1792,1795,1798,1801,1804,1807,1810,1813,1816,1819,1822,1825,1828,1831,1834,1837,1840,1843,1846,1849,1852,1855,1858,1861,1864,1867,1870,1873,1876,1879,1882,1885,1888,1891,1894,1897,1900,1903,1906,1909,1912,1915,1918,1921,1924,1927,1930,1933,1936,1939,1942,1945,1948,1951,1954,1957,1960,1963,1966,1969,1972,1975,1978,1981,1984,1987,1990,1993,1996,1999,2002,2005,2008,2011,2014,2017,2020,2023,2026,2029,2032,2035,2038,2041,2044,2047,2050,2053,2056,2059,2062,2065,2068,2071,2074,2077,2080,2083,2086,2089,2092,2095,2098,2101,2104,2107,2110,2113,2116,2119,2122,2125,2128,2131,2134,2137,2140,2143,2146,2149,2152,2155,2158,2161,2164,2167,2170,2173,2176,2179,2182,2185,2188,2191,2194,2197,2200,2203,2206,2209,2212,2215,2218,2221,2224,2227,2230,2233,2236,2239,2242,2245,2248,2251,2254,2257,2260,2263,2266,2269,2272,2275,2278,2281,2284,2287,2290,2293,2296,2299,2302,2305,2308,2311,2314,2317,2320,2323,2326,2329,2332,2335,2338,2341,2344,2347,2350,2353,2356,2359,2362,2365,2368,2371,2374,2377,2380,2383,2386,2389,2392,2395,2398,2401,2404,2407,2410,2413,2416,2419,2422,2425,2428,2431,2434,2437,2440,2443,2446,2449,2452,2455,2458,2461,2464,2465,2468,2471,2474,2477,2480,2483,2486,2489,2492,2495,2498,2501,2504,2507,2510,2513,2516,2519,2522,2525,2528,2531,2534,2537],{"path":1697,"title":1698},"\u002Fabout","About Python CLI Toolcraft",{"path":1700,"title":1701},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies","Advanced Argument Validation Strategies",{"path":1703,"title":1704},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fparsing-nested-json-arguments-in-python-clis","Parsing Nested JSON Args in Python CLIs",{"path":1706,"title":1707},"\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":1709,"title":1710},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fvalidating-dependent-and-conflicting-options","Validating Dependent and Conflicting CLI Options",{"path":1712,"title":1713},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fvalidating-file-and-directory-paths-in-clis","Validating File and Directory Paths in CLIs",{"path":1715,"title":1716},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fvalidating-urls-hosts-and-ports","Validating URLs, Hosts and Ports in Python CLI Arguments",{"path":1718,"title":1719},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fwriting-custom-click-parameter-types","Writing Custom Click Parameter Types",{"path":1721,"title":1722},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual\u002Fbrowsing-records-with-a-textual-datatable","Browsing Records with a Textual DataTable Picker",{"path":1724,"title":1725},"\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":1727,"title":1728},"\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":1730,"title":1731},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual","Building Terminal UIs with Textual for Python CLIs",{"path":1733,"title":1734},"\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":1736,"title":1737},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual\u002Fstyling-textual-apps-with-tcss","Styling Textual Apps with TCSS",{"path":1739,"title":1740},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual\u002Ftesting-textual-apps-with-pilot","Testing Textual Apps with Pilot",{"path":1742,"title":1743},"\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":1745,"title":1746},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fdocumenting-environment-variables-in-help","Documenting Environment Variables in CLI Help",{"path":1748,"title":1749},"\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":1751,"title":1752},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation","CLI Help Output and Documentation",{"path":1754,"title":1755},"\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":1757,"title":1758},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fversioning-and-deprecating-cli-flags","Versioning and Deprecating CLI Flags",{"path":1760,"title":1761},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fwriting-help-text-users-actually-read","Writing Help Text Users Actually Read",{"path":1763,"title":1764},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Fadapting-output-to-terminal-width","Adapting Python CLI Output to Terminal Width",{"path":1766,"title":1767},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Fdetecting-ci-environments-and-non-interactive-shells","Detecting CI Environments and Non-Interactive Shells",{"path":1769,"title":1770},"\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":1772,"title":1773},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Fhandling-ansi-escape-codes-on-windows-consoles","Handling ANSI Escape Codes on Windows Consoles",{"path":1775,"title":1776},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility","Cross-Platform Terminal Compatibility for Python CLIs",{"path":1778,"title":1779},"\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":1781,"title":1782},"\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":1784,"title":1785},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fchoosing-exit-codes-for-cli-tools","Choosing Exit Codes for CLI Tools",{"path":1787,"title":1788},"\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":1790,"title":1791},"\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":1793,"title":1794},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Ffriendly-error-messages-and-tracebacks","Friendly Error Messages and Tracebacks",{"path":1796,"title":1797},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fhandling-keyboard-interrupt-cleanly","Handling Keyboard Interrupt Cleanly",{"path":1799,"title":1800},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes","Error Handling and Exit Codes for CLIs",{"path":1802,"title":1803},"\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":1805,"title":1806},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fwriting-crash-reports-users-can-send","Writing Crash Reports Users Can Send",{"path":1808,"title":1809},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Fconfig-precedence-flags-env-files-defaults","Config Precedence: Flags, Env, Files, Defaults",{"path":1811,"title":1812},"\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":1814,"title":1815},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars","Handling Config Files and Env Vars in CLIs",{"path":1817,"title":1818},"\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":1820,"title":1821},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Freading-toml-config-with-tomllib","Reading TOML Config with tomllib in Python CLIs",{"path":1823,"title":1824},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Ftyped-settings-with-pydantic-settings","Typed Settings with pydantic-settings in Python CLIs",{"path":1826,"title":1827},"\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":1829,"title":1830},"\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":1832,"title":1833},"\u002Fadvanced-input-parsing-user-experience","Advanced Input Parsing for Python CLIs",{"path":1835,"title":1836},"\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":1838,"title":1839},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Fbuilding-interactive-prompts-and-menus","Building Interactive Prompts and Menus in Python CLIs",{"path":1841,"title":1842},"\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":1844,"title":1845},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich","Interactive Terminal UI with Rich",{"path":1847,"title":1848},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Flive-dashboards-with-rich-live","Live Dashboards with Rich Live in Python CLIs",{"path":1850,"title":1851},"\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":1853,"title":1854},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Frendering-tables-and-json-with-rich","Rendering Tables and JSON with Rich",{"path":1856,"title":1857},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Ftheming-rich-output-consistently","Theming Rich Output Consistently in Python CLIs",{"path":1859,"title":1860},"\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":1862,"title":1863},"\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":1865,"title":1866},"\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":1868,"title":1869},"\u002Fadvanced-input-parsing-user-experience\u002Foutput-formats-for-data-clis","Output Formats for Data-Heavy Python CLIs",{"path":1871,"title":1872},"\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":1874,"title":1875},"\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":1877,"title":1878},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Fargcomplete-for-argparse-clis","Tab Completion for argparse CLIs with argcomplete",{"path":1880,"title":1881},"\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":1883,"title":1884},"\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":1886,"title":1887},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis","Shell Completion for Python CLIs",{"path":1889,"title":1890},"\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":1892,"title":1893},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Fshipping-completion-scripts-with-packages","Shipping Shell Completion Scripts with a Python CLI",{"path":1895,"title":1896},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Ftesting-shell-completion-in-python-clis","Testing Shell Completion in Python CLIs",{"path":1898,"title":1899},"\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":1901,"title":1902},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fadding-verbose-and-quiet-logging-flags","Adding Verbose and Quiet Logging Flags",{"path":1904,"title":1905},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps","Structured Logging for CLI Apps",{"path":1907,"title":1908},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Flogging-with-structlog-in-a-cli","Logging with structlog in a Python CLI",{"path":1910,"title":1911},"\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":1913,"title":1914},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fstructured-json-logging-in-python-clis","Structured JSON Logging in Python CLIs",{"path":1916,"title":1917},"\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":1919,"title":1920},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Fdetecting-tty-and-adapting-output","Detecting a TTY and Adapting Output",{"path":1922,"title":1923},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Femitting-json-output-for-scripting","Emitting JSON Output for Scripting",{"path":1925,"title":1926},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Fhandling-broken-pipe-and-sigpipe","Handling Broken Pipe and SIGPIPE",{"path":1928,"title":1929},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes","Working with stdin, stdout and Pipes",{"path":1931,"title":1932},"\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":1934,"title":1935},"\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":1937,"title":1938},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Freading-piped-input-in-python-clis","Reading Piped Input in Python CLIs",{"path":1940,"title":1941},"\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":1943,"title":1944},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Fdownloading-files-with-progress-in-python","Downloading Files with Progress in Python CLIs",{"path":1946,"title":1947},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis","Calling HTTP APIs from Python CLIs",{"path":1949,"title":1950},"\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":1952,"title":1953},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Foauth-device-flow-login-for-clis","OAuth Device Flow Login for Python CLIs",{"path":1955,"title":1956},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Fpaginating-api-results-in-a-cli","Paginating API Results in a Python CLI",{"path":1958,"title":1959},"\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":1961,"title":1962},"\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":1964,"title":1965},"\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":1967,"title":1968},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis","Concurrency and Async in Python CLIs",{"path":1970,"title":1971},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Fmultiprocessing-for-cpu-bound-cli-tasks","Multiprocessing for CPU-Bound CLI Tasks",{"path":1973,"title":1974},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Fparallelising-cli-work-with-thread-pools","Parallelising CLI Work with Thread Pools",{"path":1976,"title":1977},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Frate-limiting-concurrent-requests-in-clis","Rate-Limiting Concurrent Requests in Python CLIs",{"path":1979,"title":1980},"\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":1982,"title":1983},"\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":1985,"title":1986},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Fstructured-concurrency-with-taskgroups-in-clis","Structured Concurrency with TaskGroups in Python CLIs",{"path":1988,"title":1989},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fcross-platform-paths-with-pathlib","Cross-Platform Paths with pathlib in CLIs",{"path":1991,"title":1992},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Ffile-locking-for-concurrent-cli-runs","File Locking for Concurrent CLI Runs in Python",{"path":1994,"title":1995},"\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":1997,"title":1998},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes","Filesystem Paths and Atomic Writes for CLIs",{"path":2000,"title":2001},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fsafe-temporary-files-and-directories","Safe Temporary Files and Directories in CLIs",{"path":2003,"title":2004},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fstoring-app-data-with-platformdirs","Storing CLI App Data with platformdirs",{"path":2006,"title":2007},"\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":2009,"title":2010},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fwriting-files-atomically-in-python-clis","Writing Files Atomically in Python CLIs",{"path":2012,"title":2013},"\u002Fcli-runtime-systems-integration","CLI Runtime & Systems Integration for Python",{"path":2015,"title":2016},"\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":2018,"title":2019},"\u002Fcli-runtime-systems-integration\u002Flocal-state-and-sqlite-in-python-clis","Local State and SQLite in Python CLIs",{"path":2021,"title":2022},"\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":2024,"title":2025},"\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":2027,"title":2028},"\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":2030,"title":2031},"\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":2033,"title":2034},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis\u002Fhandling-sigterm-and-graceful-shutdown","Handling SIGTERM and Graceful Shutdown in CLIs",{"path":2036,"title":2037},"\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":2039,"title":2040},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis","Long-Running and Watch-Mode Python CLIs",{"path":2042,"title":2043},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis\u002Freloading-config-on-sighup","Reloading Configuration on SIGHUP in a Python CLI",{"path":2045,"title":2046},"\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":2048,"title":2049},"\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":2051,"title":2052},"\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":2054,"title":2055},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Favoiding-shell-injection-in-python-clis","Avoiding Shell Injection in Python CLIs",{"path":2057,"title":2058},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fcalling-external-commands-safely-with-subprocess","Calling External Commands Safely with subprocess",{"path":2060,"title":2061},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fhandling-subprocess-timeouts-and-exit-codes","Handling Subprocess Timeouts and Exit Codes",{"path":2063,"title":2064},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis","Running Subprocesses from Python CLIs",{"path":2066,"title":2067},"\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":2069,"title":2070},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fpiping-between-subprocesses-in-python","Piping Between Subprocesses in Python Without a Shell",{"path":2072,"title":2073},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fstreaming-subprocess-output-in-real-time","Streaming Subprocess Output in Real Time",{"path":2075,"title":2076},"\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":2078,"title":2079},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis","Secrets and Credentials in Python CLIs",{"path":2081,"title":2082},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Fprompting-for-passwords-securely","Prompting for Passwords Securely in Python CLIs",{"path":2084,"title":2085},"\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":2087,"title":2088},"\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":2090,"title":2091},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Frefreshing-expired-tokens-automatically","Refreshing Expired Tokens Automatically in a Python CLI",{"path":2093,"title":2094},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Fstoring-tokens-with-keyring","Storing CLI Tokens Securely with keyring",{"path":2096,"title":2097},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Fsupporting-multiple-profiles-and-accounts","Supporting Multiple Profiles and Accounts in CLIs",{"path":2099,"title":2100},"\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":2102,"title":2103},"\u002Fcli-runtime-systems-integration\u002Fupdate-checks-and-self-updating-clis","Update Checks, Upgrade Commands and Telemetry for Python CLIs",{"path":2105,"title":2106},"\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":2108,"title":2109},"\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":2111,"title":2112},"\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":2114,"title":2115},"\u002F","Python CLI Toolcraft",{"path":2117,"title":2118},"\u002Fmodern-python-cli-frameworks-architecture\u002Falternative-python-cli-frameworks\u002Fbuilding-a-cli-with-cleo","Building a Python CLI with Cleo Command Classes",{"path":2120,"title":2121},"\u002Fmodern-python-cli-frameworks-architecture\u002Falternative-python-cli-frameworks\u002Fbuilding-a-cli-with-cyclopts","Building a Type-Hinted Python CLI with Cyclopts",{"path":2123,"title":2124},"\u002Fmodern-python-cli-frameworks-architecture\u002Falternative-python-cli-frameworks","Beyond Click and Typer: Alternative Python CLI Frameworks",{"path":2126,"title":2127},"\u002Fmodern-python-cli-frameworks-architecture\u002Falternative-python-cli-frameworks\u002Fquick-clis-from-functions-with-python-fire","Quick CLIs from Functions with Python Fire",{"path":2129,"title":2130},"\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":2132,"title":2133},"\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":2135,"title":2136},"\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":2138,"title":2139},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Fguarding-startup-with-import-tests","Guarding CLI Startup with Import Tests",{"path":2141,"title":2142},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading","CLI Startup Performance and Lazy Loading",{"path":2144,"title":2145},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Flazy-loading-subcommands-for-faster-startup","Lazy Loading Subcommands for Faster Startup",{"path":2147,"title":2148},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Fprofiling-python-cli-startup-time","Profiling Python CLI Startup Time",{"path":2150,"title":2151},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Freducing-cli-dependency-weight","Reducing CLI Dependency Weight",{"path":2153,"title":2154},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargparse-subparsers-for-subcommands","argparse Subparsers for Subcommands",{"path":2156,"title":2157},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargparse-vs-click-vs-typer-comparison","argparse vs Click vs Typer Compared",{"path":2159,"title":2160},"\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":2162,"title":2163},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse","Command-Line Parsing with argparse",{"path":2165,"title":2166},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fmigrating-from-argparse-to-typer","Migrating from argparse to Typer",{"path":2168,"title":2169},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fmutually-exclusive-options-in-argparse","Mutually Exclusive Options in argparse",{"path":2171,"title":2172},"\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":2174,"title":2175},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fwriting-custom-argparse-actions","Writing Custom argparse Actions in Python",{"path":2177,"title":2178},"\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":2180,"title":2181},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Fdesigning-idempotent-commands","Designing Idempotent Commands in a Python CLI",{"path":2183,"title":2184},"\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":2186,"title":2187},"\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":2189,"title":2190},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions","Designing CLI Interfaces and Conventions in Python",{"path":2192,"title":2193},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Fnaming-commands-and-flags-consistently","Naming Commands and Flags Consistently in Python CLIs",{"path":2195,"title":2196},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Fpositional-arguments-vs-options","Positional Arguments vs Options: Designing a CLI Signature",{"path":2198,"title":2199},"\u002Fmodern-python-cli-frameworks-architecture","Python CLI Frameworks and Architecture",{"path":2201,"title":2202},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fdiscovering-plugins-with-entry-points","Discovering Plugins with Entry Points in Python CLIs",{"path":2204,"title":2205},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fhook-based-plugins-with-pluggy","Hook-Based Plugins for Python CLIs with pluggy",{"path":2207,"title":2208},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis","Plugin Architectures for Extensible CLIs",{"path":2210,"title":2211},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fisolating-plugin-failures","Isolating Plugin Failures in an Extensible Python CLI",{"path":2213,"title":2214},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Ftesting-plugins-against-the-host-cli","Testing Plugins Against the Host CLI",{"path":2216,"title":2217},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fversioning-a-plugin-api","Versioning a Plugin API for a Python CLI",{"path":2219,"title":2220},"\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":2222,"title":2223},"\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":2225,"title":2226},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fdependency-injection-patterns-for-cli-commands","Dependency Injection Patterns for CLI Commands",{"path":2228,"title":2229},"\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":2231,"title":2232},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis","Structuring Multi-Command Python CLIs",{"path":2234,"title":2235},"\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":2237,"title":2238},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fregistering-commands-from-modules-automatically","Registering CLI Commands from Modules Automatically",{"path":2240,"title":2241},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fsharing-common-options-across-commands","Sharing Common Options Across Python CLI Commands",{"path":2243,"title":2244},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fsharing-state-with-click-context-objects","Sharing State with Click Context Objects",{"path":2246,"title":2247},"\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":2249,"title":2250},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications","Testing Python CLI Applications",{"path":2252,"title":2253},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fmeasuring-cli-test-coverage","Measuring CLI Test Coverage",{"path":2255,"title":2256},"\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":2258,"title":2259},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fproperty-based-testing-cli-arguments-with-hypothesis","Property-Based Testing CLI Arguments with Hypothesis",{"path":2261,"title":2262},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fsnapshot-testing-cli-output","Snapshot Testing CLI Output",{"path":2264,"title":2265},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftesting-click-commands-with-clirunner","Testing Click Commands with CliRunner",{"path":2267,"title":2268},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftesting-commands-that-call-subprocesses","Testing Python CLI Commands That Call Subprocesses",{"path":2270,"title":2271},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftesting-interactive-prompts-and-stdin","Testing Interactive Prompts and stdin",{"path":2273,"title":2274},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftranscript-tests-for-cli-commands","Transcript Tests for Python CLI Commands",{"path":2276,"title":2277},"\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":2279,"title":2280},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Fbuilding-dynamic-commands-in-click","Building Dynamic Commands in Click",{"path":2282,"title":2283},"\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":2285,"title":2286},"\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":2288,"title":2289},"\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":2291,"title":2292},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each","Typer vs Click: When to Use Each",{"path":2294,"title":2295},"\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":2297,"title":2298},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Ftyper-callback-functions-explained","Typer callback functions explained",{"path":2300,"title":2301},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Fusing-annotated-options-in-typer","Using Annotated Options in Typer",{"path":2303,"title":2304},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis\u002Fautomating-releases-from-git-tags","Automating Python CLI Releases from Git Tags",{"path":2306,"title":2307},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis\u002Fcaching-uv-dependencies-in-ci","Caching uv Dependencies in CI for Python CLIs",{"path":2309,"title":2310},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis","CI\u002FCD Pipelines for Python CLIs",{"path":2312,"title":2313},"\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":2315,"title":2316},"\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":2318,"title":2319},"\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":2321,"title":2322},"\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":2324,"title":2325},"\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":2327,"title":2328},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fbuilding-a-cookiecutter-template-for-typer-clis","Building a Cookiecutter Template for Typer CLIs",{"path":2330,"title":2331},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fcopier-vs-cookiecutter-for-cli-templates","Copier vs Cookiecutter for CLI Templates",{"path":2333,"title":2334},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter","CLI Project Scaffolding with Cookiecutter",{"path":2336,"title":2337},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fpost-generation-hooks-in-cli-templates","Post-Generation Hooks in Python CLI Templates",{"path":2339,"title":2340},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Ftemplate-variables-and-conditional-files","Template Variables and Conditional Files in CLI Templates",{"path":2342,"title":2343},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Ftesting-a-project-template-with-pytest","Testing a CLI Project Template with pytest",{"path":2345,"title":2346},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fupdating-generated-projects-with-copier-update","Updating Generated CLI Projects with copier update",{"path":2348,"title":2349},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fbuilding-cross-platform-release-binaries-in-ci","Building Cross-Platform Release Binaries in CI",{"path":2351,"title":2352},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fbundling-a-python-cli-with-pyinstaller","Bundling a Python CLI with PyInstaller",{"path":2354,"title":2355},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fcode-signing-and-notarizing-cli-binaries","Code Signing and Notarizing Python CLI Binaries",{"path":2357,"title":2358},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fhomebrew-and-scoop-packaging-for-python-clis","Homebrew and Scoop Packaging for Python CLIs",{"path":2360,"title":2361},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries","Distributing CLIs as Standalone Binaries",{"path":2363,"title":2364},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fnuitka-vs-pyinstaller-for-python-clis","Nuitka vs PyInstaller for Python CLI Binaries",{"path":2366,"title":2367},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Freducing-pyinstaller-binary-size","Reducing the Size of a PyInstaller CLI Binary",{"path":2369,"title":2370},"\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":2372,"title":2373},"\u002Fproject-setup-dependency-management","Project Setup & Dependency Management",{"path":2375,"title":2376},"\u002Fproject-setup-dependency-management\u002Flinting-and-type-checking-cli-code\u002Fconfiguring-ruff-for-a-cli-project","Configuring Ruff for a Python CLI Project",{"path":2378,"title":2379},"\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":2381,"title":2382},"\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":2384,"title":2385},"\u002Fproject-setup-dependency-management\u002Flinting-and-type-checking-cli-code","Linting and Type-Checking Python CLI Code",{"path":2387,"title":2388},"\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":2390,"title":2391},"\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":2393,"title":2394},"\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":2396,"title":2397},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fautomating-changelogs-with-conventional-commits","Automating Changelogs with Conventional Commits",{"path":2399,"title":2400},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fbumping-versions-consistently-across-a-cli-project","Bumping Versions Consistently Across a Python CLI Project",{"path":2402,"title":2403},"\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":2405,"title":2406},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fexposing-version-info-and-build-metadata","Exposing Version Info and Build Metadata",{"path":2408,"title":2409},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs","Managing CLI Versioning & Changelogs",{"path":2411,"title":2412},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fsemantic-versioning-policy-for-cli-tools","A Semantic Versioning Policy for CLI Tools",{"path":2414,"title":2415},"\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":2417,"title":2418},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fbuilding-wheels-and-sdists-for-python-clis","Building Wheels and sdists for Python CLIs",{"path":2420,"title":2421},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fbundling-data-files-with-importlib-resources","Bundling Data Files with importlib.resources in CLIs",{"path":2423,"title":2424},"\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":2426,"title":2427},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution","Packaging Python CLIs for Distribution",{"path":2429,"title":2430},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Finstalling-and-distributing-clis-with-pipx","Installing and Distributing CLIs with pipx",{"path":2432,"title":2433},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Foptional-dependencies-and-extras-for-clis","Optional Dependencies and Extras for Python CLIs",{"path":2435,"title":2436},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fpublishing-a-python-cli-to-pypi","Publishing a Python CLI to PyPI",{"path":2438,"title":2439},"\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":2441,"title":2442},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fdynamic-versioning-with-poetry-plugins","Dynamic Versioning for a Poetry CLI from Git Tags",{"path":2444,"title":2445},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development","Poetry Workflows for CLI Development",{"path":2447,"title":2448},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fmanaging-poetry-lock-files-for-cli-tools","Managing Poetry Lock Files for CLI Tools",{"path":2450,"title":2451},"\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":2453,"title":2454},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpoetry-dependency-groups-for-cli-tooling","Poetry Dependency Groups for CLI Tooling",{"path":2456,"title":2457},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpoetry-entry-points-and-scripts-for-clis","Poetry Entry Points and Scripts for CLIs",{"path":2459,"title":2460},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpublishing-a-cli-with-poetry","Building and Publishing a Python CLI with Poetry",{"path":2462,"title":2463},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects","Pre-commit Hooks for CLI Projects",{"path":1686,"title":5},{"path":2466,"title":2467},"\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":2469,"title":2470},"\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":2472,"title":2473},"\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":2475,"title":2476},"\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":2478,"title":2479},"\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":2481,"title":2482},"\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":2484,"title":2485},"\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":2487,"title":2488},"\u002Fproject-setup-dependency-management\u002Fsecuring-a-python-cli-supply-chain","Securing the Supply Chain of a Python CLI",{"path":2490,"title":2491},"\u002Fproject-setup-dependency-management\u002Fsecuring-a-python-cli-supply-chain\u002Fpinning-dependencies-with-hashes","Pinning a Python CLI’s Dependencies with Hashes",{"path":2493,"title":2494},"\u002Fproject-setup-dependency-management\u002Fsecuring-a-python-cli-supply-chain\u002Fpublishing-attestations-and-verifying-releases","Publishing Attestations and Verifying Python CLI Releases",{"path":2496,"title":2497},"\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":2499,"title":2500},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management","uv for Python CLI Dependency Management",{"path":2502,"title":2503},"\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":2505,"title":2506},"\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":2508,"title":2509},"\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":2511,"title":2512},"\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":2514,"title":2515},"\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":2517,"title":2518},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002Fuv-workspaces-for-multi-package-clis","uv Workspaces for Multi-Package Python CLIs",{"path":2520,"title":2521},"\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":2523,"title":2524},"\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":2526,"title":2527},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices","Python CLI Env Isolation Best Practices",{"path":2529,"title":2530},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fisolating-cli-tools-from-project-dependencies","Isolating CLI Tools from Your Project’s Dependencies",{"path":2532,"title":2533},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fmanaging-virtual-environments-for-cross-platform-clis","Managing Python CLI Virtual Environments",{"path":2535,"title":2536},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fpinning-the-python-version-for-a-cli","Pinning the Python Version for a CLI",{"path":2538,"title":2539},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fsupporting-multiple-python-versions-with-nox","Supporting Multiple Python Versions with nox",1790967542982]