How to upgrade packages with uv: single-package updates, version constraints, uv.lock, and rollback

Why does uv sync leave packages unchanged? Learn single-package upgrades, uv add version constraints, --locked versus --frozen, and how to verify and roll back dependency updates.

To upgrade a dependency in a uv project, first update the lockfile with uv lock --upgrade-package 包名, then install and verify it with uv sync --locked. If the project’s declared version range excludes the target version, adjust the constraints in pyproject.toml first.

The common mistake is treating “synchronize the environment” as “find the latest version.” When uv sync leaves a package unchanged, uv is often following the existing lockfile.

This guide covers Python projects that already have a pyproject.toml. Examples use PowerShell; the core uv commands also work on Linux and macOS. The examples use requests and pytest so you can try them in an isolated practice directory without connecting to a model API. Documentation was checked on October 11, 2026. This guide does not establish upgrade compatibility for any specific AI project.

Identify which layer you want to update

“Update uv” can refer to three different kinds of objects. Identify the target first so you do not upgrade the tool and wonder why project dependencies stayed unchanged.

Target Typical operation Main effect
The uv executable Upgrade uv through its original installation channel Package manager version
One project dependency uv lock --upgrade-package requests Dependency resolution recorded in the lockfile
Allowed dependency range uv add "requests>=2.32,<3" Project declaration, lockfile, and environment
All project dependencies uv lock --upgrade All locked dependencies eligible for an upgrade
A tool installed through uv tool uv tool upgrade 工具名 The tool’s isolated environment

Do not use uv tool upgrade to update requests inside your project. Upgrading the uv executable alone also does not automatically update packages in .venv. Installed tools and project dependencies have separate maintenance scopes; see the uv tools guide.

What pyproject.toml, uv.lock, and .venv control

A dependency change involves three questions: which versions the project allows, which versions resolution selects, and which versions this machine actually has installed.

File or directory Purpose Commit to Git?
pyproject.toml Declare direct dependencies and allowed ranges Yes
uv.lock Store exact resolution results Yes
.python-version Record the project’s selected Python version Usually
.venv The environment installed on this machine No

For example, a project might declare:

1
2
3
4
5
6
7
[project]
name = "dependency-demo"
version = "0.1.0"
requires-python = ">=3.12,<3.13"
dependencies = [
    "requests>=2.31,<3",
]

This configuration permits a range of requests versions. It does not ask uv to install the latest version in that range on every run.

If uv.lock already locks a compatible version, a normal synchronization will generally keep using it. This reduces surprises where a project works yesterday but breaks after a fresh installation today. See the locking and syncing documentation for the underlying behavior.

Practice a single-package upgrade in a separate directory

First check that the tools are available:

1
2
uv --version
git --version

Choose a directory name that does not already exist, so practice files stay out of your active repository. The following example uses a script-style project and does not require a Python package build backend.

1
2
3
4
5
6
uv init --bare --python 3.12 uv-update-demo
Set-Location uv-update-demo
uv python pin 3.12
uv add "requests>=2.31,<3"
uv add --dev pytest
uv sync --locked

If the required Python version is missing, uv may need to download an interpreter. On a restricted network, resolve download or interpreter discovery problems before diagnosing dependency conflicts. Python selection rules are explained in the official interpreter documentation.

Create main.py:

1
2
3
4
5
6
7
from importlib.metadata import version
import requests

request = requests.Request("GET", "https://example.com/").prepare()
print("requests:", version("requests"))
print("method:", request.method)
print("url:", request.url)

This code prepares a request without sending it over the network. It checks that the package imports and shows the version actually installed in the current environment.

1
2
uv run --locked python main.py
uv tree --locked

Then record a practice baseline:

1
2
3
4
git init
git status --short
git add pyproject.toml uv.lock .python-version main.py
git commit -m "Record dependency baseline"

Before committing, confirm that .venv is not staged. If you have not configured your Git identity, keep file copies for now; the Git rollback commands later require an existing commit.

Upgrade one package and inspect the changes

Run these commands in the project directory you just created:

1
2
3
4
uv lock --upgrade-package requests
git diff -- uv.lock
uv sync --locked
uv run --locked python main.py

Separating resolution from installation lets you inspect lockfile changes before changing the working environment. If you prefer one operation, you can use uv sync --upgrade-package requests instead.

A “single-package upgrade” targets that package; it does not guarantee that only one lockfile entry changes. The new version may require different transitive dependencies, and the resolver must still find a combination that satisfies the entire project.

When reviewing the diff, focus on:

  • Whether requests itself changed.
  • Whether transitive dependencies were added or removed.
  • Whether download sources still match project expectations.
  • Whether another constraint keeps the target package at its previous version.

A newly created practice project will usually already have selected a currently available version. An upgrade that produces no diff can therefore be normal, rather than a failed command.

Use the project interpreter to check the installation:

1
2
uv run --locked python -c "import sys; print(sys.executable)"
uv run --locked python -c "from importlib.metadata import version; print(version('requests'))"

The first command should print a path inside the project environment. If your editor shows a different version, check its selected Python interpreter first.

Why an upgrade can stay on an older version

Suppose the project declares:

1
2
3
dependencies = [
    "requests==2.31.0",
]

An exact constraint permits only that version. --upgrade-package does not override the project declaration.

After assessing compatibility and deciding to allow later versions, change the range explicitly:

1
2
3
uv add "requests>=2.32,<3"
git diff -- pyproject.toml uv.lock
uv run --locked python main.py

By default, uv add updates the declaration, lockfile, and environment, so this step may already install a new package version. Run it on a separate branch; it is not a read-only query.

If you only want to evaluate a particular version, declare that exact version and test it. Do not remove every upper bound merely to make resolution succeed: an existing bound may represent a known compatibility limit.

Other packages, Python versions, and platform conditions can also constrain the range. When resolution fails, identify the contradictory requirements in the error message. See dependency management for editing declarations and dependency groups.

Reserve a separate maintenance window for full upgrades

To update the whole project:

1
2
3
4
uv lock --upgrade
git diff --stat
git diff -- uv.lock
uv sync --locked

A full upgrade belongs in a dependency maintenance task, rather than a commit intended only to change page text. The larger the change, the harder it becomes to identify which package caused a regression.

Start with one application dependency and run acceptance checks, then handle development tools separately. Projects with native extensions, GPU runtimes, or model inference backends also need system library and driver checks. A lockfile records package resolution; it does not replace testing on actual hardware.

Choosing between –locked and –frozen

Both flags commonly appear in deployment commands, but they perform different checks.

Command Behavior Use case
uv lock --check Check that the lockfile matches the declaration Pre-commit checks
uv sync --locked Require an up-to-date lockfile, then synchronize Deployment and reproduction
uv run --locked python main.py Require an up-to-date lockfile, then run Routine verification
uv sync --frozen Use the existing lockfile without checking freshness Workflows that validate the lockfile elsewhere

If you change the dependency declaration but forget to update the lockfile, --locked helps expose the mismatch. Skipping that check with --frozen does not establish that your new declaration took effect.

Environment cleanup is another distinction: uv sync uses exact synchronization by default and removes packages absent from the lockfile. A debugging package manually installed into .venv may therefore disappear on the next synchronization. Declare development tools you need to retain with uv add --dev.

Check a key behavior with a small test after upgrading

Create test_main.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import requests


def test_prepare_request_keeps_query_parameter():
    prepared = requests.Request(
        "GET",
        "https://example.com/search",
        params={"q": "hello world"},
    ).prepare()
    assert prepared.method == "GET"
    assert prepared.url == "https://example.com/search?q=hello+world"

Run:

1
2
3
4
uv lock --check
uv run --locked pytest -q
uv run --locked python main.py
git diff --check

This test covers the specific behavior of encoding request parameters. It does not prove that network connections, proxies, TLS, or application endpoints work. Real projects should cover their own critical paths, such as document parsing results, database operations, or API response structures.

If your project uses MinerU for document parsing, choose a fixed PDF and compare page counts, tables, and image references before and after upgrading. Keeping a small regression input gives you stronger evidence than checking only whether import succeeds.

Recovering from a failed update

Keep the error logs and diff first, and check for work unrelated to the upgrade. The following commands discard uncommitted changes in these two files. Use them only when all those changes belong to this upgrade.

1
2
3
4
git diff -- pyproject.toml uv.lock
git restore --source=HEAD -- pyproject.toml uv.lock
uv sync --locked
uv run --locked pytest -q

Restoring the declaration and lockfile together prevents them from contradicting each other. Run the tests again after synchronization to confirm that you restored a working state, not merely matching files.

If the upgrade has already been committed, find a known-good commit before restoring the corresponding file versions or reverting the upgrade commit. Do not simply copy a colleague’s .venv: environment paths, platform binaries, and interpreter versions may differ.

Rolling back dependencies does not automatically undo database migrations or data changes made by the application. Upgrades that affect data formats require the application’s own backup and recovery procedure.

What if you only have requirements.txt?

Decide whether you want to keep the requirements workflow or migrate fully to a uv project. Do not combine both goals into a single upgrade command.

To retain the current workflow, install into a separate virtual environment:

1
2
uv venv
uv pip install -r requirements.txt

If requirements already pins exact versions, this installation command does not remove those constraints. To migrate to the project workflow, initialize a project on a migration branch and import dependencies:

1
2
3
uv init --bare
uv add -r requirements.txt
uv lock --check

Importing a file generated by pip freeze can turn transitive dependencies into direct declarations. After migration, distinguish packages your application actually uses from dependencies brought in by other packages. Private indexes, editable installs, and platform conditions need separate review; see the official migration guide.

Export dependencies for deployments that still use pip

If you cannot move the whole project to uv yet, export from the lockfile:

1
uv export --locked --format requirements.txt --no-dev --output-file requirements.txt

Treat the exported file as a lockfile-derived artifact. After dependency changes, update the project and lockfile before exporting again, rather than maintaining two version lists manually.

This example project does not package itself. Installable application projects must also check whether the export includes the project itself or local path references. Deployment must provide the corresponding source code; copying requirements alone may not be enough. See the official export documentation for options.

Quick troubleshooting reference

Symptom Check first Next step
Version unchanged after sync A compatible version is already locked Request a single-package upgrade explicitly
Still on an old version after upgrade Exact constraints or restrictions from other packages Review resolution and assess declaration changes
Terminal works, editor imports fail Python interpreter path Select the project’s .venv
Lockfile is correct, installation fails Network, certificates, disk space, platform wheels Keep the error and identify the download or build stage
Deployment reports a stale lockfile Whether declaration and lockfile were committed together Update and validate the lockfile on a development branch
Temporary tools disappear after sync Whether tools are declared as dev dependencies Manage them with uv add --dev

A dependency update is complete when declarations are clear, the lockfile is consistent, the installed environment is correct, and critical application behaviors pass verification. Once you identify the target, start with one package and keep the diff and a recovery path.