|
| 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-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 |
| 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