uv icon indicating copy to clipboard operation
uv copied to clipboard

Add `uv add --bounds` to configure the version constraint

Open konstin opened this issue 7 months ago • 5 comments

By default, uv uses only a lower bound in uv add, which avoids dependency conflicts due to upper bounds. With this PR, this cna be changed by setting a different bound kind. The bound kind can be configured in uv.toml, as a user preference, in pyproject.toml, as a project preference, or on the CLI, when adding a specific project.

We add two options that add an upper bound on the constraint, one for SemVer (>=1.2.3,<2.0.0, dubbed "major", modeled after the SemVer caret) and another one for dependencies that make breaking changes in minor version (>=1.2.3,<1.3.0, dubbed "minor", modeled after the SemVer tilde). Intuitively, the major option bumps the most significant version component, while the minor option bumps the second most significant version component. There is also an exact bounds option (==1.2.3), though generally we recommend setting a wider bound and using the lockfile for pinning.

Versions can have leading zeroes, such as 0.1 or 0.0.1. For a single leading 0, we shift the the meaning of major and minor similar to cargo. For two or more leading zeroes, the difference between major and minor becomes inapplicable, instead both bump the most significant component:

  • major: 0.1 -> >=0.1,<0.2
  • major: 0.0.1 -> >=0.0.1,<0.0.2
  • major: 0.0.1.1 -> >=0.0.1.1,<0.0.2.0
  • major: 0.0.0.1 -> >=0.0.0.1,<0.0.0.2
  • minor: 0.1 -> >=0.1,<0.1.1
  • minor: 0.0.1 -> >=0.0.1,<0.0.2
  • minor: 0.0.1.1 -> >=0.0.1.1,<0.0.2.0
  • minor: 0.0.0.1 -> >=0.0.0.1,<0.0.0.2

For a consistent appearance, we try to preserve the number of components in the upper bound. For example, adding a version 2.17 with the major option is stored as >=2.17,<3.0. If a version uses three components and is greater than 0, both bounds will also use three components (SemVer versions always have three components). Of the top 100 PyPI packages, 8 use a non-three-component version (docutils, idna, pycparser and soupsieve with two components, packaging, pytz and tzdata with two component, CalVer and trove-classifiers with four component CalVer). Example pyproject.toml files with the top 100 packages: --bounds major and --bounds minor. While many projects follow version scheme that roughly or directly matches the major or minor options, these compatibility ranges are usually not applicable for the also popular CalVer versioning.

For pre-release versions, there are two framings we could take: One is that pre-releases generally make no guarantees about compatibility between them and are used to introduce breaking changes, so we should pin them exactly. In many cases however, pre-release specifiers are used because a project needs a bugfix or a feature that hasn't made it into a stable release, or because a project is compatible with the next version before a final version for that release is published. In those cases, compatibility with other packages that depend on the same library is more important, so the desired bound is the same as it would be for the stable release, except with the lower bound lowered to include pre-release.

The names of the bounds and the name of the flag is up for bikeshedding. Currently, the option is call tool.uv.bounds, but we could also move it under tool.uv.edit.bounds, where it would be the first/only entry.

Fixes #6783

konstin avatar Apr 17 '25 14:04 konstin

We'll need a small documentation section in https://docs.astral.sh/uv/concepts/projects/dependencies/ and https://docs.astral.sh/uv/concepts/projects/config/ (not sure which should link to the other yet)

zanieb avatar Apr 21 '25 14:04 zanieb

This should be ready now

konstin avatar Apr 25 '25 11:04 konstin

Also, great PR description! It was super helpful during review.

BurntSushi avatar May 02 '25 15:05 BurntSushi

While many projects follow version scheme that roughly or directly matches the major or minor options, these compatibility ranges are usually not applicable for the also popular CalVer versioning.

For fixing that versioning, I recommend a per package configuration option in user/workspace uv.toml:

add-bounds = "major"

[per-package-add-bounds]
tzdata = "lower"

(Yes, it also could be separate work as follow-up...)

T-256 avatar May 02 '25 16:05 T-256

@T-256 I would expect something like that to rather go into uv upgrade, as uv add usually only happens once per package.

konstin avatar May 04 '25 18:05 konstin

@konstin I'm okay with this, but I'd like to change this message

https://github.com/astral-sh/uv/pull/12946/files#r2071770491

so that it's not scoped to saying "its configuration" may change. We have more leeway with the behavior that way.

zanieb avatar May 23 '25 15:05 zanieb