Skip to content

Added support for [tool.uv.preview-features] in TOML configs (#15767)#16452

Open
j-helland wants to merge 8 commits into
astral-sh:mainfrom
j-helland:jwh/toml-config-preview-features
Open

Added support for [tool.uv.preview-features] in TOML configs (#15767)#16452
j-helland wants to merge 8 commits into
astral-sh:mainfrom
j-helland:jwh/toml-config-preview-features

Conversation

@j-helland

@j-helland j-helland commented Oct 26, 2025

Copy link
Copy Markdown

Relates to (#15767)

Summary

Before this PR, specific preview features could only be specified via a comma-separated list in CLI --preview-features. Now, preview features can be specified as a list of strings in pyproject.toml or uv.toml. For example,

# pyproject.toml
[tool.uv]
preview-features = ["format"]

Commandline arguments (--preview, --no-preview, and --preview-features) take precedence over config settings (tool.uv.preview, tool.uv.preview-features), which matches the semantics of other settings in uv.

Alternatives Considered

From #15767 (comment):

setting tool.uv.preview-features.pylock = false would disable it, taking precedence over the feature potentially being enabled at a user level. If tool.uv.preview were changed to false, all preview features would be disabled, regardless of the content of tool.uv.preview-features.

I chose not to take this approach because it adds complexity and the semantics don't align with uv today. For example, --no-preview will currently take priority, even if pyproject.toml has uv.tool.preview = true. I think driving uv from the top-down like this is generally the right decision (and changing the semantics for a small feature like this would certainly not be).

Test Plan

  • cargo nextest run
  • cargo build
  • New snapshot tests.

In addition to the above, I also tried a small test project:

pyproject.toml test

From the uv/ project root:

$ mkdir tmp && cd tmp
$ cargo run -- init

# Warning
$ cargo run -- format 
warning: `uv format` is experimental and may change without warning. Pass `--preview-features format` to disable this warning.
1 file left unchanged

# No warning
$ echo "\n[tool.uv]\npreview-features = [\"format\"]" >> pyproject.toml
$ cargo run -- format
1 file left unchanged

# `uv.toml` takes priority over `pyproject.toml`
$ echo "preview-features = []" >> uv.toml
warning: Found both a `uv.toml` file and a `[tool.uv]` section in an adjacent `pyproject.toml`. The following fields from `[tool.uv]` will be ignored in favor of the `uv.toml` file:
- preview-features
warning: `uv format` is experimental and may change without warning. Pass `--preview-features format` to disable this warning.
1 file left unchanged

@j-helland

Copy link
Copy Markdown
Author

Some docker CI jobs failing, all due to

Error: missing API token, please run `depot login`
Error: failed with: Error: missing API token, please run `depot login`

I've checked other recent PRs and I'm seeing similar failures (example), so I suspect this is a transient issue not related to my changes.

@j-helland j-helland marked this pull request as ready for review October 26, 2025 08:16
@j-helland

Copy link
Copy Markdown
Author

This being my first PR in uv, I'm especially open to any and all feedback / changes requested (not that I wouldn't be if it was my 500th 🙂). If there's anything I can do to make the review process easier, don't hesitate to let me know!

@zanieb

zanieb commented Oct 26, 2025

Copy link
Copy Markdown
Member

Cool thanks!

Some quick high-level thoughts (I haven't looked at the code yet)

Passing --preview-features via CLI does NOT combine with uv.tool.preview-features, it overrides it completely. This behavior makes sense to me, otherwise there isn't a clear way to say "No, don't use your config feature X, use Y and Z instead".

I think we should actually combine them. If we get feedback that people need to be able to disable specific features, then the canonical way to do so in uv's interface would be to add a --no-preview-features <names> flag.

Don't worry about those Depot authentication failures — we're working with them to resolve those.

@j-helland

Copy link
Copy Markdown
Author

I think we should actually combine them. If we get feedback that people need to be able to disable specific features, then the canonical way to do so in uv's interface would be to add a --no-preview-features flag.

Got it, thanks for explaining. That should be an easy fix, I’ll get to that later today.

@j-helland

j-helland commented Oct 27, 2025

Copy link
Copy Markdown
Author

@zanieb I want to clarify one thing before submitting my revision. Suppose we pass --preview-features pylock with an underlying config like

preview = false
preview-features = ["format"]
  1. Would the final set of features be format | pylock or would we prefer to take only pylock since uv.tool.preview = false?
  2. Similarly, if tool.uv.preview = true, should any --preview-features merge into that de facto, resulting in all features being enabled?

I would intuitively expect that

  1. We just take pylock when tool.uv.preview = false, ignoring the config features.
  2. Extra --preview-features are swallowed by uv.tool.preview = true.

@j-helland j-helland force-pushed the jwh/toml-config-preview-features branch from b920a3e to f4d3d7d Compare October 29, 2025 01:47
@j-helland

Copy link
Copy Markdown
Author

I pushed what made intuitive sense to me:

  1. We just take pylock when tool.uv.preview = false, ignoring the config features.
  2. Extra --preview-features are swallowed by uv.tool.preview = true.

Happy to change this if requested!

@j-helland j-helland force-pushed the jwh/toml-config-preview-features branch 2 times, most recently from 9b37414 to b3dad59 Compare November 27, 2025 19:54
@j-helland

j-helland commented Nov 27, 2025

Copy link
Copy Markdown
Author

Nice, no more Depot auth failures.

I'm curious if anyone will get a chance to look at this PR? It would be nice to have this functionality at my current job. I'm sure it's very low-priority, though, so no worries.

If I made any mistakes in my approach that makes this work too time-consuming to review, I'd be happy to take that feedback so I can contribute more productively to uv in the future 🙂

@konstin konstin requested a review from zanieb November 28, 2025 08:19
@j-helland j-helland force-pushed the jwh/toml-config-preview-features branch from b3dad59 to 598773d Compare December 10, 2025 07:29
Preview features are specified as a list of strings in TOML, which parse
to PreviewFeatures values. CLI arguments like --preview and --no-preview
take precedence over config values. --preview-features combines with
config values.
@j-helland j-helland force-pushed the jwh/toml-config-preview-features branch from 598773d to 46bf1ff Compare December 10, 2025 07:41

@laundmo laundmo left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I'm just a random python/rust dev who was looking for something like this, but to me the code looks reasonable and seems to match how other config options are written.

Would be nice to get this feature, I specifically want to enable python-upgrade globally

Comment thread crates/uv-preview/src/lib.rs Outdated
@zanieb

zanieb commented Dec 18, 2025

Copy link
Copy Markdown
Member

Suppose we pass --preview-features pylock with an underlying config like [...]

I think if we see a config file like

preview = false
preview-features = ["format"]

we should probably just fail?

@zanieb

zanieb commented Dec 18, 2025

Copy link
Copy Markdown
Member

Similarly, if tool.uv.preview = true, should any --preview-features merge into that de facto, resulting in all features being enabled?

I think I'd expect us just to activate the subset of preview features specified in the CLI since the CLI takes precedence over the configuration file, though I'm not entirely sure and don't feel strongly.

Comment thread crates/uv-preview/src/lib.rs Outdated
@zanieb

zanieb commented Dec 18, 2025

Copy link
Copy Markdown
Member

Sorry for the delayed review, we've just had a lot on our backlog — it's not anything you did wrong.

@j-helland

j-helland commented Dec 20, 2025

Copy link
Copy Markdown
Author

I think if we see a config file like

preview = false
preview-features = ["format"]

we should probably just fail?

That makes intuitive sense to me, but doesn't match the current behavior of the commandline arguments, where you can pass --no-preview --preview-features upgrade-python without complaint.

Unless I'm missing something (very possible), it seems like implementing the validation you suggest is nontrivial. The closest thing I can find is validate_uv_toml, seems like we'd have to add an entirely new validation flow. Does that sound right?

Does it make sense to add that validation in this PR or in a separate effort to keep the scope here limited? I'm open to either option.

@zanieb

zanieb commented Dec 20, 2025

Copy link
Copy Markdown
Member

I think we can be more strict in the configuration file than in the CLI.

The closest thing I can find is validate_uv_toml, seems like we'd have to add an entirely new validation flow.

Ah, hm. That's a little unfortunate. It does seem useful long-term though.

Does it make sense to add that validation in this PR or in a separate effort to keep the scope here limited? I'm open to either option.

I think the only trouble with doing it in a separate pull request is that we don't want to release support for including both fields then "break" it in a subsequent release. Would you mind sketching it in a separate pull request and we can merge them together?

…e fields

* Instead of merging commandline `--preview-features` with config file `preview-features`, now the commandline arguments take precedence. This matches the semantics of how uv handles other commandline arguments.
* Removed unnecessary `PreviewFeaturesMode` type.
* Fixed bug with `PreviewFeatures` serde serializer that produced a list of lists instead of a flat list as expected.
@j-helland j-helland force-pushed the jwh/toml-config-preview-features branch from 6e70259 to 7b2c211 Compare December 20, 2025 05:11
@j-helland

Copy link
Copy Markdown
Author

Would you mind sketching it in a separate pull request and we can merge them together?

Sure thing! I'll try to get to that this weekend.

@codspeed-hq

codspeed-hq Bot commented Dec 20, 2025

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 5 untouched benchmarks


Comparing j-helland:jwh/toml-config-preview-features (44d7749) with main (deb394c)

Open in CodSpeed

@j-helland

Copy link
Copy Markdown
Author

@zanieb I sketched what you suggested in #17202 let me know what you think.

@zanieb

zanieb commented Jan 22, 2026

Copy link
Copy Markdown
Member

Sorry I lost track of this over the holidays! Will revisit.

EliteTK added a commit that referenced this pull request Jan 23, 2026
…17670)

## Summary

This PR replaces `bitflags` in favour of `enumflags2` (which we already
transitively depended on) so that `PreviewFeatures` can be replaced with
`PreviewFeature` which is an enum. This clarifies intent in cases where
we only care about one specific `PreviewFeature`.

To avoid a bunch of boilerplate changes, the `Preview` wrapper has been
kept and creation now involves a `&[PreviewFeature]` in all cases. The
alternative was to have everything which initialises a `Preview` use
`BitFlags` directly and possibly to remove `Preview` entirely but this
keeps things simpler and limits the changes throughout the rest of the
codebase solely to changes which deal with the name changes (ALL_CAPS to
PascalCase) and the impact on `--show-settings` which I don't believe we
care about stability of output for?

The changes to `--show-settings` could be avoided with some custom
`Debug` implementation but that seems excessive.

This PR will impact #16452. But the changes were inspired by trying to
remove the need for that particular PR to add more runtime type
checking.

## Test Plan

Existing tests were adjusted (I also fixed some missing cases). The test
for panicking in cases which are now prevented through the use of type
changes has been dropped. All the rest of the tests were ran, snapshot
changes reviewed and applied.
@EliteTK EliteTK self-assigned this Jan 30, 2026
@EliteTK

EliteTK commented Feb 6, 2026

Copy link
Copy Markdown
Member

It hurt my soul to do a merge for this but hey, the precedent was set 🤷 .

I've fixed up the conflicts with the Preview changes, as a bonus the code is cleaner and doesn't do runtime type checking anymore. I'll rebase the other PR onto this one once this one passes CI.

@EliteTK EliteTK force-pushed the jwh/toml-config-preview-features branch from 5fd3464 to 8f330a6 Compare February 6, 2026 21:13
@EliteTK EliteTK force-pushed the jwh/toml-config-preview-features branch from 5694446 to 44d7749 Compare February 6, 2026 21:23
@j-helland

Copy link
Copy Markdown
Author

Work has been busy lately and I lost track of this myself!

Thanks @EliteTK for fixing this up, I really like your simplifications with the PreviewFeature enum.

I believe what remains here is deciding how to proceed with #17202 (259a9d4), specifically #17202 (comment). Happy to continue working on it if you all have feedback!

@EliteTK

EliteTK commented Feb 8, 2026

Copy link
Copy Markdown
Member

There's a potential issue here that now if I have a local pyproject.toml with preview-features = ["foo", "bar"] and a user uv.toml with preview = false then after combining (even with the validation patch AIUI) you will get an inconsistent preview = false ; preview-features = ["foo", "bar"] config when in reality you want the project config to override the user level one.

But that could be fixed by having Options just hold a Option<Preview> I think, which would also require the changes suggested in #17202 (comment) .

That being said, I am wondering if there's justification to have preview take a bool or a table instead. e.g.

preview = false
preview = true
preview = ["foo", "bar", "baz"]
# Or if that's not clear, then alternatively:
preview = { features = ["foo", "bar", "baz"] }

It kind of breaks with the established convention we've had going in this regard with --preview and --preview-features. I don't personally like denormalised data when it can be avoided though. Maybe @zanieb can chip in their opinion on this specific design aspect.

It would mean that Preview can implement its own Deserialize and then the validation can sit there without needing to add a Deserialize impl for Options. But that route is not particularly bad either.

@j-helland

j-helland commented Feb 8, 2026

Copy link
Copy Markdown
Author

There's a potential issue here that now if I have a local pyproject.toml with preview-features = ["foo", "bar"] and a user uv.toml with preview = false then after combining (even with the validation patch AIUI) you will get an inconsistent preview = false ; preview-features = ["foo", "bar"] config when in reality you want the project config to override the user level one.

That's true, that should be fixed.

I agree that denormalized data leads to clunkier code here. I'd personally be fine with

# boolean values
preview = true
preview = false
preview = { features = ["foo", "bar", "baz"] }

as a compromise between the implementation's elegance and the clarity of having config fields correspond one-to-one with commandline args per the original design.

I'll defer to you all about whether the convention is worth breaking, though. I could see a reasonable argument for the clarity of the UI taking priority here.

@EliteTK

EliteTK commented Feb 13, 2026

Copy link
Copy Markdown
Member

We discussed this internally, there's a few other options which follow the same duplicate pattern, so in that regard, we are going to keep preview and preview-features here for now. But what we can do is this:

Keep preview = bool for now. But hide it.

Add preview-features = bool | list (to implement this, just add an enum with serde(untagged) on it to OptionsWire), document the behaviour.

Use Preview directly in the Options struct. Do the validation (as suggested in the other PR) by replacing impl From<OptionsWire> for Options with impl Deserialize for Options. All that so that we use the default logic for how we do combining between different files - for consistency.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants