Essential uv Usage for Daily Development

essential-uv-usage-for-daily-development

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 add3 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 remove5.

# 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

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!

  1. https://github.com/astral-sh/uv/issues/6265 ↩

  2. https://docs.astral.sh/uv/guides/projects/#uvlock ↩

  3. https://docs.astral.sh/uv/reference/cli/#uv-add ↩

  4. https://docs.astral.sh/uv/reference/resolver-internals/ ↩

  5. https://docs.astral.sh/uv/reference/cli/#uv-remove ↩

  6. https://docs.astral.sh/uv/guides/projects/#project-structure ↩

  7. https://docs.astral.sh/uv/guides/projects/#running-commands ↩

  8. https://docs.astral.sh/uv/pip/compatibility/ ↩

Total
0
Shares
Leave a Reply

Your email address will not be published. Required fields are marked *

Previous Post
maharashtra’s-first-digital-forest-by-bajaj-institute-of-technology,-wardha

Maharashtra’s first Digital Forest by Bajaj Institute of technology, Wardha

Next Post
goodbye-lag,-hello-smoothness-my-journey-exploring-efficient-web-development-frameworks

Goodbye Lag, Hello Smoothness My Journey Exploring Efficient Web Development Frameworks

Related Posts