Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions crates/uv-preview/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ workspace = true
uv-warnings = { workspace = true }

bitflags = { workspace = true }
schemars = { workspace = true, optional = true }
serde = { workspace = true }
thiserror = { workspace = true }

[dev-dependencies]
Expand Down
123 changes: 94 additions & 29 deletions crates/uv-preview/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#[cfg(feature = "schemars")]
use std::borrow::Cow;
use std::{
fmt::{Display, Formatter},
str::FromStr,
Expand Down Expand Up @@ -70,6 +72,57 @@ impl Display for PreviewFeatures {
}
}

#[cfg(feature = "schemars")]
impl schemars::JsonSchema for PreviewFeatures {
fn schema_name() -> Cow<'static, str> {
Cow::Borrowed("PreviewFeatures")
}

fn json_schema(_generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
let choices: Vec<&str> = Self::all().iter().map(Self::flag_as_str).collect();
schemars::json_schema!({
"type": "string",
"enum": choices,
})
}
}

impl<'de> serde::Deserialize<'de> for PreviewFeatures {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct Visitor;

impl serde::de::Visitor<'_> for Visitor {
type Value = PreviewFeatures;

fn expecting(&self, f: &mut Formatter) -> std::fmt::Result {
f.write_str("a string")
}

fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
PreviewFeatures::from_str(v).map_err(serde::de::Error::custom)
}
}

deserializer.deserialize_str(Visitor)
}
}

impl serde::Serialize for PreviewFeatures {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let features: Vec<&str> = self.iter().map(Self::flag_as_str).collect();
features.serialize(serializer)
Comment thread
j-helland marked this conversation as resolved.
Outdated
}
}

#[derive(Debug, Error, Clone)]
pub enum PreviewFeaturesParseError {
#[error("Empty string in preview features: {0}")]
Expand Down Expand Up @@ -121,6 +174,28 @@ impl FromStr for PreviewFeatures {
}
}

pub enum PreviewFeaturesMode {
EnableAll,
DisableAll,
Selection(PreviewFeatures),
}
Comment thread
j-helland marked this conversation as resolved.
Outdated

impl PreviewFeaturesMode {
pub fn from_bool(b: bool) -> Self {
if b { Self::EnableAll } else { Self::DisableAll }
}
}

impl<'a, I> From<I> for PreviewFeaturesMode
where
I: Iterator<Item = &'a PreviewFeatures>,
{
fn from(features: I) -> Self {
let flags = features.fold(PreviewFeatures::empty(), |f1, f2| f1 | *f2);
Self::Selection(flags)
}
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Preview {
flags: PreviewFeatures,
Expand All @@ -135,33 +210,21 @@ impl Preview {
Self::new(PreviewFeatures::all())
}

pub fn from_args(
preview: bool,
no_preview: bool,
preview_features: &[PreviewFeatures],
) -> Self {
if no_preview {
return Self::default();
}

if preview {
return Self::all();
}

let mut flags = PreviewFeatures::empty();

for features in preview_features {
flags |= *features;
}

Self { flags }
}

pub fn is_enabled(&self, flag: PreviewFeatures) -> bool {
self.flags.contains(flag)
}
}

impl From<PreviewFeaturesMode> for Preview {
fn from(mode: PreviewFeaturesMode) -> Self {
match mode {
PreviewFeaturesMode::EnableAll => Self::all(),
PreviewFeaturesMode::DisableAll => Self::default(),
PreviewFeaturesMode::Selection(flags) => Self { flags },
}
}
}

impl Display for Preview {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if self.flags.is_empty() {
Expand Down Expand Up @@ -239,19 +302,21 @@ mod tests {
#[test]
fn test_preview_from_args() {
// Test no_preview
let preview = Preview::from_args(true, true, &[]);
let preview = Preview::from(PreviewFeaturesMode::DisableAll);
assert_eq!(preview.to_string(), "disabled");

// Test preview (all features)
let preview = Preview::from_args(true, false, &[]);
let preview = Preview::from(PreviewFeaturesMode::EnableAll);
assert_eq!(preview.to_string(), "enabled");

// Test specific features
let features = vec![
PreviewFeatures::PYTHON_UPGRADE,
PreviewFeatures::JSON_OUTPUT,
];
let preview = Preview::from_args(false, false, &features);
let preview = Preview::from(PreviewFeaturesMode::from(
[
PreviewFeatures::PYTHON_UPGRADE,
PreviewFeatures::JSON_OUTPUT,
]
.iter(),
));
assert!(preview.is_enabled(PreviewFeatures::PYTHON_UPGRADE));
assert!(preview.is_enabled(PreviewFeatures::JSON_OUTPUT));
assert!(!preview.is_enabled(PreviewFeatures::PYLOCK));
Expand Down
1 change: 1 addition & 0 deletions crates/uv-settings/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ uv-macros = { workspace = true }
uv-normalize = { workspace = true, features = ["schemars"] }
uv-options-metadata = { workspace = true }
uv-pep508 = { workspace = true }
uv-preview = { workspace = true, features = ["schemars"] }
uv-pypi-types = { workspace = true }
uv-python = { workspace = true, features = ["schemars", "clap"] }
uv-redacted = { workspace = true }
Expand Down
2 changes: 2 additions & 0 deletions crates/uv-settings/src/combine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use uv_distribution_types::{
PipFindLinks, PipIndex,
};
use uv_install_wheel::LinkMode;
use uv_preview::PreviewFeatures;
use uv_pypi_types::{SchemaConflicts, SupportedEnvironments};
use uv_python::{PythonDownloads, PythonPreference, PythonVersion};
use uv_redacted::DisplaySafeUrl;
Expand Down Expand Up @@ -100,6 +101,7 @@ impl_combine_or!(PipExtraIndex);
impl_combine_or!(PipFindLinks);
impl_combine_or!(PipIndex);
impl_combine_or!(PrereleaseMode);
impl_combine_or!(PreviewFeatures);
impl_combine_or!(PythonDownloads);
impl_combine_or!(PythonPreference);
impl_combine_or!(PythonVersion);
Expand Down
4 changes: 4 additions & 0 deletions crates/uv-settings/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,7 @@ fn warn_uv_toml_masked_fields(options: &Options) {
no_cache,
cache_dir,
preview,
preview_features,
python_preference,
python_downloads,
concurrent_downloads,
Expand Down Expand Up @@ -389,6 +390,9 @@ fn warn_uv_toml_masked_fields(options: &Options) {
if preview.is_some() {
masked_fields.push("preview");
}
if preview_features.is_some() {
masked_fields.push("preview-features");
}
if python_preference.is_some() {
masked_fields.push("python-preference");
}
Expand Down
15 changes: 14 additions & 1 deletion crates/uv-settings/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use uv_install_wheel::LinkMode;
use uv_macros::{CombineOptions, OptionsMetadata};
use uv_normalize::{ExtraName, PackageName, PipGroupName};
use uv_pep508::Requirement;
use uv_preview::PreviewFeatures;
use uv_pypi_types::{SupportedEnvironments, VerbatimParsedUrl};
use uv_python::{PythonDownloads, PythonPreference, PythonVersion};
use uv_redacted::DisplaySafeUrl;
Expand Down Expand Up @@ -248,7 +249,7 @@ pub struct GlobalOptions {
"#
)]
pub cache_dir: Option<PathBuf>,
/// Whether to enable experimental, preview features.
/// Whether to enable all experimental, preview features.
#[option(
default = "false",
value_type = "bool",
Expand All @@ -257,6 +258,15 @@ pub struct GlobalOptions {
"#
)]
pub preview: Option<bool>,
/// Whether to enable specific experimental, preview features.
#[option(
default = "[]",
value_type = "list[str]",
example = r#"
preview-features = ["python-upgrade"]
"#
)]
pub preview_features: Option<Vec<PreviewFeatures>>,
/// Whether to prefer using Python installations that are already present on the system, or
/// those that are downloaded and installed by uv.
#[option(
Expand Down Expand Up @@ -2048,6 +2058,7 @@ pub struct OptionsWire {
no_cache: Option<bool>,
cache_dir: Option<PathBuf>,
preview: Option<bool>,
preview_features: Option<Vec<PreviewFeatures>>,
python_preference: Option<PythonPreference>,
python_downloads: Option<PythonDownloads>,
concurrent_downloads: Option<NonZeroUsize>,
Expand Down Expand Up @@ -2142,6 +2153,7 @@ impl From<OptionsWire> for Options {
no_cache,
cache_dir,
preview,
preview_features,
python_preference,
python_downloads,
python_install_mirror,
Expand Down Expand Up @@ -2213,6 +2225,7 @@ impl From<OptionsWire> for Options {
no_cache,
cache_dir,
preview,
preview_features,
python_preference,
python_downloads,
concurrent_downloads,
Expand Down
32 changes: 24 additions & 8 deletions crates/uv/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ use uv_distribution_types::{
use uv_install_wheel::LinkMode;
use uv_normalize::{ExtraName, PackageName, PipGroupName};
use uv_pep508::{MarkerTree, RequirementOrigin};
use uv_preview::Preview;
use uv_preview::{Preview, PreviewFeaturesMode};
use uv_pypi_types::SupportedEnvironments;
use uv_python::{Prefix, PythonDownloads, PythonPreference, PythonVersion, Target};
use uv_redacted::DisplaySafeUrl;
Expand Down Expand Up @@ -139,13 +139,7 @@ impl GlobalSettings {
.unwrap_or_else(Concurrency::threads),
},
show_settings: args.show_settings,
preview: Preview::from_args(
flag(args.preview, args.no_preview, "preview")
.combine(workspace.and_then(|workspace| workspace.globals.preview))
.unwrap_or(false),
args.no_preview,
&args.preview_features,
),
preview: Preview::from(resolve_preview_settings(args, workspace)),
python_preference,
python_downloads: flag(
args.allow_python_downloads,
Expand Down Expand Up @@ -179,6 +173,28 @@ fn resolve_python_preference(
}
}

fn resolve_preview_settings(
args: &GlobalArgs,
workspace: Option<&FilesystemOptions>,
) -> PreviewFeaturesMode {
if let Some(preview) = flag(args.preview, args.no_preview, "preview") {
return PreviewFeaturesMode::from_bool(preview);
}
match workspace.and_then(|workspace| workspace.globals.preview) {
Some(true) => PreviewFeaturesMode::EnableAll,
// Disable workspace `preview-features` but keep any CLI `--preview-features`
Some(false) => PreviewFeaturesMode::from(args.preview_features.iter()),
None => {
let features = args.preview_features.iter().chain(
workspace
.and_then(|workspace| workspace.globals.preview_features.as_deref())
.unwrap_or_default(),
);
PreviewFeaturesMode::from(features)
}
}
}

/// The resolved network settings to use for any invocation of the CLI.
#[derive(Debug, Clone)]
pub(crate) struct NetworkSettings {
Expand Down
Loading
Loading