Skip to content

Commit bab4610

Browse files
committed
improve python uv cheat sheet
1 parent 6f6479b commit bab4610

File tree

1 file changed

+173
-5
lines changed

1 file changed

+173
-5
lines changed

docs/posts/2025/2025-08-26-python-uv-cheat-sheet.md

Lines changed: 173 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@ authors:
44
categories:
55
- python
66
- package
7+
- cicd
8+
- cache
9+
- github
710
comments: true
811
date:
912
created: 2025-08-26
10-
updated: 2025-08-30
13+
updated: 2025-09-07
1114
---
1215

1316
# Python uv cheat sheet
@@ -33,7 +36,7 @@ pkg-1 = "pkg_1:main"
3336
```
3437

3538
!!! warning "uv init inside an existing package with already a pyproject.toml"
36-
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](https://docs.astral.sh/uv/concepts/projects/workspaces/#using-workspaces) which is a little bit more complex. You might need to use `uv sync --active` to install dependencies in the separate venv of the sub module if needed.
39+
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](https://docs.astral.sh/uv/concepts/projects/workspaces/#using-workspaces) which is a little bit more complex. You might need to use `uv sync --active` to install dependencies in the active venv.
3740

3841
## Add dependencies
3942

@@ -154,6 +157,64 @@ conflicts = [
154157
]
155158
```
156159

160+
### tool.uv.environments vs tool.uv.required-environments
161+
162+
[`tool.uv.environments`](https://docs.astral.sh/uv/concepts/resolution/#limited-resolution-environments) forces uv to ==only resolve the dependencies and cache== the defined environments, whereas [`tool.uv.required-environments`](https://docs.astral.sh/uv/concepts/resolution/#required-environments) will only ensure the required environments are always available, but ==other environments (the wheel files) could still be resolved and cached==.
163+
164+
```toml title="pyproject.toml with tool.uv.environments" hl_lines="2"
165+
[tool.uv]
166+
environments = [
167+
"sys_platform == 'darwin'",
168+
"sys_platform == 'linux'",
169+
]
170+
```
171+
172+
```toml title="pyproject.toml with tool.uv.required-environments" hl_lines="2"
173+
[tool.uv]
174+
required-environments = [
175+
"sys_platform == 'darwin' and platform_machine == 'x86_64'"
176+
]
177+
```
178+
179+
`tool.uv.required-environments` is useful when some packages (like PyTorch) have only wheels (no [sdist](https://docs.astral.sh/uv/concepts/resolution/#source-distribution)), and its wheels are not available for all platforms.
180+
181+
Example:
182+
183+
Say a package `foo` publishes only linux platform wheels, and no `sdist`:
184+
185+
- `foo-1.0.0-cp311-cp311-manylinux_x86_64.whl`
186+
187+
If you're on Linux, and run `uv add foo`:
188+
189+
- With no `required-environments` → ✅ works (Linux wheel available, resolution succeeds). uv only guarantees installability for your own environment.
190+
- 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 surprisingly `foo` in the `pyproject.toml`
191+
192+
!!! note "If we have the sdist, that's all right"
193+
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.
194+
195+
!!! note
196+
[`--only-binary`](https://docs.astral.sh/uv/pip/compatibility/#-only-binary-enforcement) will restrict to use wheels only, and [`--no-binary`](https://docs.astral.sh/uv/pip/compatibility/#-no-binary-enforcement) will restrict to use `sdist` only.
197+
198+
### Dependency overrides
199+
200+
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](https://docs.astral.sh/uv/concepts/resolution/#dependency-overrides) to force uv to install a specific version of a package.
201+
202+
```toml title="pyproject.toml"
203+
[tool.uv]
204+
override-dependencies = [
205+
206+
# Always install Werkzeug 2.3.0, regardless of whether
207+
# transitive dependencies request a different version.
208+
"werkzeug==2.3.0",
209+
210+
# Install pydantic>=2.0 even though
211+
# a transitive dependency declares the requirement pydantic>=1.0,<2.0
212+
"pydantic>=1.0,<3",
213+
]
214+
```
215+
216+
While [constraints](https://docs.astral.sh/uv/concepts/resolution/#dependency-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.
217+
157218
## Install dependencies
158219

159220
```toml title="pyproject.toml"
@@ -227,6 +288,92 @@ official doc: https://docs.astral.sh/uv/concepts/projects/sync/#automatic-lock-a
227288
`venv` related:
228289

229290
- `--no-sync`: Do not update the venv.
291+
|
292+
293+
### --resolution lowest-direct and --resolution lowest
294+
295+
With `--resolution lowest`, uv will install the lowest possible version for all dependencies, both direct and indirect (transitive). `--resolution lowest`
296+
297+
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.
298+
299+
!!! important "testing compatibility"
300+
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](https://docs.astral.sh/uv/concepts/resolution/#lower-bounds).
301+
302+
!!! warning "set back to default `--resolution highest` after testing"
303+
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.
304+
305+
`--resolution lowest-direct` is easier than `--resolution lowest` as it only affects direct dependencies, so it is less likely to cause dependency conflicts:
306+
307+
```bash hl_lines="1"
308+
$ uv sync --resolution lowest
309+
Ignoring existing lockfile due to change in resolution mode: `highest` vs. `lowest`
310+
× Failed to build `idna==0.2`
311+
├─▶ The build backend returned an error
312+
╰─▶ Call to `setuptools.build_meta:__legacy__.build_wheel` failed (exit status: 1)
313+
314+
[stderr]
315+
Sorry, Python 3 not yet supported
316+
317+
hint: This usually indicates a problem with the package or the build environment.
318+
help: `idna` (v0.2) was included because `temp` (v0.1.0) depends on `httpx` (v0.28.1) which depends on
319+
`idna`
320+
```
321+
322+
Whereas `--resolution lowest-direct` works fine for the same project:
323+
324+
```bash hl_lines="1"
325+
$ uv sync --resolution lowest-direct
326+
Ignoring existing lockfile due to change in resolution mode: `highest` vs. `lowest-direct`
327+
Resolved 26 packages in 330ms
328+
Uninstalled 3 packages in 10ms
329+
Installed 3 packages in 9ms
330+
- fastapi==0.116.1
331+
+ fastapi==0.115.1
332+
- ruff==0.12.10
333+
+ ruff==0.12.4
334+
- starlette==0.47.3
335+
+ starlette==0.38.6
336+
```
337+
338+
### Reproducible builds
339+
340+
- 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](https://docs.astral.sh/uv/guides/integration/docker/) for more information.
341+
- If you're not using uv.lock, and you previously (on `2025-09-07`) used `uv pip install -r requirements.txt` to install dependencies, and not all the dependencies and transitive dependencies are pinned in `requirements.txt`. One day there's bug in the production, you want to reproduce the same environment as before. One of the ways is to use `uv pip install -r requirements.txt --exclude-newer 2025-09-07` to exclude any package released after `2025-09-07`. See [Reproducible resolutions](https://docs.astral.sh/uv/concepts/resolution/#reproducible-resolutions) for more information. We can also add `exclude-older` in [tool.uv] in `pyproject.toml` to make it permanent.
342+
343+
!!! note
344+
An [RFC 3339](https://www.rfc-editor.org/rfc/rfc3339.html) 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`
345+
346+
### link-mode: clone, copy, hardlink, symlink
347+
348+
| OS | Default link-mode | Fallback |
349+
| ------- | --------------------- | ----------------- |
350+
| Linux | hardlink | fallbacks to copy |
351+
| Windows | hardlink | fallbacks to copy |
352+
| MacOS | clone (copy-on-write) | |
353+
354+
Possible values:
355+
356+
- `clone`: Clone (i.e., [copy-on-write](https://en.wikipedia.org/wiki/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 to `copy` mode.
357+
- `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==.
358+
- `hardlink`: Hard link packages from the wheel into the site-packages directory. fallbacks to `copy` mode if the cache and the venv are on different filesystems.
359+
- `symlink`: Symbolically link packages from the wheel into the site-packages directory, use with caution as `uv cache clean` will break all installed packages.
360+
361+
```toml title="pyproject.toml"
362+
[tool.uv]
363+
link-mode = "copy"
364+
```
365+
366+
or `export UV_LINK_MODE=copy` or `--link-mode=copy` in the command line.
367+
368+
### Bytecode compilation
369+
370+
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`.
371+
372+
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).
373+
374+
### Concurrent install
375+
376+
[As per uv's documentation](https://docs.astral.sh/uv/concepts/cache/#cache-safety), 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.
230377

231378
## Dependencies tree
232379

@@ -235,16 +382,18 @@ official doc: https://docs.astral.sh/uv/concepts/projects/sync/#automatic-lock-a
235382
| `uv pip tree` | Display the **installed packages** in a tree format |
236383
| `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` |
237384
| `uv tree --frozen` | Don't update `uv.lock`, just display tree based on the current `uv.lock` |
238-
| `uv tree --locked` | If `uv.lock` is not updated, display a warning message. This command is not very useful |
385+
| `uv tree --locked` | If `uv.lock` is not updated, display a warning message. This command is not very useful
239386

240387
## List outdated packages
241388

242-
- `uv tree --outdated`: display a list of outdated packages with their latest **public** versions, no matter what `pyproject.toml` declares.
389+
- `uv tree --outdated` or `uv tree --outdated | grep latest`: display a list of outdated packages with their latest **public** versions, no matter what`pyproject.toml` declares.
243390
- [`uv lock --check`](https://docs.astral.sh/uv/concepts/projects/sync/#checking-if-the-lockfile-is-up-to-date): check if `uv.lock` is up-to-date with `pyproject.toml`.
244391

245392
## Upgrade packages and uv.lock
246393

247-
[`uv.lock` file](https://docs.astral.sh/uv/concepts/projects/layout/#the-lockfile)can be updated by `uv lock`, `uv sync`, `uv run`, `uv add`, `uv remove`.
394+
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.
395+
396+
[`uv.lock` file](https://docs.astral.sh/uv/concepts/projects/layout/#the-lockfile) can be updated by `uv lock`, `uv sync`, `uv run`, `uv add`, `uv remove`.
248397

249398
| Command | Description |
250399
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
@@ -277,6 +426,25 @@ uv can also [build with extension module by `--build-backend` flag](https://docs
277426

278427
uv build isolation is by default, but some packages need to [build against the same version of some packages installed in the project environment](https://docs.astral.sh/uv/concepts/projects/config/#build-isolation). For example, [flask-attn](https://pypi.org/project/flash-attn/), [deepspeed](https://pypi.org/project/deepspeed/), [cchardet](https://pypi.org/project/cchardet/), etc.
279428

429+
For a list of packages that are known to fail under PEP 517 build isolation, see [#2252](hhttps://github.com/astral-sh/uv/issues/2252).
430+
431+
## Caching
432+
433+
- `uv cache dir`: show the cache directory.
434+
- `uv cache clean`: clear the cache entirely.
435+
- `uv cache clean ruff`: clear only the ruff package cache.
436+
- `uv cache prune`: safely removes all unused cache entries.
437+
- `uv sync --refresh` or `uv pip install --refresh`: force revalidate cached data for all dependencies, use `--refresh-package ruff` to revalidate only `ruff`.
438+
439+
### Caching in CI
440+
441+
As per [uv's documentation on Caching in continuous integration](https://docs.astral.sh/uv/concepts/cache/#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.
442+
443+
There's [an example of using this in Github Actions](https://docs.astral.sh/uv/guides/integration/github/#caching). It also provides `enable-cache: true` to achieve the same effect.
444+
445+
!!! note
446+
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.
447+
280448
## Run
281449

282450
### Run single script file with isolated environment

0 commit comments

Comments
 (0)