-> What is this?
-> How does this work?
-> How to install this?
-> How to run tests
-> Why did I make this?
-> Does this work with ...?
-> Notes and caveats
A small, pyenv-like shim layer for python that delegates interpreter
resolution to uv.
A "shim" in this context is a small script that intercepts the invocation of
a python interpreter and then dispatches to some auto-discovered python path.
The goal is to let you type python, python3.11, etc. and have it resolve
to the correct python path by using uv's python discovery rules. The result is
very similar to the result from shims installed by pyenv but with greater
visibility into more installed pythons (like the pythons installed by uv itself).
This project provides:
python— a shim script that interceptspython/python3.*invocations and resolves an appropriate interpreter usinguv python find.install.bash— an installer that copies the shim to~/.local/uv-python-shims/and creates symlinks forpython3and a set ofpython3.Xnames.tests/— a lightweight bash test harness with mockuv+ mock “python” executables.
References:
Requires: bash and uv installed and on your PATH
The shim runs uv python find to locate the best Python interpreter for the current
working directory:
-
The find operation first looks for any python version constraints defined within a
.python-versionfile or a project/workspacepyproject.tomlfile within the current directory or any parent directories. -
It then searches the following locations in order until it finds the first available python that satisfies the version constraints (or the first one found if there are no constraints):
- Closest non-activated virtual environment (activated environments bypass the shim entirely so they are always selected with no version constraints).
- UV-managed python installations in order from newest to oldest versions.
- The first suitable python found on the
PATH.
-
If a suitable python is found, that python is executed via its absolute path. If a suitable python is NOT found, the script will exit and display the error message given by uv.
If the shim is invoked via a symlink with a versioned name like python3.10
or python3.14, it enforces that major.minor. It does this by first checking
what uv would pick by default and if the resolved interpreter does not match
the requested 3.X, it reruns uv python find 3.X and uses that interpreter instead.
When invoking uv and executing the resolved python interpreter, the shim avoids
recursion by removing the shim directory from PATH.
There are a number of possible configuration flags that can modify the behavior
of uv python find. See the commented-out section near the top of the shim script
and uncomment the flags desired to control python discovery.
Requires: bash (macOS default bash 3.2 is fine)
From the project root:
bash ./install.bashBy default, this installs to:
~/.local/uv-python-shims/
Activate the shims by adding something like this to your shell startup file:
export PATH="$HOME/.local/uv-python-shims:$PATH"Common locations:
- bash:
~/.bashrc(and/or~/.bash_profile) - zsh:
~/.zshrc
Then restart your shell, or run the export line once in your current session.
Windows PATH: If you install this in a Windows environment with bash support (Git Bash / MSYS2 / Cygwin / WSL), be aware that the bash shell PATH update suggested above will affect bash sessions only. To use from PowerShell/cmd, you may also need to add the directory to Windows PATH.
which python
which python3.11
python --version
python3.11 --versionIf you have a .python-version in a directory, try running from inside that
directory and from a subdirectory to confirm .python-version discovery
behaves as expected.
If you have a project or workspace defined with a pyproject.toml that includes
a requires-python field, try running within that project directory or from a
subdirectory to confirm project/workspace discovery behaves as expected.
If you have a non-activated virtual environment installed in a .venv subdirectory
of a project, try running within the project directory or a subdirectory of that
project to confirm virtual environment discovery behaves as expected.
You can override the install destination or installer pacing:
DEST_DIR="$HOME/.local/uv-python-shims" bash ./install.bash
PAUSE=0 bash ./install.bash
PAUSE=0.25 bash ./install.bashYou can also change which python3.X symlinks are created by setting MINOR_VERSIONS
and instead of symlinks the installer can create copies or wrapper scripts:
MINOR_VERSIONS="10 11 12" bash ./install.bash
SHIM_MODE=copy bash ./install.bash
SHIM_MODE=wrapper bash ./install.bashBefore installing the shims, the installer wipes the target directory. This is
done to allow for easy updates and experimentation. There are guardrails to
help prevent unintentional destructive wipes. If the target directory looks fishy
or unexpected items are seen in the target directory, the script will exit
with an error message. Set UV_PYTHON_SHIMS_FORCE to override the guardrails:
UV_PYTHON_SHIMS_FORCE=1 bash ./install.bashThe tests are written in bash and do not require a real uv install.
They work by:
- copying the shim under test into a temp directory
- placing mock stubs on
PATH(tests/bin/uv, plus fake interpreters) - asserting calls/behavior via logs and captured output
From the project root:
bash tests/run.bashThe speed and ease of python interpreter installations and version management via the 'uv' tool is very attractive but it doesn't natively support the python shim behavior that I'm used to with 'pyenv'. That shim behavior is particularly attractive if you need to work with multiple projects, many of which do not use 'uv'.
I appreciate the convenience of managing which pythons to use depending on directory context and not locking myself into using 'uv' python invocation semantics everywhere I need this. It's also a bit of a drag to explain why I'm using 'uv' commands in a non-uv context when I'm collaborating with some colleagues on a non-uv managed project.
Keeping python version management conceptually separate from project and virtualenv management is also a core good practice that I try to promote in all my engagements.
There are others who have been asking for variations of combining the features of pyenv-like python shims with uv-like python management. As of today, Feb 9, 2026, I see the following open tickets that are related:
-
In the
pyenvproject,
pyenv/pyenv#2334 -
In the
uvproject,
astral-sh/uv#6265
astral-sh/uv#7677
I'm guessing 'uv' will grow this feature eventually so my solution here is probably just temporary... unless I don't like whatever 'uv' comes up with.
There are also alternative solutions possible using tools like
direnv or mise. I already use
direnv a lot for auto-activating a project's virtualenv but I wanted the
python version switching to be less coupled from project management.
Many tools will "just work" as long as they select the python found from the current
PATH. Some tools can be helped along to do this more consistently.
-
uv: In most cases,
uvwill by default either attempt to use the python shim found fromPATHor follow the same python discovery heuristic that the shim uses. -
hatch: Set
HATCH_PYTHON=pythonin your environment to tell hatch to default to using the python shim found fromPATH. -
poetry: Set
poetry config virtualenvs.prefer-active-python true --globalto configure poetry to default to using the python shim found fromPATH. -
pyenv: The
uv-python-shimssolution is meant to be a replacement forpyenvshims. However,uvinstalls pre-built pythons whilepyenvbuilds pythons from source. Pre-built python installation is much faster than building from source so they are preferred in most usecases. But if you still wish to build from source, you can continue usingpyenvfor this purpose. Just ensure that theuv-python-shimsdirectory is found in the PATH before thepyenvshims directory. Theuv python findwill then be able to discover the pyenv-managed pythons as long as a suitable uv-managed python matching the version constraints is not found first. -
pixi: At this time, I know of no way to configure
pixito use a uv-managed python or to default to using the python shim found fromPATH. Thepixitool manages pythons entirely within the pixi-managed virtual environment similar to howcondaworks. -
pipx: By default, the python selection when using
pipxis deterministic and is not affected by the presence of.python-versionfiles or virtual environments. This makes sense since having all your pipx-installed tools use different pythons can be surprising and difficult to manage. If despite this, you still want to use the dynamic python discovery made possible with these uv-python-shims, you can add something like the following to your shell startup file.export PIPX_DEFAULT_PYTHON="$(command -v python)" -
uv tool: UV's equivalent to
pipx installisuv tool install. Both install globally-accessible python-based tools in isolated virtual environments but there is a subtle difference between how the two select the python version installed in that environment. Theuv toolmechanism appears to follow the same discovery heuristic as the rest ofuvalthough it ignores version constraints defined in "local" configuration (local.python-versionfiles or project/workspacepyproject.tomlfiles). However the presence of an activated virtual environment or the existence of a non-activated virtual environment found in the current directory or parents may result in the selection of the python from that environment. This feels like a possible footgun if you expect the more deterministic behavior followed bypipx. To get the more deterministic pipx-like behavior, you can setUV_PYTHONenvironment variable to point to the desired default python path. UV_PYTHON can be either a version constraint or a path to a python executable. However this will set the default python for alluvoperations which is likely undesirable. An alternative is just to define a special alias/function within your shell startup file just foruv tool install:uv-tool-install() { UV_PYTHON=[...] uv tool install "$@" }
-
Caution:
uv-python-shimsshould NOT be used as a solution for auto-activating a project's virtual environment. It's not suitable for that task. This shim will only auto-discover the environment's python interpreter and will not directly expose any other executables within that environment. Either manually activate using the mechanism recommended for your environment manager, or possibly auto-activate the environment with a solution likedirenvormise. -
This shim delegates nearly all “what Python should I use?” logic to
uv. Ifuvchanges discovery rules or output formats, the shim may need tweaks. -
The
python3.Xbehavior is intentionally “major.minor enforcement” (not micro/patch). -
If you also want shims for other tools (
pip,pytest, etc.), it should be straightforward to add siblings that run"$resolved_python" -m pip ...or similar. I don't need this so I didn't bother adding it. Also check the "Caution" note above. If you need more than just a python shim, you probably would do better with a different solution.
Licensed under the MIT License; see LICENSE.