mirror of https://github.com/astral-sh/uv
Document why uv discards upper bounds on `requires-python` (#15927)
We're regularly get questions about this. The DPO thread is the best ressource, but it's also a long read, so I summarized some points for uv's decision. --------- Co-authored-by: Zanie Blue <contact@zanie.dev>
This commit is contained in:
parent
3e6fe1da86
commit
787d035d5e
|
|
@ -156,26 +156,62 @@ Windows, Linux and macOS.
|
|||
To ensure that a resolution with `requires-python = ">=3.9"` can actually be installed for the
|
||||
included Python versions, uv requires that all dependencies have the same minimum Python version.
|
||||
Package versions that declare a higher minimum Python version, e.g., `requires-python = ">=3.10"`,
|
||||
are rejected, because a resolution with that version can't be installed on Python 3.9. For
|
||||
simplicity and forward compatibility, only lower bounds in `requires-python` are respected. For
|
||||
example, if a package declares `requires-python = ">=3.8,<4"`, the `<4` marker is not propagated to
|
||||
the entire resolution.
|
||||
are rejected, because a resolution with that version can't be installed on Python 3.9. This ensures
|
||||
that when you are on an old Python version, you can install old packages, instead of getting newer
|
||||
packages that require newer Python syntax or standard library features.
|
||||
|
||||
This default is a problem for packages that use the version-dependent C API of CPython, such as
|
||||
numpy. Each numpy release support 4 Python minor versions, e.g., numpy 2.0.0 has wheels for CPython
|
||||
3.9 through 3.12 and declares `requires-python = ">=3.9"`, while numpy 2.1.0 has wheels for CPython
|
||||
3.10 through 3.13 and declares `requires-python = ">=3.10"`. The means that when we resolve a
|
||||
`numpy>=2,<3` requirement in a project with `requires-python = ">=3.9"`, we resolve numpy 2.0.0 and
|
||||
the lockfile doesn't install on Python 3.13 or newer. To alleviate this, whenever we reject a
|
||||
version due to a too high Python requirement, we fork on that Python version. This behavior is
|
||||
controlled by `--fork-strategy`. In the example case, upon encountering numpy 2.1.0 we fork into
|
||||
Python versions `>=3.9,<3.10` and `>=3.10` and resolve two different numpy versions:
|
||||
uv ignores upper-bounds on `requires-python`, with special handling for packages with only
|
||||
ABI-specific wheels. For example, if a package declares `requires-python = ">=3.8,<4"`, the `<4`
|
||||
part is ignored. There is a detailed discussion with drawbacks and alternatives in
|
||||
[#4022](https://github.com/astral-sh/uv/issues/4022) and this
|
||||
[DPO thread](https://discuss.python.org/t/requires-python-upper-limits/12663), this section
|
||||
summarizes the aspects most relevant to uv's design.
|
||||
|
||||
For most projects, it's not possible to determine whether they will be compatible with a new version
|
||||
before it's released, so blocking newer versions in advance would block users from upgrading or
|
||||
testing newer Python versions. The exceptions are packages which use the unstable C ABI or internals
|
||||
of CPython such as its bytecode format.
|
||||
|
||||
Introducing a `requires-python` upper bound to a project that previously wasn't using one will not
|
||||
prevent the project from being used on a too recent Python version. Instead of failing, the resolver
|
||||
will pick an older version without the bound, circumventing the bound.
|
||||
|
||||
For the resolution to be as universally installable as possible, uv ensures that the selected
|
||||
dependency versions are compatible with the `requires-python` range of the project. For example, for
|
||||
a project with `requires-python = ">=3.12"`, uv will not use a dependency version with
|
||||
`requires-python = ">=3.13"`, as otherwise the resolution is not installable on Python 3.12, which
|
||||
the project declares to support. Applying the same logic to upper bounds means that bumping the
|
||||
upper Python version bound on a project makes it compatible with less dependency versions,
|
||||
potentially failing to resolve when no version of a dependency supports the required range. (Bumping
|
||||
the lower Python version bound has the inverse effect, it only increases the set of supported
|
||||
dependency versions.)
|
||||
|
||||
Note that this is different for Conda, as the Conda solver also determines the Python version, so it
|
||||
can choose a lower Python version instead. Conda can also change metadata after a release, so it can
|
||||
update compatibility for a new Python version, while metadata on PyPI cannot be changed once
|
||||
published.
|
||||
|
||||
Ignoring an upper bound is a problem for packages such as numpy which use the version-dependent C
|
||||
API of CPython. As of writing, each numpy release support 4 Python minor versions, e.g., numpy 2.0.0
|
||||
has wheels for CPython 3.9 through 3.12 and declares `requires-python = ">=3.9"`, while numpy 2.1.0
|
||||
has wheels for CPython 3.10 through 3.13 and declares `requires-python = ">=3.10"`. The means that
|
||||
when uv resolves a `numpy>=2,<3` requirement in a project with `requires-python = ">=3.9"`, it
|
||||
selects numpy 2.0.0 and the lockfile doesn't install on Python 3.13 or newer. To alleviate this,
|
||||
whenever uv rejects a version that requires a newer Python version, we fork by splitting the
|
||||
resolution markers on that Python version. This behavior can be controlled by `--fork-strategy`. In
|
||||
the example case, upon encountering numpy 2.1.0 we fork into Python versions `>=3.9,<3.10` and
|
||||
`>=3.10` and resolve two different numpy versions:
|
||||
|
||||
```
|
||||
numpy==2.0.0; python_version >= "3.9" and python_version < "3.10"
|
||||
numpy==2.1.0; python_version >= "3.10"
|
||||
```
|
||||
|
||||
There's one case where uv does consider the upper bound: When the project uses an upper bound on
|
||||
requires Python, such as `requires-python = "==3.13.*"` for an application that only deploys to
|
||||
Python 3.13. uv prunes wheels from the lockfile that are outside the range (e.g., `cp312` and
|
||||
`cp314`) in a post-processing step, which does not influence the resolution itself.
|
||||
|
||||
## URL dependencies
|
||||
|
||||
In uv, a dependency can either be a registry dependency, a package with a version specifier or the
|
||||
|
|
|
|||
Loading…
Reference in New Issue