Skip to main content

uv-refresh

Rebuilds a uv project's pyproject.toml so every dependency gets freshly resolved instead of dragging along old version pins.

How it works

  1. Read pyproject.toml, collect the dependencies without their version specifiers (extras and environment markers are kept; include-group entries in dependency-groups are resolved, not dropped)
  2. Back up pyproject.toml and uv.lock into .uv-refresh-backup/<timestamp>/
  3. Run uv init --bare + uv add <packages> in a temp directory next to the project, with the project's [tool.uv] settings (indexes, sources, constraints) and .python-version -- the real pyproject.toml stays untouched the whole time
  4. Merge only dependencies/optional-dependencies/dependency-groups from the result into a copy of the ORIGINAL pyproject.toml -- everything else stays untouched
  5. Atomically swap the result in place of the old pyproject.toml/uv.lock

If any step fails -- including Ctrl+C -- the real pyproject.toml was never touched, since the whole build happened in the temp directory. The backup is kept around as an extra reference regardless.

Installation

uv tool install uv-refresh

Makes uv-refresh permanently available as a command (globally on PATH).

For a one-off test run, without installing anything:

uvx uv-refresh --dry-run

Or straight from the repo, e.g. to try an unreleased version:

uv tool install git+https://github.com/Zorakidd/uv-refresh

Usage

uv-refresh --dry-run     # just show what would happen, change nothing
uv-refresh               # runs with a confirmation prompt
uv-refresh -y            # no confirmation prompt

Options

Flag Effect
--path PATH different project directory (default: current)
--dry-run only show what would happen, touch nothing
-y, --yes run without asking for confirmation
-v, --verbose print the full new pyproject.toml at the end
-q, --quiet only print warnings/errors, no status output
--timeout SECONDS timeout per uv call, default 300s
--raw add packages with no version bound at all
--bounds {lower,major,minor,exact} kind of version bound uv add sets
--keep-lock keep uv.lock (uv will then prefer the old versions!)
--keep-backups N how many past backups to keep, oldest deleted first (default: 5, 0 keeps all)
--no-groups ignore optional-dependencies and dependency-groups
--full also bump requires-python and re-pin .python-version to the newest installed Python
--drop-extras shrink fastapi[standard] down to fastapi
--drop-markers drop environment markers
--version show the uv-refresh version

Note

Only dependencies, optional-dependencies and dependency-groups are rewritten. Everything else -- description, readme, license, authors, keywords, [project.urls], [project.scripts], [build-system], [tool.*] and so on -- stays unchanged, because it's never deleted: the tool only builds a minimal pyproject.toml temporarily to resolve versions, then takes just the freshly resolved dependency lists from it and writes those back into a copy of the original file.

One exception: with --no-groups, any existing optional-dependencies/dependency-groups are intentionally removed (the tool warns beforehand). If [tool.uv] still names one of them (a default-groups list, or a source limited to an extra/group), --no-groups is refused up front instead, since uv rejects references to extras/groups that no longer exist. Individual entries that can't be interpreted as a PEP 508 string are skipped and reported per entry.

--full additionally bumps requires-python to the newest already installed Python it can find (uv python list --only-installed -- it never triggers a download on its own), e.g. >=3.11 becomes >=3.13. That bump is part of the same atomic pyproject.toml rebuild as the dependency refresh, so it's covered by the same backup/all-or-nothing guarantee.

It only ever goes up: pre-release Pythons (e.g. 3.15.0rc2) are ignored, a requires-python that already starts at that minor version or above is kept as is, and if the newest installed Python doesn't satisfy a kept requires-python (older than its floor, or e.g. an exact ==3.13 with 3.13.5 installed), --full leaves both requires-python and .python-version alone (with a warning) and just does the normal refresh.

Only once that rebuild has landed does --full re-pin .python-version via uv python pin to that same version. This runs after the rebuild on purpose: uv python pin refuses to write anything if the target version doesn't satisfy requires-python, and by pinning after the bump above, it's checked against the new requires-python -- so jumping to a newer Python than the project previously allowed still works. If the pin itself then fails, the dependency refresh and requires-python bump are kept regardless (they already succeeded); only .python-version is left as it was.

Why the temp build copies .python-version

A bare uv init --python=<requires-python floor> only writes that floor (e.g. >=3.11) into requires-python, not an exact interpreter pin. Left alone, the temp uv add would then run against the newest installed Python satisfying that floor, even if the real project's own .python-version pins an older one -- and fail on any dependency (e.g. torch) that has no wheel for that newer version, despite the real project working fine on its actual pinned interpreter. So uv-refresh copies the pin into the temp directory before running uv add. --full is the one exception: when it re-pins, the temp build already uses the new version instead of the old pin.

Limitations

The temp directory only ever holds pyproject.toml (plus uv.lock and .python-version), one level below the project. So uv-refresh refuses these projects up front, before any backup is made, instead of failing halfway with a uv or build-backend error:

  • uv workspaces ([tool.uv.workspace]) and workspace = true sources
  • path sources with a relative path, and ${PROJECT_ROOT} references (absolute paths work fine)
  • a relative local path in [[tool.uv.index]], index-url, extra-index-url or find-links (absolute paths and URLs work fine)
  • a dynamic version, dependencies, optional-dependencies or requires-python (e.g. setuptools-scm, hatch-vcs), because uv would have to build the project to lock it, and its files aren't in the temp directory

An exact requires-python = "==3.14" can make the refresh fail: uv may pick a newer 3.14.x interpreter to lock with and then reject it. Plain uv lock fails the same way on such a project, so that's not something uv-refresh can fix.

Metadata

Release files for uv-refresh 0.1.13

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for uv-refresh 0.1.13
File Size Uploaded
uv_refresh-0.1.13.tar.gz 17.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for uv-refresh 0.1.13
File Interpreter ABI Platform
uv_refresh-0.1.13-py3-none-any.whl Python 3 none any Details

Total release size: 36.6 kB

Release files / uv_refresh-0.1.13.tar.gz

Download URL uv_refresh-0.1.13.tar.gz
Size 17.6 kB
Tags Source
SHA-256 checksum
How to use checksums
9f02c50e99f3fe6c828d7c0391461a0f99f74a9fbeb3652a7254137df47a345f
BLAKE2b-256 checksum
How to use checksums
d01550a4f29017c8e657e1ba9de975c852dc63aab47a75729f854cc185fed550
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 18, 2026.

Transparency log

Release files / uv_refresh-0.1.13-py3-none-any.whl

Download URL uv_refresh-0.1.13-py3-none-any.whl
Size 19.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
8d6cfb1a14ed3d94587eeba7e21af44fe2567dde835741a19c1f8d6509555c95
BLAKE2b-256 checksum
How to use checksums
dc4a2a1a0c9d5abe0ca6ca6b2bbb1be778920b67d9c5ddb47de784da9d8db97d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 18, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.13 This release

2 release files

0.1.12

2 release files

0.1.11

2 release files

0.1.10

2 release files

0.1.9

2 release files

0.1.8

2 release files

0.1.7

2 release files

0.1.6

2 release files

0.1.0

2 release 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