Skip to content

How to migrate from uv to pip

Moving a uv-managed project to pip is mostly an audit of pyproject.toml, not a rebuild of the project. uv consolidates Python interpreter installs, virtual environments, resolution, package installation, and lockfile generation into one tool, but if a project’s metadata sticks to packaging standards, most of the substitutes are short. This how-to walks through the audit and the runtime equivalents: pyenv on macOS and Linux, the official python.org installer on Windows.

Audit your pyproject.toml for uv-specific tables

Before exporting a lockfile, scan your pyproject.toml for settings under [tool.uv] and tables under [tool.uv.*]. Anything outside that namespace is already standardized and pip can read it. The settings that matter:

Setting Standard equivalent Action
[tool.uv.dev-dependencies] [dependency-groups] (PEP 735) Move entries to [dependency-groups] so pip 25.1+ can install them with pip install --group <name>.
[tool.uv.sources] None for git, path, or URL deps Inline these as direct-URL dependency specifiers in [project.dependencies], or document them so a teammate can reproduce the install.
[tool.uv.workspace] None A single pip install can target only one project at a time. Treat each member as its own install.
[tool.uv.index] pip.conf or --index-url Configure the alternative index in pip directly, not in pyproject.toml.
[tool.uv.build-backend] and build-backend = "uv_build" in [build-system] Any PEP 517 backend such as hatchling uv_build ships as a standalone PyPI package, so builds still work after removing uv. Swap the backend only if a complete exit from Astral tooling is the goal.
tool.uv.constraint-dependencies constraints.txt plus pip install --constraint Preserve these as pip constraints. Dropping them silently changes the resolved version set.
tool.uv.override-dependencies None Document or remove these before migrating. pip has no equivalent override mechanism.

Runtime dependencies in [project.dependencies] and extras in [project.optional-dependencies] follow PEP 621 and need no change. pip reads them natively.

Install Python without uv

uv installs Python interpreters on demand. Without uv, the canonical replacements are pyenv on macOS and Linux and the official python.org installer on Windows.

Install pyenv (full instructions on the pyenv reference) and then install the Python version your project’s requires-python allows:

pyenv install 3.13.0
pyenv local 3.13.0
python --version

pyenv local writes a .python-version file in the current directory so subsequent python invocations resolve to that interpreter.

Create a virtual environment

Python’s standard library ships venv. It is enough for everything pip needs:

python -m venv .venv
source .venv/bin/activate

The activated shell’s python and pip now point inside .venv/. A deactivated shell falls back to the system interpreter.

Generate a PEP 751 lockfile

uv’s native lockfile, uv.lock, is not a portable format. Export it to PEP 751’s standard pylock.toml, which any installer can consume:

uv export --format pylock.toml --no-default-groups > pylock.toml

--no-default-groups excludes whatever dependency groups uv would otherwise include by default, usually dev. Pass --group <name> if you also need a specific dependency group resolved into the lock.

The generated file starts with a header pip recognizes:

lock-version = "1.0"
created-by = "uv"
requires-python = ">=3.13"

[[packages]]
name = "cowsay"
version = "6.1"

Install dependencies from the lockfile

Upgrade pip to 26.1 or newer inside the activated venv. The -r pylock.toml form was added in that release:

python -m pip install --upgrade pip
pip install -r pylock.toml --no-deps

--no-deps keeps the install strictly to what the lockfile records. Without it, pip can pull in additional transitive packages and silently drift from the locked set. The companion how-to on installing from a pylock.toml lockfile with pip covers the experimental-feature warning, hash verification, and the filename rules pip enforces.

Install a dependency group from pyproject.toml

For dev, test, or lint dependencies, pip can read [dependency-groups] directly out of pyproject.toml. No lockfile is involved:

pip install --group dev

This was added in pip 25.1. It resolves the named group fresh, so versions can shift between runs unless you also lock the group into a pylock.toml.

Replace the rest with non-pip tools

A few uv features have no direct equivalent in pip alone. Each has a working substitute outside the pip toolchain:

  • Inline-script metadata (PEP 723) that uv run script.py resolves and executes. The closest substitutes are pipx (pipx run script.py) and pip-run.
  • Persistent CLI tools installed with uv tool install. Use pipx (pipx install <tool>).
  • Building and publishing wheels. uv’s uv build and uv publish map to build (python -m build) plus twine (twine upload). If build-backend = "uv_build" is set in [build-system], swap it for a PEP 517 backend (the audit table covers this).
  • Workspace-wide installs across multiple packages. There is no pip equivalent. Treat each workspace member as a separate project, or keep uv for the workspace orchestration even after switching the leaf installs.

If your project depends on any of these, plan their replacement before removing uv from your environment.

Learn More

Last updated on

Please submit corrections and feedback...