Bug fixes
- A
pipx runthat names an app the package does not provide, such aspipx run --spec black blackk, no longer expires the cached environment, so the corrected command reuses it instead of rebuilding. A failed venv creation or install still expires the entry (#2354) by @gaborbernat. - A second Ctrl-C during
pipx reinstall-all,upgrade-allorinstall-allno longer hangs pipx. A signal handled just after a worker took the shared lock could leave it held, so every other task waited forever (#2348) by @gaborbernat. - A self-managed pipx no longer adopts a recorded default interpreter that no longer backs its own environment, such as the
python3of a bootstrap environment removed afterpipx install pipx --python python3. pipx failed every command with aPIPX_DEFAULT_PYTHONerror, or built new environments on whatever appeared at that path later (#2245) by @gaborbernat. - Apply
--cooldownandPIPX_COOLDOWNto the shared pip that the pip backend installs or refreshes during the command. pipx used to fetch the newest pip, even one uploaded minutes earlier, while it held back the package itself. A new shared environment now starts from the oldest pip pipx supports and upgrades only to a release older than the cooldown (#2311) by @gaborbernat. - Finish restoring an environment when Ctrl-C,
SIGTERMorSIGHUParrives while a failed or interruptedpipx reinstall,install --force,inject,upgradeormanifest syncrolls it back. The rollback deletes the half-built environment and then moves the backup in, and a signal between the two steps left the environment deleted and its only copy in the trash, which the next pipx command emptied. pipx now holds such a signal until the backup is back in place and acts on it afterwards (#2329) by @gaborbernat. - For an editable install, pipx now copies only the man pages that resolve inside the project directory. Before, it followed absolute paths,
..and symlinks, and a wheel installed by name could ship its own editabledirect_url.jsonto make pipx copy any readable file into the environment and link it as a man page (#2268) by @gaborbernat. - Ignore apps, man pages and completions whose name is not a plain file name, such as
.., and never delete a directory to make room for an app link, sopipx reinstallcan no longer remove the parent ofPIPX_BIN_DIR(#2228) by @gaborbernat. - Ignore recorded
venv_argsother than--system-site-packages, with a warning, when reading a snapshot orpipx_metadata.json, sopipx install-allandpipx reinstallno longer pass an edited--clear <dir>or an extra directory topython -m venv(#2239) by @gaborbernat. - Installing an editable project whose man page or project directory is a looping symlink no longer crashes pipx on Python 3.11 and 3.12; pipx skips that man page (#2334) by @gaborbernat.
- Keep
pipx reinstall-all,upgrade-all, andinstall-allwaiting for the environments they change when pipx can no longer write their output, as withpipx reinstall-all 2>&1 | heador a closed stderr. pipx used to log a traceback and return while it still rebuilt environments in the background, so an interrupt then skipped the rollback, and an environment waiting for its turn hung (#2290) by @gaborbernat. - Keep a command, man page or completion link that another package took over while
pipx reinstall,reinstall-all,uninjectoruninstall-allwas running. These commands gathered the environment's links first and deleted them by name later, so a name another package claimed in between, for example in the same parallelreinstall-all, disappeared without a message. pipx now deletes a link only if it still points into the environment by Bernát Gábor. - Keep pipx's
pipx_metadata.jsonwhen the installer replaces it with a wheel's data file of the same name. A failedpipx install --force,pipx upgradeorpipx inject, or apipx runpip APP installof a dependency, used to leave the wheel's copy in place, and later commands read its index arguments, spec and suffix as pipx's own. pipx now puts its record back after every installer run (#2269) by @gaborbernat. - Keep standalone Python archive members inside the unpack directory on Python 3.11.0 to 3.11.3, which lack
tarfile.data_filter. pipx's own extractor checked member names as text, so a member whose path went through a symlink unpacked earlier from the same archive was written wherever that link led. It now resolves each path through the links already on disk, extracts under normalized names like the data filter does, and refuses a hard link to a symlink (#2279) by @gaborbernat. - Keep the apps, manual pages, and completions of injected packages exposed when
pipx manifest syncreinstalls an existing environment, and expose the environment again when the sync is interrupted. pipx used to drop those links without a message, so another package could take the freed names without--force, and an interrupted sync left the rolled-back environment unexposed (#2273) by @gaborbernat. - Keep the environment when
pipx install --forceorpipx install-all --forcecannot move it aside before aRequires-Pythonrebuild. On Windows the move fails while any file inside the environment is open, and pipx then deleted the environment and reported aFileNotFoundErrorinstead of the real error. pipx now retries the move the waypipx reinstalldoes, and if it still fails it leaves the environment in place and reports the original error (#2350) by @gaborbernat. - Keep the recorded source of a local wheel when
pipx runpip <venv> installabbreviates a value-taking pip option whose value names the package, such as--only-bin <name>for--only-binary <name>, and record an install through an abbreviated--editable, such as--edit <path>, as editable (#2225) by @gaborbernat. - Keep the rollback backup of a running
pipx reinstall,reinstall-all,install --force,upgradeorinjectwhen another pipx command starts. Every command used to empty the trash at startup, so apipx listrun meanwhile deleted the backup, and when the first command then failed or was interrupted it lost the environment with aFileNotFoundError. pipx now skips a backup while its environment's lock is held and removes it only once orphaned (#2298) by @gaborbernat. - Leave a command, man page or completion that the environment did not expose before with its owner when
pipx reinstall,reinstall-all,interpreter upgrade, the repair step ofpipx upgrade, or a repeatedpipx manifest syncrebuilds the environment. These used to replace such a file as if--forcehad been passed, deleting a user's own script or repointing another environment's command, althoughpipx installhad left it alone with a warning. A name two environments both declare now stays with the one that held it, instead of going to whicheverreinstall-allrebuilt last (#2321) by @gaborbernat. - Leave an existing environment alone in
pipx install-allwithout--force. pipx said it was not modifying the environment, then injected the snapshot'sinjected_packagesinto it anyway. Pass--forceto bring existing environments in line with the snapshot (#2333) by @gaborbernat. - Let an environment take over an app, manual page, or completion name whose link points to a file that another installed environment no longer ships. pipx before 1.18.0 left such links behind when an upgrade dropped an app, and pipx kept them as collisions that only
--forcecleared. A link counts as left over when the environment it points into no longer lists the file in its metadata. The collision warning for a link whose target is missing now says so (#2356) by @gaborbernat. - Mask URL passwords and tokens in the remaining places that printed or logged them as typed: the
Injecting packageslog line, the--output jsonerror fields ofinject,installanduninject, the progress lines ofpipx runscript dependencies and--preinstall, the local source thatpipx upgradereports, invalid requirement errors from manifests and inline script metadata,--pip-argsrejected by the uv backend ofpipx run, the "looks like a path" error, and installer output that pipx captures and logs (#2252) by @gaborbernat. - Mask a URL password that contains a literal space or tab, such as
--index-url "http://alice:correct horse@host/simple", in the command log,--verboseoutput and messages; pipx now masks each argument of a command line before joining it (#2241) by @gaborbernat. - Mask the user part of URL credentials that contain a colon, so a token written as
https://TOKEN:@hostorTOKEN:x-oauth-basic@no longer shows in the command log,--verboseoutput and messages. Such URLs now show as****:****@, which also hides a plain user name such asalice(#2244) by @gaborbernat. - On Windows under the uv backend,
pipx run script.pynow releases the run-cache lock before the script starts. pipx held it until the script exited, so a second run of the same script waited without a message, and a script that ran itself through pipx never finished (#2352) by @gaborbernat. - Refuse
pipx install --lockandpipx manifest syncwith a lock under the pip backend when the environment's pip is older than 26.1. That pip readpylock.tomlas a requirements file: a real lock failed with a confusingInvalid requirementerror, and a file that was not a lock had its lines applied as pip options. The error now names the pip version and suggestspipx upgrade-shared(#2289) by @gaborbernat. - Refuse a URL spec whose installed
METADATAnames a different project than its.dist-infodirectory. A wheel could rewrite that name through its data files and makepipx install --upgrade URLinstall it into another application's environment (#2236) by @gaborbernat. - Refuse a
--pythonvalue that is not a version before--fetch-python=missingor--fetch-python=alwayslooks in the standalone interpreter cache. pipx joined the raw value ontoPIPX_HOME/pyand deleted the result as a failed earlier download, so a directory path,..,.or a barepythonremoved that directory,PIPX_HOMEor the whole interpreter cache (#2262) by @gaborbernat. - Refuse an empty or whitespace-only
--index-urlinstead of silently installing from the default index (#2217) by @Svastikkka. - Refuse an empty or whitespace-only index option inside
--pip-args(--index-url,-i,--extra-index-url,--default-index,--index), and under the uv backend an empty index recorded by an earlier install, instead of letting uv install from the default index (#2253) by @gaborbernat. - Refuse an environment name that is empty or a path instead of acting outside
$PIPX_HOME/venvs.pipx uninstall "", for example from an unset shell variable, used to remove every environment and exit 0, and aninstall-allsnapshot, manifest tool name or reported package name holding a path built the environment at that path. pipx now stops withInvalid environment name(#2331) by @gaborbernat. - Refuse to overwrite
/etc/profile.d/pipx.shor/etc/paths.d/pipxinpipx ensurepath --globalwhen the file holds content pipx did not write, such as an administrator'sPIPX_GLOBAL_HOMEorPIP_INDEX_URLexports. pipx used to replace the whole file with itsPATHentry and report success. It now leaves the file and its mode alone and exits with an error that names--force, which still replaces the file. pipx replaces a file that holds only an earlier pipx entry, as before (#2315) by @gaborbernat. - Refuse to run
pipx manifest lockwhen a tool'slockpath resolves outside the manifest directory, instead of replacing apylock.tomlelsewhere on disk and discarding its previous contents.pipx manifest syncstill reads such locks (#2223) by @gaborbernat. - Refuse to run pip or uv in an environment whose
pyvenv.cfgis gone, and suggestpipx reinstall. Without that file the environment'spythonlink starts the interpreter it was created from, so apipx upgradeafter a wheel had shipped and then droppedpyvenv.cfginstalled the package into that interpreter's prefix and reported success (#2326) by @gaborbernat. - Reject
--global,--skip-maintenance,--verboseand--quietplaced betweenmanifest,interpreterorcacheand their subcommand, as inpipx manifest --global sync. pipx accepted the option there and then dropped it, sopipx manifest --global sync --prunesynced and pruned the per-user store with exit 0. Put the option after the subcommand, as inpipx manifest sync --global(#2320) by @gaborbernat. - Report an environment whose
pipx_metadata.jsonthe current user cannot read as unreadable inpipx list, instead of as missing metadata from a pipx older than 0.15 that needs reinstalling. A non-root user sees this for every environment ofpipx list --global, because pipx keeps the file owner-only: it can hold index credentials.pipx liststill lists the environments the user can read (#2323) by @gaborbernat. - Resolve every relative path given to a pip option, such as
--cert ca.pem,-r requirements.txt,--logor--cache-dir, against the directory pipx was started in, also when the option is clustered or abbreviated, as in-qr requirements.txtor--find-l wheels. The shared pip refresh runs inside the shared libraries directory, sopipx upgrade-shared --pip-args='--cert=ca.pem'failed withinvalid path: ca.pem; installs, upgrades andpipx runnow also resolve these values against the starting directory (#2351) by @gaborbernat. - Restore every earlier lock when
pipx manifest lockfails because a directory sits at a later tool's lock path. pipx used to stop rolling back at that directory, keep the new locks of the tools before it, and delete their previous versions (#2260) by @gaborbernat. - Restore the existing environment when
pipx install --forceorpipx install-all --forcerebuilds it on another interpreter because of the package'sRequires-Pythonand the rebuild fails. pipx used to delete the environment first and leave behind an empty one without its app, injected packages, orpipx_metadata.json, while it reported it had kept the environment. It now moves the environment to its trash before the rebuild and moves it back on failure (#2229) by @gaborbernat. - Stop
pipx ensurepath --global --forcefrom crashing withUnicodeDecodeErrorwhen/etc/profile.d/pipx.shor/etc/paths.d/pipxholds bytes that are not UTF-8.--forcenow replaces the file without reading it, and without--forcesuch a file gets the same error that names--forceas any other file pipx did not write (#2355) by @gaborbernat. - Stop
pipx interpreter upgradefrom moving an environment to another Python found onPATHwhen the newest release of its package has aRequires-Pythonthat rules out the new standalone build. The command reported the patch upgrade as done while the environment now ran on, for example,/usr/bin/python3.11. It now fails, restores the environment on its standalone build, and exits 1 (#2301) by @gaborbernat. - Stop
pipx upgrade-allfrom moving an app, manual page, or completion that two environments expose to the other environment while the installer of its owner is replacing the linked file. pipx treated any link whose target was missing as left over and replaced it without--force; a missing target inside another environment that still exists now counts as a collision (#2303) by @gaborbernat. - Stop a self-managed pipx (
pipx install pipx) started through its own environment's script from taking that environment'sbindirectory forPIPX_BIN_DIRwhen the path to the script holds..or a symlinked directory, such as../share/pipx/venvs/pipx/bin/pipxfrom~/.local/bin. Such a command linked the apps it installed into pipx's own environment, offPATHand onto pipx's own import path. pipx now recognizes the exposed link by real locations, which also works when you reach the pipx home through a symlink (#2309) by @gaborbernat. - Stop pipx from importing modules out of the directory of its own launcher script, or out of the working directory under
python -m pipx, oncepipx.__main__starts. Afterpip install --user pipxthat directory is~/.local/bin, where pipx links apps, so pipx imported ajson.pyorshlex.pyscript a package shipped there in place of the standard library module, and an exposedjson.pybroke every later pipx command,pipx uninstallincluded. For modules pip's launcher imports before that point, such asreandenum, pipx refuses to expose apps named after standard library modules. Programs that importpipx.__main__no longer see their script directory onsys.patheither (#2316) by @gaborbernat. - Stop recording pip options that name a one-shot input or an output location, such as
-r requirements.txt,--log,--report,--cache-diror--target: they apply to the command they were given on, and a laterpipx upgradeorpipx reinstallno longer reads or writes those paths. pipx reads pip arguments as pip does, so a clustered short option such as-qr requirements.txtor an abbreviation such as--log-f pip.logcounts too. On POSIX,pipx upgrade,pipx reinstalland the other commands that replay recorded arguments stop with an error, before the installer runs and without changing the record, when a recorded--find-links,--constraint,--build-constraint,--cert,--client-cert, orfile://or local-path index URL points at a path another account could change. Pass the options again with--pip-argsto use them for that run (#2359) by @gaborbernat. - Stop two pipx commands that fetch the same standalone Python at once from leaving an interpreter with part of its standard library missing in
PIPX_HOME/py. Both staged the build in one fixed directory and each deleted the other's half-copied tree as a leftover; pipx now holds a per-version lock while it fetches, so the second command waits and reuses the first one's build.pipx interpreter list,pruneandupgradealso skip the staging and backup directories of a fetch in progress instead of treating them as interpreters. An interpreter that an earlier fetch already left incomplete still needs deleting by hand (#2293) by @gaborbernat. - The pip backend now removes
pip.conf(pip.inion Windows) from the root of an environment before it starts pip there. A wheel could ship that file as a data file, and pip read it as its site configuration, so atargetor index in it made a laterpipx upgradeorpipx injectinstall outside the environment or from another index (#2264) by @gaborbernat. - Under the uv backend,
--index-url(-i) now reaches uv as--default-index. uv letUV_DEFAULT_INDEXor a default[[index]]entry override a command-line--index-url, sopipx run,install,upgradeandoutdatedused the default index instead of the one named on the command line (#2247) by @gaborbernat. - Under the uv backend,
pipx run script.pynow keeps the script's environment in pipx's run cache, keyed to the script's path, the interpreter,--pip-argsand the cooldown. uv kept one environment per script path and reused it while it satisfied the script, so a changed--index-url,--pip-argsor--cooldown, or--refresh, still ran what an earlier run installed.pipx cache purgeand the 14-day expiry now cover these environments (#2274) by @gaborbernat. - Under the uv backend,
pipx runnow puts--before the app name it hands touv tool run. An app word that started with a single dash, such aspipx run -- -ihttp://host/simple demodep, was read by uv as one of its own options, and uv installed and ran the next word as the package (#2299) by @gaborbernat. - Under the uv backend,
pipx upgrade,upgrade-allandinstall --upgradenow pass uv--upgrade-package NAMEinstead of--upgrade, so they upgrade only the named package and leave its dependencies alone unless the new release needs others, as pip's defaultonly-if-neededstrategy does. Before, uv upgraded every dependency, replacing packages injected withpipx inject --forceand pinned withpipx pin --injected-only(#2248) by @gaborbernat. - When
pipx installorpipx run --specunder the pip backend has to install a URL, VCS or sdist spec to learn its name, the shared pip it installs along the way now comes from the command's--index-urland--pip-argsinstead of pip's default index (#2275) by @gaborbernat. - When an environment holds two
.dist-inforecords with the same name, pipx now reads the first one onsys.path, the one the environment's interpreter uses. Since 1.15.1 it read the last one, so--system-site-packageswith a different version of the package in the base interpreter reported that version and exposed its commands (#2258) by @gaborbernat. pipx install --preinstallandpipx run --withnow refuse a value that starts with-. Written as--with=--find-links=DIRor--preinstall=-rFILE, such a value reached pip oruv pip installas an installer option and changed where packages came from, instead of failing like the main package spec does. Pass installer options with--pip-args(#2307) by @gaborbernat.pipx install-all --backend uvnow translates index options that a pip recording spells as an abbreviation pip accepts, such as--index-u URL, instead of dropping them and installing from the default index. A recorded pip--index URLnow becomes uv--default-index URL, matching how pip read it (#2232) by @gaborbernat.pipx installwith several packages no longer builds every environment on the Python it picked for one package whoseRequires-Pythonrules out the default interpreter; the other packages keep the default Python (#2222) by @gaborbernat.pipx list --outdatedandpipx upgrade-allnow check uv environments against the index recorded with uv's option names (--default-index,--index,--allow-insecure-host,--index-strategy,--keyring-provider) instead of uv's default index (#2220) by @gaborbernat.pipx reinstall,pipx reinstall-allandpipx repairnow refuse an environment whosepipx_metadata.jsonis unreadable or was lost to a killed install, instead of rebuilding it from the default index under the directory name.pipx installmarks the environment unfinished until it writes the record, andpipx uninstallmoves the environment to its trash before deleting it, so a killed uninstall no longer leaves a venv without its record (#2234) by @gaborbernat.pipx run --withnow installs the--withpackages in the same installer call as the app. pipx skipped a--withnaming a package the app already depends on, such as--with 'dep==1.0', as already installed, so the app ran with the newest release on the pip backend. A--withnaming the app with an extra no longer links the cached environment's commands intoPIPX_BIN_DIR(#2266) by @gaborbernat.pipx run script.pynow expires its cache entry when creating the environment fails or is interrupted, not only when installing the script's dependencies does. A Ctrl-C duringpython -m venvleft an empty cache directory, and every later run of the script reused it and failed withFileNotFoundErroruntil the entry expired (#2294) by @gaborbernat.pipx runandpipx installnow fail when a script download ends before itsContent-Length. Before, pipx ran or installed the part that arrived and could exit 0 (#2251) by @gaborbernat.pipx runandpipx installnow refuse anhttporhttpsscript URL with credentials, naming it with the password masked. Before, a URL such ashttps://user:password@host/tool.pyfailed with an error that printed the password in clear text and wrote it to the log.pipx installalso reports a malformed script URL as an error instead of a traceback (#2249) by @gaborbernat.pipx runno longer keys its cache on thePIP_*andUV_*variables that cannot change what the installer resolves or installs: timeouts, retries, parallelism, verbosity, progress and color output, cache directories, and theUV_RUN_RECURSION_DEPTHthatuv runsets for its children. Setting one of them rebuilt the environment for an unchanged command. Every other variable with those prefixes stays in the key, whatever its case or-/_spelling, since pip readsPIP_index_urlthe same asPIP_INDEX_URL(#2353) by @gaborbernat.pipx runnow includes the installer's environment variables (PIP_*, orUV_*under the uv backend) in its cache key. A run after changingPIP_INDEX_URL,PIP_FIND_LINKSorUV_INDEX_URLreused the environment built from the previous index for up to 14 days (#2282) by @gaborbernat.pipx runnow keys its cache on the absolute path of a local--spec,--withor--find-linksvalue. The same relative path typed in two directories, such as--spec ./dist/tool-1.0-py3-none-any.whl, shared one environment, so the second directory ran the first directory's build by Bernát Gábor.pipx runnow names the script URL, with credentials masked, when a download fails, aspipx installalready did.pipx installof a script URL whose body does not decode in its declared charset, or declares an unknown one, now reports an error instead of a traceback, aspipx runalready did (#2255) by @gaborbernat.pipx runpip <venv> installnow records a relative--find-links(-f) path resolved against the directory you ran it from. Before, a laterpipx upgradeorpipx reinstallread it against the environment's directory, failed withFailed to read --find-links directoryorNo matching distribution found, and rolled back (#2349) by @gaborbernat.pipx runpipstarts pip or uv in your working directory again instead of the environment's root, so relative paths such as-r requirements.txt,-c constraints.txtor./dist/app.whlname your files. Since 1.12.0 the installer looked them up in the environment, where they were missing or named a data file an installed wheel had put there (#2318) by @gaborbernat.pipx uninjectno longer removes apps, man pages and completions that the main package or another injected package of the environment still exposes, such as a command of a dependency both packages share under--include-deps(#2296) by @gaborbernat.pipx uninjectnow ignores installed distributions whose metadataNameis not a valid project name when it looks for orphaned dependencies to remove. A name such as-t/some/diror-rFILEreacheduv pip uninstallorpip uninstallas an option, and with uv could delete packages from a directory outside pipx (#2306) by @gaborbernat.- pipx no longer crashes on Python 3.11 and 3.12 when a package's
installed-files.txtlists a looping symlink, and skips that entry instead (#2336) by @gaborbernat. - pipx no longer exposes an app whose name is a standard library module or
pipxplus an import suffix, such asenum.py,re.pycorpipx.py, or a directory named after one, and warns instead. Python searches the directory of the script it starts first, so in~/.local/binsuch an app replaced the module for pip'spipxlauncher, which importsreand thenpipxbefore pipx can drop that directory fromsys.path, and for every other Python script installed there (#2342) by @gaborbernat. - pipx no longer reads
installed-files.txtfrom a distribution that has aRECORD, matchingimportlib.metadata. A dependency, or a package injected without--include-apps, could ship that file into the main package's.dist-infoand get its own apps, man pages and completions exposed without--include-deps(#2277) by @gaborbernat. - pipx now adds
-Eto an app script's shebang by writing a new file and renaming it over the script, instead of writing into the existing file. With the uv backend the script can be a hard link or symlink into uv's cache, so the in-place write also changed the cache entry and the same script in every other environment uv had installed that wheel into (#2285) by @gaborbernat. - pipx now reads a package's
entry_points.txtonly when that package'sRECORDlists it, or when the package has noRECORD. A dependency could ship that file into the.dist-infoof a main package that has no entry points of its own and get its apps exposed without--include-deps(#2340) by @gaborbernat.
Documentation
- Document what the security policy covers: which inputs pipx trusts, and which reports count as vulnerabilities (#2198) by @gaborbernat.
Security
- A bare package name such as
pipx install pycowsaynow always means the package on the index. A file of that name without a suffix in the working directory no longer gets installed as a PEP 723 script; pass a path such as./pycowsayto install one. URL specs no longer pick up a script at the matching relative path below the working directory either (#2243) by @gaborbernat. - Ask the Python Launcher for Unix for a
--pythonvalue only when it is anXorX.Yversion. The launcher ran any other value, such as3.14.8or3.13t, on the first interpreter its search found, which could be a.venvin a parent of the current directory (#2242) by @gaborbernat. - Create the self-managed bootstrap environment in the installation guide with
mktemp -dinstead of the fixed/tmp/bootstrappath, which another local user could create first and plant code in that ran as the reader, and as root at thesudostep (#2219) by @gaborbernat. - Look up a command name in
PIPX_DEFAULT_PYTHON, such aspython3, onPATHonly: pipx no longer uses a file of that name in the current directory as the default interpreter. Write./python3to pick that file on purpose (#2226) by @gaborbernat. - Pass the absolute path
PATHlookup found for--python python3.11on to the backend instead of the bare name. uv read the bare name as a version request and could pick a.venvinterpreter from a parent of the current directory. A file in the current directory named like the value, such as./3.11, no longer counts as a path either (#2240) by @gaborbernat. - Read a package name from a local
pyproject.tomlor wheel only for local path specs. A URL spec such ashttps://host/app.whlno longer takes its name, and with it the target environment, from a file at the matching relative path below the working directory (#2238) by @gaborbernat. - Record relative
--find-linksand--constraintvalues passed topipx upgrade --pip-argsandpipx runscripts as absolute paths, resolve relative values already inpipx_metadata.jsonagainst the environment's directory, and run the shared pip refresh inside the shared libraries directory, so a laterpipx upgradeorpipx reinstallno longer reads wheels from whatever directory you start it in (#2233) by @gaborbernat. - Record the index options of a
pipx runpip <venv> installthat replaces the main package, so a laterpipx upgradeorpipx reinstallasks the same index instead of resolving the bare name on the default one (#2231) by @gaborbernat. - Start scripts read from a URL or a named pipe,
pipx.runentry points, and apps run with--python-argswithpython -Pso a module in the current directory, such as a plantedjson.py, no longer shadows what the program imports (#2221) by @gaborbernat. - Stop
pipx reinstallfrom replacing the main package while it replays injected packages. The replay passed--force-reinstall, so an injected package that depends on the main package made the installer fetch the main package again by name from the injected package's index and install that release over the recorded source.pipx inject --forcenow passes--force-reinstallonly when the package is already injected, and under the uv backend reinstalls just that package with--reinstall-package(#2227) by @gaborbernat. - Stop
pipx runfrom retrying a failed uv install with pip. A venv directory withoutpipx_metadata.jsonno longer counts as recorded with the pip backend, so it resolves its backend like a new venv, and apipx runwhose install fails now expires its cache entry so the next run rebuilds it. pip ignoresUV_INDEX_URL, so the retry fetched the package from pip's default index instead of the private one (#2235) by @gaborbernat. - The uv backend no longer applies a
uv.tomlorpyproject.tomlfound in the directory uv runs in, the script's directory, or their parents, such as one another local account wrote to a shared/tmp. pipx now points uv's project-config search atPIPX_HOMEthroughUV_PROJECT, and through--projectforuv run --script. Your user and systemuv.tomlandUV_CONFIG_FILEstill apply. KeepPIPX_HOMEout of directories other accounts can write (#2246) by @gaborbernat. - When
pipx install --forceorpipx manifest syncrebuilds an existing environment, either on another Python because the release'sRequires-Pythonrules out the current one or to apply a lock file, keep the backend recorded for that environment. The rebuild used to pick the backend as for a new environment, so a pip-backed environment could move to uv and install from uv's default index instead of the one set inPIP_INDEX_URLorpip.conf(#2237) by @gaborbernat. pipx inject ENV 'APP[extra]'now adds the extra to the main package's recorded source instead of replacing it with the bare nameAPP[extra]. An app installed from a wheel, a directory or a URL no longer turns into the index's project of the same name on the nextpipx upgradeorpipx reinstall. Extras accumulate with the ones already recorded, and a main-package spec that changes the version, path or URL needs--force(#2250) by @gaborbernat.pipx manifest syncnow keeps the installer and venv options recorded for an environment that already exists, such as--index-url. It used to reinstall the tool from the default index and erase the recorded options (#2224) by @gaborbernat.- pipx now creates its log directory readable by its owner only (
0700), tightens an existing one, and writescmd_*.logandcmd_*_{pip,uv}_errors.logas0600. The logs record the full command line, including the argumentspipx runandpipx execpass to the application, and in a--globalstore under/opt/pipx/logsany local account could read them (#2230) by @gaborbernat.
New Contributors
- @Svastikkka made their first contribution in #2217