Skip to main content

uvbump

Upgrades the version bounds declared in your pyproject.toml, in place, without touching your comments or your formatting. For anyone whose pyproject.toml still says requests>=2.28 while the lock file has been on 2.32.5 for a year.

pip install uv-pyproject-bump
uvbump --help

uv lock and uv sync --upgrade move your lock file. They leave the bounds you wrote in pyproject.toml exactly where they were, so a project can sit on requests>=2.28 for two years while actually running 2.32.5. The usual fix is a uv remove then uv add cycle, which rewrites the file and drops every comment in it. uvbump edits the bound and nothing else.

$ uvbump
  requests           >=2.28 -> >=2.32.5
  click              >=8.0,<9 -> >=8.1.7,<9
  attrs              ~=23.1 -> ~=23.2
  rich               ==13.0.0 -> ==14.0.0
  typing-extensions  skipped: no version bound to update
pyproject.toml: 4 bound(s) updated
uv.lock updated

The diff is one line per bound. Comments, key order, quoting style and blank lines come back byte for byte.

What it reads

The six tables it reads:

  • project.dependencies
  • project.optional-dependencies.*
  • dependency-groups.*
  • tool.uv.dev-dependencies
  • tool.uv.constraint-dependencies
  • tool.uv.override-dependencies

What it writes

  • requests>=2.28, latest 2.32.5, becomes requests>=2.32.5.
  • rich==13.0.0, latest 14.0.0, becomes rich==14.0.0.
  • attrs~=23.1, latest 23.2.0, becomes attrs~=23.2, kept at the same precision so the ceiling still means something.
  • django==4.2.*, latest 5.1.3, becomes django==5.1.*.
  • click>=8.0,<9, latest 8.1.7, becomes click>=8.1.7,<9.
  • click>=8.0,<8.1, latest 8.1.7, is left alone, and uvbump tells you the upper bound is what held it back.
  • typing-extensions with no bound is left alone, there is nothing to move.
  • pkg @ https://... is left alone, direct references are never touched.

It never moves a bound backwards, and it never invents a bound you did not write. A version whose every file has been yanked is not a candidate, the same way uv and pip pass over it. Pass --widen if you do want the upper bound raised to the next major instead of being skipped.

Limits

Worth knowing before you run it:

  • It reads the index, not your environment. It offers the latest release, without checking that release against your requires-python or against what your other dependencies allow. uv lock is what tells you whether the set still resolves, which is why uvbump runs it for you unless you pass --no-lock.
  • A requirement with two bounds to move, such as pkg>=1.0,==2.0, is left alone rather than guessed at.
  • Anything left alone is invisible to --check, which is the one to know before you put it in CI. When a bound is held back by an upper bound (click>=8.0,<8.1) or by a second anchor (pkg>=1.0,==2.0), uvbump plans no change, so --check exits 0 even though the declared bound is behind the index. It reports the reason on stderr either way. --check answers "is there a bound I can move", not "is every bound current".
  • A dependency with no bound at all stays without one. uvbump moves bounds, it does not add them.
  • Markers, extras and direct URL references are carried through untouched, never interpreted.
  • It edits pyproject.toml only. Your requirements.txt files are not its business.

In CI

- run: uvx --from uv-pyproject-bump uvbump --check

--check writes nothing and exits 1 when it has a bound it can move forward, so a job can fail on a stale pyproject.toml the same way it fails on unformatted code. Exit 0 means it has nothing to move, which is not quite the same as every bound being current: read the two cases under Limits before you rely on it. Exit 2 means uvbump could not do its job.

Options

uvbump [PATH] [options]

  PATH               pyproject.toml, or the directory holding it (default: ./pyproject.toml)
  --check            write nothing, exit 1 when a bound can be moved forward
  -n, --dry-run      show what would change, write nothing, exit 0
  --only NAME        only this dependency, repeatable
  --exclude NAME     never this dependency, repeatable
  --group TABLE      only tables whose name contains this, repeatable (for example: --group dev)
  --widen            raise an upper bound that holds a dependency back, instead of skipping it
  --pre              consider pre-releases
  --index-url URL    PEP 691 simple index (default: $UV_INDEX_URL, then $PIP_INDEX_URL, else PyPI)
  --no-lock          do not run `uv lock` after writing
  --quiet            only print what changed

Examples:

# Two packages, nothing else.
$ uvbump --only requests --only httpx

# Dev tables only, and leave the lock file alone.
$ uvbump --group dev --no-lock

# Look first.
$ uvbump --dry-run

# A mirror or a private index.
$ uvbump --index-url https://my.index/simple

Install

The distribution on PyPI is named uv-pyproject-bump; the command it installs is uvbump.

$ pip install uv-pyproject-bump
$ uvx --from uv-pyproject-bump uvbump

Pinning to a commit instead of the PyPI release still works:

$ pip install git+https://github.com/Rezarys/uvbump@v0.2.0

uvbump needs Python 3.11 or later and has no dependencies. It asks your index over the PEP 691 JSON API, which means a mirror or a private index works too. It sends nothing anywhere else and collects nothing.

Why the bounds matter

If you publish a library, the bounds in pyproject.toml are the contract your users resolve against, and a lock file does not reach them. If you ship an application, stale lower bounds quietly widen the set of resolutions your CI has never tried. Either way the file is the thing that has to be current, and it is the one thing no tool was updating.

Tests

$ python3 run_tests.py

No test dependencies either.

Prior art

uv itself does not do this yet, and the request is one of the most upvoted on its tracker: astral-sh/uv#6794 and astral-sh/uv#1419. Several people in those threads have published their own take, and they are worth a look if uvbump is not the shape you want.

The closest match by name and by aim is uv-bump, around since February 2025: it bumps the minimum bounds in your pyproject.toml in sync with your uv.lock, keeps your formatting and your comments, and supports workspaces. It works from a clean project, and its Howto asks you for an up to date uv.lock and a synced .venv before you run it. Its README documents no check mode and no exit code for CI. uvbump reads the package index directly, so it needs neither a lock file nor a virtual environment to tell you what has moved, and --check is there for CI. Comments survive both tools, so that is not a reason to pick one over the other. If uv-bump is the shape you want, use it.

If uv ships this natively, uvbump has done its job and you should use uv.

Not affiliated with Astral or with the uv project.

License

MIT.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

uv_pyproject_bump-0.2.0.tar.gz (18.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

uv_pyproject_bump-0.2.0-py3-none-any.whl (15.5 kB view details)

Uploaded Python 3

File details

Details for the file uv_pyproject_bump-0.2.0.tar.gz.

File metadata

  • Download URL: uv_pyproject_bump-0.2.0.tar.gz
  • Upload date:
  • Size: 18.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.12.10 {"installer":{"name":"uv","version":"0.12.10","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for uv_pyproject_bump-0.2.0.tar.gz
Algorithm Hash digest
SHA256 c31cd91904efe725e7bfe33a437da62a1defdf6f1401d8334fe7be237523ab1d
MD5 a5417003e421549dc29a2315eedae2ef
BLAKE2b-256 a060f1fb71996c9c01f9b2570d91725029bfc496f6b9b0c9b0471ca3149d74e9

See more details on using hashes here.

File details

Details for the file uv_pyproject_bump-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: uv_pyproject_bump-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 15.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.12.10 {"installer":{"name":"uv","version":"0.12.10","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for uv_pyproject_bump-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1a89cf7413e87eaf43d051b39ca0b80b7db712c0653b9be8a7662e0661b27afa
MD5 96fc72c2e3fbc5c2abe3ea0aaf9a7e61
BLAKE2b-256 9b29121dfa89d848ec1cd83270baf8ac53e403e8b89ae7f36779d235fd06c99f

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page