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