[{"data":1,"prerenderedAt":1223},["ShallowReactive",2],{"page-\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Finstalling-and-distributing-clis-with-pipx\u002F":3,"content-directory":1074},{"id":4,"title":5,"body":6,"date":1059,"description":1060,"difficulty":1061,"draft":1062,"extension":1063,"meta":1064,"navigation":637,"path":1065,"seo":1066,"stem":1067,"tags":1068,"updated":1059,"__hash__":1073},"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":1046},"minimark",[9,26,31,98,105,109,130,156,159,216,227,231,244,325,328,347,351,354,414,435,501,510,514,520,553,560,598,605,609,612,701,717,721,727,770,788,792,811,863,873,877,884,918,932,936,1003,1007,1042],[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.",[10,99,100],{},[101,102],"img",{"alt":103,"src":104},"Each pipx-installed CLI lives in its own virtual environment while its command is symlinked onto the user's PATH, keeping tools isolated from each other and from system Python.","\u002Fillustrations\u002Fpipx-isolation.svg",[27,106,108],{"id":107},"what-pipx-is-and-the-isolation-model","What pipx is and the isolation model",[10,110,111,113,114,117,118,121,122,125,126,129],{},[14,112,20],{}," is a tool for installing and running Python applications — programs you invoke by name,\nnot libraries you ",[14,115,116],{},"import",". Its whole design is one idea: ",[38,119,120],{},"isolate the environment, expose the\ncommand."," For each package you install, pipx creates a dedicated virtual environment under\n",[14,123,124],{},"~\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,127,128],{},"~\u002F.local\u002Fbin\u002F"," pointing at each console entry\npoint the package declares.",[10,131,132,133,136,137,140,141,146,147,150,151,155],{},"The payoff is that two tools with conflicting requirements — say one needing ",[14,134,135],{},"rich\u003C13"," and\nanother needing ",[14,138,139],{},"rich>=14"," — coexist happily because neither can see the other's dependencies.\nIt is the same principle as the isolated venvs in\n",[142,143,145],"a",{"href":144},"\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,148,149],{},"[project.scripts]",", covered in the\n",[142,152,154],{"href":153},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002F","packaging overview",".",[10,157,158],{},"Install pipx itself (it is deliberately kept outside the environments it manages):",[160,161,166],"pre",{"className":162,"code":163,"language":164,"meta":165,"style":165},"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,167,168,197],{"__ignoreMap":165},[169,170,173,177,181,185,188,191,194],"span",{"class":171,"line":172},"line",1,[169,174,176],{"class":175},"sScJk","$",[169,178,180],{"class":179},"sZZnC"," python",[169,182,184],{"class":183},"sj4cs"," -m",[169,186,187],{"class":179}," pip",[169,189,190],{"class":179}," install",[169,192,193],{"class":183}," --user",[169,195,196],{"class":179}," pipx\n",[169,198,200,202,204,206,209,212],{"class":171,"line":199},2,[169,201,176],{"class":175},[169,203,180],{"class":179},[169,205,184],{"class":183},[169,207,208],{"class":179}," pipx",[169,210,211],{"class":179}," ensurepath",[169,213,215],{"class":214},"sJ8bj","      # add ~\u002F.local\u002Fbin to PATH; restart your shell after\n",[10,217,218,219,222,223,226],{},"On macOS ",[14,220,221],{},"brew install pipx"," and on recent Debian\u002FUbuntu ",[14,224,225],{},"apt install pipx"," also work.",[27,228,230],{"id":229},"path-setup-with-pipx-ensurepath","PATH setup with pipx ensurepath",[10,232,233,234,237,238,240,241,243],{},"The single most common \"pipx installed it but the command isn't found\" problem is that\n",[14,235,236],{},"~\u002F.local\u002Fbin"," is not on your ",[14,239,24],{},". ",[14,242,95],{}," edits your shell profile to add it and\ntells you to restart the shell:",[160,245,247],{"className":162,"code":246,"language":164,"meta":165,"style":165},"$ 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,248,249,258,284],{"__ignoreMap":165},[169,250,251,253,255],{"class":171,"line":172},[169,252,176],{"class":175},[169,254,208],{"class":179},[169,256,257],{"class":179}," ensurepath\n",[169,259,260,263,266,269,272,275,278,281],{"class":171,"line":199},[169,261,262],{"class":175},"Success!",[169,264,265],{"class":179}," Added",[169,267,268],{"class":179}," \u002Fhome\u002Fyou\u002F.local\u002Fbin",[169,270,271],{"class":179}," to",[169,273,274],{"class":179}," the",[169,276,277],{"class":179}," PATH",[169,279,280],{"class":179}," environment",[169,282,283],{"class":179}," variable.\n",[169,285,287,290,293,296,298,301,304,307,310,313,316,319,322],{"class":171,"line":286},3,[169,288,289],{"class":175},"You",[169,291,292],{"class":179}," will",[169,294,295],{"class":179}," need",[169,297,271],{"class":179},[169,299,300],{"class":179}," open",[169,302,303],{"class":179}," a",[169,305,306],{"class":179}," new",[169,308,309],{"class":179}," terminal",[169,311,312],{"class":179}," or",[169,314,315],{"class":179}," re-source",[169,317,318],{"class":179}," your",[169,320,321],{"class":179}," shell",[169,323,324],{"class":179}," configuration...\n",[10,326,327],{},"Run it once per machine. In a Dockerfile or CI job where you cannot \"restart the shell,\" set\nthe variable directly instead:",[160,329,331],{"className":162,"code":330,"language":164,"meta":165,"style":165},"ENV PATH=\"\u002Froot\u002F.local\u002Fbin:${PATH}\"\n",[14,332,333],{"__ignoreMap":165},[169,334,335,338,341,344],{"class":171,"line":172},[169,336,337],{"class":175},"ENV",[169,339,340],{"class":179}," PATH=\"\u002Froot\u002F.local\u002Fbin:${",[169,342,24],{"class":343},"sVt8B",[169,345,346],{"class":179},"}\"\n",[27,348,350],{"id":349},"install-list-upgrade-uninstall","Install, list, upgrade, uninstall",[10,352,353],{},"The core lifecycle is four commands. Everything else is a variation on these.",[160,355,357],{"className":162,"code":356,"language":164,"meta":165,"style":165},"$ 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":165},[169,360,361,363,365,367,370],{"class":171,"line":172},[169,362,176],{"class":175},[169,364,208],{"class":179},[169,366,190],{"class":179},[169,368,369],{"class":179}," httpie",[169,371,372],{"class":214},"            # create a venv, install, link the `http` command\n",[169,374,375,377,379,382],{"class":171,"line":199},[169,376,176],{"class":175},[169,378,208],{"class":179},[169,380,381],{"class":179}," list",[169,383,384],{"class":214},"                      # show every installed app, its version, and its commands\n",[169,386,387,389,391,394,396],{"class":171,"line":286},[169,388,176],{"class":175},[169,390,208],{"class":179},[169,392,393],{"class":179}," upgrade",[169,395,369],{"class":179},[169,397,398],{"class":214},"            # reinstall the latest compatible version in its venv\n",[169,400,402,404,406,409,411],{"class":171,"line":401},4,[169,403,176],{"class":175},[169,405,208],{"class":179},[169,407,408],{"class":179}," uninstall",[169,410,369],{"class":179},[169,412,413],{"class":214},"          # 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:",[160,436,438],{"className":162,"code":437,"language":164,"meta":165,"style":165},"$ 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":165},[169,441,442,444,446],{"class":171,"line":172},[169,443,176],{"class":175},[169,445,208],{"class":179},[169,447,448],{"class":179}," list\n",[169,450,451,454,457,460],{"class":171,"line":199},[169,452,453],{"class":175},"venvs",[169,455,456],{"class":179}," are",[169,458,459],{"class":179}," in",[169,461,462],{"class":179}," \u002Fhome\u002Fyou\u002F.local\u002Fpipx\u002Fvenvs\n",[169,464,465,468,470,473,476,479,482],{"class":171,"line":286},[169,466,467],{"class":175},"   package",[169,469,369],{"class":179},[169,471,472],{"class":179}," 3.2.2,",[169,474,475],{"class":179}," installed",[169,477,478],{"class":179}," using",[169,480,481],{"class":179}," Python",[169,483,484],{"class":183}," 3.12.3\n",[169,486,487,490],{"class":171,"line":401},[169,488,489],{"class":175},"    -",[169,491,492],{"class":179}," http\n",[169,494,496,498],{"class":171,"line":495},5,[169,497,489],{"class":175},[169,499,500],{"class":179}," https\n",[10,502,503,504,506,507,155],{},"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:",[160,521,523],{"className":162,"code":522,"language":164,"meta":165,"style":165},"$ pipx install \"httpie==3.2.2\"          # exact pin\n$ pipx install \"ruff>=0.6,\u003C0.7\"         # compatible range\n",[14,524,525,539],{"__ignoreMap":165},[169,526,527,529,531,533,536],{"class":171,"line":172},[169,528,176],{"class":175},[169,530,208],{"class":179},[169,532,190],{"class":179},[169,534,535],{"class":179}," \"httpie==3.2.2\"",[169,537,538],{"class":214},"          # exact pin\n",[169,540,541,543,545,547,550],{"class":171,"line":199},[169,542,176],{"class":175},[169,544,208],{"class":179},[169,546,190],{"class":179},[169,548,549],{"class":179}," \"ruff>=0.6,\u003C0.7\"",[169,551,552],{"class":214},"         # 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:",[160,561,563],{"className":162,"code":562,"language":164,"meta":165,"style":165},"$ pipx install --python 3.11 some-legacy-cli\n$ pipx install --python \u002Fusr\u002Fbin\u002Fpython3.13 my-cli\n",[14,564,565,582],{"__ignoreMap":165},[169,566,567,569,571,573,576,579],{"class":171,"line":172},[169,568,176],{"class":175},[169,570,208],{"class":179},[169,572,190],{"class":179},[169,574,575],{"class":183}," --python",[169,577,578],{"class":183}," 3.11",[169,580,581],{"class":179}," some-legacy-cli\n",[169,583,584,586,588,590,592,595],{"class":171,"line":199},[169,585,176],{"class":175},[169,587,208],{"class":179},[169,589,190],{"class":179},[169,591,575],{"class":183},[169,593,594],{"class":179}," \u002Fusr\u002Fbin\u002Fpython3.13",[169,596,597],{"class":179}," 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.",[160,613,615],{"className":162,"code":614,"language":164,"meta":165,"style":165},"# 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":165},[169,618,619],{"class":171,"line":172},[169,620,621],{"class":214},"# From PyPI (the published, public case)\n",[169,623,624,626,628,630],{"class":171,"line":199},[169,625,176],{"class":175},[169,627,208],{"class":179},[169,629,190],{"class":179},[169,631,632],{"class":179}," greet-cli\n",[169,634,635],{"class":171,"line":286},[169,636,638],{"emptyLinePlaceholder":637},true,"\n",[169,640,641],{"class":171,"line":401},[169,642,643],{"class":214},"# From a locally built wheel — great for sharing a pre-release\n",[169,645,646,648,650,652],{"class":171,"line":495},[169,647,176],{"class":175},[169,649,208],{"class":179},[169,651,190],{"class":179},[169,653,654],{"class":179}," .\u002Fdist\u002Fgreet_cli-0.1.0-py3-none-any.whl\n",[169,656,658],{"class":171,"line":657},6,[169,659,638],{"emptyLinePlaceholder":637},[169,661,663],{"class":171,"line":662},7,[169,664,665],{"class":214},"# From a git repository, no release required (installs from a branch or tag)\n",[169,667,669,671,673,675],{"class":171,"line":668},8,[169,670,176],{"class":175},[169,672,208],{"class":179},[169,674,190],{"class":179},[169,676,677],{"class":179}," \"git+https:\u002F\u002Fgithub.com\u002Fada\u002Fgreet-cli.git@main\"\n",[169,679,681],{"class":171,"line":680},9,[169,682,638],{"emptyLinePlaceholder":637},[169,684,686],{"class":171,"line":685},10,[169,687,688],{"class":214},"# From a subdirectory of a monorepo\n",[169,690,692,694,696,698],{"class":171,"line":691},11,[169,693,176],{"class":175},[169,695,208],{"class":179},[169,697,190],{"class":179},[169,699,700],{"class":179}," \"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",[142,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:",[160,728,730],{"className":162,"code":729,"language":164,"meta":165,"style":165},"$ pipx run cowsay -t \"shipped it\"\n$ pipx run --spec \"cookiecutter\" cookiecutter gh:audreyfeldroy\u002Fcookiecutter-pypackage\n",[14,731,732,750],{"__ignoreMap":165},[169,733,734,736,738,741,744,747],{"class":171,"line":172},[169,735,176],{"class":175},[169,737,208],{"class":179},[169,739,740],{"class":179}," run",[169,742,743],{"class":179}," cowsay",[169,745,746],{"class":183}," -t",[169,748,749],{"class":179}," \"shipped it\"\n",[169,751,752,754,756,758,761,764,767],{"class":171,"line":199},[169,753,176],{"class":175},[169,755,208],{"class":179},[169,757,740],{"class":179},[169,759,760],{"class":183}," --spec",[169,762,763],{"class":179}," \"cookiecutter\"",[169,765,766],{"class":179}," cookiecutter",[169,768,769],{"class":179}," gh:audreyfeldroy\u002Fcookiecutter-pypackage\n",[10,771,772,773,776,777,780,781,783,784,155],{},"Use ",[14,774,775],{},"--spec"," when the command name differs from the package name, or to pin the one-off run\n(",[14,778,779],{},"pipx run --spec \"black==24.8.0\" black .","). The cache is reused for a while, so a repeated\n",[14,782,56],{}," of the same tool is fast on the second call. This is the mechanism behind the\n\"just run it\" instructions in\n",[142,785,787],{"href":786},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002F","CLI project scaffolding with Cookiecutter",[27,789,791],{"id":790},"pipx-inject-adding-plugins-to-a-tool","pipx inject: adding plugins to a tool",[10,793,794,795,799,800,803,804,807,808,810],{},"Some CLIs load plugins that must live in the ",[796,797,798],"em",{},"same"," environment as the tool — think a ",[14,801,802],{},"mkdocs","\ntheme or a ",[14,805,806],{},"pytest"," plugin you want globally. ",[14,809,68],{}," installs extra packages into an\nexisting app's private venv without polluting your own environment:",[160,812,814],{"className":162,"code":813,"language":164,"meta":165,"style":165},"$ 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,815,816,827,845],{"__ignoreMap":165},[169,817,818,820,822,824],{"class":171,"line":172},[169,819,176],{"class":175},[169,821,208],{"class":179},[169,823,190],{"class":179},[169,825,826],{"class":179}," mkdocs\n",[169,828,829,831,833,836,839,842],{"class":171,"line":199},[169,830,176],{"class":175},[169,832,208],{"class":179},[169,834,835],{"class":179}," inject",[169,837,838],{"class":179}," mkdocs",[169,840,841],{"class":179}," mkdocs-material",[169,843,844],{"class":214},"          # add the theme into mkdocs' venv\n",[169,846,847,849,851,853,855,857,860],{"class":171,"line":286},[169,848,176],{"class":175},[169,850,208],{"class":179},[169,852,835],{"class":179},[169,854,838],{"class":179},[169,856,841],{"class":179},[169,858,859],{"class":183}," --include-apps",[169,861,862],{"class":214},"   # also link any new commands\n",[10,864,865,866,869,870,872],{},"Without ",[14,867,868],{},"--include-apps",", injected packages are importable by the tool but their own commands\nare not linked onto your ",[14,871,24],{}," — which is usually what you want for a pure plugin.",[27,874,876],{"id":875},"when-pipx-and-when-a-plain-venv","When pipx, and when a plain venv",[10,878,879,880,883],{},"pipx is the right tool when the thing you are installing is an ",[796,881,882],{},"application"," you run by name and\nwant available everywhere. Reach for a plain project virtual environment instead when:",[32,885,886,901,911],{},[35,887,888,889,892,893,896,897,900],{},"You are ",[38,890,891],{},"developing"," the CLI, not just running it — you want an editable install\n(",[14,894,895],{},"pip install -e ."," or ",[14,898,899],{},"uv pip install -e .",") inside a project venv so code changes take\neffect immediately.",[35,902,903,904,907,908,910],{},"The tool is a ",[38,905,906],{},"library"," other code imports; pipx is for commands, not ",[14,909,116],{}," targets.",[35,912,913,914,917],{},"You need the tool's dependencies ",[38,915,916],{},"available to your project's code",", not sequestered in a\nprivate environment.",[10,919,920,921,924,925,927,928,155],{},"There is also a fast-moving alternative in the same niche: ",[14,922,923],{},"uv tool install"," does what ",[14,926,518],{}," does using uv's resolver and shared cache. The trade-offs are worth understanding\nbefore you standardize a team on one; see\n",[142,929,931],{"href":930},"\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,933,935],{"id":934},"production-notes","Production notes",[32,937,938,952,968,980,990],{},[35,939,940,943,944,947,948,951],{},[38,941,942],{},"Document pipx as the install path in your README."," For a published CLI, ",[14,945,946],{},"pipx install your-cli"," is the recommendation that spares users the \"it broke my system Python\" class of\nbug. Show ",[14,949,950],{},"pipx run your-cli"," too for the try-before-you-commit case.",[35,953,954,957,958,960,961,964,965,967],{},[38,955,956],{},"CI has no interactive shell."," Prefer setting ",[14,959,24],{}," explicitly over relying on\n",[14,962,963],{},"ensurepath",", and pass ",[14,966,558],{}," to pin the interpreter so builds are reproducible.",[35,969,970,975,976,979],{},[38,971,972],{},[14,973,974],{},"pipx runpip \u003Capp> ..."," reaches into a tool's venv to inspect or debug it (e.g.\n",[14,977,978],{},"pipx runpip httpie freeze",") without breaking its isolation.",[35,981,982,985,986,989],{},[38,983,984],{},"Reinstall after a Python upgrade."," A venv built against a Python that gets removed will\nbreak; ",[14,987,988],{},"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,991,992,41,995,998,999,1002],{},[38,993,994],{},"Environment location is configurable.",[14,996,997],{},"PIPX_HOME"," and ",[14,1000,1001],{},"PIPX_BIN_DIR"," relocate the venvs\nand the linked commands — useful for shared or read-only-home CI images.",[27,1004,1006],{"id":1005},"related","Related",[32,1008,1009,1015,1021,1031,1036],{},[35,1010,1011,1014],{},[142,1012,1013],{"href":153},"Packaging Python CLIs for Distribution"," — the overview this guide sits under.",[35,1016,1017,1020],{},[142,1018,1019],{"href":706},"Building wheels and sdists for Python CLIs"," — produce the wheel you install from disk.",[35,1022,1023,1027,1028,1030],{},[142,1024,1026],{"href":1025},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fpublishing-a-python-cli-to-pypi\u002F","Publishing a Python CLI to PyPI"," — so ",[14,1029,946],{}," resolves for everyone.",[35,1032,1033,1035],{},[142,1034,931],{"href":930}," — the faster uv-based alternative and its trade-offs.",[35,1037,1038,1041],{},[142,1039,1040],{"href":144},"Virtual environments and isolation best practices"," — the isolation model pipx automates.",[1043,1044,1045],"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":165,"searchDepth":199,"depth":199,"links":1047},[1048,1049,1050,1051,1052,1053,1054,1055,1056,1057,1058],{"id":29,"depth":199,"text":30},{"id":107,"depth":199,"text":108},{"id":229,"depth":199,"text":230},{"id":349,"depth":199,"text":350},{"id":512,"depth":199,"text":513},{"id":607,"depth":199,"text":608},{"id":719,"depth":199,"text":720},{"id":790,"depth":199,"text":791},{"id":875,"depth":199,"text":876},{"id":934,"depth":199,"text":935},{"id":1005,"depth":199,"text":1006},"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":1060},"project-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Finstalling-and-distributing-clis-with-pipx\u002Findex",[20,1069,1070,1071,1072],"distribution","cli","packaging","pypi","52vk400BBV1HONjXNaTCZWMwHHGQb3Cyza0taitmL0w",[1075,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,1194,1195,1197,1200,1203,1206,1209,1212,1215,1217,1220],{"path":1076,"title":1077},"\u002Fabout","About Python CLI Toolcraft",{"path":1079,"title":1080},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies","Advanced Argument Validation Strategies",{"path":1082,"title":1083},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fparsing-nested-json-arguments-in-python-clis","Parsing Nested JSON Args in Python CLIs",{"path":1085,"title":1086},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fchoosing-exit-codes-for-cli-tools","Choosing Exit Codes for CLI Tools",{"path":1088,"title":1089},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Ffriendly-error-messages-and-tracebacks","Friendly Error Messages and Tracebacks",{"path":1091,"title":1092},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes","Error Handling and Exit Codes for CLIs",{"path":1094,"title":1095},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Fconfig-precedence-flags-env-files-defaults","Config Precedence: Flags, Env, Files, Defaults",{"path":1097,"title":1098},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars","Handling Config Files and Env Vars in CLIs",{"path":1100,"title":1101},"\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":1103,"title":1104},"\u002Fadvanced-input-parsing-user-experience","Advanced Input Parsing for Python CLIs",{"path":1106,"title":1107},"\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":1109,"title":1110},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich","Interactive Terminal UI with Rich",{"path":1112,"title":1113},"\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":1115,"title":1116},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis","Shell Completion for Python CLIs",{"path":1118,"title":1119},"\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":1121,"title":1122},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fadding-verbose-and-quiet-logging-flags","Adding Verbose and Quiet Logging Flags",{"path":1124,"title":1125},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps","Structured Logging for CLI Apps",{"path":1127,"title":1128},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fstructured-json-logging-in-python-clis","Structured JSON Logging in Python CLIs",{"path":1130,"title":1131},"\u002F","Python CLI Toolcraft",{"path":1133,"title":1134},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading","CLI Startup Performance and Lazy Loading",{"path":1136,"title":1137},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Flazy-loading-subcommands-for-faster-startup","Lazy Loading Subcommands for Faster Startup",{"path":1139,"title":1140},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Fprofiling-python-cli-startup-time","Profiling Python CLI Startup Time",{"path":1142,"title":1143},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargparse-subparsers-for-subcommands","argparse Subparsers for Subcommands",{"path":1145,"title":1146},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse","Command-Line Parsing with argparse",{"path":1148,"title":1149},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fmigrating-from-argparse-to-typer","Migrating from argparse to Typer",{"path":1151,"title":1152},"\u002Fmodern-python-cli-frameworks-architecture","Python CLI Frameworks and Architecture",{"path":1154,"title":1155},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis","Plugin Architectures for Extensible CLIs",{"path":1157,"title":1158},"\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":1160,"title":1161},"\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":1163,"title":1164},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis","Structuring Multi-Command Python CLIs",{"path":1166,"title":1167},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fsharing-state-with-click-context-objects","Sharing State with Click Context Objects",{"path":1169,"title":1170},"\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":1172,"title":1173},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each","Typer vs Click: When to Use Each",{"path":1175,"title":1176},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Ftyper-callback-functions-explained","Typer callback functions explained",{"path":1178,"title":1179},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter","CLI Project Scaffolding with Cookiecutter",{"path":1181,"title":1182},"\u002Fproject-setup-dependency-management","Project Setup & Dependency Management",{"path":1184,"title":1185},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fautomating-changelogs-with-conventional-commits","Automating Changelogs with Conventional Commits",{"path":1187,"title":1188},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs","Managing CLI Versioning & Changelogs",{"path":1190,"title":1191},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fbuilding-wheels-and-sdists-for-python-clis","Building Wheels and sdists for Python CLIs",{"path":1193,"title":1013},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution",{"path":1065,"title":5},{"path":1196,"title":1026},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fpublishing-a-python-cli-to-pypi",{"path":1198,"title":1199},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development","Poetry Workflows for CLI Development",{"path":1201,"title":1202},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpoetry-entry-points-and-scripts-for-clis","Poetry Entry Points and Scripts for CLIs",{"path":1204,"title":1205},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects","Pre-commit Hooks for CLI Projects",{"path":1207,"title":1208},"\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":1210,"title":1211},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management","uv for Python CLI Dependency Management",{"path":1213,"title":1214},"\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":1216,"title":931},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002Fuv-tool-install-vs-pipx-for-clis",{"path":1218,"title":1219},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices","Python CLI Env Isolation Best Practices",{"path":1221,"title":1222},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fmanaging-virtual-environments-for-cross-platform-clis","Managing Python CLI Virtual Environments",1783281867199]