mirror of
https://github.com/astral-sh/uv.git
synced 2025-12-23 09:19:48 +00:00
Merge 7b2c211726 into 7865672918
This commit is contained in:
commit
8e192c323c
10 changed files with 1198 additions and 37 deletions
5
Cargo.lock
generated
5
Cargo.lock
generated
|
|
@ -6454,6 +6454,10 @@ name = "uv-preview"
|
|||
version = "0.0.8"
|
||||
dependencies = [
|
||||
"bitflags 2.9.4",
|
||||
"insta",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.17",
|
||||
"uv-warnings",
|
||||
]
|
||||
|
|
@ -6787,6 +6791,7 @@ dependencies = [
|
|||
"uv-normalize",
|
||||
"uv-options-metadata",
|
||||
"uv-pep508",
|
||||
"uv-preview",
|
||||
"uv-pypi-types",
|
||||
"uv-python",
|
||||
"uv-redacted",
|
||||
|
|
|
|||
|
|
@ -19,9 +19,13 @@ workspace = true
|
|||
uv-warnings = { workspace = true }
|
||||
|
||||
bitflags = { workspace = true }
|
||||
schemars = { workspace = true, optional = true }
|
||||
serde = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
insta = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
#[cfg(feature = "schemars")]
|
||||
use std::borrow::Cow;
|
||||
use std::{
|
||||
fmt::{Display, Formatter},
|
||||
str::FromStr,
|
||||
|
|
@ -70,6 +72,67 @@ 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,
|
||||
{
|
||||
if *self == Self::default() {
|
||||
return Err(serde::ser::Error::custom(
|
||||
"Cannot serialize disabled feature flags.",
|
||||
));
|
||||
}
|
||||
if self.bits().count_ones() > 1 {
|
||||
return Err(serde::ser::Error::custom(
|
||||
"More than one preview feature flag enabled. \
|
||||
Only individual flags should be serialized at a time.",
|
||||
));
|
||||
}
|
||||
serializer.serialize_str(self.flag_as_str())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error, Clone)]
|
||||
pub enum PreviewFeaturesParseError {
|
||||
#[error("Empty string in preview features: {0}")]
|
||||
|
|
@ -135,33 +198,27 @@ 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<'flag> FromIterator<&'flag PreviewFeatures> for Preview {
|
||||
fn from_iter<T: IntoIterator<Item = &'flag PreviewFeatures>>(iter: T) -> Self {
|
||||
let flags = iter
|
||||
.into_iter()
|
||||
.copied()
|
||||
.fold(PreviewFeatures::empty(), |f1, f2| f1 | f2);
|
||||
Self::new(flags)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for Preview {
|
||||
fn from(value: bool) -> Self {
|
||||
if value { Self::all() } else { Self::default() }
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Preview {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
if self.flags.is_empty() {
|
||||
|
|
@ -239,19 +296,20 @@ mod tests {
|
|||
#[test]
|
||||
fn test_preview_from_args() {
|
||||
// Test no_preview
|
||||
let preview = Preview::from_args(true, true, &[]);
|
||||
let preview = Preview::default();
|
||||
assert_eq!(preview.to_string(), "disabled");
|
||||
|
||||
// Test preview (all features)
|
||||
let preview = Preview::from_args(true, false, &[]);
|
||||
let preview = Preview::all();
|
||||
assert_eq!(preview.to_string(), "enabled");
|
||||
|
||||
// Test specific features
|
||||
let features = vec![
|
||||
let preview: Preview = [
|
||||
PreviewFeatures::PYTHON_UPGRADE,
|
||||
PreviewFeatures::JSON_OUTPUT,
|
||||
];
|
||||
let preview = Preview::from_args(false, false, &features);
|
||||
]
|
||||
.iter()
|
||||
.collect();
|
||||
assert!(preview.is_enabled(PreviewFeatures::PYTHON_UPGRADE));
|
||||
assert!(preview.is_enabled(PreviewFeatures::JSON_OUTPUT));
|
||||
assert!(!preview.is_enabled(PreviewFeatures::PYLOCK));
|
||||
|
|
@ -293,4 +351,27 @@ mod tests {
|
|||
let features = PreviewFeatures::PYTHON_UPGRADE | PreviewFeatures::JSON_OUTPUT;
|
||||
let _ = features.flag_as_str();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_serde_roundtrip() {
|
||||
let input = r#"["python-upgrade", "format"]"#;
|
||||
|
||||
let deserialized: Vec<PreviewFeatures> = serde_json::from_str(input).unwrap();
|
||||
assert_eq!(deserialized.len(), 2);
|
||||
assert_eq!(deserialized[0], PreviewFeatures::PYTHON_UPGRADE);
|
||||
assert_eq!(deserialized[1], PreviewFeatures::FORMAT);
|
||||
|
||||
let serialized = serde_json::to_string(&deserialized).unwrap();
|
||||
insta::assert_snapshot!(serialized, @r#"["python-upgrade","format"]"#);
|
||||
|
||||
let roundtrip: Vec<PreviewFeatures> = serde_json::from_str(&serialized).unwrap();
|
||||
assert_eq!(roundtrip, deserialized);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Cannot serialize disabled feature flags.")]
|
||||
fn test_serialize_default() {
|
||||
let disabled = PreviewFeatures::default();
|
||||
let _ = serde_json::to_string(&disabled).unwrap();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
@ -390,6 +391,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");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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",
|
||||
|
|
@ -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(
|
||||
|
|
@ -2090,6 +2100,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>,
|
||||
|
|
@ -2185,6 +2196,7 @@ impl From<OptionsWire> for Options {
|
|||
no_cache,
|
||||
cache_dir,
|
||||
preview,
|
||||
preview_features,
|
||||
python_preference,
|
||||
python_downloads,
|
||||
python_install_mirror,
|
||||
|
|
@ -2257,6 +2269,7 @@ impl From<OptionsWire> for Options {
|
|||
no_cache,
|
||||
cache_dir,
|
||||
preview,
|
||||
preview_features,
|
||||
python_preference,
|
||||
python_downloads,
|
||||
concurrent_downloads,
|
||||
|
|
|
|||
|
|
@ -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: resolve_preview_settings(args, workspace),
|
||||
python_preference,
|
||||
python_downloads: flag(
|
||||
args.allow_python_downloads,
|
||||
|
|
@ -179,6 +173,28 @@ fn resolve_python_preference(
|
|||
}
|
||||
}
|
||||
|
||||
fn resolve_preview_settings(args: &GlobalArgs, workspace: Option<&FilesystemOptions>) -> Preview {
|
||||
// Commandline arguments take priority.
|
||||
if let Some(preview) = flag(args.preview, args.no_preview, "preview") {
|
||||
return Preview::from(preview);
|
||||
}
|
||||
if !args.preview_features.is_empty() {
|
||||
return Preview::from_iter(&args.preview_features);
|
||||
}
|
||||
|
||||
workspace
|
||||
.and_then(|workspace| {
|
||||
workspace.globals.preview.map(Preview::from).or_else(|| {
|
||||
workspace
|
||||
.globals
|
||||
.preview_features
|
||||
.as_ref()
|
||||
.map(Preview::from_iter)
|
||||
})
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// The resolved network settings to use for any invocation of the CLI.
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct NetworkSettings {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
32
uv.schema.json
generated
32
uv.schema.json
generated
|
|
@ -385,9 +385,16 @@
|
|||
]
|
||||
},
|
||||
"preview": {
|
||||
"description": "Whether to enable experimental, preview features.",
|
||||
"description": "Whether to enable all experimental, preview features.",
|
||||
"type": ["boolean", "null"]
|
||||
},
|
||||
"preview-features": {
|
||||
"description": "Whether to enable specific experimental, preview features.",
|
||||
"type": ["array", "null"],
|
||||
"items": {
|
||||
"$ref": "#/definitions/PreviewFeatures"
|
||||
}
|
||||
},
|
||||
"publish-url": {
|
||||
"description": "The URL for publishing packages to the Python package index (by default:\n<https://upload.pypi.org/legacy/>).",
|
||||
"anyOf": [
|
||||
|
|
@ -1521,6 +1528,29 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"PreviewFeatures": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"python-install-default",
|
||||
"python-upgrade",
|
||||
"json-output",
|
||||
"pylock",
|
||||
"add-bounds",
|
||||
"package-conflicts",
|
||||
"extra-build-dependencies",
|
||||
"detect-module-conflicts",
|
||||
"format",
|
||||
"native-auth",
|
||||
"s3-endpoint",
|
||||
"cache-size",
|
||||
"init-project-flag",
|
||||
"workspace-metadata",
|
||||
"workspace-dir",
|
||||
"workspace-list",
|
||||
"sbom-export",
|
||||
"auth-helper"
|
||||
]
|
||||
},
|
||||
"PythonDownloads": {
|
||||
"oneOf": [
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue