Allow configuring preview features in uv.toml and pyproject.toml (#18437)

## Summary

This is a continuation of the excellent work by @j-helland in #16452 and
#17202 and closes #15767.

This PR adds `preview-features` to `uv.toml` and `pyproject.toml`.

This field can be set to a boolean or to a list of features.

It is intended to supersede the `preview` setting itself, and conflicts
with it.

There's a little bit of complexity required to ensure that setting
either `preview` or `preview-features` is combined at the right point,
but it's also necessary to leave them split to accurately warn when a
`uv.toml` masks a `pyproject.toml` in the same directory.

Additionally there's complexity involved in making things work with
`deny_unknown_fields` and `flatten` (they're incompatible so require
manual flattening) and also this improves error messages somewhat.

Also (supersedes) closes #16452 and (supersedes) closes #17202.

## Test Plan

Tests taken from the original PR with some alterations and a bunch of
additional tests.

---------

Co-authored-by: j-helland <jonathan.w.helland@gmail.com>
Co-authored-by: Zanie Blue <contact@zanie.dev>
This commit is contained in:
Tomasz Kramkowski
2026-06-14 12:06:53 +01:00
committed by GitHub
parent 6736974a73
commit ea707737bb
13 changed files with 1212 additions and 100 deletions
Generated
+4
View File
@@ -6839,6 +6839,8 @@ version = "0.0.54"
dependencies = [
"enumflags2",
"itertools 0.14.0",
"schemars",
"serde",
"thiserror",
"uv-preview",
"uv-warnings",
@@ -7162,6 +7164,7 @@ dependencies = [
"fs-err",
"schemars",
"serde",
"serde-untagged",
"textwrap",
"thiserror",
"toml",
@@ -7180,6 +7183,7 @@ dependencies = [
"uv-options-metadata",
"uv-pep440",
"uv-pep508",
"uv-preview",
"uv-pypi-types",
"uv-python",
"uv-redacted",
+3
View File
@@ -23,6 +23,8 @@ uv-warnings = { workspace = true }
enumflags2 = { workspace = true }
itertools = { workspace = true }
schemars = { workspace = true, optional = true }
serde = { workspace = true }
thiserror = { workspace = true }
[dev-dependencies]
@@ -30,4 +32,5 @@ uv-preview = { path = ".", features = ["testing"] }
[features]
default = []
schemars = ["dep:schemars"]
testing = []
+40 -46
View File
@@ -1,3 +1,4 @@
use std::borrow::Cow;
use std::sync::{Mutex, OnceLock};
use std::{
fmt::{Debug, Display, Formatter},
@@ -384,6 +385,43 @@ impl FromStr for MaybePreviewFeature {
}
}
impl<'de> serde::Deserialize<'de> for MaybePreviewFeature {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let name: Cow<'de, str> = serde::Deserialize::deserialize(deserializer)?;
Self::from_str(&name).map_err(serde::de::Error::custom)
}
}
#[cfg(feature = "schemars")]
impl schemars::JsonSchema for MaybePreviewFeature {
fn schema_name() -> Cow<'static, str> {
Cow::Borrowed("PreviewFeature")
}
fn json_schema(_generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
// Advertise canonical names for editor completions, while accepting any nonempty name to
// match the forwards-compatible runtime parsing behavior.
let choices: Vec<&str> = BitFlags::<PreviewFeature>::all()
.iter()
.map(PreviewFeature::as_str)
.collect();
schemars::json_schema!({
"type": "string",
"anyOf": [
{
"enum": choices,
},
{
"pattern": "\\S",
},
],
})
}
}
#[derive(Clone, Copy, PartialEq, Eq, Default)]
pub struct Preview {
flags: BitFlags<PreviewFeature>,
@@ -409,23 +447,6 @@ impl Preview {
}
}
/// Resolve preview arguments, warning and ignoring unknown feature names.
pub fn from_args(
preview: bool,
no_preview: bool,
preview_features: &[MaybePreviewFeature],
) -> Self {
if no_preview {
return Self::default();
}
if preview {
return Self::all();
}
Self::from_feature_names(preview_features)
}
/// Check if a single feature is enabled.
pub fn is_enabled(&self, flag: PreviewFeature) -> bool {
self.flags.contains(flag)
@@ -441,7 +462,8 @@ impl Preview {
!self.flags.is_empty()
}
fn from_feature_names<'a>(
/// Resolve preview feature names, warning and ignoring unknown names.
pub fn from_feature_names<'a>(
feature_names: impl IntoIterator<Item = &'a MaybePreviewFeature>,
) -> Self {
let mut flags = BitFlags::empty();
@@ -547,34 +569,6 @@ mod tests {
assert_eq!(preview.to_string(), "python-upgrade,pylock");
}
#[test]
fn test_preview_from_args() {
// Test no preview and no no_preview, and no features
let preview = Preview::from_args(false, false, &[]);
assert_eq!(preview.to_string(), "disabled");
// Test no_preview
let preview = Preview::from_args(true, true, &[]);
assert_eq!(preview.to_string(), "disabled");
// Test preview (all features)
let preview = Preview::from_args(true, false, &[]);
assert_eq!(preview.to_string(), "enabled");
// Test specific features
let features = ["python-upgrade", "json-output"].map(|name| name.parse().unwrap());
let preview = Preview::from_args(false, false, &features);
assert!(preview.is_enabled(PreviewFeature::PythonUpgrade));
assert!(preview.is_enabled(PreviewFeature::JsonOutput));
assert!(!preview.is_enabled(PreviewFeature::Pylock));
// Test unknown features
let features = ["unknown-feature", "pylock"].map(|name| name.parse().unwrap());
let preview = Preview::from_args(false, false, &features);
assert!(preview.is_enabled(PreviewFeature::Pylock));
assert_eq!(preview.flags.bits().count_ones(), 1);
}
#[test]
fn test_preview_feature_as_str() {
assert_eq!(
+4 -1
View File
@@ -30,6 +30,7 @@ uv-normalize = { workspace = true }
uv-options-metadata = { workspace = true }
uv-pep440 = { workspace = true }
uv-pep508 = { workspace = true }
uv-preview = { workspace = true }
uv-pypi-types = { workspace = true }
uv-python = { workspace = true, features = ["clap"] }
uv-redacted = { workspace = true }
@@ -44,6 +45,7 @@ clap = { workspace = true }
fs-err = { workspace = true }
schemars = { workspace = true, optional = true }
serde = { workspace = true }
serde-untagged = { workspace = true }
textwrap = { workspace = true }
thiserror = { workspace = true }
toml = { workspace = true }
@@ -51,7 +53,7 @@ tracing = { workspace = true }
url = { workspace = true }
[package.metadata.cargo-shear]
ignored = ["uv-options-metadata", "clap"]
ignored = ["clap"]
[features]
schemars = [
@@ -61,6 +63,7 @@ schemars = [
"uv-distribution-types/schemars",
"uv-install-wheel/schemars",
"uv-normalize/schemars",
"uv-preview/schemars",
"uv-python/schemars",
"uv-resolver/schemars",
"uv-torch/schemars",
+2 -1
View File
@@ -23,7 +23,7 @@ use uv_torch::TorchMode;
use uv_workspace::pyproject::ExtraBuildDependencies;
use uv_workspace::pyproject_mut::AddBoundsKind;
use crate::{AuditOptions, FilesystemOptions, Options, PipOptions};
use crate::{AuditOptions, FilesystemOptions, Options, PipOptions, PreviewOption};
pub trait Combine {
/// Combine two values, preferring the values in `self`.
@@ -109,6 +109,7 @@ impl_combine_or!(PipExtraIndex);
impl_combine_or!(PipFindLinks);
impl_combine_or!(PipIndex);
impl_combine_or!(PrereleaseMode);
impl_combine_or!(PreviewOption);
impl_combine_or!(ProxyUrl);
impl_combine_or!(PythonDownloads);
impl_combine_or!(PythonPreference);
+4 -2
View File
@@ -481,8 +481,10 @@ fn warn_uv_toml_masked_fields(options: &Options) {
if cache_dir.is_some() {
masked_fields.push("cache-dir");
}
if preview.is_some() {
masked_fields.push("preview");
match preview {
Some(PreviewOption::Preview(_)) => masked_fields.push("preview"),
Some(PreviewOption::PreviewFeatures(_)) => masked_fields.push("preview-features"),
None => (),
}
if python_preference.is_some() {
masked_fields.push("python-preference");
+206 -17
View File
@@ -1,3 +1,5 @@
#[cfg(feature = "schemars")]
use std::borrow::Cow;
use std::{fmt::Debug, num::NonZeroUsize, path::Path, path::PathBuf};
use serde::{Deserialize, Serialize};
@@ -15,6 +17,7 @@ use uv_install_wheel::LinkMode;
use uv_macros::{CombineOptions, OptionsMetadata};
use uv_normalize::{ExtraName, PackageName, PipGroupName};
use uv_pep508::Requirement;
use uv_preview::{MaybePreviewFeature, Preview};
use uv_pypi_types::{SupportedEnvironments, VerbatimParsedUrl};
use uv_python::{PythonDownloads, PythonPreference, PythonVersion};
use uv_redacted::DisplaySafeUrl;
@@ -69,9 +72,9 @@ pub(crate) struct UvRequiredVersionToml {
/// A `[tool.uv]` section.
#[allow(dead_code)]
#[derive(Debug, Clone, Default, Deserialize, CombineOptions, OptionsMetadata)]
#[serde(from = "OptionsWire", rename_all = "kebab-case")]
#[serde(try_from = "OptionsWire", rename_all = "kebab-case")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "schemars", schemars(!from))]
#[cfg_attr(feature = "schemars", schemars(!try_from))]
pub struct Options {
#[serde(flatten)]
pub globals: GlobalOptions,
@@ -246,8 +249,9 @@ impl Options {
/// Global settings, relevant to all invocations.
#[derive(Debug, Clone, Default, Deserialize, CombineOptions, OptionsMetadata)]
#[serde(rename_all = "kebab-case")]
#[serde(try_from = "GlobalOptionsWire", rename_all = "kebab-case")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "schemars", schemars(!try_from))]
pub struct GlobalOptions {
/// Enforce a requirement on the version of uv.
///
@@ -324,15 +328,11 @@ pub struct GlobalOptions {
"#
)]
pub cache_dir: Option<PathBuf>,
/// Whether to enable experimental, preview features.
#[option(
default = "false",
value_type = "bool",
example = r#"
preview = true
"#
)]
pub preview: Option<bool>,
/// The user's preview configuration.
#[serde(flatten)]
pub preview: Option<PreviewOption>,
/// Whether to prefer using Python installations that are already present on the system, or
/// those that are downloaded and installed by uv.
#[option(
@@ -435,6 +435,78 @@ pub struct GlobalOptions {
pub allow_insecure_host: Option<Vec<TrustedHost>>,
}
/// Like [`GlobalOptions`], but with any `#[serde(flatten)]` fields inlined.
/// This improves line/column information in error messages.
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(rename_all = "kebab-case")]
struct GlobalOptionsWire {
required_version: Option<RequiredVersion>,
system_certs: Option<bool>,
native_tls: Option<bool>,
offline: Option<bool>,
no_cache: Option<bool>,
cache_dir: Option<PathBuf>,
preview: Option<bool>,
preview_features: Option<PreviewFeaturesOption>,
python_preference: Option<PythonPreference>,
python_downloads: Option<PythonDownloads>,
concurrent_downloads: Option<NonZeroUsize>,
concurrent_builds: Option<NonZeroUsize>,
concurrent_installs: Option<NonZeroUsize>,
http_proxy: Option<ProxyUrl>,
https_proxy: Option<ProxyUrl>,
no_proxy: Option<Vec<String>>,
allow_insecure_host: Option<Vec<TrustedHost>>,
}
impl TryFrom<GlobalOptionsWire> for GlobalOptions {
type Error = &'static str;
#[allow(deprecated)]
fn try_from(value: GlobalOptionsWire) -> Result<Self, Self::Error> {
let GlobalOptionsWire {
required_version,
system_certs,
native_tls,
offline,
no_cache,
cache_dir,
preview,
preview_features,
python_preference,
python_downloads,
concurrent_downloads,
concurrent_builds,
concurrent_installs,
http_proxy,
https_proxy,
no_proxy,
allow_insecure_host,
} = value;
Ok(Self {
required_version,
system_certs,
native_tls,
offline,
no_cache,
cache_dir,
preview: PreviewOption::try_from(preview, preview_features)?,
python_preference,
python_downloads,
concurrent_downloads,
concurrent_builds,
concurrent_installs,
http_proxy,
https_proxy,
no_proxy,
allow_insecure_host,
})
}
}
/// Settings relevant to all installer operations.
#[derive(Debug, Clone, Default, CombineOptions)]
pub struct InstallerOptions {
@@ -2381,6 +2453,7 @@ struct OptionsWire {
no_cache: Option<bool>,
cache_dir: Option<PathBuf>,
preview: Option<bool>,
preview_features: Option<PreviewFeaturesOption>,
python_preference: Option<PythonPreference>,
python_downloads: Option<PythonDownloads>,
concurrent_downloads: Option<NonZeroUsize>,
@@ -2472,9 +2545,11 @@ struct OptionsWire {
build_backend: Option<serde::de::IgnoredAny>,
}
impl From<OptionsWire> for Options {
impl TryFrom<OptionsWire> for Options {
type Error = &'static str;
#[allow(deprecated)]
fn from(value: OptionsWire) -> Self {
fn try_from(value: OptionsWire) -> Result<Self, Self::Error> {
let OptionsWire {
required_version,
system_certs,
@@ -2483,6 +2558,7 @@ impl From<OptionsWire> for Options {
no_cache,
cache_dir,
preview,
preview_features,
python_preference,
python_downloads,
python_install_mirror,
@@ -2552,7 +2628,7 @@ impl From<OptionsWire> for Options {
build_backend,
} = value;
Self {
Ok(Self {
globals: GlobalOptions {
required_version,
system_certs,
@@ -2560,7 +2636,7 @@ impl From<OptionsWire> for Options {
offline,
no_cache,
cache_dir,
preview,
preview: PreviewOption::try_from(preview, preview_features)?,
python_preference,
python_downloads,
concurrent_downloads,
@@ -2635,7 +2711,7 @@ impl From<OptionsWire> for Options {
dependency_groups,
managed,
package,
}
})
}
}
@@ -2765,3 +2841,116 @@ impl From<&crate::EnvironmentOptions> for MalwareCheckSettings {
}
}
}
/// Represents the `preview-features` configuration option.
#[derive(Debug, Clone)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "schemars", schemars(untagged))]
pub enum PreviewFeaturesOption {
Toggle(bool),
Features(Vec<MaybePreviewFeature>),
}
// A derived `#[serde(untagged)]` implementation collapses detailed type and element errors into
// "data did not match any variant", so use a type-directed visitor to preserve useful diagnostics.
impl<'de> Deserialize<'de> for PreviewFeaturesOption {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
serde_untagged::UntaggedEnumVisitor::new()
.expecting("a boolean or a list of preview feature names")
.bool(|value| Ok(Self::Toggle(value)))
.seq(|sequence| sequence.deserialize().map(Self::Features))
.deserialize(deserializer)
}
}
#[expect(
dead_code,
reason = "Fields are only used by the OptionsMetadata and JsonSchema derives"
)]
#[derive(OptionsMetadata)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "schemars", schemars(rename_all = "kebab-case"))]
struct PreviewOptionsDefinition {
// This legacy setting remains supported and included in the JSON schema, but is omitted from
// option metadata so the generated settings reference documents only `preview-features`.
/// Whether to enable all experimental, preview features.
///
/// Use `preview-features` instead.
#[deprecated(note = "use `preview-features` instead")]
preview: Option<bool>,
/// Whether to enable specific or all experimental preview features.
///
/// Unknown feature names are ignored with a warning.
#[option(
default = "false",
value_type = "bool | list[str]",
example = r#"
preview-features = true
# or
preview-features = ["python-upgrade"]
"#
)]
preview_features: Option<PreviewFeaturesOption>,
}
/// Represents the user's preview configuration from either `preview` or `preview-features`.
#[derive(Debug, Clone)]
pub enum PreviewOption {
/// Whether to enable all experimental, preview features.
Preview(bool),
/// Whether to enable specific or all experimental preview features.
PreviewFeatures(PreviewFeaturesOption),
}
impl uv_options_metadata::OptionsMetadata for PreviewOption {
fn record(visit: &mut dyn uv_options_metadata::Visit) {
<PreviewOptionsDefinition as uv_options_metadata::OptionsMetadata>::record(visit);
}
}
#[cfg(feature = "schemars")]
impl schemars::JsonSchema for PreviewOption {
fn schema_name() -> Cow<'static, str> {
Cow::Borrowed("PreviewOption")
}
fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
let mut schema = <PreviewOptionsDefinition as schemars::JsonSchema>::json_schema(generator);
schema.insert(
"not".to_string(),
schemars::json_schema!({
"required": ["preview", "preview-features"],
})
.into(),
);
schema
}
}
impl PreviewOption {
fn try_from(
preview: Option<bool>,
preview_features: Option<PreviewFeaturesOption>,
) -> Result<Option<Self>, &'static str> {
match (preview, preview_features) {
(Some(_), Some(_)) => Err("cannot specify both `preview` and `preview-features`"),
(Some(b), None) => Ok(Some(Self::Preview(b))),
(None, Some(features)) => Ok(Some(Self::PreviewFeatures(features))),
(None, None) => Ok(None),
}
}
/// Resolve the preview configuration, warning and ignoring unknown feature names.
pub fn resolve(&self) -> Preview {
use PreviewFeaturesOption::{Features, Toggle};
match self {
Self::Preview(false) | Self::PreviewFeatures(Toggle(false)) => Preview::default(),
Self::Preview(true) | Self::PreviewFeatures(Toggle(true)) => Preview::all(),
Self::PreviewFeatures(Features(features)) => Preview::from_feature_names(features),
}
}
}
+2 -6
View File
@@ -39,7 +39,7 @@ use uv_fs::{CWD, Simplified, normalize_path};
#[cfg(feature = "self-update")]
use uv_pep440::release_specifiers_to_ranges;
use uv_pep508::VersionOrUrl;
use uv_preview::{Preview, PreviewFeature};
use uv_preview::PreviewFeature;
use uv_pypi_types::{ParsedDirectoryUrl, ParsedUrl};
use uv_python::PythonRequest;
use uv_requirements::{GroupsSpecification, RequirementsSource};
@@ -127,11 +127,7 @@ async fn run(cli: Cli) -> Result<ExitStatus> {
let environment = EnvironmentOptions::new()?;
// Resolve preview flags before config discovery for decisions that affect the discovery root.
let early_preview = Preview::from_args(
settings::resolve_preview(&cli.top_level.global_args, None, &environment),
cli.top_level.global_args.no_preview,
&cli.top_level.global_args.preview_features,
);
let early_preview = settings::resolve_preview(&cli.top_level.global_args, None, &environment);
// Make the early preview flags globally available.
uv_preview::set(early_preview)?;
+34 -19
View File
@@ -57,8 +57,8 @@ use uv_resolver::{
};
use uv_settings::{
Combine, EnvironmentOptions, FilesystemOptions, MalwareCheckSettings, Options, PipOptions,
PublishOptions, PythonInstallMirrors, ResolverInstallerOptions, ResolverInstallerSchema,
ResolverOptions,
PreviewFeaturesOption, PreviewOption, PublishOptions, PythonInstallMirrors,
ResolverInstallerOptions, ResolverInstallerSchema, ResolverOptions,
};
use uv_static::EnvVars;
use uv_torch::{AmdGpuArchitecture, TorchMode};
@@ -129,11 +129,7 @@ impl GlobalSettings {
.unwrap_or_else(Concurrency::threads),
),
show_settings: args.show_settings,
preview: Preview::from_args(
resolve_preview(args, workspace, environment),
args.no_preview,
&args.preview_features,
),
preview: resolve_preview(args, workspace, environment),
python_preference,
python_downloads: flag(
args.allow_python_downloads,
@@ -228,22 +224,41 @@ pub(crate) fn resolve_preview(
args: &GlobalArgs,
workspace: Option<&FilesystemOptions>,
environment: &EnvironmentOptions,
) -> bool {
// CLI takes precedence
match flag(args.preview, args.no_preview, "preview") {
Some(value) => value,
None => {
// Check environment variable
if environment.preview.value == Some(true) {
true
) -> Preview {
// Explicit `--preview` and `--no-preview` flags take priority.
if let Some(enabled) = flag(args.preview, args.no_preview, "preview") {
return if enabled {
Preview::all()
} else {
// Fall back to workspace config
workspace
.and_then(|workspace| workspace.globals.preview)
.unwrap_or(false)
Preview::default()
};
}
// `UV_PREVIEW=true` enables all preview features.
if environment.preview.value == Some(true) {
return Preview::all();
}
let configured = workspace.and_then(|workspace| workspace.globals.preview.as_ref());
// Boolean enable-all configuration takes priority.
if matches!(
configured,
Some(
PreviewOption::Preview(true)
| PreviewOption::PreviewFeatures(PreviewFeaturesOption::Toggle(true))
)
) {
return Preview::all();
}
// Explicit preview feature names take priority over configured feature names.
if !args.preview_features.is_empty() {
return Preview::from_feature_names(&args.preview_features);
}
// Fall back to workspace configuration.
configured.map(PreviewOption::resolve).unwrap_or_default()
}
/// The resolved network settings to use for any invocation of the CLI.
+1 -1
View File
@@ -265,7 +265,7 @@ fn invalid_pyproject_toml_option_unknown_field() -> Result<()> {
|
2 | unknown = "field"
| ^^^^^^^
unknown field `unknown`, expected one of `required-version`, `system-certs`, `native-tls`, `offline`, `no-cache`, `cache-dir`, `preview`, `python-preference`, `python-downloads`, `concurrent-downloads`, `concurrent-builds`, `concurrent-installs`, `index`, `index-url`, `extra-index-url`, `no-index`, `find-links`, `index-strategy`, `keyring-provider`, `http-proxy`, `https-proxy`, `no-proxy`, `allow-insecure-host`, `resolution`, `prerelease`, `fork-strategy`, `dependency-metadata`, `config-settings`, `config-settings-package`, `no-build-isolation`, `no-build-isolation-package`, `extra-build-dependencies`, `extra-build-variables`, `exclude-newer`, `exclude-newer-package`, `link-mode`, `compile-bytecode`, `no-sources`, `no-sources-package`, `upgrade`, `upgrade-package`, `reinstall`, `reinstall-package`, `no-build`, `no-build-package`, `no-binary`, `no-binary-package`, `torch-backend`, `python-install-mirror`, `pypy-install-mirror`, `python-downloads-json-url`, `publish-url`, `trusted-publishing`, `check-url`, `add-bounds`, `audit`, `pip`, `cache-keys`, `override-dependencies`, `exclude-dependencies`, `constraint-dependencies`, `build-constraint-dependencies`, `environments`, `required-environments`, `conflicts`, `workspace`, `sources`, `managed`, `package`, `default-groups`, `dependency-groups`, `dev-dependencies`, `build-backend`
unknown field `unknown`, expected one of `required-version`, `system-certs`, `native-tls`, `offline`, `no-cache`, `cache-dir`, `preview`, `preview-features`, `python-preference`, `python-downloads`, `concurrent-downloads`, `concurrent-builds`, `concurrent-installs`, `index`, `index-url`, `extra-index-url`, `no-index`, `find-links`, `index-strategy`, `keyring-provider`, `http-proxy`, `https-proxy`, `no-proxy`, `allow-insecure-host`, `resolution`, `prerelease`, `fork-strategy`, `dependency-metadata`, `config-settings`, `config-settings-package`, `no-build-isolation`, `no-build-isolation-package`, `extra-build-dependencies`, `extra-build-variables`, `exclude-newer`, `exclude-newer-package`, `link-mode`, `compile-bytecode`, `no-sources`, `no-sources-package`, `upgrade`, `upgrade-package`, `reinstall`, `reinstall-package`, `no-build`, `no-build-package`, `no-binary`, `no-binary-package`, `torch-backend`, `python-install-mirror`, `pypy-install-mirror`, `python-downloads-json-url`, `publish-url`, `trusted-publishing`, `check-url`, `add-bounds`, `audit`, `pip`, `cache-keys`, `override-dependencies`, `exclude-dependencies`, `constraint-dependencies`, `build-constraint-dependencies`, `environments`, `required-environments`, `conflicts`, `workspace`, `sources`, `managed`, `package`, `default-groups`, `dependency-groups`, `dev-dependencies`, `build-backend`
Resolved in [TIME]
Checked in [TIME]
+811 -1
View File
@@ -2119,6 +2119,95 @@ fn resolve_both_special_fields() -> anyhow::Result<()> {
Ok(())
}
/// Read preview settings from both a `uv.toml` and `pyproject.toml` file in the current directory.
#[test]
#[cfg_attr(
windows,
ignore = "Configuration tests are not yet supported on Windows"
)]
fn resolve_both_preview() -> anyhow::Result<()> {
let context = uv_test::test_context!("3.12");
let uv_config = context.temp_dir.child("uv.toml");
let pyproject = context.temp_dir.child("pyproject.toml");
let baseline = capture_uv_snapshot!(
context.filters(),
add_shared_args(context.version()).arg("--show-settings")
);
pyproject.write_str(indoc::indoc! {r"
[tool.uv]
preview = true
"})?;
uv_config.write_str(r#"preview-features = ["pylock"]"#)?;
// The warning should name the ignored `preview` setting.
// Compare against the baseline.
let preview_masked = diff_uv_snapshot!(
context.filters(),
&baseline,
add_shared_args(context.version()).arg("--show-settings"),
@"
...
},
show_settings: true,
preview: Preview {
- flags: [],
+ flags: [
+ Pylock,
+ ],
},
python_preference: Managed,
python_downloads: Automatic,
...
}
----- stderr -----
+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
...
"
);
pyproject.write_str(indoc::indoc! {r#"
[tool.uv]
preview-features = ["unknown-preview-feature"]
"#})?;
uv_config.write_str("preview = false")?;
// The warning should name the ignored `preview-features` setting without warning about its
// unknown feature name.
// Compare against the inverse spelling combination.
diff_uv_snapshot!(
context.filters(),
&preview_masked,
add_shared_args(context.version()).arg("--show-settings"),
@"
...
},
show_settings: true,
preview: Preview {
- flags: [
- Pylock,
- ],
+ flags: [],
},
python_preference: Managed,
python_downloads: Automatic,
...
----- stderr -----
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
+- preview-features
...
"
);
Ok(())
}
/// Tests that errors when parsing `conflicts` are reported.
#[test]
fn invalid_conflicts() -> anyhow::Result<()> {
@@ -2404,7 +2493,7 @@ fn resolve_config_file() -> anyhow::Result<()> {
|
1 | [project]
| ^^^^^^^
unknown field `project`, expected one of `required-version`, `system-certs`, `native-tls`, `offline`, `no-cache`, `cache-dir`, `preview`, `python-preference`, `python-downloads`, `concurrent-downloads`, `concurrent-builds`, `concurrent-installs`, `index`, `index-url`, `extra-index-url`, `no-index`, `find-links`, `index-strategy`, `keyring-provider`, `http-proxy`, `https-proxy`, `no-proxy`, `allow-insecure-host`, `resolution`, `prerelease`, `fork-strategy`, `dependency-metadata`, `config-settings`, `config-settings-package`, `no-build-isolation`, `no-build-isolation-package`, `extra-build-dependencies`, `extra-build-variables`, `exclude-newer`, `exclude-newer-package`, `link-mode`, `compile-bytecode`, `no-sources`, `no-sources-package`, `upgrade`, `upgrade-package`, `reinstall`, `reinstall-package`, `no-build`, `no-build-package`, `no-binary`, `no-binary-package`, `torch-backend`, `python-install-mirror`, `pypy-install-mirror`, `python-downloads-json-url`, `publish-url`, `trusted-publishing`, `check-url`, `add-bounds`, `audit`, `pip`, `cache-keys`, `override-dependencies`, `exclude-dependencies`, `constraint-dependencies`, `build-constraint-dependencies`, `environments`, `required-environments`, `conflicts`, `workspace`, `sources`, `managed`, `package`, `default-groups`, `dependency-groups`, `dev-dependencies`, `build-backend`
unknown field `project`, expected one of `required-version`, `system-certs`, `native-tls`, `offline`, `no-cache`, `cache-dir`, `preview`, `preview-features`, `python-preference`, `python-downloads`, `concurrent-downloads`, `concurrent-builds`, `concurrent-installs`, `index`, `index-url`, `extra-index-url`, `no-index`, `find-links`, `index-strategy`, `keyring-provider`, `http-proxy`, `https-proxy`, `no-proxy`, `allow-insecure-host`, `resolution`, `prerelease`, `fork-strategy`, `dependency-metadata`, `config-settings`, `config-settings-package`, `no-build-isolation`, `no-build-isolation-package`, `extra-build-dependencies`, `extra-build-variables`, `exclude-newer`, `exclude-newer-package`, `link-mode`, `compile-bytecode`, `no-sources`, `no-sources-package`, `upgrade`, `upgrade-package`, `reinstall`, `reinstall-package`, `no-build`, `no-build-package`, `no-binary`, `no-binary-package`, `torch-backend`, `python-install-mirror`, `pypy-install-mirror`, `python-downloads-json-url`, `publish-url`, `trusted-publishing`, `check-url`, `add-bounds`, `audit`, `pip`, `cache-keys`, `override-dependencies`, `exclude-dependencies`, `constraint-dependencies`, `build-constraint-dependencies`, `environments`, `required-environments`, `conflicts`, `workspace`, `sources`, `managed`, `package`, `default-groups`, `dependency-groups`, `dev-dependencies`, `build-backend`
"
);
@@ -3088,6 +3177,727 @@ fn preview_features() {
);
}
/// Test preview precedence across configuration, environment, and CLI settings.
#[test]
#[cfg_attr(
windows,
ignore = "Configuration tests are not yet supported on Windows"
)]
fn preview_precedence() -> anyhow::Result<()> {
let context = uv_test::test_context!("3.12");
let show_settings = || {
let mut cmd = context.version();
cmd.arg("--show-settings");
add_shared_args(cmd)
};
let disabled = capture_uv_snapshot!(context.filters(), show_settings());
let enabled = capture_uv_snapshot!(context.filters(), show_settings().arg("--preview"));
let user_config_dir = context.user_config_dir.child("uv");
user_config_dir.create_dir_all()?;
let user_config = user_config_dir.child("uv.toml");
user_config.write_str("preview = true")?;
// `preview = true` in user `uv.toml` should be the same as passing `--preview`.
diff_uv_snapshot!(
context.filters(),
&enabled,
show_settings(),
@""
);
let project_config = context.temp_dir.child("pyproject.toml");
project_config.write_str(indoc::indoc! {r"
[tool.uv]
preview = false
"})?;
// `preview = false` in `pyproject.toml` should mask user `uv.toml`.
diff_uv_snapshot!(
context.filters(),
&disabled,
show_settings(),
@""
);
// `--preview-features` should mask `preview = false` in a config file.
diff_uv_snapshot!(
context.filters(),
&disabled,
show_settings().arg("--preview-features").arg("pylock"),
@"
...
},
show_settings: true,
preview: Preview {
- flags: [],
+ flags: [
+ Pylock,
+ ],
},
python_preference: Managed,
python_downloads: Automatic,
...
"
);
user_config.write_str("preview = false")?;
project_config.write_str(indoc::indoc! {r"
[tool.uv]
preview = true
"})?;
// `preview = true` in a config file should mask `--preview-features`.
diff_uv_snapshot!(
context.filters(),
&enabled,
show_settings().arg("--preview-features").arg("pylock"),
@""
);
// `UV_PREVIEW=false` should fall through to configuration.
// Compare against `--preview`.
diff_uv_snapshot!(
context.filters(),
&enabled,
show_settings().env(EnvVars::UV_PREVIEW, "0"),
@""
);
project_config.write_str("")?;
user_config.write_str("")?;
// `UV_PREVIEW=true` should override any explicit CLI feature list.
diff_uv_snapshot!(
context.filters(),
&enabled,
show_settings()
.arg("--preview-features")
.arg("pylock")
.env(EnvVars::UV_PREVIEW, "1"),
@""
);
// `--no-preview` should override `UV_PREVIEW=true`.
diff_uv_snapshot!(
context.filters(),
&disabled,
show_settings()
.arg("--no-preview")
.env(EnvVars::UV_PREVIEW, "1"),
@""
);
user_config.write_str("preview = true")?;
project_config.write_str(indoc::indoc! {r#"
[tool.uv]
preview-features = ["format-command"]
"#})?;
// The project setting atomically overrides the differently-spelled user setting.
// Compare against the disabled baseline.
let project = diff_uv_snapshot!(
context.filters(),
&disabled,
show_settings(),
@"
...
},
show_settings: true,
preview: Preview {
- flags: [],
+ flags: [
+ Format,
+ ],
},
python_preference: Managed,
python_downloads: Automatic,
...
"
);
user_config.write_str(r#"preview-features = ["unknown-preview-feature"]"#)?;
// An unknown lower-priority setting is masked without warning.
// Compare against the recognized project setting.
diff_uv_snapshot!(
context.filters(),
&project,
show_settings(),
@""
);
user_config.write_str("preview = true")?;
project_config.write_str(indoc::indoc! {r"
[tool.uv]
preview-features = false
"})?;
// An explicit disabled project setting atomically overrides the enabled user setting.
// Compare against the disabled baseline.
diff_uv_snapshot!(
context.filters(),
&disabled,
show_settings(),
@""
);
project_config.write_str(indoc::indoc! {r#"
[tool.uv]
preview-features = ["format-command"]
"#})?;
// An explicit CLI feature list should override the project feature list.
// Compare against the project feature list.
let cli_features = diff_uv_snapshot!(
context.filters(),
&project,
show_settings().arg("--preview-features").arg("pylock"),
@"
...
show_settings: true,
preview: Preview {
flags: [
- Format,
+ Pylock,
],
},
python_preference: Managed,
...
"
);
project_config.write_str(indoc::indoc! {r"
[tool.uv]
preview-features = false
"})?;
// An explicit CLI feature list should override the disabled project setting.
// Compare against the explicit CLI feature list.
diff_uv_snapshot!(
context.filters(),
&cli_features,
show_settings().arg("--preview-features").arg("pylock"),
@""
);
project_config.write_str(indoc::indoc! {r#"
[tool.uv]
preview-features = ["unknown-preview-feature"]
"#})?;
// An unknown-only project setting atomically overrides the enabled user setting.
// Compare against the disabled baseline.
diff_uv_snapshot!(
context.filters(),
&disabled,
show_settings(),
@"
...
}
----- stderr -----
+warning: Unknown preview feature: `unknown-preview-feature`
...
"
);
// An explicit CLI setting masks the unknown project setting without warning.
// Compare against the explicit CLI feature list.
diff_uv_snapshot!(
context.filters(),
&cli_features,
show_settings().arg("--preview-features").arg("pylock"),
@""
);
// `--preview` masks the unknown project setting without warning.
// Compare against `--preview`.
diff_uv_snapshot!(
context.filters(),
&enabled,
show_settings().arg("--preview"),
@""
);
project_config.write_str(indoc::indoc! {r"
[tool.uv]
preview-features = true
"})?;
// Both enable-all spellings have the same precedence.
// Compare against `--preview`.
diff_uv_snapshot!(
context.filters(),
&enabled,
show_settings().arg("--preview-features").arg("pylock"),
@""
);
Ok(())
}
/// Test `preview` and `preview-features` parsing in `uv.toml`.
#[test]
#[cfg_attr(
windows,
ignore = "Configuration tests are not yet supported on Windows"
)]
fn preview_features_uv_toml() -> anyhow::Result<()> {
let context = uv_test::test_context!("3.12");
let config = context.temp_dir.child("uv.toml");
let enabled = capture_uv_snapshot!(
context.filters(),
add_shared_args(context.version())
.arg("--show-settings")
.arg("--preview")
);
let disabled = capture_uv_snapshot!(
context.filters(),
add_shared_args(context.version()).arg("--show-settings")
);
config.write_str(r#"preview-features = ["format-command"]"#)?;
// A feature list should enable only the named preview features.
// Compare against the disabled baseline.
diff_uv_snapshot!(
context.filters(),
&disabled,
add_shared_args(context.version()).arg("--show-settings"),
@"
...
},
show_settings: true,
preview: Preview {
- flags: [],
+ flags: [
+ Format,
+ ],
},
python_preference: Managed,
python_downloads: Automatic,
...
"
);
config.write_str("preview-features = true")?;
// `preview-features = true` should enable all preview features.
// Compare against `--preview`.
diff_uv_snapshot!(
context.filters(),
&enabled,
add_shared_args(context.version()).arg("--show-settings"),
@""
);
config.write_str("preview-features = false")?;
// `preview-features = false` should be equivalent to `preview = false`.
// Compare against the disabled baseline.
diff_uv_snapshot!(
context.filters(),
&disabled,
add_shared_args(context.version()).arg("--show-settings"),
@""
);
config.write_str("preview-features = []")?;
// An empty feature list should not enable any preview features.
// Compare against the disabled baseline.
diff_uv_snapshot!(
context.filters(),
&disabled,
add_shared_args(context.version()).arg("--show-settings"),
@""
);
config.write_str(indoc::indoc! {r#"
preview = true
preview-features = ["format-command"]
"#})?;
// The two settings should be rejected when used together.
uv_snapshot!(context.filters(), add_shared_args(context.version()).arg("--show-settings"), @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: Failed to parse: `uv.toml`
Caused by: cannot specify both `preview` and `preview-features`
");
config.write_str(r#"preview-features = ["unknown-preview-feature"]"#)?;
// Unknown names should warn without enabling any recognized preview features.
// Compare against the disabled baseline.
diff_uv_snapshot!(
context.filters(),
&disabled,
add_shared_args(context.version()).arg("--show-settings"),
@"
...
}
----- stderr -----
+warning: Unknown preview feature: `unknown-preview-feature`
...
"
);
config.write_str(r#"preview-features = [" "]"#)?;
// Empty preview feature names should be rejected.
uv_snapshot!(context.filters(), add_shared_args(context.version()).arg("--show-settings"), @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: Failed to parse: `uv.toml`
Caused by: TOML parse error at line 1, column 20
|
1 | preview-features = [\" \"]
| ^^^^^^
preview feature name cannot be empty
");
config.write_str("preview-features = 123")?;
// Invalid preview feature types should be rejected.
uv_snapshot!(context.filters(), add_shared_args(context.version()).arg("--show-settings"), @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: Failed to parse: `uv.toml`
Caused by: TOML parse error at line 1, column 20
|
1 | preview-features = 123
| ^^^
invalid type: integer `123`, expected a boolean or a list of preview feature names
");
Ok(())
}
/// Test preview setting discovery and diagnostics in `pyproject.toml`.
#[test]
#[cfg_attr(
windows,
ignore = "Configuration tests are not yet supported on Windows"
)]
fn preview_features_pyproject_toml() -> anyhow::Result<()> {
let context = uv_test::test_context!("3.12");
let pyproject = context.temp_dir.child("pyproject.toml");
let baseline = capture_uv_snapshot!(
context.filters(),
add_shared_args(context.version()).arg("--show-settings")
);
pyproject.write_str(indoc::indoc! {r#"
[tool.uv]
preview-features = ["format-command"]
"#})?;
// A feature list should enable only the named preview features.
// Compare against the baseline.
diff_uv_snapshot!(
context.filters(),
&baseline,
add_shared_args(context.version()).arg("--show-settings"),
@"
...
},
show_settings: true,
preview: Preview {
- flags: [],
+ flags: [
+ Format,
+ ],
},
python_preference: Managed,
python_downloads: Automatic,
...
"
);
pyproject.write_str(indoc::indoc! {r#"
[tool.uv]
preview = true
preview-features = ["format-command"]
"#})?;
// Conflicting preview settings should be ignored during settings discovery with a warning.
// Compare against the baseline.
diff_uv_snapshot!(
context.filters(),
&baseline,
add_shared_args(context.version()).arg("--show-settings"),
@"
...
}
----- stderr -----
+warning: Failed to parse `pyproject.toml` during settings discovery:
+ TOML parse error at line 1, column 1
+ |
+ 1 | [tool.uv]
+ | ^^^^^^^^^
+ cannot specify both `preview` and `preview-features`
+
...
"
);
pyproject.write_str(indoc::indoc! {r"
[tool.uv]
preview-features = 123
"})?;
// Invalid preview feature types should be ignored during settings discovery with a warning.
// Compare against the baseline.
diff_uv_snapshot!(
context.filters(),
&baseline,
add_shared_args(context.version()).arg("--show-settings"),
@"
...
}
----- stderr -----
+warning: Failed to parse `pyproject.toml` during settings discovery:
+ TOML parse error at line 2, column 20
+ |
+ 2 | preview-features = 123
+ | ^^^
+ invalid type: integer `123`, expected a boolean or a list of preview feature names
+
...
"
);
Ok(())
}
/// Test PEP 723-specific preview parsing, precedence, and diagnostics.
#[test]
#[cfg_attr(
windows,
ignore = "Configuration tests are not yet supported on Windows"
)]
fn run_pep723_script_preview_features() -> anyhow::Result<()> {
let context = uv_test::test_context!("3.12");
let show_settings = || {
let mut cmd = context.run();
cmd.arg("--show-settings").arg("main.py");
cmd
};
let test_script = context.temp_dir.child("main.py");
test_script.write_str(indoc::indoc! { r#"
# /// script
# requires-python = ">=3.11"
# dependencies = []
# ///
print("hello")
"#
})?;
let baseline = capture_uv_snapshot!(context.filters(), show_settings());
let enabled = capture_uv_snapshot!(
context.filters(),
context
.run()
.arg("--show-settings")
.arg("--preview")
.arg("main.py")
);
test_script.write_str(indoc::indoc! { r#"
# /// script
# requires-python = ">=3.11"
# dependencies = []
#
# [tool.uv]
# preview-features = ["format-command"]
# ///
print("hello")
"#
})?;
let preview_features = diff_uv_snapshot!(
context.filters(),
&baseline,
show_settings(),
@"
...
},
show_settings: true,
preview: Preview {
- flags: [],
+ flags: [
+ Format,
+ ],
},
python_preference: Managed,
python_downloads: Never,
...
"
);
test_script.write_str(indoc::indoc! { r#"
# /// script
# requires-python = ">=3.11"
# dependencies = []
#
# [tool.uv]
# preview-features = ["format-command", "unknown-preview-feature"]
# ///
print("hello")
"#
})?;
// Unknown names should warn without changing the recognized feature set.
// Compare against the recognized feature list to isolate the warning.
diff_uv_snapshot!(
context.filters(),
&preview_features,
show_settings(),
@"
...
}
----- stderr -----
+warning: Unknown preview feature: `unknown-preview-feature`
...
"
);
test_script.write_str(indoc::indoc! { r#"
# /// script
# requires-python = ">=3.11"
# dependencies = []
#
# [tool.uv]
# preview = true
# ///
print("hello")
"#
})?;
// The legacy `preview = true` setting should enable all preview features.
// Compare against `--preview`.
diff_uv_snapshot!(
context.filters(),
&enabled,
show_settings(),
@""
);
test_script.write_str(indoc::indoc! { r#"
# /// script
# requires-python = ">=3.11"
# dependencies = []
#
# [tool.uv]
# preview-features = [123]
# ///
print("hello")
"#
})?;
// The diagnostic should reject a non-string preview feature name.
uv_snapshot!(context.filters(), show_settings(), @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: TOML parse error at line 4, column 1
|
4 | [tool.uv]
| ^^^^^^^^^
invalid type: integer `123`, expected a string
");
context
.temp_dir
.child("pyproject.toml")
.write_str(indoc::indoc! {r"
[tool.uv]
preview = true
"})?;
test_script.write_str(indoc::indoc! { r#"
# /// script
# requires-python = ">=3.11"
# dependencies = []
#
# [tool.uv]
# preview-features = false
# ///
print("hello")
"#
})?;
// The script setting should atomically override the project setting.
assert_eq!(
baseline,
capture_uv_snapshot!(context.filters(), show_settings())
);
test_script.write_str(indoc::indoc! { r#"
# /// script
# requires-python = ">=3.11"
# dependencies = []
#
# [tool.uv]
# preview = true
# preview-features = ["format-command"]
# ///
print("hello")
"#
})?;
// The two settings should be rejected when used together.
uv_snapshot!(context.filters(), show_settings(), @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: TOML parse error at line 4, column 1
|
4 | [tool.uv]
| ^^^^^^^^^
cannot specify both `preview` and `preview-features`
");
Ok(())
}
#[test]
#[cfg_attr(
windows,
+20 -1
View File
@@ -41,7 +41,20 @@ The `UV_PREVIEW_FEATURES` environment variable can be used similarly, e.g.:
$ UV_PREVIEW_FEATURES=foo,bar uv run ...
```
For backwards compatibility, enabling preview features that do not exist will warn, but not error.
Preview features can also be enabled in `uv.toml`, or under `[tool.uv]` in `pyproject.toml` and PEP
723 metadata:
```toml
preview-features = ["foo", "bar"]
```
Set `preview-features = true` to enable all preview features.
Some preview features take effect before configuration files are loaded and cannot be enabled from
configuration.
For backwards compatibility, enabling preview features that do not exist will warn, but not error,
regardless of the source.
## Using preview features
@@ -71,6 +84,12 @@ The following preview features are available:
- `workspace-metadata`: Allows using `uv workspace metadata`.
- `workspace-dir`: Allows using `uv workspace dir`.
- `workspace-list`: Allows using `uv workspace list`.
- `target-workspace-discovery`: Uses the directory containing a local `uv run` target, rather than
the current working directory, as the starting point for project and workspace discovery. This
feature takes effect before configuration is loaded.
- `project-directory-must-exist`: Rejects an invalid `--project` path instead of warning and
continuing. Except for `uv init`, the path must already exist as a directory or point to a
`pyproject.toml` file. This feature takes effect before configuration is loaded.
- `malware-check`: Allows `uv sync` and other commands to check for malware using
[OSV](https://osv.dev) before installing packages.
+78 -2
View File
@@ -432,8 +432,20 @@
]
},
"preview": {
"description": "Whether to enable experimental, preview features.",
"type": ["boolean", "null"]
"description": "Whether to enable all experimental, preview features.\n\nUse `preview-features` instead.",
"type": ["boolean", "null"],
"deprecated": true
},
"preview-features": {
"description": "Whether to enable specific or all experimental preview features.\n\nUnknown feature names are ignored with a warning.",
"anyOf": [
{
"$ref": "#/definitions/PreviewFeaturesOption"
},
{
"type": "null"
}
]
},
"publish-url": {
"description": "The URL for publishing packages to the Python package index (by default:\n<https://upload.pypi.org/legacy/>).",
@@ -581,6 +593,9 @@
}
},
"additionalProperties": false,
"not": {
"required": ["preview", "preview-features"]
},
"definitions": {
"AddBoundsKind": {
"description": "The default version specifier when adding a dependency.",
@@ -1619,6 +1634,67 @@
}
]
},
"PreviewFeature": {
"type": "string",
"anyOf": [
{
"enum": [
"python-install-default",
"python-upgrade",
"json-output",
"pylock",
"add-bounds",
"package-conflicts",
"extra-build-dependencies",
"detect-module-conflicts",
"format-command",
"native-auth",
"s3-endpoint",
"cache-size",
"init-project-flag",
"workspace-metadata",
"workspace-dir",
"workspace-list",
"sbom-export",
"auth-helper",
"direct-publish",
"target-workspace-discovery",
"metadata-json",
"gcs-endpoint",
"adjust-ulimit",
"special-conda-env-names",
"relocatable-envs-default",
"publish-require-normalized",
"audit-command",
"project-directory-must-exist",
"index-exclude-newer",
"azure-endpoint",
"toml-backwards-compatibility",
"malware-check",
"venv-safe-clear",
"check-command",
"packaged-init"
]
},
{
"pattern": "\\S"
}
]
},
"PreviewFeaturesOption": {
"description": "Represents the `preview-features` configuration option.",
"anyOf": [
{
"type": "boolean"
},
{
"type": "array",
"items": {
"$ref": "#/definitions/PreviewFeature"
}
}
]
},
"ProxyUrl": {
"description": "A proxy URL (e.g., `http://proxy.example.com:8080`).",
"type": "string",