Skip to content

Commit 1ee9b52

Browse files
committed
v13: pipenv to uv upgrade guide
1 parent f91bd59 commit 1ee9b52

File tree

3 files changed

+1018
-0
lines changed

3 files changed

+1018
-0
lines changed

docs/releases/uv-upgrade.md

Lines changed: 317 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,317 @@
1+
## Overview
2+
3+
Migrating from Pipenv to [`uv`](https://docs.astral.sh/uv/) brings significantly faster
4+
dependency resolution and better alignment with modern Python packaging standards using
5+
`pyproject.toml`.
6+
7+
### Install `uv`
8+
9+
Before starting with any migration steps, [install
10+
uv](https://docs.astral.sh/uv/getting-started/installation/) on your system. It's
11+
recommended to use the standalone installer, since it's independent of any system
12+
package managers (like `apt` or `brew`) and the installed binary comes with a
13+
self-upgrading mechanism using `uv self upgrade`.
14+
15+
On top of this it's recommended that you start using `uv` to manage the `invenio-cli` tool
16+
installation. Make sure you uninstall any existing `invenio-cli` installation first, and
17+
then run `uv tool install invenio-cli`.
18+
19+
## Migration script
20+
21+
To ease the migration process, a [helper Python script](./uv_migrate.py) is available
22+
for automating the following steps of this guide:
23+
24+
- Converting your `Pipfile` to a `pyproject.toml` file
25+
- Updating your `site/` package configuration to use `pyproject.toml`
26+
- Updating your `.invenio` configuration to use `uv`
27+
- Removing old unnecessary files (`Pipfile`, `Pipfile.lock`, `setup.cfg`, `MANIFEST.in`, etc.)
28+
29+
The script assumes a "standard" InvenioRDM bootstrapped project structure (e.g. it reads
30+
from the `.invenio` file to auto-detect the project name, Python version, and author
31+
info), so you may come across issues if your project structure and configuration has
32+
deviated significantly. In any case, the script is just a starting point, and you will
33+
still need to manually verify and adjust the following:
34+
35+
- your `Dockerfile` (if you rely on Docker for application development or deployment)
36+
- your tests suite
37+
- CI/CD configuration (e.g. if you're using GitHub Actions)
38+
- any other custom scripts that use Pipenv
39+
40+
This guide covers the most common aspects, but as with any migration, testing of the
41+
final result is crucial. Make sure you run your application and try all the development
42+
and operational workflows that you would normally use.
43+
44+
To run the script follow these steps:
45+
46+
```bash
47+
# Navigate to your InvenioRDM instance directory
48+
cd my-site/
49+
50+
# Download the script in a temporary location
51+
curl -LsSf https://raw.githubusercontent.com/inveniosoftware/docs-invenio-rdm/main/docs/releases/uv_migrate.py -o /tmp/uv_migrate.py
52+
53+
# Run the script using uv
54+
uv run /tmp/uv_migrate.py
55+
```
56+
57+
## Step-by-Step Migration Guide
58+
59+
!!! note "Steps covered by the script"
60+
Steps that the `uv_migrate.py` script covers are marked with a 📜 icon in their header.
61+
62+
### Convert `Pipfile` to `pyproject.toml` 📜
63+
64+
Create a root `pyproject.toml` file to replace your `Pipfile` in the root of your
65+
project. All dependencies, including test dependencies, are now managed in this single
66+
file:
67+
68+
#### Before: `Pipfile`
69+
70+
```toml title="Pipfile"
71+
[[source]]
72+
name = "pypi"
73+
url = "https://pypi.org/simple"
74+
verify_ssl = true
75+
76+
[packages]
77+
invenio-app-rdm = {version = "~=13.0.0", extras = ["opensearch2"]}
78+
my_site = {editable=true, path="./site"}
79+
# ... other dependencies
80+
81+
[requires]
82+
python_version = ">=3.12"
83+
```
84+
85+
#### After: `pyproject.toml`
86+
87+
```toml title="pyproject.toml"
88+
[project]
89+
name = "my-site-app" # (1)!
90+
version = "1.0.0" # (2)!
91+
authors = [{ name = "My Organization" }] # (3)!
92+
license = "MIT"
93+
requires-python = ">=3.12"
94+
dependencies = [
95+
"invenio-app-rdm[opensearch2]~=13.0.0",
96+
"my-site", # (4)!
97+
# ... other dependencies
98+
]
99+
100+
[tool.uv.sources] # (5)!
101+
my-site = { workspace = true }
102+
103+
[tool.uv.workspace] # (6)!
104+
members = ["site"]
105+
106+
[dependency-groups] # (7)!
107+
dev = [
108+
"pytest-invenio>=3.0.0,<4.0.0",
109+
# ... other dev dependencies
110+
]
111+
```
112+
113+
1. The project needs the `-app` suffix to avoid package naming conflicts between the root project and site package (see below)
114+
2. Project version is now required in `pyproject.toml`
115+
3. Project metadata like authors and license are now explicitly defined
116+
4. References the workspace member defined in `[tool.uv.workspace]`
117+
5. Defines where uv should find local packages (including workspace members)
118+
6. Declares this is a workspace project with "site" as a member package
119+
7. Replaces Pipfile's `[dev-packages]` - groups dependencies by their purpose
120+
121+
??? info "Understanding uv Workspaces"
122+
123+
`uv` introduces the concept of
124+
[workspaces](https://docs.astral.sh/uv/concepts/projects/workspaces/) - a way to
125+
manage multiple related packages in a single repository. In InvenioRDM projects,
126+
your custom code in the `site/` directory becomes a workspace member, allowing uv
127+
to manage dependencies across multiple packages in a unified way at the root
128+
`pyproject.toml`.
129+
130+
### Update `site/pyproject.toml` 📜
131+
132+
Update your site-specific package configuration:
133+
134+
#### Before: `site/setup.cfg`
135+
136+
```ini title="site/setup.cfg"
137+
[metadata]
138+
name = my-site
139+
140+
[options.extras_require]
141+
tests =
142+
pytest-invenio>=3.0.0,<4.0.0
143+
# ...other test dependencies
144+
145+
[options.entry_points]
146+
invenio_base.blueprints =
147+
my_site_views = my_site.views:create_blueprint
148+
invenio_assets.webpack =
149+
my_site_theme = my_site.webpack:theme
150+
# ...other entry points for Celery tasks, models, CLI commands, etc.
151+
```
152+
153+
#### After: `site/pyproject.toml`
154+
155+
```toml title="site/pyproject.toml"
156+
[project]
157+
name = "my-site" # (1)!
158+
version = "1.0.0" # (2)!
159+
description = "My Site customizations for Invenio RDM."
160+
# (3)!
161+
162+
[project.entry-points."invenio_base.blueprints"] # (4)!
163+
my_site_views = "my_site.views:create_blueprint"
164+
[project.entry-points."invenio_assets.webpack"]
165+
my_site_theme = "my_site.webpack:theme"
166+
# ...other entry points for Celery tasks, models, CLI commands, etc.
167+
168+
[build-system] # (5)!
169+
requires = ["hatchling"]
170+
build-backend = "hatchling.build"
171+
```
172+
173+
1. Package name should match what's referenced in the root `pyproject.toml`
174+
2. Version is a required field in `pyproject.toml`
175+
3. We don't need to define any dependencies here, since they are managed at the root `pyproject.toml`
176+
4. Entry points for blueprints, assets, Celery tasks, etc. remain similar
177+
5. Modern build system - `hatchling` is recommended for pure Python packages
178+
179+
### Update Invenio configuration 📜
180+
181+
Update the `.invenio` configuration file so that `invenio-cli` uses `uv` commands instead of `pipenv` for dependency management:
182+
183+
```ini title=".invenio" hl_lines="4"
184+
[cli]
185+
flavour = RDM
186+
logfile = /logs/invenio-cli.log
187+
python_package_manager = uv
188+
189+
[cookiecutter]
190+
project_name = My Site
191+
...
192+
```
193+
194+
### Clean up old files 📜
195+
196+
Remove any old files:
197+
198+
```bash
199+
rm Pipfile Pipfile.lock site/setup.cfg site/setup.py site/MANIFEST.in
200+
```
201+
202+
### Generate new Python dependencies lockfile
203+
204+
Generate the `uv.lock` file:
205+
206+
```bash
207+
invenio-cli packages lock
208+
```
209+
210+
### Update Dockerfile
211+
212+
#### Before: Docker with Pipenv
213+
214+
```dockerfile title="Dockerfile"
215+
...
216+
COPY site ./site
217+
COPY Pipfile Pipfile.lock ./
218+
RUN pipenv install --deploy --system
219+
...
220+
```
221+
222+
#### After: Docker with uv
223+
224+
```dockerfile title="Dockerfile"
225+
...
226+
# Python and uv configuration
227+
ENV PYTHONDONTWRITEBYTECODE=1 \
228+
PYTHONUNBUFFERED=1 \
229+
# Cache directory for uv's package downloads - persisted across builds with Docker BuildKit
230+
UV_CACHE_DIR=/opt/.cache/uv \
231+
# Pre-compile Python bytecode for faster startup times in production
232+
UV_COMPILE_BYTECODE=1 \
233+
# Strictly use versions from uv.lock file, failing if lock file is outdated
234+
UV_FROZEN=1 \
235+
# Copy packages instead of symlinking - required for Docker's layered filesystem
236+
UV_LINK_MODE=copy \
237+
# Use the system's Python installation instead of uv managing Python versions
238+
UV_NO_MANAGED_PYTHON=1 \
239+
UV_SYSTEM_PYTHON=1 \
240+
UV_PROJECT_ENVIRONMENT=/usr/ \
241+
UV_PYTHON_DOWNLOADS=never \
242+
# Require and verify package hashes match those in the lock file
243+
UV_REQUIRE_HASHES=1 \
244+
UV_VERIFY_HASHES=1
245+
246+
# Copy uv binary from official image
247+
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
248+
249+
# First sync: install only external dependencies without workspace packages
250+
RUN --mount=type=cache,target=/opt/.cache/uv \
251+
--mount=type=bind,source=uv.lock,target=uv.lock \
252+
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
253+
uv sync --no-dev --no-install-workspace --no-editable
254+
255+
# Copy static code, assets, and configuration files
256+
COPY site ./site
257+
COPY legacy ./legacy
258+
259+
COPY ./docker/uwsgi/ ${INVENIO_INSTANCE_PATH}
260+
COPY ./invenio.cfg ${INVENIO_INSTANCE_PATH}
261+
COPY ./templates/ ${INVENIO_INSTANCE_PATH}/templates/
262+
COPY ./app_data/ ${INVENIO_INSTANCE_PATH}/app_data/
263+
COPY ./translations ${INVENIO_INSTANCE_PATH}/translations
264+
COPY ./ .
265+
266+
# Second sync: install workspace packages
267+
RUN --mount=type=cache,target=/opt/.cache/uv \
268+
uv sync --frozen --no-dev
269+
...
270+
```
271+
272+
### Update CI/CD Configuration
273+
274+
#### Before: GitHub Actions with Pipenv
275+
276+
```yaml
277+
- name: Set up Python ${{ matrix.python-version }}
278+
uses: actions/setup-python@v5
279+
with:
280+
python-version: ${{ matrix.python-version }}
281+
cache: pip
282+
cache-dependency-path: Pipfile.lock
283+
284+
- name: Install dependencies
285+
run: |
286+
pip install "pipenv==2023.12.1"
287+
pipenv install --deploy --system
288+
pip install -e ./site
289+
pip freeze
290+
```
291+
292+
#### After: GitHub Actions with uv
293+
```yaml
294+
- name: Install uv # (1)!
295+
uses: astral-sh/setup-uv@v5
296+
with:
297+
python-version: ${{ matrix.python-version }}
298+
enable-cache: true # (2)!
299+
300+
- name: Install dependencies
301+
run: | # (3)!
302+
uv sync --locked
303+
uv pip list
304+
```
305+
306+
1. Use the official uv GitHub Action instead of installing via pip
307+
2. uv has built-in caching that's faster than pip's cache
308+
3. `--locked` ensures exact versions from `uv.lock` are installed (like `pipenv install --deploy`).
309+
310+
## Next steps
311+
312+
After completing the migration:
313+
314+
1. Test your application to ensure all dependencies are correctly installed
315+
2. Update your development team's documentation with the new uv commands
316+
3. Verify that your CI/CD pipelines work with the new configuration
317+
4. Consider removing any old Pipenv-specific scripts or documentation

0 commit comments

Comments
 (0)