# Resolution

Resolution is the process of taking a list of requirements and converting them to a list of package
versions that fulfill the requirements. Resolution requires recursively searching for compatible
versions of packages, ensuring that the requested requirements are fulfilled and that the
requirements of the requested packages are compatible.

## Dependencies

Most projects and packages have dependencies. Dependencies are other packages that are necessary in
order for the current package to work. A package defines its dependencies as _requirements_, roughly
a combination of a package name and acceptable versions. The dependencies defined by the current
project are called _direct dependencies_. The dependencies added by each dependency of the current
project are called _indirect_ or _transitive dependencies_.

!!! note

    See the [dependency specifiers
    page](https://packaging.python.org/en/latest/specifications/dependency-specifiers/)
    in the Python Packaging documentation for details about dependencies.

## Basic examples

To help demonstrate the resolution process, consider the following dependencies:

<!-- prettier-ignore -->
- The project depends on `foo` and `bar`.
- `foo` has one version, 1.0.0:
    - `foo 1.0.0` depends on `lib>=1.0.0`.
- `bar` has one version, 1.0.0:
    - `bar 1.0.0` depends on `lib>=2.0.0`.
- `lib` has two versions, 1.0.0 and 2.0.0. Both versions have no dependencies.

In this example, the resolver must find a set of package versions which satisfies the project
requirements. Since there is only one version of both `foo` and `bar`, those will be used. The
resolution must also include the transitive dependencies, so a version of `lib` must be chosen.
`foo 1.0.0` allows all available versions of `lib`, but `bar 1.0.0` requires `lib>=2.0.0` so
`lib 2.0.0` must be used.

In some resolutions, there may be more than one valid solution. Consider the following dependencies:

<!-- prettier-ignore -->
- The project depends on `foo` and `bar`.
- `foo` has two versions, 1.0.0 and 2.0.0:
    - `foo 1.0.0` has no dependencies.
    - `foo 2.0.0` depends on `lib==2.0.0`.
- `bar` has two versions, 1.0.0 and 2.0.0:
    - `bar 1.0.0` has no dependencies.
    - `bar 2.0.0` depends on `lib==1.0.0`
- `lib` has two versions, 1.0.0 and 2.0.0. Both versions have no dependencies.

In this example, some version of both `foo` and `bar` must be selected; however, determining which
version requires considering the dependencies of each version of `foo` and `bar`. `foo 2.0.0` and
`bar 2.0.0` cannot be installed together as they conflict on their required version of `lib`, so the
resolver must select either `foo 1.0.0` (along with `bar 2.0.0`) or `bar 1.0.0` (along with
`foo 2.0.0`). Both are valid solutions, and different resolution algorithms may yield either result.

## Platform markers

Markers allow attaching an expression to requirements that indicate when the dependency should be
used. For example `bar ; python_version < "3.9"` indicates that `bar` should only be installed on
Python 3.8 and earlier.

Markers are used to adjust a package's dependencies based on the current environment or platform.
For example, markers can be used to modify dependencies by operating system, CPU architecture,
Python version, Python implementation, and more.

!!! note

    See the [environment
    markers](https://packaging.python.org/en/latest/specifications/dependency-specifiers/#environment-markers)
    section in the Python Packaging documentation for more details about markers.

Markers are important for resolution because their values change the required dependencies.
Typically, Python package resolvers use the markers of the _current_ platform to determine which
dependencies to use since the package is often being _installed_ on the current platform. However,
for _locking_ dependencies this is problematic — the lockfile would only work for developers using
the same platform the lockfile was created on. To solve this problem, platform-independent, or
"universal" resolvers exist.

uv supports both [platform-specific](#platform-specific-resolution) and
[universal](#universal-resolution) resolution.

## Platform-specific resolution

By default, uv's pip interface, i.e., [`uv pip compile`](../pip/compile.md), produces a resolution
that is platform-specific, like `pip-tools`. There is no way to use platform-specific resolution in
the uv's project interface.

uv also supports resolving for specific, alternate platforms and Python versions with the
`--python-platform` and `--python-version` options. For example, if using Python 3.12 on macOS,
`uv pip compile --python-platform linux --python-version 3.10 requirements.in` can be used to
produce a resolution for Python 3.10 on Linux instead. Unlike universal resolution, during
platform-specific resolution, the provided `--python-version` is the exact python version to use,
not a lower bound.

!!! note

    Python's environment markers expose far more information about the current machine
    than can be expressed by a simple `--python-platform` argument. For example, the `platform_version` marker
    on macOS includes the time at which the kernel was built, which can (in theory) be encoded in
    package requirements. uv's resolver makes a best-effort attempt to generate a resolution that is
    compatible with any machine running on the target `--python-platform`, which should be sufficient for
    most use cases, but may lose fidelity for complex package and platform combinations.

## Universal resolution

uv's lockfile (`uv.lock`) is created with a universal resolution and is portable across platforms.
This ensures that dependencies are locked for everyone working on the project, regardless of
operating system, architecture, and Python version. The uv lockfile is created and modified by
[project](../concepts/projects/index.md) commands such as `uv lock`, `uv sync`, and `uv add`.

Universal resolution is also available in uv's pip interface, i.e.,
[`uv pip compile`](../pip/compile.md), with the `--universal` flag. The resulting requirements file
will contain markers to indicate which platform each dependency is relevant for.

During universal resolution, a package may be listed multiple times with different versions or URLs
if different versions are needed for different platforms — the markers determine which version will
be used. A universal resolution is often more constrained than a platform-specific resolution, since
we need to take the requirements for all markers into account.

During universal resolution, all required packages must be compatible with the _entire_ range of
`requires-python` declared in the `pyproject.toml`. For example, if a project's `requires-python` is
`>=3.8`, resolution will fail if all versions of given dependency require Python 3.9 or later, since
the dependency lacks a usable version for (e.g.) Python 3.8, the lower bound of the project's
supported range. In other words, the project's `requires-python` must be a subset of the
`requires-python` of all its dependencies.

When selecting the compatible version for a given dependency, uv will
([by default](#multi-version-resolution)) attempt to choose the latest compatible version for each
supported Python version. For example, if a project's `requires-python` is `>=3.8`, and the latest
version of a dependency requires Python 3.9 or later, while all prior versions supporting Python
3.8, the resolver will select the latest version for users running Python 3.9 or later, and previous
versions for users running Python 3.8.

When evaluating `requires-python` ranges for dependencies, uv only considers lower bounds and
ignores upper bounds entirely. For example, `>=3.8, <4` is treated as `>=3.8`. Respecting upper
bounds on `requires-python` often leads to formally correct but practically incorrect resolutions,
as, e.g., resolvers will backtrack to the first published version that omits the upper bound (see:
[`Requires-Python` upper limits](https://discuss.python.org/t/requires-python-upper-limits/12663)).

## Limited resolution environments

By default, the universal resolver attempts to solve for all platforms and Python versions.

If your project supports only a limited set of platforms or Python versions, you can constrain the
set of solved platforms via the `environments` setting, which accepts a list of
[PEP 508 environment markers](https://packaging.python.org/en/latest/specifications/dependency-specifiers/#environment-markers).
In other words, you can use the `environments` setting to _reduce_ the set of supported platforms.

For example, to constrain the lockfile to macOS and Linux, and avoid solving for Windows:

```toml title="pyproject.toml"
[tool.uv]
environments = [
    "sys_platform == 'darwin'",
    "sys_platform == 'linux'",
]
```

Or, to avoid solving for alternative Python implementations:

```toml title="pyproject.toml"
[tool.uv]
environments = [
    "implementation_name == 'cpython'"
]
```

Entries in the `environments` setting must be disjoint (i.e., they must not overlap). For example,
`sys_platform == 'darwin'` and `sys_platform == 'linux'` are disjoint, but
`sys_platform == 'darwin'` and `python_version >= '3.9'` are not, since both could be true at the
same time.

## Required environments

In the Python ecosystem, packages can be published as source distributions, built distributions
(wheels), or both; but to install a package, a built distribution is required. If a package lacks a
built distribution, or lacks a distribution for the current platform or Python version (built
distributions are often platform-specific), uv will attempt to build the package from source, then
install the resulting built distribution.

Some packages (like PyTorch) publish built distributions, but omit a source distribution. Such
packages are _only_ installable on platforms for which a built distribution is available. For
example, if a package publishes built distributions for Linux, but not macOS or Windows, then that
package will _only_ be installable on Linux.

Packages that lack source distributions cause problems for universal resolution, since there will
typically be at least one platform or Python version for which the package is not installable.

By default, uv requires each such package to include at least one wheel that is compatible with the
target Python version. The `required-environments` setting can be used to ensure that the resulting
resolution contains wheels for specific platforms, or fails if no such wheels are available. The
setting accepts a list of
[PEP 508 environment markers](https://packaging.python.org/en/latest/specifications/dependency-specifiers/#environment-markers).

While the `environments` setting _limits_ the set of environments that uv will consider when
resolving dependencies, `required-environments` _expands_ the set of platforms that uv _must_
support when resolving dependencies.

For example, `environments = ["sys_platform == 'darwin'"]` would limit uv to solving for macOS (and
ignoring Linux and Windows). On the other hand,
`required-environments = ["sys_platform == 'darwin'"]` would _require_ that any package without a
source distribution include a wheel for macOS in order to be installable (and would fail if no such
wheel is available).

In practice, `required-environments` can be useful for declaring explicit support for non-latest
platforms, since this often requires backtracking past the latest published versions of those
packages. For example, to guarantee that any built distribution-only packages includes support for
Intel macOS:

```toml title="pyproject.toml"
[tool.uv]
required-environments = [
    "sys_platform == 'darwin' and platform_machine == 'x86_64'"
]
```

## Common marker values

The `environments` and `required-environments` settings accept
[PEP 508 environment markers](https://packaging.python.org/en/latest/specifications/dependency-specifiers/#environment-markers).
The values for these markers are derived from the Python runtime (e.g.,
[`sys.platform`](https://docs.python.org/3/library/sys.html#sys.platform),
[`platform.machine()`](https://docs.python.org/3/library/platform.html#platform.machine),
[`platform.system()`](https://docs.python.org/3/library/platform.html#platform.system), and
[`os.name`](https://docs.python.org/3/library/os.html#os.name)).

For quick reference, the most common marker values by platform are:

| Marker                      | Linux       | macOS      | Windows     |
| --------------------------- | ----------- | ---------- | ----------- |
| `sys_platform`              | `'linux'`   | `'darwin'` | `'win32'`   |
| `platform_system`           | `'Linux'`   | `'Darwin'` | `'Windows'` |
| `platform_machine` (x86-64) | `'x86_64'`  | `'x86_64'` | `'AMD64'`   |
| `platform_machine` (ARM64)  | `'aarch64'` | `'arm64'`  | `'ARM64'`   |
| `os_name`                   | `'posix'`   | `'posix'`  | `'nt'`      |

!!! note

    On Windows, `sys_platform` is always `'win32'`, even on 64-bit systems.

You can check the values for your current platform by running:

```console
$ uvx python -c "import sysconfig; print(sysconfig.get_config_vars())"
```

## Dependency preferences

If resolution output file exists, i.e., a uv lockfile (`uv.lock`) or a requirements output file
(`requirements.txt`), uv will _prefer_ the dependency versions listed there. Similarly, if
installing a package into a virtual environment, uv will prefer the already installed version if
present. This means that locked or installed versions will not change unless an incompatible version
is requested or an upgrade is explicitly requested with `--upgrade`.

## Resolution strategy

By default, uv tries to use the latest version of each package. For example,
`uv pip install flask>=2.0.0` will install the latest version of Flask, e.g., 3.0.0. If
`flask>=2.0.0` is a dependency of the project, only `flask` 3.0.0 will be used. This is important,
for example, because running tests will not check that the project is actually compatible with its
stated lower bound of `flask` 2.0.0.

With `--resolution lowest`, uv will install the lowest possible version for all dependencies, both
direct and indirect (transitive). Alternatively, `--resolution lowest-direct` will use the lowest
compatible versions for all direct dependencies, while using the latest compatible versions for all
other dependencies. uv will always use the latest versions for build dependencies.

For example, given the following `requirements.in` file:

```requirements title="requirements.in"
flask>=2.0.0
```

Running `uv pip compile requirements.in -o requirements.txt` would produce the following
`requirements.txt` file:

```requirements title="requirements.txt"
# This file was autogenerated by uv via the following command:
#    uv pip compile requirements.in -o requirements.txt
blinker==1.7.0
    # via flask
click==8.1.7
    # via flask
flask==3.0.0
itsdangerous==2.1.2
    # via flask
jinja2==3.1.2
    # via flask
markupsafe==2.1.3
    # via
    #   jinja2
    #   werkzeug
werkzeug==3.0.1
    # via flask
```

However, `uv pip compile --resolution lowest requirements.in -o requirements.txt` would instead
produce:

```requirements title="requirements.txt"
# This file was autogenerated by uv via the following command:
#    uv pip compile --resolution lowest requirements.in -o requirements.txt
click==7.1.2
    # via flask
flask==2.0.0
itsdangerous==2.0.0
    # via flask
jinja2==3.0.0
    # via flask
markupsafe==2.0.0
    # via jinja2
werkzeug==2.0.0
    # via flask
```

When publishing libraries, it is recommended to separately run tests with `--resolution lowest` or
`--resolution lowest-direct` in continuous integration to ensure compatibility with the declared
lower bounds.

## Pre-release handling

By default, uv will accept pre-release versions during dependency resolution in two cases:

1. If the package is a direct dependency, and its version specifiers include a pre-release specifier
   (e.g., `flask>=2.0.0rc1`).
1. If _all_ published versions of a package are pre-releases.

If dependency resolution fails due to a transitive pre-release, uv will prompt use of
`--prerelease allow` to allow pre-releases for all dependencies.

Alternatively, the transitive dependency can be added as a [constraint](#dependency-constraints) or
direct dependency (i.e. in `requirements.in` or `pyproject.toml`) with a pre-release version
specifier (e.g., `flask>=2.0.0rc1`) to opt in to pre-release support for that specific dependency.

Pre-releases are
[notoriously difficult](https://pubgrub-rs-guide.netlify.app/limitations/prerelease_versions) to
model, and are a frequent source of bugs in other packaging tools. uv's pre-release handling is
_intentionally_ limited and requires user opt-in for pre-releases to ensure correctness.

For more details, see
[Pre-release compatibility](../pip/compatibility.md#pre-release-compatibility).

## Multi-version resolution

During universal resolution, a package may be listed multiple times with different versions or URLs
within the same lockfile, since different versions may be needed for different platforms or Python
versions.

The `--fork-strategy` setting can be used to control how uv trades off between (1) minimizing the
number of selected versions and (2) selecting the latest-possible version for each platform. The
former leads to greater consistency across platforms, while the latter leads to use of newer package
versions where possible.

By default (`--fork-strategy requires-python`), uv will optimize for selecting the latest version of
each package for each supported Python version, while minimizing the number of selected versions
across platforms.

For example, when resolving `numpy` with a Python requirement of `>=3.8`, uv would select the
following versions:

```txt
numpy==1.24.4 ; python_version == "3.8"
numpy==2.0.2 ; python_version == "3.9"
numpy==2.2.0 ; python_version >= "3.10"
```

This resolution reflects the fact that NumPy 2.2.0 and later require at least Python 3.10, while
earlier versions are compatible with Python 3.8 and 3.9.

Under `--fork-strategy fewest`, uv will instead minimize the number of selected versions for each
package, preferring older versions that are compatible with a wider range of supported Python
versions or platforms.

For example, when in the scenario above, uv would select `numpy==1.24.4` for all Python versions,
rather than upgrading to `numpy==2.0.2` for Python 3.9 and `numpy==2.2.0` for Python 3.10 and later.

## Dependency constraints

Like pip, uv supports constraint files (`--constraint constraints.txt`) which narrow the set of
acceptable versions for the given packages. Constraint files are similar to requirements files, but
being listed as a constraint alone will not cause a package to be included to the resolution.
Instead, constraints only take effect if a requested package is already pulled in as a direct or
transitive dependency. Constraints are useful for reducing the range of available versions for a
transitive dependency. They can also be used to keep a resolution in sync with some other set of
resolved versions, regardless of which packages are overlapping between the two.

## Dependency overrides

Dependency overrides allow bypassing unsuccessful or undesirable resolutions by overriding a
package's declared dependencies. Overrides are a useful last resort for cases in which you _know_
that a dependency is compatible with a certain version of a package, despite the metadata indicating
otherwise.

For example, if a transitive dependency declares the requirement `pydantic>=1.0,<2.0`, but _does_
work with `pydantic>=2.0`, the user can override the declared dependency by including
`pydantic>=1.0,<3` in the overrides, thereby allowing the resolver to choose a newer version of
`pydantic`.

Concretely, if `pydantic>=1.0,<3` is included as an override, uv will ignore all declared
requirements on `pydantic`, replacing them with the override. In the above example, the
`pydantic>=1.0,<2.0` requirement would be ignored completely, and would instead be replaced with
`pydantic>=1.0,<3`.

While constraints can only _reduce_ the set of acceptable versions for a package, overrides can
_expand_ the set of acceptable versions, providing an escape hatch for erroneous upper version
bounds. As with constraints, global overrides do not add a dependency on the package and only take
effect if the package is requested in a direct or transitive dependency.

In a `pyproject.toml`, use `tool.uv.override-dependencies` to define a list of overrides. In the
pip-compatible interface, the `--override` option can be used to pass files with the same format as
constraints files.

By default, an override applies to every requirement for the named dependency, including direct
requirements. An override can instead be scoped to the dependencies of a particular package version
by using an inline table:

```toml
[tool.uv]
override-dependencies = [
    "foo>1",
    { package = { name = "bar", version = "0.0.5" }, dependencies = ["foo>2"] },
]
```

In this example, `foo>1` is the global override, while `foo>2` replaces requirements for `foo`
declared by `bar==0.0.5`. If `bar` does not declare a dependency on `foo`, the scoped override adds
it. Other dependencies declared by `bar` are unchanged. The `version` field in `package` can be
omitted to apply the scoped overrides to all versions of `bar`. A version-specific entry takes
precedence over an all-versions entry, and a scoped override takes precedence over a global override
for the same dependency.

Scoped overrides currently support registry version specifiers only. Direct URL and path sources,
including Git sources, and explicit indexes are not supported.

Pre-release and yanked-version permissions are determined before uv knows which scoped overrides
will apply. As a result, an explicit pre-release or yanked-version pin in any scoped override opts
that package into the corresponding candidate-selection behavior for the entire resolution, even if
the scope is not selected.

If multiple overrides are provided for the same package, they must be differentiated with
[markers](#platform-markers). If a package has a dependency with a marker, it is replaced
unconditionally when using overrides — it does not matter if the marker evaluates to true or false.

## Dependency exclusions

Dependency exclusions remove packages from the dependency graph. By default, an exclusion applies to
every requirement for the named dependency, including direct requirements:

```toml
[tool.uv]
exclude-dependencies = ["foo"]
```

An exclusion can instead be scoped to the dependencies of a particular package version:

```toml
[tool.uv]
exclude-dependencies = [
    { package = { name = "bar", version = "0.0.5" }, dependencies = ["foo"] },
]
```

In this example, requirements for `foo` declared by `bar==0.0.5` are removed, while requirements for
`foo` from other packages are unchanged. The `version` field in `package` can be omitted to apply
the scoped exclusions to all versions of `bar`. A version-specific entry takes precedence over an
all-versions entry.

Scoped exclusions can be combined with scoped overrides to replace one dependency with another:

```toml
[tool.uv]
override-dependencies = [
    { package = { name = "bar", version = "0.0.5" }, dependencies = ["pytorch-lightning"] },
]
exclude-dependencies = [
    { package = { name = "bar", version = "0.0.5" }, dependencies = ["lightning"] },
]
```

If the same dependency is both overridden and excluded in a matching scope, the exclusion takes
precedence.

## Dependency metadata

During resolution, uv needs to resolve the metadata for each package it encounters, in order to
determine its dependencies. This metadata is often available as a static file in the package index;
however, for packages that only provide source distributions, the metadata may not be available
upfront.

In such cases, uv has to build the package to determine its metadata (e.g., by invoking `setup.py`).
This can introduce a performance penalty during resolution. Further, it imposes the requirement that
the package can be built on all platforms, which may not be true.

For example, you may have a package that should only be built and installed on Linux, but doesn't
build successfully on macOS or Windows. While uv can construct a perfectly valid lockfile for this
scenario, doing so would require building the package, which would fail on non-Linux platforms.

The `tool.uv.dependency-metadata` table can be used to provide static metadata for such dependencies
upfront, thereby allowing uv to skip the build step and use the provided metadata instead.

For example, to provide metadata for `chumpy` upfront, include its `dependency-metadata` in the
`pyproject.toml`:

```toml
[[tool.uv.dependency-metadata]]
name = "chumpy"
version = "0.70"
requires-dist = ["numpy>=1.8.1", "scipy>=0.13.0", "six>=1.11.0"]
```

These declarations are intended for cases in which a package does _not_ declare static metadata
upfront, though they are also useful for packages that require
[disabling build isolation](./projects/config.md#build-isolation) In such cases, it may be easier to
declare the package metadata upfront, rather than creating a custom build environment prior to
resolving the package.

For example, past versions of `flash-attn` did not declare static metadata. By declaring metadata
for `flash-attn` upfront, uv can resolve `flash-attn` without building the package from source
(which itself requires installing `torch`):

```toml
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["flash-attn"]

[tool.uv.sources]
flash-attn = { git = "https://github.com/Dao-AILab/flash-attention", tag = "v2.6.3" }

[[tool.uv.dependency-metadata]]
name = "flash-attn"
version = "2.6.3"
requires-dist = ["torch", "einops"]
```

Like dependency overrides, `tool.uv.dependency-metadata` can also be used for cases in which a
package's metadata is incorrect or incomplete, or when a package is not available in the package
index. While dependency overrides allow overriding the allowed versions of a package globally,
metadata overrides allow overriding the declared metadata of a _specific package_.

!!! note

    The `version` field in `tool.uv.dependency-metadata` is optional for registry-based
    dependencies (when omitted, uv will assume the metadata applies to all versions of the package),
    but _required_ for direct URL dependencies (like Git dependencies).

Entries in the `tool.uv.dependency-metadata` table follow the
[Metadata 2.3](https://packaging.python.org/en/latest/specifications/core-metadata/) specification,
though only `name`, `version`, `requires-dist`, `requires-python`, and `provides-extra` are read by
uv. The `version` field is also considered optional. If omitted, the metadata will be used for all
versions of the specified package.

## Conflicting dependencies

uv requires that all dependencies declared by a project are compatible with each other and resolves
all dependencies together when creating the lockfile. This includes project dependencies, optional
dependencies ("extras"), and dependency groups (development dependencies).

If dependencies declared in one extra are not compatible with those in another extra, uv will fail
to resolve the requirements of the project with an error. For example, consider two sets of optional
dependencies that conflict with one another:

```toml title="pyproject.toml"
[project.optional-dependencies]
extra1 = ["numpy==2.1.2"]
extra2 = ["numpy==2.0.0"]
```

If you run `uv lock` with the above dependencies, resolution will fail:

```console
$ uv lock
  x No solution found when resolving dependencies:
  `-> Because myproject[extra2] depends on numpy==2.0.0 and myproject[extra1] depends on numpy==2.1.2, we can conclude that myproject[extra1] and
      myproject[extra2] are incompatible.
      And because your project requires myproject[extra1] and myproject[extra2], we can conclude that your projects's requirements are unsatisfiable.
```

To work around this, uv supports explicit declaration of conflicts. If you specify that `extra1` and
`extra2` are conflicting, uv will resolve them separately. Specify conflicts in the `tool.uv`
section:

```toml title="pyproject.toml"
[tool.uv]
conflicts = [
    [
      { extra = "extra1" },
      { extra = "extra2" },
    ],
]
```

Now, running `uv lock` will succeed. However, now you cannot install both `extra1` and `extra2` at
the same time:

```console
$ uv sync --extra extra1 --extra extra2
Resolved 3 packages in 14ms
error: extra `extra1`, extra `extra2` are incompatible with the declared conflicts: {`myproject[extra1]`, `myproject[extra2]`}
```

This error occurs because installing both `extra1` and `extra2` would result in installing two
different versions of a package into the same environment.

The above strategy for dealing with conflicting optional dependencies also works with dependency
groups:

```toml title="pyproject.toml"
[dependency-groups]
group1 = ["numpy==2.1.2"]
group2 = ["numpy==2.0.0"]

[tool.uv]
conflicts = [
    [
      { group = "group1" },
      { group = "group2" },
    ],
]
```

The only difference from conflicting extras is that you need to use the `group` key instead of
`extra`.

When using a workspace with multiple projects, the same restrictions apply — uv requires all
workspace members to be compatible with each other. Similarly, conflicts can be declared across
workspace members.

For example, consider the following workspace:

```toml title="member1/pyproject.toml"
[project]
name = "member1"

[project.optional-dependencies]
extra1 = ["numpy==2.1.2"]
```

```toml title="member2/pyproject.toml"
[project]
name = "member2"

[project.optional-dependencies]
extra2 = ["numpy==2.0.0"]
```

To declare a conflict between extras in these different workspace members, use the `package` key:

```toml title="pyproject.toml"
[tool.uv]
conflicts = [
    [
      { package = "member1", extra = "extra1" },
      { package = "member2", extra = "extra2" },
    ],
]
```

It's also possible for the project dependencies (i.e., `project.dependencies`) of one workspace
member to conflict with the extra of another member, for example:

```toml title="member1/pyproject.toml"
[project]
name = "member1"
dependencies = ["numpy==2.1.2"]
```

```toml title="member2/pyproject.toml"
[project]
name = "member2"

[project.optional-dependencies]
extra2 = ["numpy==2.0.0"]
```

This conflict can also be declared using the `package` key:

```toml title="pyproject.toml"
[tool.uv]
conflicts = [
    [
      { package = "member1" },
      { package = "member2", extra = "extra2" },
    ],
]
```

Similarly, it's possible for some workspace members to have conflicting project dependencies:

```toml title="member1/pyproject.toml"
[project]
name = "member1"
dependencies = ["numpy==2.1.2"]
```

```toml title="member2/pyproject.toml"
[project]
name = "member2"
dependencies = ["numpy==2.0.0"]
```

This conflict can also be declared using the `package` key:

```toml title="pyproject.toml"
[tool.uv]
conflicts = [
    [
      { package = "member1" },
      { package = "member2" },
    ],
]
```

These workspace members will not be installable together, e.g., the workspace root cannot define:

```toml title="pyproject.toml"
[project]
name = "root"
dependencies = ["member1", "member2"]
```

## Lower bounds

By default, `uv add` adds lower bounds to dependencies and, when using uv to manage projects, uv
will warn if direct dependencies don't have lower bounds.

Lower bounds are not critical in the "happy path", but they are important for cases where there are
dependency conflicts. For example, consider a project that requires two packages and those packages
have conflicting dependencies. The resolver needs to check all combinations of all versions within
the constraints for the two packages — if all of them conflict, an error is reported because the
dependencies are not satisfiable. If there are no lower bounds, the resolver can (and often will)
backtrack down to the oldest version of a package. This isn't only problematic because it's slow,
the old version of the package often fails to build, or the resolver can end up picking a version
that's old enough that it doesn't depend on the conflicting package, but also doesn't work with your
code.

Lower bounds are particularly critical when writing a library. It's important to declare the lowest
version for each dependency that your library works with, and to validate that the bounds are
correct — testing with
[`--resolution lowest` or `--resolution lowest-direct`](#resolution-strategy). Otherwise, a user may
receive an old, incompatible version of one of your library's dependencies and the library will fail
with an unexpected error.

## Reproducible resolutions

uv supports an `--exclude-newer` option to limit resolution to distributions uploaded before a
specific date, allowing reproduction of installations regardless of new package releases. The date
is compared against the upload time of each individual distribution artifact (i.e., when each file
was uploaded to the package index), not the release date of the package version. The date may be
specified as an [RFC 3339](https://www.rfc-editor.org/rfc/rfc3339.html) timestamp (e.g.,
`2006-12-02T02:07:43Z`) or a local date in the same format (e.g., `2006-12-02`) in your system's
configured time zone.

!!! important

    The package index must support the `upload-time` field as specified in
    [`PEP 700`](https://peps.python.org/pep-0700/). If the field is not present for a given
    distribution, the distribution will be treated as unavailable unless the package is opted out
    via `--exclude-newer-package <package>=false`, or the index is configured with its own
    `exclude-newer` value, or the index is opted out via `[[tool.uv.index]] exclude-newer = false`.
    PyPI provides `upload-time` for all packages.

To ensure reproducibility, messages for unsatisfiable resolutions will not mention that
distributions were excluded due to the `--exclude-newer` flag — newer distributions will be treated
as if they do not exist.

!!! note

    The `--exclude-newer` option is only applied to packages that are read from a registry (as
    opposed to, e.g., Git dependencies). Further, when using the `uv pip` interface, uv will not
    downgrade previously installed packages unless the `--reinstall` flag is provided, in which case
    uv will perform a new resolution.

This option is also supported in the `pyproject.toml`, e.g.:

```pyproject.toml
[tool.uv]
exclude-newer = "2006-12-02T02:07:43Z"
```

To disable a global cutoff from a lower-priority configuration source, pass `--exclude-newer false`,
set `UV_EXCLUDE_NEWER=false`, or set `exclude-newer = false` in a higher-priority configuration
file.

When specified in persistent configuration, local date times are not allowed.

Values may also be specified for specific packages, e.g.,
`--exclude-newer-package setuptools=2006-12-02`, or:

```pyproject.toml
[tool.uv]
exclude-newer-package = { setuptools = "2006-12-02T02:07:43Z" }
```

The package option also accepts `<package>=false` to opt a package out of the restriction, e.g.,
`--exclude-newer-package setuptools=false`, or:

```pyproject.toml
[tool.uv]
exclude-newer-package = { setuptools = false }
```

This is useful to temporarily use a newer version of package or to allow resolving a package from an
index that does not publish upload times.

Package-specific values will take precedence over both global and index-specific values.

Likewise, an individual index can override the global cutoff:

```pyproject.toml
[tool.uv]
exclude-newer = "2006-12-02T02:07:43Z"

[[tool.uv.index]]
name = "internal"
url = "https://internal.example.com/simple"
exclude-newer = "7 days"
```

Or disable it entirely for that index:

```pyproject.toml
[[tool.uv.index]]
name = "internal"
url = "https://internal.example.com/simple"
exclude-newer = false
```

This is useful for private indexes that don't publish `upload-time`, or for applying a different
reproducibility window to a specific index while preserving the global behavior elsewhere.

## Dependency cooldowns

uv also supports dependency "cooldowns" in which resolution will ignore packages newer than a
duration. This is a good way to improve security posture by delaying package updates until the
community has had the opportunity to vet new versions of packages.

This feature is available via the [`exclude-newer` option](#reproducible-resolutions) and shares the
same semantics.

Define a dependency cooldown by specifying a duration instead of an absolute value. Either a
"friendly" duration (e.g., `24 hours`, `1 week`, `30 days`) or an ISO 8601 duration (e.g., `PT24H`,
`P7D`, `P30D`) can be used.

!!! note

    Durations do not respect semantics of the local time zone and are always resolved to a fixed
    number of seconds assuming that a day is 24 hours (e.g., DST transitions are ignored). Calendar
    units such as months and years are not allowed since they are inherently inconsistent lengths.

When a duration is used for resolution, a timestamp is calculated relative to the current time. When
using a `uv.lock` file, the timestamp is included in the lockfile. uv will not update the lockfile
when the current time changes, instead, uv will update the timestamp when a new resolution is
performed, e.g., when `--upgrade` or `--refresh` is used.

This option is also supported in the `pyproject.toml`, e.g.:

```pyproject.toml
[tool.uv]
exclude-newer = "1 week"
```

Values may also be specified for specific packages, e.g.,
`--exclude-newer-package "setuptools=30 days"`, or:

```pyproject.toml
[tool.uv]
exclude-newer = "1 week"
exclude-newer-package = { setuptools = "30 days" }
```

## Source distribution

[PEP 625](https://peps.python.org/pep-0625/) specifies that packages must distribute source
distributions as gzip tarball (`.tar.gz`) archives. Prior to this specification, other archive
formats, which need to be supported for backward compatibility, were also allowed. uv supports
reading and extracting archives in the following formats:

- gzip tarball (`.tar.gz`, `.tgz`)
- bzip2 tarball (`.tar.bz2`, `.tbz`)
- xz tarball (`.tar.xz`, `.txz`)
- zstd tarball (`.tar.zst`)
- lzip tarball (`.tar.lz`)
- lzma tarball (`.tar.lzma`)
- zip (`.zip`)

!!! important

    Using source distribution extensions other than `.tar.gz` is strongly
    discouraged, as these extensions are not widely or consistently
    supported across the Python packaging ecosystem.

!!! warning "Deprecated"

    Support for source distribution extensions other than `.tar.gz` is
    deprecated and will be removed in a future release of uv.

## Lockfile versioning

The `uv.lock` file uses a versioned schema. The schema version is included in the `version` field of
the lockfile.

Any given version of uv can read and write lockfiles with the same schema version, but will reject
lockfiles with a greater schema version. For example, if your uv version supports schema v1,
`uv lock` will error if it encounters an existing lockfile with schema v2.

uv versions that support schema v2 _may_ be able to read lockfiles with schema v1 if the schema
update was backwards-compatible. However, this is not guaranteed, and uv may exit with an error if
it encounters a lockfile with an outdated schema version.

The schema version is considered part of the public API, and so is only bumped in minor releases, as
a breaking change (see [Versioning](../reference/policies/versioning.md)). As such, all uv patch
versions within a given minor uv release are guaranteed to have full lockfile compatibility. In
other words, lockfiles may only be rejected across minor releases.

The `revision` field of the lockfile is used to track backwards compatible changes to the lockfile.
For example, adding a new field to distributions. Changes to the revision will not cause older
versions of uv to error.

## Learn more

For more details about the internals of the resolver, see the
[resolver reference](../reference/internals/resolver.md) documentation.
