|
| 1 | +# A Guide to Python Package Management with `uv` |
| 2 | + |
| 3 | +`uv` is a fast Python package installer and resolver, designed to be a drop-in replacement for `pip` and `pip-tools`. It aims to provide a more efficient and reliable experience for managing Python dependencies. |
| 4 | + |
| 5 | +This guide will walk you through common `uv` workflows: upgrading packages, locking dependencies, creating `requirements.txt` files, and resolving conflicts. |
| 6 | + |
| 7 | +More Info: https://docs.astral.sh/uv/ |
| 8 | +--- |
| 9 | + |
| 10 | +## 1. Adding Packages |
| 11 | + |
| 12 | +Packages can be added to the `pyproject.toml` file using `uv add`. |
| 13 | + |
| 14 | +```bash |
| 15 | +# Add a single package |
| 16 | +uv add requests # Adds the latest compatible release of requests |
| 17 | + |
| 18 | +# Add a single package at a specified version |
| 19 | +uv add requests~=2.25 # Adds the latest 2.25.* release of requests |
| 20 | + |
| 21 | +# Add multiple packages |
| 22 | +uv add beautifulsoup4 lxml # Adds the latest compatible release of both beautifulsoup4 and lxml |
| 23 | + |
| 24 | +# Add a single package to a certain group |
| 25 | +uv add requests --group group1 # Adds the latest compatible release of requests to only group1 |
| 26 | +``` |
| 27 | + |
| 28 | +--- |
| 29 | + |
| 30 | +## 2. Upgrading Packages |
| 31 | + |
| 32 | +Navigate to the `pyproject.toml` file, locate the image group, and update the version to your desired one. Afterward, execute `uv lock` to prevent any dependency conflicts. |
| 33 | + |
| 34 | +Locking dependencies means creating a precise list of *all* direct and transitive dependencies, including their exact versions, that your project needs. This ensures reproducible builds across different environments. `uv` uses `uv lock`. |
| 35 | + |
| 36 | +### Creating an `uv lock` File |
| 37 | + |
| 38 | +The pyproject.toml file lists dependencies, with each image having its own dependency group. |
| 39 | + |
| 40 | +Now, use `uv lock` to generate a locked `uv.lock` file. This file will contain all direct and transitive dependencies with their exact versions for the entire repo. |
| 41 | + |
| 42 | +### Compiling and Locking |
| 43 | + |
| 44 | +Now, use `uv pip compile` to generate a locked `requirements.txt` file. This file will contain the exact versions of all direct and transitive dependencies for each image, determined by specifying its dependency group. |
| 45 | + |
| 46 | +```bash |
| 47 | +# Generate requirements.txt for the datascience image |
| 48 | +uv pip compile --format requirements.txt --python 3.11 -o jupyter/datascience/ubi9-python-3.11/requirements.txt --generate-hashes --group jupyter-datascience-image --no-annotate -q pyproject.toml |
| 49 | +``` |
| 50 | + |
| 51 | +After running this, `requirements.txt` might look something like this (versions will vary): |
| 52 | + |
| 53 | +**`requirements.txt` example:** |
| 54 | +``` |
| 55 | +# This file was autogenerated by uv via the following command: |
| 56 | +# uv pip compile --format requirements.txt --python 3.11 -o jupyter/datascience/ubi9-python-3.11/requirements.txt --generate-hashes --group jupyter-datascience-image --python-platform linux --no-annotate |
| 57 | +aiohappyeyeballs==2.6.1 \ |
| 58 | + --hash=sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558 \ |
| 59 | + --hash=sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8 |
| 60 | +aiohttp==3.12.13 \ |
| 61 | + --hash=sha256:03d5eb3cfb4949ab4c74822fb3326cd9655c2b9fe22e4257e2100d44215b2e2b \ |
| 62 | + --hash=sha256:04076d8c63471e51e3689c93940775dc3d12d855c0c80d18ac5a1c68f0904358 \ |
| 63 | +``` |
| 64 | + |
| 65 | +### Installing from a Locked File |
| 66 | + |
| 67 | +To install exactly the versions specified in your locked file: |
| 68 | + |
| 69 | +```bash |
| 70 | +uv pip install --no-deps -r requirements.txt |
| 71 | +``` |
| 72 | + |
| 73 | +This is crucial for deployment and team collaboration, as everyone will be using the exact same dependency versions. |
| 74 | + |
| 75 | +--- |
| 76 | + |
| 77 | +## 3. Resolving Conflicts |
| 78 | + |
| 79 | +Dependency conflicts occur when two or more packages require incompatible versions of the same dependency or incompatible indices from different sources. `uv`'s resolver is designed to be fast and provide helpful error messages when conflicts arise. |
| 80 | + |
| 81 | +### How `uv` Handles Conflicts |
| 82 | + |
| 83 | +When you run `uv lock`, `uv pip install`, or `uv pip compile`, `uv` attempts to find a set of package versions that satisfy all requirements. If it cannot, it will report a conflict. |
| 84 | + |
| 85 | +**Example of a potential conflict:** |
| 86 | + |
| 87 | +Imagine `package_A` requires `dependency_X==1.0` and `package_B` requires `dependency_X>=2.0`. |
| 88 | + |
| 89 | +If you try to install both: |
| 90 | + |
| 91 | +```toml |
| 92 | +# pyproject.toml |
| 93 | +package_A |
| 94 | +package_B |
| 95 | +``` |
| 96 | +```bash |
| 97 | +uv lock |
| 98 | +``` |
| 99 | + |
| 100 | +`uv` would output an error similar to this (simplified): |
| 101 | + |
| 102 | +``` |
| 103 | +error: Failed to resolve dependencies: |
| 104 | + - package_A requires dependency_X==1.0 |
| 105 | + - package_B requires dependency_X>=2.0 |
| 106 | + The requested versions of dependency_X (1.0 and >=2.0) are incompatible. |
| 107 | +``` |
| 108 | + |
| 109 | +### Strategies for Resolving Conflicts |
| 110 | + |
| 111 | +1. **Review Error Messages:** `uv`'s error messages are usually very informative, pointing out exactly which packages are in conflict and what their version requirements are. Read them carefully. |
| 112 | + |
| 113 | +2. **Utilize uv group and conflict:** |
| 114 | + `uv` introduces the concept of "groups" (similar to extras or development dependencies) which can be defined in `pyproject.toml`. This is a powerful way to manage different sets of dependencies, especially when some dependencies might conflict with others required for different aspects of your project. |
| 115 | + ```toml |
| 116 | + [dependency-groups] |
| 117 | + group1 = [ |
| 118 | + "package_A==1.0" |
| 119 | + ] |
| 120 | + group2 = [ |
| 121 | + "package_B>=2.0" |
| 122 | + ] |
| 123 | + |
| 124 | + [tool.uv] |
| 125 | + conflicts = [ |
| 126 | + [ |
| 127 | + { group = "group1" }, |
| 128 | + { group = "group2" } |
| 129 | + ], |
| 130 | + ] |
| 131 | + ``` |
| 132 | + |
| 133 | +4. **Utilize markers for Conditional Dependencies:** |
| 134 | + When working with projects where different packages are needed, in the same aspects of your project, for various platforms, systems, or Python versions, you can leverage environment markers (PEP 508). uv supports these markers in `pyproject.toml` files, allowing you to specify dependencies that are only installed under certain conditions. This approach is crucial for resolving conflicts that might arise on specific platforms or with particular Python versions. |
| 135 | + ``` |
| 136 | + # Works for different package |
| 137 | + requests |
| 138 | + package_A; sys_platform == "win32" |
| 139 | + package_B; os_name == "posix" |
| 140 | + |
| 141 | + # Works for same package |
| 142 | + requests |
| 143 | + package_A~=1.24.3; sys_platform == "win32" |
| 144 | + package_A~=2.35.6; os_name == "posix" |
| 145 | + ``` |
| 146 | + |
| 147 | +5. **Utilize dependency-metadata:** |
| 148 | + When managing projects where different packages are needed, in the same aspects of your project, for the identical environment markers, and yet still yield dependency resolution conflicts, you can utilize dependency metadata to force `uv` to install specific dependencies at particular versions and/or particular environment markers. |
| 149 | + ``` |
| 150 | + [[tool.uv.dependency-metadata]] |
| 151 | + name = "package_A" |
| 152 | + version = "1.0" |
| 153 | + requires-dist = [ |
| 154 | + "dependency_X==2.0" |
| 155 | + ] |
| 156 | + ``` |
| 157 | + |
| 158 | +--- |
| 159 | + |
| 160 | +## Conclusion |
| 161 | + |
| 162 | +`uv` offers a powerful and fast way to manage Python packages. By understanding how to use `uv lock` for locking dependencies and the strategies for resolving conflicts, you can maintain a robust and reproducible development environment for your Python projects. |
0 commit comments