Python uv cheat sheet#
Python uv common usage cheat sheet, but doesn't cover all the features.
Init project#
uv init can have 3 templates: --app
(by default), --lib
and --package
.
Command | Description |
---|---|
uv init my-project -p python3.13 | Init new project with new folder |
uv init -p python3.13 | Init an existing project with python3.13 |
uv init --lib | Libraries template creates a src folder whereas application template only creates a main.py file |
uv init --package | Init a project with package template, same as libraries but pyproject.toml has a [project.scripts] key, so this is for command-line interface you can later run the command by: uv run pkg-1 |
uv init inside an existing package with already a pyproject.toml
If upper folders has already a pyproject.toml
file, uv will also add the new project (created by uv init
) as [tool.uv.workspace]
members. This cheat sheet doesn't cover that yet. As that converts the repo to a uv workspace which is a little bit more complex. You might need to use uv sync --active
to install dependencies in the active venv.
Add dependencies#
optional-dependencies vs dependency-groups#
Ref:
optional-dependencies
are part of the published metadata for your package, whiledependency-groups
are only visible when working with your package locally
Add to project.dependencies as main dependencies#
Could be installed by end users with uv pip install temp
Add to project.optional-dependencies as extra packages#
Could be installed by end users with uv pip install temp[aio]
[project]
name = "temp"
...
[project.optional-dependencies]
aio = [
"aiohttp>=3.12.15",
]
Add to dependency-groups for local development#
Dependencies declared in the dependency-groups
part are not added to the package metadata, so end users cannot install them directly.
[project]
name = "temp"
...
[dependency-groups]
dev = [
"ruff>=0.12.4",
]
typing = [
"ty>=0.0.1a19",
]
Add to dependency sources#
Dependency sources are for local development only.
[project]
name = "temp"
...
[tool.uv.sources]
httpx = { git = "https://github.com/encode/httpx" }
lib-1 = { path = "../temp1/lib_1", editable = true }
Note
For multiple packages in the same repository, workspaces may be a better fit.
Declare conflicting dependencies#
uv supports explicit declaration of conflicting dependency groups. For example, to declare that the optional-dependency groups extra1 and extra2 are incompatible:
Or, to declare the development dependency groups group1 and group2 incompatible:
tool.uv.environments vs tool.uv.required-environments#
tool.uv.environments
forces uv to only resolve the dependencies and cache the defined environments, whereas tool.uv.required-environments
will only ensure the required environments are always available, but other environments (the wheel files) could still be resolved and cached.
[tool.uv]
environments = [
"sys_platform == 'darwin'",
"sys_platform == 'linux'",
]
[tool.uv]
required-environments = [
"sys_platform == 'darwin' and platform_machine == 'x86_64'"
]
tool.uv.required-environments
is useful when some packages (like PyTorch) have only wheels (no sdist), and its wheels are not available for all platforms.
Example:
Say a package foo
publishes only linux platform wheels, and no sdist
:
foo-1.0.0-cp311-cp311-manylinux_x86_64.whl
If you're on Linux, and run uv add foo
:
- With no
required-environments
→ ✅ works (Linux wheel available, resolution succeeds). uv only guarantees installability for your own environment. - With
required-environments = ["sys_platform == 'darwin'"]
→ ❌ fails (no macOS wheel, and no sdist to build one). With this settings, your colleagues on macOS won't see surprisinglyfoo
in thepyproject.toml
If we have the sdist, that's all right
For packages providing also sdist
(source distribution), even if no wheel (built distributions) is available for a specific platform, uv can still try to build from sdist
on the specific platform, so it should work. But as said above, some packages like pytorch
don't provide sdist
, only wheels.
Note
--only-binary
will restrict to use wheels only, and --no-binary
will restrict to use sdist
only.
Dependency overrides#
If you want to install a version of a package that is resolved as conflict with existing dependencies, but you're sure it's compatible or you want to test it intentionally, you can use dependency overrides to force uv to install a specific version of a package.
[tool.uv]
override-dependencies = [
# Always install Werkzeug 2.3.0, regardless of whether
# transitive dependencies request a different version.
"werkzeug==2.3.0",
# Install pydantic>=2.0 even though
# a transitive dependency declares the requirement pydantic>=1.0,<2.0
"pydantic>=1.0,<3",
]
While constraints can only reduce the set of acceptable versions for a package, overrides can expand the set of acceptable versions, providing an escape hatch for erroneous upper version bounds. As with constraints, overrides do not add a dependency on the package and only take effect if the package is requested in a direct or transitive dependency.
Install dependencies#
[project]
name = "temp"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.13"
dependencies = [
"fastapi>=0.116.1",
]
# this is the optional extra part, useful for end user
[project.optional-dependencies]
aio = [
"aiohttp>=3.12.15",
]
# this is the local development groups part, useful for developers
[dependency-groups]
dev = [
"ruff>=0.12.4",
]
typing = [
"ty>=0.0.1a19",
]
all = [
{include-group = "dev"},
{include-group = "typing"},
]
The --dev
, --only-dev
, and --no-dev
flags are equivalent to --group dev
, --only-group dev
, and --no-group dev
respectively.
without --no-dev
, the dev
group is already installed with uv sync
Use uv sync --no-dev
or uv sync --no-default-groups
to avoid installing the dev
group. By default, uv includes the dev
dependency group in the environment (e.g., during uv run
or uv sync
). The default groups to include can be changed using the tool.uv.default-groups
setting.
use uv sync --dry-run
to see what will be the changes
Command | Description |
---|---|
uv sync --no-dev | Install dependencies only (without any extra nor any group) |
uv sync | Install dependencies and dev group, no extras, no other groups than dev |
uv sync --all-groups | Install dependencies and all groups (dependency-groups ) |
uv sync --all-extras | Install dependencies and all extras (project.optional-dependencies ) and dev group (dev group is by default) |
uv sync --all-extras --all-groups | Install dependencies and all extras and all groups |
uv sync --extra aio | Install dependencies and extra aio and dev group |
uv sync --extra aio --no-dev | Install dependencies and extra aio but without dev group |
uv sync --extra aio --inexact | Install dependencies and dev groups and retain already installed extraneous packages not declared in pyproject.toml |
uv sync --locked --no-dev | Ensure install by respecting uv.lock (ensure uv.lock won't be changed after uv sync) and raise an error if lock file doesn't confirm with pyproject.toml.In 🐳 Dockerfile (official uv Dockerfile example), we often use uv sync --locked --no-install-project --no-dev , see Using uv in Docker to understand the usage of each parameters. |
$ uv sync --locked --no-dev
Resolved 21 packages in 33ms
The lockfile at `uv.lock` needs to be updated, but `--locked` was provided.
To update the lockfile, run `uv lock`.
--locked
vs --frozen
vs --no-sync
#
official doc: https://docs.astral.sh/uv/concepts/projects/sync/#automatic-lock-and-sync
uv.lock
file related:
--locked
: If the lockfile is not up-to-date, uv will raise an error instead of updating the lockfile.--locked
could be considered as--ensure-locked
.--frozen
: Use the lockfile without checking if it is up-to-date, no error will be raised.
venv
related:
--no-sync
: Do not update the venv. |
--resolution lowest-direct and --resolution lowest#
With --resolution lowest
, uv will install the lowest possible version for all dependencies, both direct and indirect (transitive). --resolution lowest
Alternatively, --resolution lowest-direct
will use the lowest compatible versions for all direct dependencies, while using the latest compatible versions for all other dependencies. uv will always use the latest versions for build dependencies.
testing compatibility
When publishing libraries, it is recommended to separately run tests with --resolution lowest
or --resolution lowest-direct
in CI/CD to ensure compatibility with the declared lower bounds.
set back to default --resolution highest
after testing
After testing with --resolution lowest
or --resolution lowest-direct
, remember to set back to the default --resolution highest
to avoid potential dependency conflicts in future installations.
--resolution lowest-direct
is easier than --resolution lowest
as it only affects direct dependencies, so it is less likely to cause dependency conflicts:
$ uv sync --resolution lowest
Ignoring existing lockfile due to change in resolution mode: `highest` vs. `lowest`
× Failed to build `idna==0.2`
├─▶ The build backend returned an error
╰─▶ Call to `setuptools.build_meta:__legacy__.build_wheel` failed (exit status: 1)
[stderr]
Sorry, Python 3 not yet supported
hint: This usually indicates a problem with the package or the build environment.
help: `idna` (v0.2) was included because `temp` (v0.1.0) depends on `httpx` (v0.28.1) which depends on
`idna`
Whereas --resolution lowest-direct
works fine for the same project:
$ uv sync --resolution lowest-direct
Ignoring existing lockfile due to change in resolution mode: `highest` vs. `lowest-direct`
Resolved 26 packages in 330ms
Uninstalled 3 packages in 10ms
Installed 3 packages in 9ms
- fastapi==0.116.1
+ fastapi==0.115.1
- ruff==0.12.10
+ ruff==0.12.4
- starlette==0.47.3
+ starlette==0.38.6
Reproducible builds#
- If you're using uv.lock with Dockerfile to build your application, you could use
uv sync --locked --no-install-project --no-dev
to ensure reproducible builds, or even the image itself is reproducible. See Using uv in Docker for more information. - If you're not using uv.lock, and you previously (on
2025-09-07
) useduv pip install -r requirements.txt
to install dependencies, and not all the dependencies and transitive dependencies are pinned inrequirements.txt
. One day there's bug in the production, you want to reproduce the same environment as before. One of the ways is to useuv pip install -r requirements.txt --exclude-newer 2025-09-07
to exclude any package released after2025-09-07
. See Reproducible resolutions for more information. We can also addexclude-older
in [tool.uv] inpyproject.toml
to make it permanent.
Note
An RFC 3339 timestamp (e.g., 2006-12-02T02:07:43Z
) and a local date in the same format (e.g., 2006-12-02
) in your system's configured time zone are both supported in --exclude-newer
link-mode: clone, copy, hardlink, symlink#
OS | Default link-mode | Fallback |
---|---|---|
Linux | hardlink | fallbacks to copy |
Windows | hardlink | fallbacks to copy |
MacOS | clone (copy-on-write) | unknown |
Possible values:
clone
: Clone (i.e., copy-on-write) packages from the wheel into the site-packages directory. With copy-on-write (COW), initially no extra disk space is used as long as the dependencies are cached, we're using shared cache files. But if you modify the code of a package, extra disk space will be used to store the modified files. To ensure your modification to the dependencies won't impact other projects which are using the same dependencies from the shared cache. This mode minimizes disk space usage, but it requires a filesystem that supports copy-on-write, such as Btrfs or APFS. If the filesystem doesn't support copy-on-write, uv will fallback tocopy
mode.copy
: Copy packages from the wheel into the site-packages directory. This is also the default mode for traditional pip install, useful if you want to modify dependencies' code for debugging purpose.hardlink
: Hard link packages from the wheel into the site-packages directory. fallbacks tocopy
mode if the cache and the venv are on different filesystems.symlink
: Symbolically link packages from the wheel into the site-packages directory, use with caution asuv cache clean
will break all installed packages.
or export UV_LINK_MODE=copy
or --link-mode=copy
in the command line.
Bytecode compilation#
Unlike pip
, uv does not compile .py
files to .pyc
files during installation by default (i.e., uv does not create or populate __pycache__
directories). To enable bytecode compilation during installs, pass the --compile-bytecode
flag to uv pip install
or uv pip sync
, or set the environment variable UV_COMPILE_BYTECODE=1
.
Skipping bytecode compilation can be undesirable in workflows; for example, we recommend enabling bytecode compilation in Docker builds to improve startup times (at the cost of increased build times).
Concurrent install#
As per uv's documentation, concurrent installations are supported even against the same virtual environment. uv applies a file-based lock to the target virtual environment when installing, to avoid concurrent modifications across processes.
Dependencies tree#
Command | Description |
---|---|
uv pip tree | Display the installed packages in a tree format |
uv tree | Update uv.lock based on pyproject.toml and display tree based on uv.lock , no package installation will occur. uv tree displays better than uv pip tree |
uv tree --frozen | Don't update uv.lock , just display tree based on the current uv.lock |
uv tree --locked | If uv.lock is not updated, display a warning message. This command is not very useful |
List outdated packages#
uv tree --outdated
oruv tree --outdated | grep latest
: display a list of outdated packages with their latest public versions, no matter whatpyproject.toml
declares.uv lock --check
: check ifuv.lock
is up-to-date withpyproject.toml
.
Upgrade packages and uv.lock#
The only way to change the package version constraints is to edit manually the pyproject.toml
file directly. After making changes to pyproject.toml
, you should run uv sync
to update the venv dependencies and the uv.lock
file accordingly. Or uv lock
to update only the uv.lock
file without changing the venv dependencies.
uv.lock
file can be updated by uv lock
, uv sync
, uv run
, uv add
, uv remove
.
Command | Description |
---|---|
uv lock | Update the uv.lock file to match the current state of pyproject.toml |
uv lock -U | Update the uv.lock file and upgrade all packages to their latest compatible versions |
uv sync | Same as uv lock but also installs the dependencies |
uv sync -U | Same as uv lock -U but also installs the dependencies |
uv lock
vs uv lock -U
and uv sync
vs uv sync -U
If latest version of fastapi is 0.116.1, and pyproject.toml declares fastapi>=0.115.1, and current uv.lock has fastapi==0.115.1. then:
uv lock
: no effect, as0.115.1
is still within the range of>=0.115.1
.uv lock -U
: upgradefastapi
to0.116.1
.
Same logic applies to uv sync
and uv sync -U
, except for sync
installing the dependencies too.
-U
(--upgrade
for all packages) or -P
(--upgrade-package
for a specific package) respect always the version constraints defined in pyproject.toml
.
Integrations#
Check this doc for more information on integrations with other tools and platforms (Docker, Jupyter, Github Actions, Pre Commit, PyTorch FastAPI, etc.).
Build#
Build with rust, C, C++, CPython backend#
uv can also build with extension module by --build-backend
flag to work with Rust and C, C++, CPython etc.
Build isolation#
uv build isolation is by default, but some packages need to build against the same version of some packages installed in the project environment. For example, flask-attn, deepspeed, cchardet, etc.
For a list of packages that are known to fail under PEP 517 build isolation, see #2252.
Caching#
uv cache dir
: show the cache directory.uv cache clean
: clear the cache entirely.uv cache clean ruff
: clear only the ruff package cache.uv cache prune
: safely removes all unused cache entries.uv sync --refresh
oruv pip install --refresh
: force revalidate cached data for all dependencies, use--refresh-package ruff
to revalidate onlyruff
.
Caching in CI#
As per uv's documentation on Caching in continuous integration, it's recommended to use uv cache prune --ci
at the end of the CI, to remove all pre-built wheels and unzipped source distributions from the cache, but retain any wheels that were built from source.
There's an example of using this in Github Actions. It also provides enable-cache: true
to achieve the same effect.
Note
If using uv pip
, use requirements.txt
along with the OS and the Python version instead of uv.lock
in the cache key. And you may need to set the env var UV_SYSTEM_PYTHON=1
or add the command line flag --system
to use the system Python in CI.
Run#
Run single script file with isolated environment#
https://docs.astral.sh/uv/concepts/projects/run/#running-scripts
# /// script
# dependencies = [
# "httpx",
# ]
# ///
import httpx
print(httpx.__file__)
$ uv pip show httpx
Name: httpx
Version: 0.28.1
Location: /home/xiang/git/copdips.github.io/.venv/lib/python3.13/site-packages
Requires: anyio, certifi, httpcore, idna
Required-by: fastapi-cloud-cli
$ uv run example.py
/home/xiang/.cache/uv/environments-v2/example-b91cf8387ef5699e/lib/python3.13/site-packages/httpx/__init__.py
Run inline script#
uv run --with httpx python -c "import httpx; print(httpx.__file__)"
to run an inline script. httpx
will be installed in an isolated environment.
Tools#
uvx and uv tool run and uv run and raw command#
uvx
and uv tool run
are the same to run command in an isolated environment. They run uv tool install
in the background
uv run
is normally used to run a command in the current environment. But will auto upgrade the package based on pyproject.toml
dependencies constraints. Whereas the raw command will not.
Command | Target | Effect | Notes |
---|---|---|---|
uvx or uv tool run | command | Always in an isolated env in user cache, no auto update. If the tool was previously installed, i.e., via uv tool install , the installed version will be used unless a version is requested or the --isolated flag is used. | Can run script by uvx --with ruff python -c "import ruff ; from importlib.metadata import version; print(ruff.__file__); print(version('ruff'))" |
uvx or uv tool run | script | Not directly supported | Can workaround by uvx --with ruff python example.py .In-script dependencies declaration is not supported, use --with , --with-requirements , --with-executables-from to add additional dependencies. |
uv run | command | Use current env, fallback to isolated env in user cache, could auto upgrade command version based on pyproject.toml | |
uv run | script | - If no in-script dependencies declared, use current env and auto upgrade dependencies version - If in-script dependencies declared, use isolated env in user cache | |
raw run | command | use current env only | |
raw run | script | use current env only |
$ grep ruff pyproject.toml
"ruff>=0.12.4",
$ uv pip install ruff==0.12.0
Resolved 1 package in 7ms
Uninstalled 1 package in 0.50ms
Installed 1 package in 3ms
- ruff==0.12.4
+ ruff==0.12.0
$ ruff --version
ruff 0.12.0
$ uv pip tree | grep ruff
ruff v0.12.0
$ uv run --no-project ruff --version # (1)
ruff 0.12.0
$ uv run ruff --version # (2)
Uninstalled 1 package in 0.62ms
Installed 1 package in 3ms
ruff 0.12.4
$ ruff --version
ruff 0.12.4
$ uv pip tree | grep ruff
ruff v0.12.4
uv run --no-project ruff --version
will use the version ofruff
installed in the current environment (0.12.0). The--no-project
flag tellsuv
to not consider the current project (pyproject.toml) and its dependencies.uv run ruff --version
upgrade automatically ruff version form 0.12.0 to 0.12.4 as constrainted inpyproject.toml
Note
if pipx
has been used to install a tool, uv tool install
will fail. The --force
flag can be used to override this behavior.
tools directory#
uv tool dir
to show the directory where tools are installed.
upgrade#
uv tool upgrade ruff
to upgraderuff
.uv tool upgrade ruff --reinstall
to upgraderuff
and all its dependencies.
Cleanup#
The tool will be installed in an isolated environment in the user's cache directory. Use uv cache clean
to clean up the cache.
Workspace#
To be continued.