[{"data":1,"prerenderedAt":1322},["ShallowReactive",2],{"page-\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Finstalling-and-distributing-clis-with-pipx\u002F":3,"content-directory":1077},{"id":4,"title":5,"body":6,"date":1062,"description":1063,"difficulty":1064,"draft":1065,"extension":1066,"meta":1067,"navigation":637,"path":1068,"seo":1069,"stem":1070,"tags":1071,"updated":1062,"__hash__":1076},"content\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Finstalling-and-distributing-clis-with-pipx\u002Findex.md","Installing and Distributing CLIs with pipx",{"type":7,"value":8,"toc":1049},"minimark",[9,26,31,98,102,106,127,153,156,213,224,228,241,322,325,344,348,351,354,414,435,501,510,514,520,553,560,598,605,609,612,701,717,721,727,730,773,791,795,814,866,876,880,887,921,935,939,1006,1010,1045],[10,11,12,13,17,18,21,22,25],"p",{},"When you install a Python CLI with plain ",[14,15,16],"code",{},"pip install",", its dependencies land in whatever\nenvironment you happened to be in — often your system Python — where they collide with the\nnext tool's dependencies. ",[14,19,20],{},"pipx"," fixes this: it gives every CLI its own private virtual\nenvironment and links just the command onto your ",[14,23,24],{},"PATH",". You get global commands with zero\ndependency conflicts. This guide covers the isolation model, the day-to-day commands,\ninstalling from a wheel\u002Fgit\u002FPyPI, and when to reach for pipx versus a plain venv.",[27,28,30],"h2",{"id":29},"tldr","TL;DR",[32,33,34,50,62,71,88],"ul",{},[35,36,37,41,42,45,46,49],"li",{},[38,39,40],"strong",{},"One venv per tool, one command on your PATH."," ",[14,43,44],{},"pipx install black"," puts Black in its own\nenvironment and exposes only the ",[14,47,48],{},"black"," command.",[35,51,52,41,58,61],{},[38,53,54,57],{},[14,55,56],{},"pipx run"," for one-offs.",[14,59,60],{},"pipx run cowsay Hello"," fetches, runs in a cache, and leaves\nnothing installed.",[35,63,64,70],{},[38,65,66,69],{},[14,67,68],{},"pipx inject"," to add plugins"," into a tool's private environment without touching your own.",[35,72,73,41,76,79,80,83,84,87],{},[38,74,75],{},"Pin and upgrade explicitly.",[14,77,78],{},"pipx install \"httpie==3.2.2\""," pins; ",[14,81,82],{},"pipx upgrade httpie","\nbumps; ",[14,85,86],{},"pipx upgrade-all"," maintains everything.",[35,89,90,97],{},[38,91,92,93,96],{},"Run ",[14,94,95],{},"pipx ensurepath"," once"," so the linked commands are actually found.",[99,100],"inline-diagram",{"name":101},"pipx-isolation",[27,103,105],{"id":104},"what-pipx-is-and-the-isolation-model","What pipx is and the isolation model",[10,107,108,110,111,114,115,118,119,122,123,126],{},[14,109,20],{}," is a tool for installing and running Python applications — programs you invoke by name,\nnot libraries you ",[14,112,113],{},"import",". Its whole design is one idea: ",[38,116,117],{},"isolate the environment, expose the\ncommand."," For each package you install, pipx creates a dedicated virtual environment under\n",[14,120,121],{},"~\u002F.local\u002Fpipx\u002Fvenvs\u002F\u003Cname>\u002F",", installs the package and its dependencies there, then creates a\nsymlink (or a small launcher on Windows) in ",[14,124,125],{},"~\u002F.local\u002Fbin\u002F"," pointing at each console entry\npoint the package declares.",[10,128,129,130,133,134,137,138,143,144,147,148,152],{},"The payoff is that two tools with conflicting requirements — say one needing ",[14,131,132],{},"rich\u003C13"," and\nanother needing ",[14,135,136],{},"rich>=14"," — coexist happily because neither can see the other's dependencies.\nIt is the same principle as the isolated venvs in\n",[139,140,142],"a",{"href":141},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002F","virtual environments and isolation best practices",",\nautomated for the specific case of installing command-line applications. Those console entry\npoints are exactly the ones you declare in ",[14,145,146],{},"[project.scripts]",", covered in the\n",[139,149,151],{"href":150},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002F","packaging overview",".",[10,154,155],{},"Install pipx itself (it is deliberately kept outside the environments it manages):",[157,158,163],"pre",{"className":159,"code":160,"language":161,"meta":162,"style":162},"language-bash shiki shiki-themes github-light github-dark","$ python -m pip install --user pipx\n$ python -m pipx ensurepath      # add ~\u002F.local\u002Fbin to PATH; restart your shell after\n","bash","",[14,164,165,194],{"__ignoreMap":162},[166,167,170,174,178,182,185,188,191],"span",{"class":168,"line":169},"line",1,[166,171,173],{"class":172},"sScJk","$",[166,175,177],{"class":176},"sZZnC"," python",[166,179,181],{"class":180},"sj4cs"," -m",[166,183,184],{"class":176}," pip",[166,186,187],{"class":176}," install",[166,189,190],{"class":180}," --user",[166,192,193],{"class":176}," pipx\n",[166,195,197,199,201,203,206,209],{"class":168,"line":196},2,[166,198,173],{"class":172},[166,200,177],{"class":176},[166,202,181],{"class":180},[166,204,205],{"class":176}," pipx",[166,207,208],{"class":176}," ensurepath",[166,210,212],{"class":211},"sJ8bj","      # add ~\u002F.local\u002Fbin to PATH; restart your shell after\n",[10,214,215,216,219,220,223],{},"On macOS ",[14,217,218],{},"brew install pipx"," and on recent Debian\u002FUbuntu ",[14,221,222],{},"apt install pipx"," also work.",[27,225,227],{"id":226},"path-setup-with-pipx-ensurepath","PATH setup with pipx ensurepath",[10,229,230,231,234,235,237,238,240],{},"The single most common \"pipx installed it but the command isn't found\" problem is that\n",[14,232,233],{},"~\u002F.local\u002Fbin"," is not on your ",[14,236,24],{},". ",[14,239,95],{}," edits your shell profile to add it and\ntells you to restart the shell:",[157,242,244],{"className":159,"code":243,"language":161,"meta":162,"style":162},"$ pipx ensurepath\nSuccess! Added \u002Fhome\u002Fyou\u002F.local\u002Fbin to the PATH environment variable.\nYou will need to open a new terminal or re-source your shell configuration...\n",[14,245,246,255,281],{"__ignoreMap":162},[166,247,248,250,252],{"class":168,"line":169},[166,249,173],{"class":172},[166,251,205],{"class":176},[166,253,254],{"class":176}," ensurepath\n",[166,256,257,260,263,266,269,272,275,278],{"class":168,"line":196},[166,258,259],{"class":172},"Success!",[166,261,262],{"class":176}," Added",[166,264,265],{"class":176}," \u002Fhome\u002Fyou\u002F.local\u002Fbin",[166,267,268],{"class":176}," to",[166,270,271],{"class":176}," the",[166,273,274],{"class":176}," PATH",[166,276,277],{"class":176}," environment",[166,279,280],{"class":176}," variable.\n",[166,282,284,287,290,293,295,298,301,304,307,310,313,316,319],{"class":168,"line":283},3,[166,285,286],{"class":172},"You",[166,288,289],{"class":176}," will",[166,291,292],{"class":176}," need",[166,294,268],{"class":176},[166,296,297],{"class":176}," open",[166,299,300],{"class":176}," a",[166,302,303],{"class":176}," new",[166,305,306],{"class":176}," terminal",[166,308,309],{"class":176}," or",[166,311,312],{"class":176}," re-source",[166,314,315],{"class":176}," your",[166,317,318],{"class":176}," shell",[166,320,321],{"class":176}," configuration...\n",[10,323,324],{},"Run it once per machine. In a Dockerfile or CI job where you cannot \"restart the shell,\" set\nthe variable directly instead:",[157,326,328],{"className":159,"code":327,"language":161,"meta":162,"style":162},"ENV PATH=\"\u002Froot\u002F.local\u002Fbin:${PATH}\"\n",[14,329,330],{"__ignoreMap":162},[166,331,332,335,338,341],{"class":168,"line":169},[166,333,334],{"class":172},"ENV",[166,336,337],{"class":176}," PATH=\"\u002Froot\u002F.local\u002Fbin:${",[166,339,24],{"class":340},"sVt8B",[166,342,343],{"class":176},"}\"\n",[27,345,347],{"id":346},"install-list-upgrade-uninstall","Install, list, upgrade, uninstall",[10,349,350],{},"The core lifecycle is four commands. Everything else is a variation on these.",[99,352],{"name":353},"pipx-command-matrix",[157,355,357],{"className":159,"code":356,"language":161,"meta":162,"style":162},"$ pipx install httpie            # create a venv, install, link the `http` command\n$ pipx list                      # show every installed app, its version, and its commands\n$ pipx upgrade httpie            # reinstall the latest compatible version in its venv\n$ pipx uninstall httpie          # remove the venv and the linked commands\n",[14,358,359,373,385,399],{"__ignoreMap":162},[166,360,361,363,365,367,370],{"class":168,"line":169},[166,362,173],{"class":172},[166,364,205],{"class":176},[166,366,187],{"class":176},[166,368,369],{"class":176}," httpie",[166,371,372],{"class":211},"            # create a venv, install, link the `http` command\n",[166,374,375,377,379,382],{"class":168,"line":196},[166,376,173],{"class":172},[166,378,205],{"class":176},[166,380,381],{"class":176}," list",[166,383,384],{"class":211},"                      # show every installed app, its version, and its commands\n",[166,386,387,389,391,394,396],{"class":168,"line":283},[166,388,173],{"class":172},[166,390,205],{"class":176},[166,392,393],{"class":176}," upgrade",[166,395,369],{"class":176},[166,397,398],{"class":211},"            # reinstall the latest compatible version in its venv\n",[166,400,402,404,406,409,411],{"class":168,"line":401},4,[166,403,173],{"class":172},[166,405,205],{"class":176},[166,407,408],{"class":176}," uninstall",[166,410,369],{"class":176},[166,412,413],{"class":211},"          # remove the venv and the linked commands\n",[10,415,416,419,420,423,424,427,428,431,432,434],{},[14,417,418],{},"pipx list"," is worth knowing well; it prints the Python version each venv uses and the exact\ncommands exposed, which is how you discover that installing ",[14,421,422],{},"httpie"," gave you the ",[14,425,426],{},"http"," and\n",[14,429,430],{},"https"," commands rather than an ",[14,433,422],{}," command:",[157,436,438],{"className":159,"code":437,"language":161,"meta":162,"style":162},"$ pipx list\nvenvs are in \u002Fhome\u002Fyou\u002F.local\u002Fpipx\u002Fvenvs\n   package httpie 3.2.2, installed using Python 3.12.3\n    - http\n    - https\n",[14,439,440,449,463,485,493],{"__ignoreMap":162},[166,441,442,444,446],{"class":168,"line":169},[166,443,173],{"class":172},[166,445,205],{"class":176},[166,447,448],{"class":176}," list\n",[166,450,451,454,457,460],{"class":168,"line":196},[166,452,453],{"class":172},"venvs",[166,455,456],{"class":176}," are",[166,458,459],{"class":176}," in",[166,461,462],{"class":176}," \u002Fhome\u002Fyou\u002F.local\u002Fpipx\u002Fvenvs\n",[166,464,465,468,470,473,476,479,482],{"class":168,"line":283},[166,466,467],{"class":172},"   package",[166,469,369],{"class":176},[166,471,472],{"class":176}," 3.2.2,",[166,474,475],{"class":176}," installed",[166,477,478],{"class":176}," using",[166,480,481],{"class":176}," Python",[166,483,484],{"class":180}," 3.12.3\n",[166,486,487,490],{"class":168,"line":401},[166,488,489],{"class":172},"    -",[166,491,492],{"class":176}," http\n",[166,494,496,498],{"class":168,"line":495},5,[166,497,489],{"class":172},[166,499,500],{"class":176}," https\n",[10,502,503,504,506,507,152],{},"To keep everything current in one shot, ",[14,505,86],{},". To rebuild every environment\nagainst a new Python after an interpreter upgrade, ",[14,508,509],{},"pipx reinstall-all --python 3.13",[27,511,513],{"id":512},"pinning-versions-and-choosing-the-interpreter","Pinning versions and choosing the interpreter",[10,515,516,519],{},[14,517,518],{},"pipx install"," accepts any pip requirement specifier, so pinning is just standard version\nsyntax. Pin when you need reproducibility — CI, or a tool whose latest release broke you:",[157,521,523],{"className":159,"code":522,"language":161,"meta":162,"style":162},"$ pipx install \"httpie==3.2.2\"          # exact pin\n$ pipx install \"ruff>=0.6,\u003C0.7\"         # compatible range\n",[14,524,525,539],{"__ignoreMap":162},[166,526,527,529,531,533,536],{"class":168,"line":169},[166,528,173],{"class":172},[166,530,205],{"class":176},[166,532,187],{"class":176},[166,534,535],{"class":176}," \"httpie==3.2.2\"",[166,537,538],{"class":211},"          # exact pin\n",[166,540,541,543,545,547,550],{"class":168,"line":196},[166,542,173],{"class":172},[166,544,205],{"class":176},[166,546,187],{"class":176},[166,548,549],{"class":176}," \"ruff>=0.6,\u003C0.7\"",[166,551,552],{"class":211},"         # compatible range\n",[10,554,555,556,559],{},"Pick the interpreter a tool runs on with ",[14,557,558],{},"--python",". This is how you keep a legacy tool on\n3.11 while your default is 3.13, or test a tool across interpreters:",[157,561,563],{"className":159,"code":562,"language":161,"meta":162,"style":162},"$ pipx install --python 3.11 some-legacy-cli\n$ pipx install --python \u002Fusr\u002Fbin\u002Fpython3.13 my-cli\n",[14,564,565,582],{"__ignoreMap":162},[166,566,567,569,571,573,576,579],{"class":168,"line":169},[166,568,173],{"class":172},[166,570,205],{"class":176},[166,572,187],{"class":176},[166,574,575],{"class":180}," --python",[166,577,578],{"class":180}," 3.11",[166,580,581],{"class":176}," some-legacy-cli\n",[166,583,584,586,588,590,592,595],{"class":168,"line":196},[166,585,173],{"class":172},[166,587,205],{"class":176},[166,589,187],{"class":176},[166,591,575],{"class":180},[166,593,594],{"class":176}," \u002Fusr\u002Fbin\u002Fpython3.13",[166,596,597],{"class":176}," my-cli\n",[10,599,600,601,604],{},"Because each install is a full environment, the pin sticks: ",[14,602,603],{},"pipx upgrade"," will not move a tool\noff a version you pinned unless you reinstall without the constraint.",[27,606,608],{"id":607},"installing-from-a-wheel-a-git-url-or-pypi","Installing from a wheel, a git URL, or PyPI",[10,610,611],{},"pipx installs from anywhere pip can, which makes it the natural way to hand someone a tool at\nany stage of its life.",[157,613,615],{"className":159,"code":614,"language":161,"meta":162,"style":162},"# From PyPI (the published, public case)\n$ pipx install greet-cli\n\n# From a locally built wheel — great for sharing a pre-release\n$ pipx install .\u002Fdist\u002Fgreet_cli-0.1.0-py3-none-any.whl\n\n# From a git repository, no release required (installs from a branch or tag)\n$ pipx install \"git+https:\u002F\u002Fgithub.com\u002Fada\u002Fgreet-cli.git@main\"\n\n# From a subdirectory of a monorepo\n$ pipx install \"git+https:\u002F\u002Fgithub.com\u002Fada\u002Ftools.git#subdirectory=greet-cli\"\n",[14,616,617,622,633,639,644,655,660,666,678,683,689],{"__ignoreMap":162},[166,618,619],{"class":168,"line":169},[166,620,621],{"class":211},"# From PyPI (the published, public case)\n",[166,623,624,626,628,630],{"class":168,"line":196},[166,625,173],{"class":172},[166,627,205],{"class":176},[166,629,187],{"class":176},[166,631,632],{"class":176}," greet-cli\n",[166,634,635],{"class":168,"line":283},[166,636,638],{"emptyLinePlaceholder":637},true,"\n",[166,640,641],{"class":168,"line":401},[166,642,643],{"class":211},"# From a locally built wheel — great for sharing a pre-release\n",[166,645,646,648,650,652],{"class":168,"line":495},[166,647,173],{"class":172},[166,649,205],{"class":176},[166,651,187],{"class":176},[166,653,654],{"class":176}," .\u002Fdist\u002Fgreet_cli-0.1.0-py3-none-any.whl\n",[166,656,658],{"class":168,"line":657},6,[166,659,638],{"emptyLinePlaceholder":637},[166,661,663],{"class":168,"line":662},7,[166,664,665],{"class":211},"# From a git repository, no release required (installs from a branch or tag)\n",[166,667,669,671,673,675],{"class":168,"line":668},8,[166,670,173],{"class":172},[166,672,205],{"class":176},[166,674,187],{"class":176},[166,676,677],{"class":176}," \"git+https:\u002F\u002Fgithub.com\u002Fada\u002Fgreet-cli.git@main\"\n",[166,679,681],{"class":168,"line":680},9,[166,682,638],{"emptyLinePlaceholder":637},[166,684,686],{"class":168,"line":685},10,[166,687,688],{"class":211},"# From a subdirectory of a monorepo\n",[166,690,692,694,696,698],{"class":168,"line":691},11,[166,693,173],{"class":172},[166,695,205],{"class":176},[166,697,187],{"class":176},[166,699,700],{"class":176}," \"git+https:\u002F\u002Fgithub.com\u002Fada\u002Ftools.git#subdirectory=greet-cli\"\n",[10,702,703,704,708,709,712,713,716],{},"The wheel case pairs directly with\n",[139,705,707],{"href":706},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fbuilding-wheels-and-sdists-for-python-clis\u002F","building wheels and sdists for Python CLIs",":\nbuild the wheel, ",[14,710,711],{},"pipx install .\u002Fdist\u002F*.whl",", and you have verified the entire packaging chain\nend to end without touching a package index. The git case is the fastest way for a colleague to\ntry your ",[14,714,715],{},"main"," branch before you have published anything to PyPI.",[27,718,720],{"id":719},"pipx-run-tools-you-use-once","pipx run: tools you use once",[10,722,723,724,726],{},"Some tools you need exactly once — a project scaffolder, a one-off formatter, a diagnostic.\nInstalling them permanently is clutter. ",[14,725,56],{}," fetches the package into a temporary cache,\nruns it, and installs nothing into your set of managed apps:",[99,728],{"name":729},"pipx-run-flow",[157,731,733],{"className":159,"code":732,"language":161,"meta":162,"style":162},"$ pipx run cowsay -t \"shipped it\"\n$ pipx run --spec \"cookiecutter\" cookiecutter gh:audreyfeldroy\u002Fcookiecutter-pypackage\n",[14,734,735,753],{"__ignoreMap":162},[166,736,737,739,741,744,747,750],{"class":168,"line":169},[166,738,173],{"class":172},[166,740,205],{"class":176},[166,742,743],{"class":176}," run",[166,745,746],{"class":176}," cowsay",[166,748,749],{"class":180}," -t",[166,751,752],{"class":176}," \"shipped it\"\n",[166,754,755,757,759,761,764,767,770],{"class":168,"line":196},[166,756,173],{"class":172},[166,758,205],{"class":176},[166,760,743],{"class":176},[166,762,763],{"class":180}," --spec",[166,765,766],{"class":176}," \"cookiecutter\"",[166,768,769],{"class":176}," cookiecutter",[166,771,772],{"class":176}," gh:audreyfeldroy\u002Fcookiecutter-pypackage\n",[10,774,775,776,779,780,783,784,786,787,152],{},"Use ",[14,777,778],{},"--spec"," when the command name differs from the package name, or to pin the one-off run\n(",[14,781,782],{},"pipx run --spec \"black==24.8.0\" black .","). The cache is reused for a while, so a repeated\n",[14,785,56],{}," of the same tool is fast on the second call. This is the mechanism behind the\n\"just run it\" instructions in\n",[139,788,790],{"href":789},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002F","CLI project scaffolding with Cookiecutter",[27,792,794],{"id":793},"pipx-inject-adding-plugins-to-a-tool","pipx inject: adding plugins to a tool",[10,796,797,798,802,803,806,807,810,811,813],{},"Some CLIs load plugins that must live in the ",[799,800,801],"em",{},"same"," environment as the tool — think a ",[14,804,805],{},"mkdocs","\ntheme or a ",[14,808,809],{},"pytest"," plugin you want globally. ",[14,812,68],{}," installs extra packages into an\nexisting app's private venv without polluting your own environment:",[157,815,817],{"className":159,"code":816,"language":161,"meta":162,"style":162},"$ pipx install mkdocs\n$ pipx inject mkdocs mkdocs-material          # add the theme into mkdocs' venv\n$ pipx inject mkdocs mkdocs-material --include-apps   # also link any new commands\n",[14,818,819,830,848],{"__ignoreMap":162},[166,820,821,823,825,827],{"class":168,"line":169},[166,822,173],{"class":172},[166,824,205],{"class":176},[166,826,187],{"class":176},[166,828,829],{"class":176}," mkdocs\n",[166,831,832,834,836,839,842,845],{"class":168,"line":196},[166,833,173],{"class":172},[166,835,205],{"class":176},[166,837,838],{"class":176}," inject",[166,840,841],{"class":176}," mkdocs",[166,843,844],{"class":176}," mkdocs-material",[166,846,847],{"class":211},"          # add the theme into mkdocs' venv\n",[166,849,850,852,854,856,858,860,863],{"class":168,"line":283},[166,851,173],{"class":172},[166,853,205],{"class":176},[166,855,838],{"class":176},[166,857,841],{"class":176},[166,859,844],{"class":176},[166,861,862],{"class":180}," --include-apps",[166,864,865],{"class":211},"   # also link any new commands\n",[10,867,868,869,872,873,875],{},"Without ",[14,870,871],{},"--include-apps",", injected packages are importable by the tool but their own commands\nare not linked onto your ",[14,874,24],{}," — which is usually what you want for a pure plugin.",[27,877,879],{"id":878},"when-pipx-and-when-a-plain-venv","When pipx, and when a plain venv",[10,881,882,883,886],{},"pipx is the right tool when the thing you are installing is an ",[799,884,885],{},"application"," you run by name and\nwant available everywhere. Reach for a plain project virtual environment instead when:",[32,888,889,904,914],{},[35,890,891,892,895,896,899,900,903],{},"You are ",[38,893,894],{},"developing"," the CLI, not just running it — you want an editable install\n(",[14,897,898],{},"pip install -e ."," or ",[14,901,902],{},"uv pip install -e .",") inside a project venv so code changes take\neffect immediately.",[35,905,906,907,910,911,913],{},"The tool is a ",[38,908,909],{},"library"," other code imports; pipx is for commands, not ",[14,912,113],{}," targets.",[35,915,916,917,920],{},"You need the tool's dependencies ",[38,918,919],{},"available to your project's code",", not sequestered in a\nprivate environment.",[10,922,923,924,927,928,930,931,152],{},"There is also a fast-moving alternative in the same niche: ",[14,925,926],{},"uv tool install"," does what ",[14,929,518],{}," does using uv's resolver and shared cache. The trade-offs are worth understanding\nbefore you standardize a team on one; see\n",[139,932,934],{"href":933},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002Fuv-tool-install-vs-pipx-for-clis\u002F","uv tool install vs pipx for CLIs",[27,936,938],{"id":937},"production-notes","Production notes",[32,940,941,955,971,983,993],{},[35,942,943,946,947,950,951,954],{},[38,944,945],{},"Document pipx as the install path in your README."," For a published CLI, ",[14,948,949],{},"pipx install your-cli"," is the recommendation that spares users the \"it broke my system Python\" class of\nbug. Show ",[14,952,953],{},"pipx run your-cli"," too for the try-before-you-commit case.",[35,956,957,960,961,963,964,967,968,970],{},[38,958,959],{},"CI has no interactive shell."," Prefer setting ",[14,962,24],{}," explicitly over relying on\n",[14,965,966],{},"ensurepath",", and pass ",[14,969,558],{}," to pin the interpreter so builds are reproducible.",[35,972,973,978,979,982],{},[38,974,975],{},[14,976,977],{},"pipx runpip \u003Capp> ..."," reaches into a tool's venv to inspect or debug it (e.g.\n",[14,980,981],{},"pipx runpip httpie freeze",") without breaking its isolation.",[35,984,985,988,989,992],{},[38,986,987],{},"Reinstall after a Python upgrade."," A venv built against a Python that gets removed will\nbreak; ",[14,990,991],{},"pipx reinstall-all"," rebuilds them against your current interpreter. On distros that\nupgrade system Python underneath you, this is the fix for \"all my pipx tools stopped working.\"",[35,994,995,41,998,1001,1002,1005],{},[38,996,997],{},"Environment location is configurable.",[14,999,1000],{},"PIPX_HOME"," and ",[14,1003,1004],{},"PIPX_BIN_DIR"," relocate the venvs\nand the linked commands — useful for shared or read-only-home CI images.",[27,1007,1009],{"id":1008},"related","Related",[32,1011,1012,1018,1024,1034,1039],{},[35,1013,1014,1017],{},[139,1015,1016],{"href":150},"Packaging Python CLIs for Distribution"," — the overview this guide sits under.",[35,1019,1020,1023],{},[139,1021,1022],{"href":706},"Building wheels and sdists for Python CLIs"," — produce the wheel you install from disk.",[35,1025,1026,1030,1031,1033],{},[139,1027,1029],{"href":1028},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fpublishing-a-python-cli-to-pypi\u002F","Publishing a Python CLI to PyPI"," — so ",[14,1032,949],{}," resolves for everyone.",[35,1035,1036,1038],{},[139,1037,934],{"href":933}," — the faster uv-based alternative and its trade-offs.",[35,1040,1041,1044],{},[139,1042,1043],{"href":141},"Virtual environments and isolation best practices"," — the isolation model pipx automates.",[1046,1047,1048],"style",{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}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 .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}",{"title":162,"searchDepth":196,"depth":196,"links":1050},[1051,1052,1053,1054,1055,1056,1057,1058,1059,1060,1061],{"id":29,"depth":196,"text":30},{"id":104,"depth":196,"text":105},{"id":226,"depth":196,"text":227},{"id":346,"depth":196,"text":347},{"id":512,"depth":196,"text":513},{"id":607,"depth":196,"text":608},{"id":719,"depth":196,"text":720},{"id":793,"depth":196,"text":794},{"id":878,"depth":196,"text":879},{"id":937,"depth":196,"text":938},{"id":1008,"depth":196,"text":1009},"2026-07-05","Use pipx to install Python CLIs into isolated environments, expose their commands globally, upgrade or pin them, and run one-off tools with pipx run.","beginner",false,"md",{},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Finstalling-and-distributing-clis-with-pipx",{"title":5,"description":1063},"project-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Finstalling-and-distributing-clis-with-pipx\u002Findex",[20,1072,1073,1074,1075],"distribution","cli","packaging","pypi","hZe7dbumwuspa0Na0Zgfcv38ytpuNKgEq1pnLITNKyQ",[1078,1081,1084,1087,1090,1093,1096,1099,1102,1105,1108,1111,1114,1117,1120,1123,1126,1129,1132,1135,1138,1141,1144,1147,1150,1153,1156,1159,1162,1165,1168,1171,1174,1177,1180,1183,1186,1189,1192,1195,1198,1201,1204,1207,1210,1213,1216,1219,1222,1225,1228,1231,1234,1237,1240,1243,1246,1249,1252,1255,1258,1261,1264,1267,1270,1273,1276,1279,1282,1285,1288,1290,1291,1293,1296,1299,1302,1305,1308,1311,1313,1316,1319],{"path":1079,"title":1080},"\u002Fabout","About Python CLI Toolcraft",{"path":1082,"title":1083},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies","Advanced Argument Validation Strategies",{"path":1085,"title":1086},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fparsing-nested-json-arguments-in-python-clis","Parsing Nested JSON Args in Python CLIs",{"path":1088,"title":1089},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fvalidating-file-and-directory-paths-in-clis","Validating File and Directory Paths in CLIs",{"path":1091,"title":1092},"\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":1094,"title":1095},"\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":1097,"title":1098},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation","CLI Help Output and Documentation",{"path":1100,"title":1101},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fversioning-and-deprecating-cli-flags","Versioning and Deprecating CLI Flags",{"path":1103,"title":1104},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fwriting-help-text-users-actually-read","Writing Help Text Users Actually Read",{"path":1106,"title":1107},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fchoosing-exit-codes-for-cli-tools","Choosing Exit Codes for CLI Tools",{"path":1109,"title":1110},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Ffriendly-error-messages-and-tracebacks","Friendly Error Messages and Tracebacks",{"path":1112,"title":1113},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fhandling-keyboard-interrupt-cleanly","Handling Keyboard Interrupt Cleanly",{"path":1115,"title":1116},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes","Error Handling and Exit Codes for CLIs",{"path":1118,"title":1119},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Fconfig-precedence-flags-env-files-defaults","Config Precedence: Flags, Env, Files, Defaults",{"path":1121,"title":1122},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars","Handling Config Files and Env Vars in CLIs",{"path":1124,"title":1125},"\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":1127,"title":1128},"\u002Fadvanced-input-parsing-user-experience","Advanced Input Parsing for Python CLIs",{"path":1130,"title":1131},"\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":1133,"title":1134},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich","Interactive Terminal UI with Rich",{"path":1136,"title":1137},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Frendering-tables-and-json-with-rich","Rendering Tables and JSON with Rich",{"path":1139,"title":1140},"\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":1142,"title":1143},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis","Shell Completion for Python CLIs",{"path":1145,"title":1146},"\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":1148,"title":1149},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fadding-verbose-and-quiet-logging-flags","Adding Verbose and Quiet Logging Flags",{"path":1151,"title":1152},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps","Structured Logging for CLI Apps",{"path":1154,"title":1155},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fstructured-json-logging-in-python-clis","Structured JSON Logging in Python CLIs",{"path":1157,"title":1158},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Fdetecting-tty-and-adapting-output","Detecting a TTY and Adapting Output",{"path":1160,"title":1161},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Femitting-json-output-for-scripting","Emitting JSON Output for Scripting",{"path":1163,"title":1164},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Fhandling-broken-pipe-and-sigpipe","Handling Broken Pipe and SIGPIPE",{"path":1166,"title":1167},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes","Working with stdin, stdout and Pipes",{"path":1169,"title":1170},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Freading-piped-input-in-python-clis","Reading Piped Input in Python CLIs",{"path":1172,"title":1173},"\u002F","Python CLI Toolcraft",{"path":1175,"title":1176},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading","CLI Startup Performance and Lazy Loading",{"path":1178,"title":1179},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Flazy-loading-subcommands-for-faster-startup","Lazy Loading Subcommands for Faster Startup",{"path":1181,"title":1182},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Fprofiling-python-cli-startup-time","Profiling Python CLI Startup Time",{"path":1184,"title":1185},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Freducing-cli-dependency-weight","Reducing CLI Dependency Weight",{"path":1187,"title":1188},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargparse-subparsers-for-subcommands","argparse Subparsers for Subcommands",{"path":1190,"title":1191},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargparse-vs-click-vs-typer-comparison","argparse vs Click vs Typer Compared",{"path":1193,"title":1194},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse","Command-Line Parsing with argparse",{"path":1196,"title":1197},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fmigrating-from-argparse-to-typer","Migrating from argparse to Typer",{"path":1199,"title":1200},"\u002Fmodern-python-cli-frameworks-architecture","Python CLI Frameworks and Architecture",{"path":1202,"title":1203},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis","Plugin Architectures for Extensible CLIs",{"path":1205,"title":1206},"\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":1208,"title":1209},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fbest-practices-for-python-cli-entry-points","Best practices for Python CLI entry points",{"path":1211,"title":1212},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fdependency-injection-patterns-for-cli-commands","Dependency Injection Patterns for CLI Commands",{"path":1214,"title":1215},"\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":1217,"title":1218},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis","Structuring Multi-Command Python CLIs",{"path":1220,"title":1221},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fsharing-state-with-click-context-objects","Sharing State with Click Context Objects",{"path":1223,"title":1224},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications","Testing Python CLI Applications",{"path":1226,"title":1227},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fmeasuring-cli-test-coverage","Measuring CLI Test Coverage",{"path":1229,"title":1230},"\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":1232,"title":1233},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fsnapshot-testing-cli-output","Snapshot Testing CLI Output",{"path":1235,"title":1236},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftesting-click-commands-with-clirunner","Testing Click Commands with CliRunner",{"path":1238,"title":1239},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftesting-interactive-prompts-and-stdin","Testing Interactive Prompts and stdin",{"path":1241,"title":1242},"\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":1244,"title":1245},"\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":1247,"title":1248},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each","Typer vs Click: When to Use Each",{"path":1250,"title":1251},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Ftyper-callback-functions-explained","Typer callback functions explained",{"path":1253,"title":1254},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fcopier-vs-cookiecutter-for-cli-templates","Copier vs Cookiecutter for CLI Templates",{"path":1256,"title":1257},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter","CLI Project Scaffolding with Cookiecutter",{"path":1259,"title":1260},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fbuilding-cross-platform-release-binaries-in-ci","Building Cross-Platform Release Binaries in CI",{"path":1262,"title":1263},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fbundling-a-python-cli-with-pyinstaller","Bundling a Python CLI with PyInstaller",{"path":1265,"title":1266},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fhomebrew-and-scoop-packaging-for-python-clis","Homebrew and Scoop Packaging for Python CLIs",{"path":1268,"title":1269},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries","Distributing CLIs as Standalone Binaries",{"path":1271,"title":1272},"\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":1274,"title":1275},"\u002Fproject-setup-dependency-management","Project Setup & Dependency Management",{"path":1277,"title":1278},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fautomating-changelogs-with-conventional-commits","Automating Changelogs with Conventional Commits",{"path":1280,"title":1281},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fexposing-version-info-and-build-metadata","Exposing Version Info and Build Metadata",{"path":1283,"title":1284},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs","Managing CLI Versioning & Changelogs",{"path":1286,"title":1287},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fbuilding-wheels-and-sdists-for-python-clis","Building Wheels and sdists for Python CLIs",{"path":1289,"title":1016},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution",{"path":1068,"title":5},{"path":1292,"title":1029},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fpublishing-a-python-cli-to-pypi",{"path":1294,"title":1295},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development","Poetry Workflows for CLI Development",{"path":1297,"title":1298},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpoetry-entry-points-and-scripts-for-clis","Poetry Entry Points and Scripts for CLIs",{"path":1300,"title":1301},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects","Pre-commit Hooks for CLI Projects",{"path":1303,"title":1304},"\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":1306,"title":1307},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management","uv for Python CLI Dependency Management",{"path":1309,"title":1310},"\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":1312,"title":934},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002Fuv-tool-install-vs-pipx-for-clis",{"path":1314,"title":1315},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices","Python CLI Env Isolation Best Practices",{"path":1317,"title":1318},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fmanaging-virtual-environments-for-cross-platform-clis","Managing Python CLI Virtual Environments",{"path":1320,"title":1321},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fpinning-the-python-version-for-a-cli","Pinning the Python Version for a CLI",1785614690035]