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 --versionpyenv 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/activateThe 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 devThis 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.pyresolves 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 buildanduv publishmap to build (python -m build) plus twine (twine upload). Ifbuild-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
- How to install from a pylock.toml lockfile with pip covers the install-side caveats in detail.
- What is PEP 621? walks through project metadata fields.
- What is PEP 735? explains dependency groups.
- What is PEP 751? covers the standardized lockfile format.
- pip reference and pyenv reference link to upstream docs.