Article Overview
This article describes how to use uv, a Python project management tool.
I use uv for Python projects both at work and in personal projects, and have compiled the essentials I frequently use in daily development based on official documentation and my own experience.
The content is based on v0.6.14.
Since it has Stable status, I don’t think the usage will change dramatically, but please refer to the official documentation for the latest information.
Main Things Managed by uv
Python project management can be done with uv alone, no other tools are needed.
- Python version used in the project
- Dependencies (complete environment reproduction possible with lock files)
- Virtual environments
About Python Version Management
uv can manage Python itself and versions, claiming to be a replacement for pyenv.
As of v0.6.14, uv can manage Python versions per project, but it doesn’t manage system-level Python and is not a complete replacement for pyenv (though I personally think pyenv is no longer needed since you basically don’t need multiple system-level Pythons).
It plans to support system-level Python management in the future1
About Lock Files
A lock file is a record file used for complete environment reproduction, and in uv, uv.lock
is the lock file (this is not a concept originating from uv, but a concept adopted by package managers in other languages like npm).
It has the following characteristics:
- Manages versions down to transitive dependencies
- Cross-platform support
- Git-managed and manual editing is prohibited 2
Unlike managing libraries with pip + requirements.txt, environment reproducibility is cross-platform.
Example of pandas in uv.lock
:
[[package]]
name = "pandas"
version = "2.2.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "numpy" },
{ name = "python-dateutil" },
{ name = "pytz" },
{ name = "tzdata" },
]
sdist = { url = "https://files.pythonhosted.org/packages/9c/d6/9f8431bacc2e19dca897724cd097b1bb224a6ad5433784a44b587c7c13af/pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667", size = 4399213 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/64/22/3b8f4e0ed70644e85cfdcd57454686b9057c6c38d2f74fe4b8bc2527214a/pandas-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f00d1345d84d8c86a63e476bb4955e46458b304b9575dcf71102b5c705320015", size = 12477643 },
{ url = "https://files.pythonhosted.org/packages/e4/93/b3f5d1838500e22c8d793625da672f3eec046b1a99257666c94446969282/pandas-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3508d914817e153ad359d7e069d752cdd736a247c322d932eb89e6bc84217f28", size = 11281573 },
{ url = "https://files.pythonhosted.org/packages/f5/94/6c79b07f0e5aab1dcfa35a75f4817f5c4f677931d4234afcd75f0e6a66ca/pandas-2.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22a9d949bfc9a502d320aa04e5d02feab689d61da4e7764b62c30b991c42c5f0", size = 15196085 },
{ url = "https://files.pythonhosted.org/packages/e8/31/aa8da88ca0eadbabd0a639788a6da13bb2ff6edbbb9f29aa786450a30a91/pandas-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3a255b2c19987fbbe62a9dfd6cff7ff2aa9ccab3fc75218fd4b7530f01efa24", size = 12711809 },
{ url = "https://files.pythonhosted.org/packages/ee/7c/c6dbdb0cb2a4344cacfb8de1c5808ca885b2e4dcfde8008266608f9372af/pandas-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:800250ecdadb6d9c78eae4990da62743b857b470883fa27f652db8bdde7f6659", size = 16356316 },
{ url = "https://files.pythonhosted.org/packages/57/b7/8b757e7d92023b832869fa8881a992696a0bfe2e26f72c9ae9f255988d42/pandas-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6374c452ff3ec675a8f46fd9ab25c4ad0ba590b71cf0656f8b6daa5202bca3fb", size = 14022055 },
{ url = "https://files.pythonhosted.org/packages/3b/bc/4b18e2b8c002572c5a441a64826252ce5da2aa738855747247a971988043/pandas-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:61c5ad4043f791b61dd4752191d9f07f0ae412515d59ba8f005832a532f8736d", size = 11481175 },
{ url = "https://files.pythonhosted.org/packages/76/a3/a5d88146815e972d40d19247b2c162e88213ef51c7c25993942c39dbf41d/pandas-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b71f27954685ee685317063bf13c7709a7ba74fc996b84fc6821c59b0f06468", size = 12615650 },
{ url = "https://files.pythonhosted.org/packages/9c/8c/f0fd18f6140ddafc0c24122c8a964e48294acc579d47def376fef12bcb4a/pandas-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:38cf8125c40dae9d5acc10fa66af8ea6fdf760b2714ee482ca691fc66e6fcb18", size = 11290177 },
{ url = "https://files.pythonhosted.org/packages/ed/f9/e995754eab9c0f14c6777401f7eece0943840b7a9fc932221c19d1abee9f/pandas-2.2.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba96630bc17c875161df3818780af30e43be9b166ce51c9a18c1feae342906c2", size = 14651526 },
{ url = "https://files.pythonhosted.org/packages/25/b0/98d6ae2e1abac4f35230aa756005e8654649d305df9a28b16b9ae4353bff/pandas-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db71525a1538b30142094edb9adc10be3f3e176748cd7acc2240c2f2e5aa3a4", size = 11871013 },
{ url = "https://files.pythonhosted.org/packages/cc/57/0f72a10f9db6a4628744c8e8f0df4e6e21de01212c7c981d31e50ffc8328/pandas-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:15c0e1e02e93116177d29ff83e8b1619c93ddc9c49083f237d4312337a61165d", size = 15711620 },
{ url = "https://files.pythonhosted.org/packages/ab/5f/b38085618b950b79d2d9164a711c52b10aefc0ae6833b96f626b7021b2ed/pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a", size = 13098436 },
]
Virtual Environment
The virtual environment is not something unique to uv, it just uses venv.
(So it’s possible to execute Python in the virtual environment by manually activating the venv environment and using the python
command instead of the uv run
described later, though this is not recommended.)
The Python version used will be the one specified in .python-version
(at the time of virtual environment creation).
Comparison of Flow from Clone to Application Execution (Without uv / With uv)
Let me show examples of the flow after git clone
of a uv-managed project, along with cases not using uv.
When managing with pyenv, pip + requirements.txt and using venv directly, it would be something like this:
# Clone repository
git clone xxx.git
# Specify Python version if .python-version is not committed to repository
pyenv local 3.13.2
# Create virtual environment with venv and install packages
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
# Verify that python command points to .venv/bin/python and is the intended version
ls -l $(which python)
lrwxrwxrwx. 1 ec2-user ec2-user 48 Apr 11 02:24 /home/ec2-user/myproj/.venv/bin/python -> /home/ec2-user/.pyenv/versions/3.13.2/bin/python
# Execute
python example.py
This procedure has subtle traps.
If you don’t specify the Python version with pyenv before creating the venv environment, you won’t be able to use the Python version specified with pyenv local
, or if you forget to activate the venv environment and run pip install
, it will be installed globally on the system.
For uv-managed projects, it would be as follows:
# Clone repository
git clone xxx.git
# A venv virtual environment with libraries from uv.lock is automatically created
# using the Python version specified in .python-version,
# and example.py is executed within it
uv run example.py
Very simple, isn’t it?
Usage
https://docs.astral.sh/uv/getting-started/features/
The official page above serves as a cheat sheet, so if you’re stuck, run uv --help
or refer to the above.
Initialize Project with uv
https://docs.astral.sh/uv/guides/projects/
Initialize a project for uv management with uv init
.
main.py
, pyproject.toml
, .python-version
, and README.md
are created.
# Initialize as a project using Python 3.13.2
uv init -p 3.13.2
# Running the created main.py with uv run also creates virtual environment and uv.lock
uv run main.py
Python Management
https://docs.astral.sh/uv/concepts/python-versions/
Check Available/Installed Python Versions
uv python list
Output Example:
Installed versions show installation paths as follows:
$ uv python list
cpython-3.14.0a6-linux-x86_64-gnu
cpython-3.14.0a6+freethreaded-linux-x86_64-gnu
cpython-3.13.3-linux-x86_64-gnu
cpython-3.13.3+freethreaded-linux-x86_64-gnu
cpython-3.13.2-linux-x86_64-gnu /home/ec2-user/.local/share/uv/python/cpython-3.13.2-linux-x86_64-gnu/bin/python3.13
cpython-3.12.10-linux-x86_64-gnu
cpython-3.11.12-linux-x86_64-gnu
cpython-3.10.17-linux-x86_64-gnu
cpython-3.9.22-linux-x86_64-gnu
cpython-3.8.20-linux-x86_64-gnu
cpython-3.7.9-linux-x86_64-gnu
Install Python
uv python install
allows you to install Python that can be used via uv.
This makes it possible to use installed Python versions in uv-managed projects.
(※However, even without pre-installation, Python is automatically installed when needed with default settings, so explicit pre-installation is not mandatory.)
# Example of installing 3.13
uv python install 3.13
Output example:
$ uv python install 3.13
Installed Python 3.13.2 in 1.89s
+ cpython-3.13.2-linux-x86_64-gnu
Note:
As of v0.6.14, Python installed this way cannot be called directly with the python
command; you need to use uv run
or manually create a virtual environment with uv venv
, source .venv/bin/activate
, and then use the python
command (recommended is uv run
described later).
https://docs.astral.sh/uv/guides/install-python/#getting-started
Permanently Change Python Version Used by uv
Command
https://docs.astral.sh/uv/reference/cli/#uv-python-pin
uv python pin
permanently changes the Python version used by the uv
command.
(It rewrites .python-version
.)
# Example of using 3.13.2
uv python pin 3.13.2
Dependency Management
Add/Update Dependencies
uv add
3 performs the following:
- Install in venv virtual environment
- Add to dependencies in
pyproject.toml
- Update
uv.lock
# Add latest version of requests package
uv add requests
# Specify version
uv add 'requests==2.31.0'
# Add git dependency
uv add git+https://github.com/psf/requests
# Migrate from requirements.txt
uv add -r requirements.txt -c constraints.txt
# If not using constraints.txt, use this
uv add -r requirements.txt
# Adding --dev makes it a development dependency
# (added to dev in [dependency-groups] of pyproject.toml).
# Example of adding moto needed only for testing as development dependency
uv add moto --dev
# Updates are also possible with uv add --upgrade
uv add requests --upgrade
Note:
If library dependencies conflict and cannot be resolved, an error occurs (for example, when you want to use pandas 2.2.3 but a dependency requires pandas < 2.0.0).
In such cases, you can use the --frozen
flag of uv add
to install while ignoring dependency conflicts.
However, since it’s not written to uv.lock
, if you use it, I recommend understanding the risks (possibility of losing consistency between library dependencies, possibility of errors related to libraries with conflicting versions, etc.).
Note:
When using uv add
, not only the package you’re trying to add but the entire project’s dependencies are re-evaluated4.
Therefore, if something that failed dependency resolution but was forcibly added with --frozen
is included in pyproject.toml
dependencies, it will error even if there’s no problem with what you’re trying to uv add
.
In such cases, you should first remove the problematic item with uv remove
, then uv add
what you want to add, and re-add the removed item with uv add --frozen
.
Remove Dependencies
You can remove dependencies using uv remove
5.
# Remove requests package from dependencies
uv remove requests
# Add --dev when removing development dependencies
uv remove moto --dev
Python Execution
https://docs.astral.sh/uv/guides/scripts/
Use the uv run
command to execute Python in uv’s virtual environment.
If they don’t exist on first run, virtual environment and uv.lock
are created.6
# Execute example.py in uv.lock's virtual environment
uv run example.py
# You can also explicitly specify Python version when running scripts
uv run --python 3.10 example.py
# To run python -m unittest test/unit/test_main.py with uv
uv run python -m unittest test/unit/test_main.py
By the way, adding the -v
option displays uv’s internal operations in logs, which is useful for troubleshooting.
Sync Virtual Environment State with uv.lock
https://docs.astral.sh/uv/concepts/projects/sync/#syncing-the-environment
uv sync
can synchronize the virtual environment state with the contents of uv.lock
.
Since using uv run
guarantees that Python can be executed in the uv.lock
environment7, there’s basically no need to explicitly run uv sync
, but if you want to manually activate and use the venv environment, you can synchronize the virtual environment state with the contents of uv.lock
by running uv sync
as follows:
uv sync
source .venv/bin/activate
flask run -p 3000
python example.py
Tool-Related
Run Tools Without Installing
https://docs.astral.sh/uv/guides/tools/#running-tools
The uvx
command allows you to run tools without installing them.
(They are actually installed in a temporary environment for uvx
)
# Example of running ruff check
uvx ruff
# Example of running with ruff 0.3.0
uvx ruff@0.3.0 check
Install/Update Tools to bin
https://docs.astral.sh/uv/guides/tools/#installing-tools
For frequently used tools, you can install them to the bin directory in PATH with uv tool install
.
# Example of installing ruff
uv tool install ruff
# ruff is now available
ruff --version
# Tools installed this way don't become available as modules, so the following will fail
python -c "import ruff"
# Update ruff installed in bin to latest version
uv tool upgrade ruff
# Update ruff installed in bin to version 0.4 or higher
uv tool install ruff>=0.4
pip-Compatible CLI
https://docs.astral.sh/uv/getting-started/features/#the-pip-interface
Although rarely used, I’ll mention that manual environment and package management is possible with pip-compatible CLI.
It’s intended for use in legacy workflows that want to use uv with the pip interface, or cases that cannot be controlled with high-level commands.
Note:
Since it doesn’t aim to completely mimic pip, you may encounter behavioral differences. 8
Simply add uv
before traditional pip
commands.
For example:
uv pip install
uv pip list
uv pip uninstall
Virtual environment (venv) creation is possible with:
uv venv
Summary
The development experience with uv is very good, so please try it!