Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for specifying conflicting extras #8976

Merged
merged 10 commits into from
Nov 13, 2024
Merged

Conversation

BurntSushi
Copy link
Member

@BurntSushi BurntSushi commented Nov 9, 2024

This PR adds support for conflicting extras. For example, consider
some optional dependencies like this:

[project.optional-dependencies]
project1 = ["numpy==1.26.3"]
project2 = ["numpy==1.26.4"]

These dependency specifications are not compatible with one another.
And if you ask uv to lock these, you'll get an unresolvable error.

With this PR, you can now add this to your pyproject.toml to get
around this:

[tool.uv]
conflicting-groups = [
    [
      { package = "project", extra = "project1" },
      { package = "project", extra = "project2" },
    ],
]

This will make the universal resolver create additional forks
internally that keep the dependencies from the project1 and
project2 extras separate. And we make all of this work by reporting
an error at install time if one tries to install with two or more
extras that have been declared as conflicting. (If we didn't do this,
it would be possible to try and install two different versions of the
same package into the same environment.)

This PR does not add support for conflicting groups, but it is
intended to add support in a follow-up PR.

Closes #6981

Fixes #8024

Ref #6729, Ref #6830

This should also hopefully unblock
dagster-io/dagster#23814, but in my testing, I
did run into other problems (specifically, with pywin). But it does
resolve the problem with incompatible dependencies in two different
extras once you declare test-airflow-1 and test-airflow-2 as
conflicting for dagster-airflow.

NOTE: This PR doesn't make conflicting-groups public yet. And in a follow-up PR, I plan to switch the name to conflicts instead of conflicting-groups, since it will be able to accept conflicting extras and conflicting groups.

@charliermarsh
Copy link
Member

Epic!

@BurntSushi
Copy link
Member Author

Reviewers should read this commit-by-commit. It should help a lot.

Also, I haven't made any attempt at hiding the schema yet, since I'm guessing we'll want to merge this without exposing it yet. cc @zanieb I know we talked about this a couple days ago.

@BurntSushi BurntSushi force-pushed the ag/conflicting-groups branch 2 times, most recently from ee1c295 to a45937e Compare November 9, 2024 18:40
Copy link
Member

@charliermarsh charliermarsh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This generally looks good to me. A few questions.

crates/uv-pypi-types/src/conflicting_groups.rs Outdated Show resolved Hide resolved
crates/uv-pypi-types/src/conflicting_groups.rs Outdated Show resolved Hide resolved
@@ -645,6 +645,7 @@ async fn do_lock(
None,
resolver_env,
python_requirement,
workspace.conflicting_groups(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To clarify: does this mean conflicting groups must be defined in the workspace root?

Could we instead have each project define its own conflicts, and then remove package = from the schema? I don't think it's necessary to allow arbitrary packages to be included in these groups, since we only allow enabling extras for members in the workspace.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this makes sense to me. From the resolver's perspective, it definitely needs the package name, but from the user's perspective, I can see how it makes sense to keep the conflicting declaration local to where the corresponding extras/groups are themselves defined. And in that context, the package name is implicit.

I do wonder if there exists a conflict that does require the user to specify the package name, but I can't quite think of how that might manifest.

I'll plan to remove package from the schema.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(But note that we still need package in the lock file.)

crates/uv-resolver/src/resolver/environment.rs Outdated Show resolved Hide resolved
crates/uv-resolver/src/resolver/mod.rs Show resolved Hide resolved
#[derive(
Debug, Default, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize, schemars::JsonSchema,
)]
pub struct ConflictingGroupList(Vec<ConflictingGroups>);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am wondering if the terminology here should just around something more general, like "Conflicts", since it has to cover both extras and dependency groups. The use of "groups" here makes it somewhat overloaded.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah @zanieb suggested just using conflicts, so I think I'll switch to that. I don't like that it doesn't say what is conflicting, but "extra" and "group" are already such general names that it's hard to think of something more general than that but still captures their essence. And in theory, we could add more types of conflicts in the future, although the use cases aren't quite clear.

crates/uv-resolver/src/resolver/mod.rs Show resolved Hide resolved
Comment on lines 35 to 40
conflicting-groups = [
[
{ package = "foo", extra = "dev" },
{ package = "bar", extra = "dev" },
]
]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a test case for this? where packages are different.
I noticed all test cases use package = "project". could you make it as default value to avoid explicitly writing?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yeah, I'll add one of these in a follow-up PR. Originally I thought we were going to get rid of package, but now it's optional. I'll add the test from the comments below, which should cover "conflicts with distinct package values" case.

@BurntSushi BurntSushi force-pushed the ag/conflicting-groups branch 2 times, most recently from 8aef474 to c022f1b Compare November 11, 2024 16:20
crates/uv/tests/it/lock.rs Outdated Show resolved Hide resolved
@BurntSushi BurntSushi force-pushed the ag/conflicting-groups branch from c022f1b to eb144e7 Compare November 11, 2024 17:47
@@ -683,15 +691,15 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
forks
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't comment that high, but now diverging packages can be empty and the debug message has a hole:

DEBUG Splitting resolution on dummy==0.1.0 over  into 3 resolution with separate markers

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I missed this. I'll have this fixed in a follow-up PR.

@konstin
Copy link
Member

konstin commented Nov 12, 2024

Sharing this from our conversation:Currently, the following example leads to an install without proxy-fork-1 but with anyio==4.2.0, while it should conflict, the conflict is unconditional.

[project]
name = "dummy"
version = "0.1.0"
requires-python = "==3.12.*"
dependencies = [
  "proxy-fork-1[project1,project2]"
]

[tool.uv.sources]
proxy-fork-1 = { path = "../proxy-fork-1" }

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "proxy-fork-1"
version = "0.1.0"
requires-python = "==3.12.*"
dependencies = []

[project.optional-dependencies]
project1 = ["anyio==4.1.0"]
project2 = ["anyio==4.2.0"]

@BurntSushi BurntSushi force-pushed the ag/conflicting-groups branch 4 times, most recently from cdd40eb to 9569462 Compare November 12, 2024 19:21
@BurntSushi
Copy link
Member Author

@konstin Aye, I think that should be resolved now. Whenever a requirement is seen with an unconditional dependency on a conflicting extra, we error. This is perhaps overly conservative, but if we figure out a way to relax it (and a use case that wants it), we can do so in the future.

@BurntSushi BurntSushi force-pushed the ag/conflicting-groups branch 2 times, most recently from fdfd7d9 to ee2be3f Compare November 12, 2024 20:04
Comment on lines 3113 to 3118
conflicting-groups = [
[{extra = "dev"}, {extra = "dev"}],
]
"#})?;

// The file should be rejected for violating the schema.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment is out of date, and i think the example, too? (dev conflicting with dev sounds like it should warn/error)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aye, fixed. I'll do some more validation in a follow-up PR.

docs/reference/settings.md Outdated Show resolved Hide resolved
@konstin konstin added the enhancement New feature or improvement to existing functionality label Nov 13, 2024
@BurntSushi
Copy link
Member Author

@konstin found another interesting case. In this case, resolution fails, even though it should be possible to declare conflicting extras in a way that makes it work. To reproduce, create a pyproject.toml with this:

[project]
name = "dummy"
version = "0.1.0"
requires-python = "==3.12.*"

[project.optional-dependencies]
project1 = [
  "proxy-fork-1[project1]",
  # "dummysub[project1]" # Fails with or without this
]
project2 = [
  "proxy-fork-1[project2]"
  # "dummysub[project2]" # Fails with or without this
]

[tool.uv.sources]
proxy-fork-1 = { path = "./proxy-fork-1" }
dummysub =  { workspace = true }

[tool.uv.workspace]
members = ["dummysub"]

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.uv]
conflicting-groups = [
  [
    { extra = "project1" },
    { extra = "project2" },
  ],
]

And another at dummysub/pyproject.toml:

[project]
name = "dummysub"
version = "0.1.0"
requires-python = "==3.12.*"

[project.optional-dependencies]
project1 = [
  "proxy-fork-1[project1]",
]
project2 = [
  "proxy-fork-1[project2]"
]

[tool.uv.sources]
proxy-fork-1 = { path = "../proxy-fork-1" }

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.uv]
conflicting-groups = [
  [
    { extra = "project1" },
    { extra = "project2" },
  ],
]

And finally, another one that isn't part of the workspace but is just an "external" package:

[project]
name = "proxy-fork-1"
version = "0.1.0"
requires-python = "==3.12.*"
dependencies = []

[project.optional-dependencies]
project1 = ["anyio==4.1.0"]
project2 = ["anyio==4.2.0"]

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

In this case, we have project1 and project2 declared as conflicting extras in both dummy and dummysub. But they manifest as two different sets of conflicting extras. In order to make this example work, I believe they need to all be in one set, because they are all mutually conflicting. But since these extras span two different packages, the easiest way to do this is to bring back package (but we can make it optional). That is, in the root pyproject.toml, we have this:

[tool.uv]
conflicting-groups = [
  [
    { extra = "project1" },
    { extra = "project2" },
    { package = "dummysub", extra = "project1" },
    { package = "dummysub", extra = "project2" },
  ],
]

And we can remove conflicting-groups from dummysub/pyproject.toml. Once done, this makes resolution succeed.

The only alternative I can see here, AFAIK, is to automatically combine conflicting groups among workspace members, so that every set of conflicts is combined with every other set of conflicts. But I think this leads to a combinatorial explosion, which would be bad juju. So I think we are left with allowing package but making it optional. (And now I think we've come full circle to @zanieb's suggestion. :P)

@BurntSushi
Copy link
Member Author

The above solution means you can't install both dummy[project1] and dummysub[project1] at the same time. So I think you actually have to list out all pairs of possible conflicts:

[tool.uv]
conflicting-groups = [
  [
    { extra = "project1" },
    { extra = "project2" },
  ],
  [
    { package = "dummysub", extra = "project1" },
    { package = "dummysub", extra = "project2" },
  ],
  [
    { extra = "project1" },
    { package = "dummysub", extra = "project2" },
  ],
  [
    { package = "dummysub", extra = "project1" },
    { extra = "project2" },
  ],
]

And I do believe you need all four entries here, since you need to declare, e.g., that dummy[project1] and dummysub[project2] are conflicting.

@BurntSushi BurntSushi force-pushed the ag/conflicting-groups branch from ee2be3f to 985f634 Compare November 13, 2024 14:16
This snuck in during my last PR unintentionally. I sometimes add these
annotations when doing a refactor to squash temporary warnings. The
intent is to remove it, but it's easy to forget.
@BurntSushi BurntSushi force-pushed the ag/conflicting-groups branch from 985f634 to bdd40ef Compare November 13, 2024 14:16
This currently requires a list of list of dicts, where each dict must
have the keys `package` and `extra`. (So we don't support groups yet.)
This is another refactor of the forking logic that essentially
abstracts out `MarkerTree`. That is, instead of doing forking in a
way that is tightly coupled with `MarkerTree`, we use an intermediate
abstraction that hides the `MarkerTree`. Internally, it is still just
using a `MarkerTree`, but the intent is to expand this with conflicting
groups in a subsequent commit.

The main idea here is that `Fork` now has a `ResolverEnvironment`
instead of a `MarkerTree`. And a somewhat sneaky simplification here
is that forking now takes the parent `ResolverEnvironment` and starts
its forking logic from there. I believe we used to do this (or wanted
to do this) in the past, but stopped because of our sub-optimal marker
simplification. But with that fixed, we can do things this way now
which is much simpler than trying to combine them later.

(I'm starting to suspect that `PythonRequirement` should also live in
`ResolverEnvironment` too, but I'm holding off on further refactoring.)
We need to write conflicting groups to the lock file so that
we can check for changes to it compared to the last resolution.

This is similar to how we deal with "supported environments."
When a change happens, it can impact resolution, so we need to
force a re-resolve.

Moreover, specific to conflicting extras, we need to report
an error at install time if conflicting extras are requested.
We're going to add a new included_by_group, so this helps
distinguish between them.
This commit implements the actual logic for creating new
forks when there are dependencies matching declared conflicts.
This tests a number of different cases, including UX
interactions like, "declared conflicts don't match
what's in the lock file."
If we don't do this, then it would be like permitting
`uv sync --extra x1 --extra x2` even when `x1` and `x2` are declared as
conflicting.

Technically, we should only report an error when 2 or more conflicting
extras are unconditionally enabled. Instead, here, we report an error
if just 1 is found. The reason is that it seems tricky to detect all
possible cases of 2 or more since I believe it would require looking at
the full dependency tree.
In order to support this, we define a nearly duplicative set of types
just for `pyproject.toml` deserialization. We also permit the conflicts
to be specified in workspace member `pyproject.toml` files. So now we
collect all of them and merged them into one giant set to feed to the
resolver. When a package name isn't specified, it is implicitly assumed
to be the name of the project that defined the conflicts.

Note that this also removes support for specifying conflicts in
`uv.toml`. I think I didn't mean to add that originally. We could add it
back, but in that context, we would need the package name I believe.
@BurntSushi BurntSushi force-pushed the ag/conflicting-groups branch from bdd40ef to 15b7dbe Compare November 13, 2024 14:42
@BurntSushi BurntSushi merged commit 15ef807 into main Nov 13, 2024
64 checks passed
@BurntSushi BurntSushi deleted the ag/conflicting-groups branch November 13, 2024 14:52
@charliermarsh
Copy link
Member

@BurntSushi is it then any different than taking all pairs of groups? I don't fully see the advantage.

@BurntSushi
Copy link
Member Author

@BurntSushi is it then any different than taking all pairs of groups? I don't fully see the advantage.

I think the advantage is that not all conflicting extras are mutually exclusionary. So the question becomes then, in what circumstances do you take all pairs? I don't think you want to do it in all cases.

@BurntSushi
Copy link
Member Author

And also, the above isn't all pairs. For example, dummy[project1] and dummysub[project1] aren't marked as conflicting. You very specifically want to be able to install both of those at the same time.

BurntSushi added a commit that referenced this pull request Nov 13, 2024
This tests comes from here:
#8976 (comment)

And it was originally thought of by Konsti.

This test case is the motivation for making `package` optional in
`conflicts` instead of forbidding it entirely.
BurntSushi added a commit that referenced this pull request Nov 13, 2024
@BurntSushi
Copy link
Member Author

NOTE: I accidentally squash-merged this PR. For anyone looking for better history in the future, my apologies.

BurntSushi added a commit that referenced this pull request Nov 14, 2024
This tests comes from here:
#8976 (comment)

And it was originally thought of by Konsti.

This test case is the motivation for making `package` optional in
`conflicts` instead of forbidding it entirely.
BurntSushi added a commit that referenced this pull request Nov 14, 2024
BurntSushi added a commit that referenced this pull request Nov 14, 2024
This tests comes from here:
#8976 (comment)

And it was originally thought of by Konsti.

This test case is the motivation for making `package` optional in
`conflicts` instead of forbidding it entirely.
BurntSushi added a commit that referenced this pull request Nov 14, 2024
tmeijn pushed a commit to tmeijn/dotfiles that referenced this pull request Nov 18, 2024
This MR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [astral-sh/uv](https://github.com/astral-sh/uv) | minor | `0.4.24` -> `0.5.2` |

MR created with the help of [el-capitano/tools/renovate-bot](https://gitlab.com/el-capitano/tools/renovate-bot).

**Proposed changes to behavior should be submitted there as MRs.**

---

### Release Notes

<details>
<summary>astral-sh/uv (astral-sh/uv)</summary>

### [`v0.5.2`](https://github.com/astral-sh/uv/blob/HEAD/CHANGELOG.md#052)

[Compare Source](astral-sh/uv@0.5.1...0.5.2)

##### Enhancements

-   Hide `--no-system` from `uv pip tree` CLI ([#&#8203;9040](astral-sh/uv#9040))
-   Allow configuration of Python and PyPy install mirrors in `uv.toml` ([#&#8203;8695](astral-sh/uv#8695))
-   Allow passing Python download mirrors to `uv python install` ([#&#8203;8695](astral-sh/uv#8695))
-   Add support for specifying conflicting extras and dependency groups ([#&#8203;8976](astral-sh/uv#8976), [#&#8203;9096](astral-sh/uv#9096))
-   Consistent colon usage in build failure errors ([#&#8203;8994](astral-sh/uv#8994))
-   Show full derivation chain when encountering build failures ([#&#8203;9108](astral-sh/uv#9108))
-   Show link we failed on parsing index pages ([#&#8203;9118](astral-sh/uv#9118))
-   Remove duplicate log when searching for interpreters ([#&#8203;9092](astral-sh/uv#9092))
-   Update uv development status classifier to "Stable" on PyPI ([#&#8203;8943](astral-sh/uv#8943))
-   Use rich diagnostic formatting for early build failures ([#&#8203;9041](astral-sh/uv#9041))
-   Use rich diagnostic formatting for install failures ([#&#8203;9043](astral-sh/uv#9043))

##### Performance

-   Avoid retraversing filesystem when testing exact glob matches ([#&#8203;9022](astral-sh/uv#9022))

##### Bug fixes

-   Allow `--no-build` to validate lock ([#&#8203;9024](astral-sh/uv#9024))
-   Allow default indexes to be marked as explicit ([#&#8203;8990](astral-sh/uv#8990))
-   Avoid creating `.venv` in `uv add --frozen` and `uv add --no-sync` ([#&#8203;8980](astral-sh/uv#8980))
-   Avoid duplicating first-entry comments in `uv add` ([#&#8203;9109](astral-sh/uv#9109))
-   Defer reporting of build failures in resolver ([#&#8203;9098](astral-sh/uv#9098))
-   Fix references to `--resolution-strategy` in error message output ([#&#8203;8971](astral-sh/uv#8971))
-   Ignore virtual environments in parent directories when choosing Python version for new projects ([#&#8203;9075](astral-sh/uv#9075))
-   Forward SIGTERM to child processes in `uv run` ([#&#8203;8933](astral-sh/uv#8933))
-   Prefer Python executable names that match the request over default names ([#&#8203;9066](astral-sh/uv#9066))
-   Prefer compatible to incompatible distributions when packages exist on multiple indexes ([#&#8203;8961](astral-sh/uv#8961))
-   Publish: Ignore non-matching files ([#&#8203;8986](astral-sh/uv#8986))
-   Revert `uv.lock` changes when `uv add` fails ([#&#8203;9030](astral-sh/uv#9030))
-   Show file extensions on available commands when not `.exe` ([#&#8203;9099](astral-sh/uv#9099))
-   Sort by name, then specifiers in `uv add` ([#&#8203;9097](astral-sh/uv#9097))
-   Split after specifiers in `--with` requirements ([#&#8203;9089](astral-sh/uv#9089))
-   Support multiple extras in universal pip compile output ([#&#8203;8960](astral-sh/uv#8960))

##### Preview features

-   Build backend: Add tests for source tree -> source dist -> wheel conversions ([#&#8203;9091](astral-sh/uv#9091))
-   Build backend: Switch to custom `glob-walkdir` implementation ([#&#8203;9013](astral-sh/uv#9013))
-   Build backend: Add minimal wheel settings ([#&#8203;9085](astral-sh/uv#9085))

##### Documentation

-   Add wget instructions for systems without curl ([#&#8203;8630](astral-sh/uv#8630))
-   Fix `.env` file example in docs ([#&#8203;9064](astral-sh/uv#9064))
-   Fix reference to `--resolution` in docs ([#&#8203;8968](astral-sh/uv#8968))
-   Fix typo in GitLab integration docs ([#&#8203;9047](astral-sh/uv#9047))
-   Update format of environment variable reference ([#&#8203;9018](astral-sh/uv#9018))
-   Use Python syntax for `value_type` consistently ([#&#8203;9017](astral-sh/uv#9017))
-   Use `[[index]]` API in configuration example ([#&#8203;9065](astral-sh/uv#9065))
-   Mention how to use extras ([#&#8203;8972](astral-sh/uv#8972))
-   Add some words about specifying conflicting extras/groups ([#&#8203;9120](astral-sh/uv#9120))

### [`v0.5.1`](https://github.com/astral-sh/uv/blob/HEAD/CHANGELOG.md#051)

[Compare Source](astral-sh/uv@0.5.0...0.5.1)

##### Enhancements

-   Allow installation of manylinux wheels on `riscv64` ([#&#8203;8934](astral-sh/uv#8934))

##### Bug fixes

-   Build source distributions at top-level of cache ([#&#8203;8905](astral-sh/uv#8905))
-   Allow non-registry dependencies in `uv pip list --outdated` ([#&#8203;8939](astral-sh/uv#8939))
-   Compute superset of existing and required hashes when healing cache ([#&#8203;8955](astral-sh/uv#8955))
-   Enable uv to replace and delete itself on Windows ([#&#8203;8914](astral-sh/uv#8914))
-   Remove source distribution filename from cache ([#&#8203;8907](astral-sh/uv#8907))
-   Respect `--index-url` in `uv pip list` ([#&#8203;8942](astral-sh/uv#8942))
-   Respect comma-separated extras in `--with` ([#&#8203;8946](astral-sh/uv#8946))

##### Documentation

-   Add uninstall note for previous versions ([#&#8203;8937](astral-sh/uv#8937))
-   Remove some missed references to `~/.cargo/bin` ([#&#8203;8936](astral-sh/uv#8936))
-   Split README's install code block into 3 ([#&#8203;8853](astral-sh/uv#8853))

### [`v0.5.0`](https://github.com/astral-sh/uv/blob/HEAD/CHANGELOG.md#050)

[Compare Source](astral-sh/uv@0.4.30...0.5.0)

Since the launch of Python version, project, and tool management capabilities back in August, we've seen extraordinary adoption of uv. We've been iterating rapidly: adding new features, fixing bugs, and improving the user experience. Despite moving quickly, stability and compatibility have been a major focus — we've made thirty releases since our last breaking change. Consequently, we've accumulated various changes that improve correctness and user experience, but could break some workflows. This release contains those changes; many have been marked as breaking out of an abundance of caution. We expect most users to be able to upgrade without making changes.

##### Breaking

-   **Use base executable to set virtualenv Python path** ([#&#8203;8481](astral-sh/uv#8481))

    Previously, uv canonicalized the path to the Python executable when setting the Python path in created virtual environments. This behavior had several undesirable effects: it would bypass stabilized version directories (as constructed by Homebrew) and it was not consistent with the Python standard library's behavior. Now, uv uses the `sys._base_executable` path.
-   **Use XDG (i.e. `~/.local/bin`) instead of the Cargo home directory in the installer** ([#&#8203;8420](astral-sh/uv#8420))

    Previously, uv's installer used `$CARGO_HOME` or `~/.cargo/bin` for its target install directory. It's been a longstanding complaint that uv uses this directory, as there's no relationship to Cargo. Now, uv will be installed into `$XDG_BIN_HOME`, `$XDG_DATA_HOME/../bin`, or `~/.local/bin` (in that order). Note that `$UV_INSTALL_DIR` can always be used to override the target directory.
-   **Discover and respect `.python-version` files in parent directories** ([#&#8203;6370](astral-sh/uv#6370))

    Previously, uv only read `.python-version` files from the working directory. Now, uv will check parent directories for `.python-version` files; however uv will not search for `.python-version` files beyond project boundaries. The new behavior is better aligned with that of `pyenv` and Rye.
-   **Error when disallowed settings are defined in `uv.toml`** ([#&#8203;8550](astral-sh/uv#8550))

    Some settings can only be defined in the `pyproject.toml`. Previously, uv would ignore these settings when present in the `uv.toml`. Now, uv will error to avoid confusion about why the settings are not respected.
-   **Implement PEP 440-compliant local version semantics** ([#&#8203;8797](astral-sh/uv#8797))

    Previously, uv's implementation of local versions (e.g. `2.0+cpu`) was not compliant with the specification due to the technical complexity of implementing the local version semantics in the PubGrub algorithm. Thanks to the work of [@&#8203;ericmarkmartin](https://github.com/ericmarkmartin), uv now has a spec-compliant implementation. Namely, uv will now allow a request for `torch==2.1.0` to install `[email protected]+cpu` regardless of whether `[email protected]` (without a local tag) actually exists.
-   **Treat the base Conda environment as a system environment** ([#&#8203;7691](astral-sh/uv#7691))

    Previously, uv would not distinguish between the base and other Conda environments. Now, uv uses `CONDA_DEFAULT_ENV` and the names `base` and `default` to determine if an environment active via `CONDA_PREFIX` is the base environment. If the base environment is active, the `--system` flag must be used to mutate it.
-   **Do not allow pre-releases when the `!=` operator is used** ([#&#8203;7974](astral-sh/uv#7974))

    Previously, uv would use the presence of a pre-release specifier in a version specifier as an opt-in to allow pre-release versions during resolution. The new behavior does not allow pre-releases when an inequals operator is used, e.g., `!= 2.0a1`.
-   **Prefer `USERPROFILE` over `FOLDERID_Profile` when selecting a home directory on Windows** ([#&#8203;8048](astral-sh/uv#8048))

    This change is a side-effect of switching from the `directories` crate to `etcetera` for determining canonical system paths. If `USERPROFILE` is not set, the behavior will be unchanged.
-   **Improve interactions between color environment variables and CLI options** ([#&#8203;8215](astral-sh/uv#8215))

    Previously, uv would respect the `FORCE_COLOR` and `NO_COLOR` environment variables over the `--color` flag. Now, when the `--color` flag is explicitly provided, uv will respect it over the environment variables.
-   **Make `allow-insecure-host` a global option** ([#&#8203;8476](astral-sh/uv#8476))

    Previously, this option was only available in some parts of uv. Now, `--allow-insecure-host` can be provided to any command. For consistency, the `allow-insecure-host` setting has been removed from the `[tool.uv.pip]` configuration in favor of `[tool.uv]`.
-   **Only write `.python-version` files during `uv init` for workspace members if the version differs** ([#&#8203;8897](astral-sh/uv#8897))

    Previously, uv would create a `.python-version` file for workspace members during `uv init`. Now, uv will only do so if the version differs from the `.python-version` file in the workspace root since uv will respect `.python-version` files in parent directories.

##### Enhancements

-   Add `uv tree --outdated` ([#&#8203;8893](astral-sh/uv#8893))
-   Add armv8l alias for armv7l to support arm 32-bit compatibility mode ([#&#8203;8881](astral-sh/uv#8881))
-   Add support for `pip list --outdated` ([#&#8203;8872](astral-sh/uv#8872))
-   Allow semicolons directly after direct URLs ([#&#8203;8836](astral-sh/uv#8836))
-   Enable support for arbitrary git transports ([#&#8203;8769](astral-sh/uv#8769))
-   Improve Python discovery source messages ([#&#8203;8890](astral-sh/uv#8890))
-   Show dedicated error for trailing `;` on URL and path requirements ([#&#8203;8835](astral-sh/uv#8835))
-   Add progress bar for `uv cache clean` ([#&#8203;8857](astral-sh/uv#8857))
-   Warn on failure to query system configuration file ([#&#8203;8829](astral-sh/uv#8829))

##### Preview features

-   Add support for building basic source distributions with the experimental uv build backend ([#&#8203;8886](astral-sh/uv#8886))

##### Bug fixes

-   Respect dynamic version updates in `uv lock` ([#&#8203;8867](astral-sh/uv#8867))
-   Respect fork markers in `--resolution-mode=lowest-direct` ([#&#8203;8839](astral-sh/uv#8839))

##### Documentation

-   Add further examples of git+https support ([#&#8203;8841](astral-sh/uv#8841))
-   Add installer variables to environment reference ([#&#8203;8874](astral-sh/uv#8874))
-   Add note on private classifier ([#&#8203;8783](astral-sh/uv#8783))
-   Update pip-and-uv strictness example ([#&#8203;8822](astral-sh/uv#8822))
-   Fix `uv python install` docs to use an existing PyPy version ([#&#8203;8845](astral-sh/uv#8845))
-   Document how to mimic `--verbose` with `RUST_LOG` ([#&#8203;8858](astral-sh/uv#8858))

### [`v0.4.30`](https://github.com/astral-sh/uv/blob/HEAD/CHANGELOG.md#0430)

[Compare Source](astral-sh/uv@0.4.29...0.4.30)

##### Enhancements

-   Add support for `.env` and custom env files in `uv run` ([#&#8203;8811](astral-sh/uv#8811))
-   Add support for `--all-packages` in `uv run`, `uv sync`, and `uv export` ([#&#8203;8742](astral-sh/uv#8742), [#&#8203;8741](astral-sh/uv#8741), [#&#8203;8739](astral-sh/uv#8739))
-   Allow use of `--frozen` with `--all-packages` in `uv sync` and `uv export` ([#&#8203;8760](astral-sh/uv#8760))
-   Show full error chain on tool upgrade failures ([#&#8203;8753](astral-sh/uv#8753))
-   Add `--check-url` to `uv publish` to check for existing distributions during upload ([#&#8203;8531](astral-sh/uv#8531))
-   Suggest using `--check-url` when `--skip-existing` is used ([#&#8203;8803](astral-sh/uv#8803))

##### Bug fixes

-   Allow incompatible `requires-python` for source distributions with static metadata ([#&#8203;8768](astral-sh/uv#8768))
-   Allow managed downloads with `--python-preference system` ([#&#8203;8808](astral-sh/uv#8808))
-   Avoid error for `--group` defined in non-root workspace member ([#&#8203;8734](astral-sh/uv#8734))
-   Avoid showing dependency group annotations on workspace members in tree ([#&#8203;8730](astral-sh/uv#8730))
-   Do not error when the Python bin directory is missing on `uv python uninstall` ([#&#8203;8725](astral-sh/uv#8725))
-   Include member groups when locking workspace ([#&#8203;8736](astral-sh/uv#8736))
-   Fix bug where `python_version < '0'` could appear in a final resolution ([#&#8203;8759](astral-sh/uv#8759))
-   Sanitize filenames during zip extraction ([#&#8203;8732](astral-sh/uv#8732))
-   Switch to RFC 9110 compatible format for exclude newer requests ([#&#8203;8752](astral-sh/uv#8752))

##### Preview features

-   Add support for installing versioned Python executables on Windows ([#&#8203;8663](astral-sh/uv#8663))
-   Improve interactions with existing Python executables during install ([#&#8203;8733](astral-sh/uv#8733))

##### Rust API

-   Extend `BaseClient` to accept extra middleware ([#&#8203;8807](astral-sh/uv#8807))
-   Add `From` for `FlatDistributions` struct ([#&#8203;8800](astral-sh/uv#8800))

##### Documentation

-   Fix environment variable name in providing credentials section ([#&#8203;8740](astral-sh/uv#8740))
-   Fix `add httpx` example with real git branch ([#&#8203;8756](astral-sh/uv#8756))
-   Fix indentation in `projects.md` ([#&#8203;8772](astral-sh/uv#8772))
-   Fix link to publish guide in `README` ([#&#8203;8720](astral-sh/uv#8720))
-   Generate environment variables documentation from code ([#&#8203;8493](astral-sh/uv#8493))
-   Improve and fix some documents ([#&#8203;8749](astral-sh/uv#8749))
-   Improve environment variables document ([#&#8203;8777](astral-sh/uv#8777))

### [`v0.4.29`](https://github.com/astral-sh/uv/blob/HEAD/CHANGELOG.md#0429)

[Compare Source](astral-sh/uv@0.4.28...0.4.29)

##### Enhancements

-   Sort errors during display in `uv python install` ([#&#8203;8684](astral-sh/uv#8684))
-   Update resolver to use disjointness checks instead of marker equality ([#&#8203;8661](astral-sh/uv#8661))
-   Add `riscv64` to supported Python platform tags ([#&#8203;8660](astral-sh/uv#8660))

##### Bug fixes

-   Fix hard and soft float libc detection for managed Python distributions on ARM ([#&#8203;8498](astral-sh/uv#8498))
-   Handle cycles in `uv pip tree` ([#&#8203;8689](astral-sh/uv#8689))
-   Respect dependency group markers in `uv export` ([#&#8203;8659](astral-sh/uv#8659))
-   Support transitive dependencies in Git workspaces ([#&#8203;8665](astral-sh/uv#8665))
-   Use portable paths for subdirectories in lock URLs ([#&#8203;8707](astral-sh/uv#8707))
-   Update `uv init --virtual` to imply `--no-package` ([#&#8203;8595](astral-sh/uv#8595))

##### Preview

-   Install versioned Python executables into the bin directory during `uv python install` (Unix only) ([#&#8203;8458](astral-sh/uv#8458))

##### Documentation

-   Clarify relationship between specifiers and `requires-python` range ([#&#8203;8688](astral-sh/uv#8688))
-   Fix broken link in docs ([#&#8203;8552](astral-sh/uv#8552))
-   Fix outdated documentation on `Requires-Python` ([#&#8203;8679](astral-sh/uv#8679))
-   Add Google Artifact Registry index authentication guide ([#&#8203;8579](astral-sh/uv#8579))

### [`v0.4.28`](https://github.com/astral-sh/uv/blob/HEAD/CHANGELOG.md#0428)

[Compare Source](astral-sh/uv@0.4.27...0.4.28)

##### Enhancements

-   Add support for requesting free-threaded builds via `+freethreaded` ([#&#8203;8645](astral-sh/uv#8645))
-   Improve trusted publishing error messages ([#&#8203;8633](astral-sh/uv#8633))
-   Remove unneeded `return` from Maturin project template ([#&#8203;8604](astral-sh/uv#8604))
-   Skip Python interpreter discovery for `uv export` ([#&#8203;8638](astral-sh/uv#8638))
-   Hint about missing trusted publishing permission ([#&#8203;8632](astral-sh/uv#8632))

##### Configuration

-   Add environment variable to disable progress output ([#&#8203;8600](astral-sh/uv#8600))

##### Bug fixes

-   Fork when minimum Python version increases ([#&#8203;8628](astral-sh/uv#8628))
-   Ignore empty groups when validating lock ([#&#8203;8598](astral-sh/uv#8598))
-   Remove duplicate word in error message ([#&#8203;8589](astral-sh/uv#8589))
-   Support cyclic dependencies in `uv tree` ([#&#8203;8564](astral-sh/uv#8564))
-   Update `uv init` to imply `--package` when using `--build-backend` ([#&#8203;8593](astral-sh/uv#8593))
-   Restore use of `dev-dependencies` and `requires-dev` for lockfile compatibility ([#&#8203;8599](astral-sh/uv#8599))

##### Documentation

-   Clarify `requires-python` requirement for dependencies ([#&#8203;8619](astral-sh/uv#8619))
-   Update CLI documentation for `--cache-dir` ([#&#8203;8627](astral-sh/uv#8627))

### [`v0.4.27`](https://github.com/astral-sh/uv/blob/HEAD/CHANGELOG.md#0427)

[Compare Source](astral-sh/uv@0.4.26...0.4.27)

This release includes support for the `[dependency-groups]` table as recently standardized in [PEP 735](https://peps.python.org/pep-0735/). The table allows for declaration of optional dependency groups that are not published as part of the package metadata, unlike `[project.optional-dependencies]`. There are new `--group`, `--only-group`, and `--no-group` options throughout the uv interface.

Previously, uv used a single `tool.uv.dev-dependencies` list for declaration of development dependencies. Now, uv supports declaring development dependencies in a standardized format and allows splitting development dependencies into multiple groups.

For compatibility, and to simplify usage for people that do not need multiple groups, uv special-cases the group named `dev`. The `dev` group is equivalent to `tool.uv.dev-dependencies`. The contents of `tool.uv.dev-dependencies` will merged into the `dev` group in uv's resolver. The `--dev`, `--only-dev`, and `--no-dev` flags remain as aliases for the corresponding `--group` options. Support for `tool.uv.dev-dependencies` remains in this release, but will display warnings in a future release.

uv syncs the `dev` group by default — this matches the exististing behavior for `tool.uv.dev-dependencies`. The default groups can be changed with the `tool.uv.default-groups` setting.

Thank you to Stephen Rosen who authored PEP 735.

##### Enhancements

-   Support for PEP 735 ([#&#8203;8272](astral-sh/uv#8272))
-   Add support for `--dry-run` mode in `uv lock` ([#&#8203;7783](astral-sh/uv#7783))
-   Don't allow non-string email in authors ([#&#8203;8520](astral-sh/uv#8520))
-   Enforce lockfile schema versions ([#&#8203;8509](astral-sh/uv#8509))

##### Bug fixes

-   Always attach URL to network errors ([#&#8203;8444](astral-sh/uv#8444))
-   Fix dangling non-platform dependencies in `uv tree` ([#&#8203;8532](astral-sh/uv#8532))
-   Prefer `lto` over `debug` free-threaded managed Python builds ([#&#8203;8515](astral-sh/uv#8515))

##### Documentation

-   Add `tool.uv.sources` to the "Settings" reference ([#&#8203;8543](astral-sh/uv#8543))
-   Add reference to `uv build` and `uv publish` in the landing pages ([#&#8203;8542](astral-sh/uv#8542))
-   Avoid duplicate `[tool.uv]` header in TOML examples ([#&#8203;8545](astral-sh/uv#8545))
-   Document `.netrc` environment variable and path ([#&#8203;8511](astral-sh/uv#8511))
-   Fix `.netrc` typo in authentication docs ([#&#8203;8521](astral-sh/uv#8521))
-   Fix heading level of "Script support" on docs landing page ([#&#8203;8544](astral-sh/uv#8544))
-   Move the installation configuration docs to a separate page ([#&#8203;8546](astral-sh/uv#8546))
-   Update docs for `--publish-url` to avoid duplication. ([#&#8203;8561](astral-sh/uv#8561))
-   Fix typo ([#&#8203;8554](astral-sh/uv#8554))
-   Fix typo in description of `--strict` flag ([#&#8203;8513](astral-sh/uv#8513))

### [`v0.4.26`](https://github.com/astral-sh/uv/blob/HEAD/CHANGELOG.md#0426)

[Compare Source](astral-sh/uv@0.4.25...0.4.26)

##### Enhancements

-   Allow static dependency metadata entries for direct URL requirements ([#&#8203;7846](astral-sh/uv#7846))
-   Use reinstall report formatting for `uv python install --reinstall` ([#&#8203;8487](astral-sh/uv#8487))
-   Add support for system-level `uv.toml` configuration ([#&#8203;7851](astral-sh/uv#7851))

##### Bug fixes

-   Apply `requires-python` narrowing with upper bounds ([#&#8203;8403](astral-sh/uv#8403))
-   Avoid rewriting `[[tool.uv.index]]` entries when credentials are provided ([#&#8203;8502](astral-sh/uv#8502))
-   Fix `uv add` comment handling for empty arrays ([#&#8203;8504](astral-sh/uv#8504))
-   Replace dashes with underscores in index credential variables ([#&#8203;8452](astral-sh/uv#8452))
-   Respect `--allow-insecure-host` in `uv publish` ([#&#8203;8440](astral-sh/uv#8440))
-   Allow arbitrary `--package` includes in `uv tree` ([#&#8203;8507](astral-sh/uv#8507))
-   Remove existing Python install after successful download in `uv python install` ([#&#8203;8485](astral-sh/uv#8485))

##### Documentation

-   Add docs example for URLs with `[tool.uv.dependency-metadata]` ([#&#8203;8484](astral-sh/uv#8484))
-   Add help page for build failures ([#&#8203;8286](astral-sh/uv#8286))
-   Fix `cache-keys` typo in `tags = true` ([#&#8203;8422](astral-sh/uv#8422))
-   Add documentation examples for manual branch, rev, and tag Git dependencies ([#&#8203;8497](astral-sh/uv#8497))

##### Error messages

-   Improve error message for cache info serialization ([#&#8203;8500](astral-sh/uv#8500))
-   Suggest `--from` command when executable is available for `uvx` ([#&#8203;8473](astral-sh/uv#8473))
-   Support `--with-editable` in `uv tool install` ([#&#8203;8472](astral-sh/uv#8472))

### [`v0.4.25`](https://github.com/astral-sh/uv/blob/HEAD/CHANGELOG.md#0425)

[Compare Source](astral-sh/uv@0.4.24...0.4.25)

##### Enhancements

-   Add support for `uv pip show --files` ([#&#8203;8369](astral-sh/uv#8369))
-   Don't prefetch unreachable packages ([#&#8203;8246](astral-sh/uv#8246))
-   Remove `tool.uv.sources` table if it is empty ([#&#8203;8365](astral-sh/uv#8365))
-   Modify cache versioning to support backwards compatibility ([#&#8203;8386](astral-sh/uv#8386))

##### Configuration

-   Add support for `UV_FROZEN` and `UV_LOCKED` ([#&#8203;8340](astral-sh/uv#8340))

##### Bug fixes

-   Allow dashes and underscores in custom index names ([#&#8203;8339](astral-sh/uv#8339))
-   Avoid panic when Git dependencies are included in fork markers ([#&#8203;8388](astral-sh/uv#8388))
-   Check existing source by normalized name before `uv add` and `uv remove` ([#&#8203;8359](astral-sh/uv#8359))
-   Fix bug where username from authentication cache could be ignored ([#&#8203;8345](astral-sh/uv#8345))
-   Fix to respect comments positioning in pyproject.toml on change ([#&#8203;8384](astral-sh/uv#8384))
-   Redact index sources in `uv.lock` ([#&#8203;8333](astral-sh/uv#8333))
-   Use correct indentation when project table contains open bracket comment ([#&#8203;8387](astral-sh/uv#8387))
-   Only remove a source from `[tool.uv.sources]` if it is no long being referenced ([#&#8203;8366](astral-sh/uv#8366))
-   Modify `uv pip list` and `uv tree` to print to stdout regardless of `--quiet` flag ([#&#8203;8392](astral-sh/uv#8392))

##### Error messages

-   Improve help message for missing `self update` invocations ([#&#8203;8337](astral-sh/uv#8337))
-   Log `.netrc` parsing errors ([#&#8203;8364](astral-sh/uv#8364))
-   Remove trailing newlines in error messages ([#&#8203;8322](astral-sh/uv#8322))
-   Use a dedicated message for incompatible Python versions in wheel ABI tags ([#&#8203;8363](astral-sh/uv#8363))
-   Remove commands available in the top-level from the suggested subcommand error ([#&#8203;8316](astral-sh/uv#8316))

##### Release

-   Run release builds for `macos-x86_64` on `macos-14` runners ([#&#8203;8327](astral-sh/uv#8327))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever MR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this MR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this MR, check this box

---

This MR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNy40NDAuNyIsInVwZGF0ZWRJblZlciI6IjM3LjQ0MC43IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJSZW5vdmF0ZSBCb3QiXX0=-->
@martinitus
Copy link

martinitus commented Dec 23, 2024

Hey folks, I have a followup question to the "conflicting extras / groups" features that were merged the last weeks.

I would like to declare conflicting extras - however these dependencies have no inherent conflict in the package or version. Instead they happen to use the same module namespace - databricks-connect and pyspark both use pyspark as module names.

What I tried so far was

[project.optional-dependencies]
databums = ["databricks-connect>=16.0.0",]
vanilla = ["pyspark>=3.5.4",]

[tool.uv]
conflicts = [
    [
        {extra = "databums" },
        {extra = "vanilla" },
    ]
]

However, when I run uv lock or uv pip install -v -e .[databums,vanilla] or even just uv pip -e . it happily installs both packages (and screws up the site-package directory afaik).

I presume the current implementation only prevents scenarios where the dependency resolution would have failed due to conflicting optional dependencies. Whereas what I want is explicitly declaring dependencies as conflicting?

Let me know if I missed something and this should actually be possible!

BTW: I also tried it with optional groups, but groups are not exposed to pip (yet, afaik). As I want to be able to install my package with pip on downstream systems, dependency groups seem to not be an option yet.

-- Edit --
I opened #10238

@zanieb
Copy link
Member

zanieb commented Dec 23, 2024

@martinitus I don't think we support that, but it does seem interesting.

Could you open a new issue please?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or improvement to existing functionality
Projects
None yet
6 participants