Create a pyproject.toml with uv init, then import your loose requirements with uv add and pass the old pinned requirements.txt as constraints. That makes uv.lock reuse the versions you already deploy instead of solving for newer ones. Importing the pinned file alone, or the loose file alone, gives different results.
The trap in a naive import
A pip and pip-tools project usually has two kinds of files. The requirements.in file lists what you want, often without exact versions. The requirements.txt file is the compiled output with every package pinned. The uv migration guide points out the nuance: if you import requirements.in, uv solves for new versions because that file does not pin them. The environment you ship tomorrow is then not the environment you shipped yesterday.
The fix in the guide is to add your locked versions as constraints, so the existing versions are retained when uv produces the lockfile.
Steps
- Create a branch and keep requirements.txt in place for now.
- Run uv init if there is no pyproject.toml.
- Import the loose requirements with the pinned file as a constraint.
- Do the same for development files, using the dev group.
- Run uv lock and review the lockfile.
- Compare the versions in uv.lock with the old requirements.txt (see the diff method in the linked post below).
uv init
uv add -r requirements.in -c requirements.txt
uv add --dev -r requirements-dev.in -c requirements-dev.txt
The guide also shows a variation for a dev file that includes the base file with a line starting with a dash r. Strip that line before piping to uv add, otherwise the base requirements land in the dev group too.
What you gain and what changes
| Before | After |
|---|---|
| requirements.in and requirements.txt | pyproject.toml and uv.lock |
| One compiled file per platform | One universal lockfile |
| Separate files for dev groups | Dependency groups in one place |
The uv documentation says its lockfile is always universal, so multiple files are not needed for each platform, and it can represent arbitrary groups. It also supports pinning a package to a specific index, which a requirements.txt cannot express. That index pinning is a real supply chain benefit, covered in pip extra index url versus uv first index.
After the import
Keep the old files for one release as a fallback, but stop editing them. Two sources of truth drift apart quickly. The uv documentation says a lockfile is not considered outdated just because new package versions were released, so nothing upgrades until you ask. Plan upgrades separately, as described in migrate and upgrade in separate pull requests.
Try it yourself
Click through it at your own pace, or open it full screen. Compare the inventory view with your old requirements.txt: the names and versions should line up.
Safeguard's dependency scanning and SBOM generation can inventory a Python project on either side of the move. A free scan is available at gold.safeguard.sh/scan.
Quick check
Why not just import requirements.txt directly?
You can, and it pins everything, but every transitive package becomes a direct dependency in pyproject.toml. Importing the loose file with the pinned file as constraints keeps your intent clean and your versions the same.
Will uv upgrade packages on its own later?
No. The uv documentation says new releases do not make the lockfile outdated. You upgrade when you choose to.
Sources
Last reviewed: 19 October 2026.