Skip to content

Commit c75a58a

Browse files
mtchoum1jiridanek
andauthored
[RHOAIENG-17006] document the migration of dependencies from pipenv to uv (#1520)
--------- Co-authored-by: Jiri Daněk <[email protected]>
1 parent d5bf26f commit c75a58a

File tree

1 file changed

+162
-0
lines changed

1 file changed

+162
-0
lines changed

docs/uv-guide.md

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
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

Comments
 (0)