diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index c3498965f2b91..22f4fbf9a4cf2 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -19,7 +19,9 @@ use uv_normalize::{ExtraName, GroupName, PackageName}; use uv_pep508::Requirement; use uv_pypi_types::VerbatimParsedUrl; use uv_python::{PythonDownloads, PythonPreference, PythonVersion}; -use uv_resolver::{AnnotationStyle, ExcludeNewer, PrereleaseMode, ResolutionMode}; +use uv_resolver::{ + AnnotationStyle, ExcludeNewer, MultiVersionMode, PrereleaseMode, ResolutionMode, +}; use uv_static::EnvVars; pub mod compat; @@ -4413,6 +4415,20 @@ pub struct ResolverArgs { #[arg(long, hide = true, help_heading = "Resolver options")] pub pre: bool, + /// The strategy to use when selecting multiple versions of a given package across Python + /// versions and platforms. + /// + /// By default, uv will minimize the number of versions selected for each package (`fewest`), + /// to minimize differences between environments. Under `latest`, uv will select the latest + /// compatible version for each environment, even if it results in more versions being selected. + #[arg( + long, + value_enum, + env = EnvVars::UV_MULTI_VERSION, + help_heading = "Resolver options" + )] + pub multi_version: Option, + /// Settings to pass to the PEP 517 build backend, specified as `KEY=VALUE` pairs. #[arg( long, @@ -4605,6 +4621,20 @@ pub struct ResolverInstallerArgs { #[arg(long, hide = true)] pub pre: bool, + /// The strategy to use when selecting multiple versions of a given package across Python + /// versions and platforms. + /// + /// By default, uv will minimize the number of versions selected for each package (`fewest`), + /// to minimize differences between environments. Under `latest`, uv will select the latest + /// compatible version for each environment, even if it results in more versions being selected. + #[arg( + long, + value_enum, + env = EnvVars::UV_MULTI_VERSION, + help_heading = "Resolver options" + )] + pub multi_version: Option, + /// Settings to pass to the PEP 517 build backend, specified as `KEY=VALUE` pairs. #[arg( long, diff --git a/crates/uv-cli/src/options.rs b/crates/uv-cli/src/options.rs index 4041297938567..2dbdcf0a33b9e 100644 --- a/crates/uv-cli/src/options.rs +++ b/crates/uv-cli/src/options.rs @@ -44,6 +44,7 @@ impl From for PipOptions { resolution, prerelease, pre, + multi_version, config_setting, no_build_isolation, no_build_isolation_package, @@ -65,6 +66,7 @@ impl From for PipOptions { .collect() }), resolution, + multi_version, prerelease: if pre { Some(PrereleaseMode::Allow) } else { @@ -141,6 +143,7 @@ impl From for PipOptions { resolution, prerelease, pre, + multi_version, config_setting, no_build_isolation, no_build_isolation_package, @@ -171,6 +174,7 @@ impl From for PipOptions { } else { prerelease }, + multi_version, config_settings: config_setting .map(|config_settings| config_settings.into_iter().collect::()), no_build_isolation: flag(no_build_isolation, build_isolation), @@ -239,6 +243,7 @@ pub fn resolver_options( resolution, prerelease, pre, + multi_version, config_setting, no_build_isolation, no_build_isolation_package, @@ -301,6 +306,7 @@ pub fn resolver_options( } else { prerelease }, + multi_version, dependency_metadata: None, config_settings: config_setting .map(|config_settings| config_settings.into_iter().collect::()), @@ -335,6 +341,7 @@ pub fn resolver_installer_options( resolution, prerelease, pre, + multi_version, config_setting, no_build_isolation, no_build_isolation_package, @@ -409,6 +416,7 @@ pub fn resolver_installer_options( } else { prerelease }, + multi_version, dependency_metadata: None, config_settings: config_setting .map(|config_settings| config_settings.into_iter().collect::()), diff --git a/crates/uv-resolver/src/lib.rs b/crates/uv-resolver/src/lib.rs index 008cbdd79a2a5..93e405287c2cf 100644 --- a/crates/uv-resolver/src/lib.rs +++ b/crates/uv-resolver/src/lib.rs @@ -8,6 +8,7 @@ pub use lock::{ TreeDisplay, VERSION, }; pub use manifest::Manifest; +pub use multi_version_mode::MultiVersionMode; pub use options::{Flexibility, Options, OptionsBuilder}; pub use preferences::{Preference, PreferenceError, Preferences}; pub use prerelease::PrereleaseMode; @@ -40,6 +41,7 @@ mod graph_ops; mod lock; mod manifest; mod marker; +mod multi_version_mode; mod options; mod pins; mod preferences; diff --git a/crates/uv-resolver/src/lock/mod.rs b/crates/uv-resolver/src/lock/mod.rs index 4601556643d2c..53f69d37763f1 100644 --- a/crates/uv-resolver/src/lock/mod.rs +++ b/crates/uv-resolver/src/lock/mod.rs @@ -16,6 +16,7 @@ use url::Url; pub use crate::lock::requirements_txt::RequirementsTxtExport; pub use crate::lock::tree::TreeDisplay; +use crate::multi_version_mode::MultiVersionMode; use crate::requires_python::SimplifiedMarkerTree; use crate::resolution::{AnnotatedDist, ResolutionGraphNode}; use crate::{ @@ -226,6 +227,7 @@ impl Lock { let options = ResolverOptions { resolution_mode: graph.options.resolution_mode, prerelease_mode: graph.options.prerelease_mode, + multi_version_mode: graph.options.multi_version_mode, exclude_newer: graph.options.exclude_newer, }; let lock = Self::new( @@ -529,6 +531,11 @@ impl Lock { self.options.prerelease_mode } + /// Returns the multi-version mode used to generate this lock. + pub fn multi_version_mode(&self) -> MultiVersionMode { + self.options.multi_version_mode + } + /// Returns the exclude newer setting used to generate this lock. pub fn exclude_newer(&self) -> Option { self.options.exclude_newer @@ -753,6 +760,12 @@ impl Lock { value(self.options.prerelease_mode.to_string()), ); } + if self.options.multi_version_mode != MultiVersionMode::default() { + options_table.insert( + "multi-version-mode", + value(self.options.multi_version_mode.to_string()), + ); + } if let Some(exclude_newer) = self.options.exclude_newer { options_table.insert("exclude-newer", value(exclude_newer.to_string())); } @@ -1382,6 +1395,9 @@ struct ResolverOptions { /// The [`PrereleaseMode`] used to generate this lock. #[serde(default)] prerelease_mode: PrereleaseMode, + /// The [`MultiVersionMode`] used to generate this lock. + #[serde(default)] + multi_version_mode: MultiVersionMode, /// The [`ExcludeNewer`] used to generate this lock. exclude_newer: Option, } diff --git a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__hash_optional_missing.snap b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__hash_optional_missing.snap index a17e0270042e3..d517bc01ff1e9 100644 --- a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__hash_optional_missing.snap +++ b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__hash_optional_missing.snap @@ -30,6 +30,7 @@ Ok( options: ResolverOptions { resolution_mode: Highest, prerelease_mode: IfNecessaryOrExplicit, + multi_version_mode: Fewest, exclude_newer: None, }, packages: [ diff --git a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__hash_optional_present.snap b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__hash_optional_present.snap index 65aaa58492100..554b9827e9fe4 100644 --- a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__hash_optional_present.snap +++ b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__hash_optional_present.snap @@ -30,6 +30,7 @@ Ok( options: ResolverOptions { resolution_mode: Highest, prerelease_mode: IfNecessaryOrExplicit, + multi_version_mode: Fewest, exclude_newer: None, }, packages: [ diff --git a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__hash_required_present.snap b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__hash_required_present.snap index 09dc7df1f3a84..89e648cf61933 100644 --- a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__hash_required_present.snap +++ b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__hash_required_present.snap @@ -30,6 +30,7 @@ Ok( options: ResolverOptions { resolution_mode: Highest, prerelease_mode: IfNecessaryOrExplicit, + multi_version_mode: Fewest, exclude_newer: None, }, packages: [ diff --git a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_source_unambiguous.snap b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_source_unambiguous.snap index e93ae32603421..57e8aaa3fcbe6 100644 --- a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_source_unambiguous.snap +++ b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_source_unambiguous.snap @@ -30,6 +30,7 @@ Ok( options: ResolverOptions { resolution_mode: Highest, prerelease_mode: IfNecessaryOrExplicit, + multi_version_mode: Fewest, exclude_newer: None, }, packages: [ diff --git a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_source_version_unambiguous.snap b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_source_version_unambiguous.snap index e93ae32603421..57e8aaa3fcbe6 100644 --- a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_source_version_unambiguous.snap +++ b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_source_version_unambiguous.snap @@ -30,6 +30,7 @@ Ok( options: ResolverOptions { resolution_mode: Highest, prerelease_mode: IfNecessaryOrExplicit, + multi_version_mode: Fewest, exclude_newer: None, }, packages: [ diff --git a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_version_unambiguous.snap b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_version_unambiguous.snap index e93ae32603421..57e8aaa3fcbe6 100644 --- a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_version_unambiguous.snap +++ b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_version_unambiguous.snap @@ -30,6 +30,7 @@ Ok( options: ResolverOptions { resolution_mode: Highest, prerelease_mode: IfNecessaryOrExplicit, + multi_version_mode: Fewest, exclude_newer: None, }, packages: [ diff --git a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__source_direct_has_subdir.snap b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__source_direct_has_subdir.snap index 8c7ca8d2c0be5..d8ee0514839a0 100644 --- a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__source_direct_has_subdir.snap +++ b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__source_direct_has_subdir.snap @@ -30,6 +30,7 @@ Ok( options: ResolverOptions { resolution_mode: Highest, prerelease_mode: IfNecessaryOrExplicit, + multi_version_mode: Fewest, exclude_newer: None, }, packages: [ diff --git a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__source_direct_no_subdir.snap b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__source_direct_no_subdir.snap index 04e3df0ba7033..cc9ad7e5e22cc 100644 --- a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__source_direct_no_subdir.snap +++ b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__source_direct_no_subdir.snap @@ -30,6 +30,7 @@ Ok( options: ResolverOptions { resolution_mode: Highest, prerelease_mode: IfNecessaryOrExplicit, + multi_version_mode: Fewest, exclude_newer: None, }, packages: [ diff --git a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__source_directory.snap b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__source_directory.snap index 3b0a488c45072..c3eed0b4c56c4 100644 --- a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__source_directory.snap +++ b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__source_directory.snap @@ -30,6 +30,7 @@ Ok( options: ResolverOptions { resolution_mode: Highest, prerelease_mode: IfNecessaryOrExplicit, + multi_version_mode: Fewest, exclude_newer: None, }, packages: [ diff --git a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__source_editable.snap b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__source_editable.snap index 2c8a20fd32562..14f3b78dbe2fc 100644 --- a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__source_editable.snap +++ b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__source_editable.snap @@ -30,6 +30,7 @@ Ok( options: ResolverOptions { resolution_mode: Highest, prerelease_mode: IfNecessaryOrExplicit, + multi_version_mode: Fewest, exclude_newer: None, }, packages: [ diff --git a/crates/uv-resolver/src/multi_version_mode.rs b/crates/uv-resolver/src/multi_version_mode.rs new file mode 100644 index 0000000000000..5890fb0fd821a --- /dev/null +++ b/crates/uv-resolver/src/multi_version_mode.rs @@ -0,0 +1,20 @@ +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, serde::Deserialize, serde::Serialize)] +#[serde(deny_unknown_fields, rename_all = "kebab-case")] +#[cfg_attr(feature = "clap", derive(clap::ValueEnum))] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +pub enum MultiVersionMode { + /// Resolve the highest compatible version of each package. + #[default] + Fewest, + /// Resolve the lowest compatible version of each package. + Latest, +} + +impl std::fmt::Display for MultiVersionMode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Fewest => write!(f, "fewest"), + Self::Latest => write!(f, "latest"), + } + } +} diff --git a/crates/uv-resolver/src/options.rs b/crates/uv-resolver/src/options.rs index 7f2c1010e2cbd..08b87947d9d4a 100644 --- a/crates/uv-resolver/src/options.rs +++ b/crates/uv-resolver/src/options.rs @@ -1,5 +1,6 @@ use uv_configuration::IndexStrategy; +use crate::multi_version_mode::MultiVersionMode; use crate::{DependencyMode, ExcludeNewer, PrereleaseMode, ResolutionMode}; /// Options for resolving a manifest. @@ -8,6 +9,7 @@ pub struct Options { pub resolution_mode: ResolutionMode, pub prerelease_mode: PrereleaseMode, pub dependency_mode: DependencyMode, + pub multi_version_mode: MultiVersionMode, pub exclude_newer: Option, pub index_strategy: IndexStrategy, pub flexibility: Flexibility, @@ -19,6 +21,7 @@ pub struct OptionsBuilder { resolution_mode: ResolutionMode, prerelease_mode: PrereleaseMode, dependency_mode: DependencyMode, + multi_version_mode: MultiVersionMode, exclude_newer: Option, index_strategy: IndexStrategy, flexibility: Flexibility, @@ -51,6 +54,13 @@ impl OptionsBuilder { self } + /// Sets the multi-version mode. + #[must_use] + pub fn multi_version_mode(mut self, multi_version_mode: MultiVersionMode) -> Self { + self.multi_version_mode = multi_version_mode; + self + } + /// Sets the exclusion date. #[must_use] pub fn exclude_newer(mut self, exclude_newer: Option) -> Self { @@ -78,6 +88,7 @@ impl OptionsBuilder { resolution_mode: self.resolution_mode, prerelease_mode: self.prerelease_mode, dependency_mode: self.dependency_mode, + multi_version_mode: self.multi_version_mode, exclude_newer: self.exclude_newer, index_strategy: self.index_strategy, flexibility: self.flexibility, diff --git a/crates/uv-resolver/src/resolver/mod.rs b/crates/uv-resolver/src/resolver/mod.rs index 077d4934d874b..8520826dffd2b 100644 --- a/crates/uv-resolver/src/resolver/mod.rs +++ b/crates/uv-resolver/src/resolver/mod.rs @@ -47,6 +47,7 @@ use crate::error::{NoSolutionError, ResolveError}; use crate::fork_indexes::ForkIndexes; use crate::fork_urls::ForkUrls; use crate::manifest::Manifest; +use crate::multi_version_mode::MultiVersionMode; use crate::pins::FilePins; use crate::preferences::Preferences; use crate::pubgrub::{ @@ -374,13 +375,15 @@ impl ResolverState>, pub resolution: Option, pub prerelease: Option, + pub multi_version: Option, pub dependency_metadata: Option>, pub config_settings: Option, pub exclude_newer: Option, @@ -456,6 +459,21 @@ pub struct ResolverInstallerOptions { possible_values = true )] pub prerelease: Option, + /// The strategy to use when selecting multiple versions of a given package across Python + /// versions and platforms. + /// + /// By default, uv will minimize the number of versions selected for each package (`fewest`), + /// to minimize differences between environments. Under `latest`, uv will select the latest + /// compatible version for each environment, even if it results in more versions being selected. + #[option( + default = "\"fewest\"", + value_type = "str", + example = r#" + resolution = "latest" + "#, + possible_values = true + )] + pub multi_version: Option, /// Pre-defined static metadata for dependencies of the project (direct or transitive). When /// provided, enables the resolver to use the specified metadata instead of querying the /// registry or building the relevant package from source. @@ -990,6 +1008,21 @@ pub struct PipOptions { possible_values = true )] pub prerelease: Option, + /// The strategy to use when selecting multiple versions of a given package across Python + /// versions and platforms. + /// + /// By default, uv will minimize the number of versions selected for each package (`fewest`), + /// to minimize differences between environments. Under `latest`, uv will select the latest + /// compatible version for each environment, even if it results in more versions being selected. + #[option( + default = "\"fewest\"", + value_type = "str", + example = r#" + resolution = "latest" + "#, + possible_values = true + )] + pub multi_version: Option, /// Pre-defined static metadata for dependencies of the project (direct or transitive). When /// provided, enables the resolver to use the specified metadata instead of querying the /// registry or building the relevant package from source. @@ -1355,6 +1388,7 @@ impl From for ResolverOptions { allow_insecure_host: value.allow_insecure_host, resolution: value.resolution, prerelease: value.prerelease, + multi_version: value.multi_version, dependency_metadata: value.dependency_metadata, config_settings: value.config_settings, exclude_newer: value.exclude_newer, @@ -1419,6 +1453,7 @@ pub struct ToolOptions { pub allow_insecure_host: Option>, pub resolution: Option, pub prerelease: Option, + pub multi_version: Option, pub dependency_metadata: Option>, pub config_settings: Option, pub no_build_isolation: Option, @@ -1446,6 +1481,7 @@ impl From for ToolOptions { allow_insecure_host: value.allow_insecure_host, resolution: value.resolution, prerelease: value.prerelease, + multi_version: value.multi_version, dependency_metadata: value.dependency_metadata, config_settings: value.config_settings, no_build_isolation: value.no_build_isolation, @@ -1475,6 +1511,7 @@ impl From for ResolverInstallerOptions { allow_insecure_host: value.allow_insecure_host, resolution: value.resolution, prerelease: value.prerelease, + multi_version: value.multi_version, dependency_metadata: value.dependency_metadata, config_settings: value.config_settings, no_build_isolation: value.no_build_isolation, @@ -1525,6 +1562,7 @@ pub struct OptionsWire { allow_insecure_host: Option>, resolution: Option, prerelease: Option, + multi_version: Option, dependency_metadata: Option>, config_settings: Option, no_build_isolation: Option, @@ -1595,6 +1633,7 @@ impl From for Options { allow_insecure_host, resolution, prerelease, + multi_version, dependency_metadata, config_settings, no_build_isolation, @@ -1650,6 +1689,7 @@ impl From for Options { allow_insecure_host, resolution, prerelease, + multi_version, dependency_metadata, config_settings, no_build_isolation, diff --git a/crates/uv-static/src/env_vars.rs b/crates/uv-static/src/env_vars.rs index dc5822b0c58d9..2098f50eaf68b 100644 --- a/crates/uv-static/src/env_vars.rs +++ b/crates/uv-static/src/env_vars.rs @@ -33,6 +33,9 @@ impl EnvVars { /// Equivalent to the `--prerelease` argument. Allows or disallows pre-release versions. pub const UV_PRERELEASE: &'static str = "UV_PRERELEASE"; + /// Equivalent to the `--multi-version` argument. Controls version selection across forks. + pub const UV_MULTI_VERSION: &'static str = "UV_MULTI_VERSION"; + /// Equivalent to the `--system` argument. Use system Python interpreter. pub const UV_SYSTEM_PYTHON: &'static str = "UV_SYSTEM_PYTHON"; diff --git a/crates/uv/src/commands/build_frontend.rs b/crates/uv/src/commands/build_frontend.rs index 458cf3cce608f..9c04b0c6d4bae 100644 --- a/crates/uv/src/commands/build_frontend.rs +++ b/crates/uv/src/commands/build_frontend.rs @@ -131,6 +131,7 @@ async fn build_impl( allow_insecure_host, resolution: _, prerelease: _, + multi_version: _, dependency_metadata, config_setting, no_build_isolation, diff --git a/crates/uv/src/commands/pip/compile.rs b/crates/uv/src/commands/pip/compile.rs index aa2c4de3b9028..5bf0f06618339 100644 --- a/crates/uv/src/commands/pip/compile.rs +++ b/crates/uv/src/commands/pip/compile.rs @@ -33,8 +33,8 @@ use uv_requirements::{ }; use uv_resolver::{ AnnotationStyle, DependencyMode, DisplayResolutionGraph, ExcludeNewer, FlatIndex, - InMemoryIndex, OptionsBuilder, PrereleaseMode, PythonRequirement, RequiresPython, - ResolutionMode, ResolverMarkers, + InMemoryIndex, MultiVersionMode, OptionsBuilder, PrereleaseMode, PythonRequirement, + RequiresPython, ResolutionMode, ResolverMarkers, }; use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy, InFlight}; use uv_warnings::warn_user; @@ -58,6 +58,7 @@ pub(crate) async fn pip_compile( output_file: Option<&Path>, resolution_mode: ResolutionMode, prerelease_mode: PrereleaseMode, + multi_version_mode: MultiVersionMode, dependency_mode: DependencyMode, upgrade: Upgrade, generate_hashes: bool, @@ -371,6 +372,7 @@ pub(crate) async fn pip_compile( let options = OptionsBuilder::new() .resolution_mode(resolution_mode) .prerelease_mode(prerelease_mode) + .multi_version_mode(multi_version_mode) .dependency_mode(dependency_mode) .exclude_newer(exclude_newer) .index_strategy(index_strategy) diff --git a/crates/uv/src/commands/project/lock.rs b/crates/uv/src/commands/project/lock.rs index 597b857f284ad..f0566365db4d9 100644 --- a/crates/uv/src/commands/project/lock.rs +++ b/crates/uv/src/commands/project/lock.rs @@ -313,6 +313,7 @@ async fn do_lock( allow_insecure_host, resolution, prerelease, + multi_version, dependency_metadata, config_setting, no_build_isolation, @@ -466,6 +467,7 @@ async fn do_lock( let options = OptionsBuilder::new() .resolution_mode(resolution) .prerelease_mode(prerelease) + .multi_version_mode(multi_version) .exclude_newer(exclude_newer) .index_strategy(index_strategy) .build(); @@ -737,6 +739,15 @@ impl ValidatedLock { ); return Ok(Self::Unusable(lock)); } + if lock.multi_version_mode() != options.multi_version_mode { + let _ = writeln!( + printer.stderr(), + "Ignoring existing lockfile due to change in multi-version mode: `{}` vs. `{}`", + lock.multi_version_mode().cyan(), + options.multi_version_mode.cyan() + ); + return Ok(Self::Unusable(lock)); + } match (lock.exclude_newer(), options.exclude_newer) { (None, None) => (), (Some(existing), Some(provided)) if existing == provided => (), diff --git a/crates/uv/src/commands/project/mod.rs b/crates/uv/src/commands/project/mod.rs index b928f1c8ab081..e2fd2c733b332 100644 --- a/crates/uv/src/commands/project/mod.rs +++ b/crates/uv/src/commands/project/mod.rs @@ -655,6 +655,7 @@ pub(crate) async fn resolve_names( allow_insecure_host, resolution: _, prerelease: _, + multi_version: _, dependency_metadata, config_setting, no_build_isolation, @@ -792,6 +793,7 @@ pub(crate) async fn resolve_environment<'a>( allow_insecure_host, resolution, prerelease, + multi_version, dependency_metadata, config_setting, no_build_isolation, @@ -852,6 +854,7 @@ pub(crate) async fn resolve_environment<'a>( let options = OptionsBuilder::new() .resolution_mode(resolution) .prerelease_mode(prerelease) + .multi_version_mode(multi_version) .exclude_newer(exclude_newer) .index_strategy(index_strategy) .build(); @@ -1120,6 +1123,7 @@ pub(crate) async fn update_environment( allow_insecure_host, resolution, prerelease, + multi_version, dependency_metadata, config_setting, no_build_isolation, @@ -1205,6 +1209,7 @@ pub(crate) async fn update_environment( let options = OptionsBuilder::new() .resolution_mode(*resolution) .prerelease_mode(*prerelease) + .multi_version_mode(*multi_version) .exclude_newer(*exclude_newer) .index_strategy(*index_strategy) .build(); diff --git a/crates/uv/src/commands/project/sync.rs b/crates/uv/src/commands/project/sync.rs index 418b6b30b847d..0c59871d4aa3b 100644 --- a/crates/uv/src/commands/project/sync.rs +++ b/crates/uv/src/commands/project/sync.rs @@ -33,7 +33,7 @@ use crate::commands::project::lock::{do_safe_lock, LockMode}; use crate::commands::project::{ default_dependency_groups, validate_dependency_groups, ProjectError, SharedState, }; -use crate::commands::{diagnostics, pip, project, ExitStatus}; +use crate::commands::{diagnostics, project, ExitStatus}; use crate::printer::Printer; use crate::settings::{InstallerSettingsRef, ResolverInstallerSettings}; @@ -363,7 +363,7 @@ pub(super) async fn do_sync( let site_packages = SitePackages::from_environment(venv)?; // Sync the environment. - pip::operations::install( + operations::install( &resolution, site_packages, modifications, diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index e29db554575f8..b6420a0ba3783 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -334,6 +334,7 @@ async fn run(mut cli: Cli) -> Result { args.settings.output_file.as_deref(), args.settings.resolution, args.settings.prerelease, + args.settings.multi_version, args.settings.dependency_mode, args.settings.upgrade, args.settings.generate_hashes, diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index c520afe68e8bf..2914549797c34 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -30,7 +30,9 @@ use uv_normalize::PackageName; use uv_pep508::{ExtraName, RequirementOrigin}; use uv_pypi_types::{Requirement, SupportedEnvironments}; use uv_python::{Prefix, PythonDownloads, PythonPreference, PythonVersion, Target}; -use uv_resolver::{AnnotationStyle, DependencyMode, ExcludeNewer, PrereleaseMode, ResolutionMode}; +use uv_resolver::{ + AnnotationStyle, DependencyMode, ExcludeNewer, MultiVersionMode, PrereleaseMode, ResolutionMode, +}; use uv_settings::{ Combine, FilesystemOptions, Options, PipOptions, PublishOptions, ResolverInstallerOptions, ResolverOptions, @@ -1940,6 +1942,7 @@ pub(crate) struct ResolverSettings { pub(crate) allow_insecure_host: Vec, pub(crate) resolution: ResolutionMode, pub(crate) prerelease: PrereleaseMode, + pub(crate) multi_version: MultiVersionMode, pub(crate) dependency_metadata: DependencyMetadata, pub(crate) config_setting: ConfigSettings, pub(crate) no_build_isolation: bool, @@ -1959,6 +1962,7 @@ pub(crate) struct ResolverSettingsRef<'a> { pub(crate) allow_insecure_host: &'a [TrustedHost], pub(crate) resolution: ResolutionMode, pub(crate) prerelease: PrereleaseMode, + pub(crate) multi_version: MultiVersionMode, pub(crate) dependency_metadata: &'a DependencyMetadata, pub(crate) config_setting: &'a ConfigSettings, pub(crate) no_build_isolation: bool, @@ -1991,6 +1995,7 @@ impl ResolverSettings { allow_insecure_host: &self.allow_insecure_host, resolution: self.resolution, prerelease: self.prerelease, + multi_version: self.multi_version, dependency_metadata: &self.dependency_metadata, config_setting: &self.config_setting, no_build_isolation: self.no_build_isolation, @@ -2025,6 +2030,7 @@ impl From for ResolverSettings { ), resolution: value.resolution.unwrap_or_default(), prerelease: value.prerelease.unwrap_or_default(), + multi_version: value.multi_version.unwrap_or_default(), dependency_metadata: DependencyMetadata::from_entries( value.dependency_metadata.into_iter().flatten(), ), @@ -2062,6 +2068,7 @@ pub(crate) struct ResolverInstallerSettingsRef<'a> { pub(crate) allow_insecure_host: &'a [TrustedHost], pub(crate) resolution: ResolutionMode, pub(crate) prerelease: PrereleaseMode, + pub(crate) multi_version: MultiVersionMode, pub(crate) dependency_metadata: &'a DependencyMetadata, pub(crate) config_setting: &'a ConfigSettings, pub(crate) no_build_isolation: bool, @@ -2089,6 +2096,7 @@ pub(crate) struct ResolverInstallerSettings { pub(crate) allow_insecure_host: Vec, pub(crate) resolution: ResolutionMode, pub(crate) prerelease: PrereleaseMode, + pub(crate) multi_version: MultiVersionMode, pub(crate) dependency_metadata: DependencyMetadata, pub(crate) config_setting: ConfigSettings, pub(crate) no_build_isolation: bool, @@ -2126,6 +2134,7 @@ impl ResolverInstallerSettings { allow_insecure_host: &self.allow_insecure_host, resolution: self.resolution, prerelease: self.prerelease, + multi_version: self.multi_version, dependency_metadata: &self.dependency_metadata, config_setting: &self.config_setting, no_build_isolation: self.no_build_isolation, @@ -2162,6 +2171,7 @@ impl From for ResolverInstallerSettings { ), resolution: value.resolution.unwrap_or_default(), prerelease: value.prerelease.unwrap_or_default(), + multi_version: value.multi_version.unwrap_or_default(), dependency_metadata: DependencyMetadata::from_entries( value.dependency_metadata.into_iter().flatten(), ), @@ -2221,6 +2231,7 @@ pub(crate) struct PipSettings { pub(crate) dependency_mode: DependencyMode, pub(crate) resolution: ResolutionMode, pub(crate) prerelease: PrereleaseMode, + pub(crate) multi_version: MultiVersionMode, pub(crate) dependency_metadata: DependencyMetadata, pub(crate) output_file: Option, pub(crate) no_strip_extras: bool, @@ -2282,6 +2293,7 @@ impl PipSettings { allow_empty_requirements, resolution, prerelease, + multi_version, dependency_metadata, output_file, no_strip_extras, @@ -2324,6 +2336,7 @@ impl PipSettings { allow_insecure_host: top_level_allow_insecure_host, resolution: top_level_resolution, prerelease: top_level_prerelease, + multi_version: top_level_multi_version, dependency_metadata: top_level_dependency_metadata, config_settings: top_level_config_settings, no_build_isolation: top_level_no_build_isolation, @@ -2356,6 +2369,7 @@ impl PipSettings { let allow_insecure_host = allow_insecure_host.combine(top_level_allow_insecure_host); let resolution = resolution.combine(top_level_resolution); let prerelease = prerelease.combine(top_level_prerelease); + let multi_version = multi_version.combine(top_level_multi_version); let dependency_metadata = dependency_metadata.combine(top_level_dependency_metadata); let config_settings = config_settings.combine(top_level_config_settings); let no_build_isolation = no_build_isolation.combine(top_level_no_build_isolation); @@ -2400,6 +2414,10 @@ impl PipSettings { }, resolution: args.resolution.combine(resolution).unwrap_or_default(), prerelease: args.prerelease.combine(prerelease).unwrap_or_default(), + multi_version: args + .multi_version + .combine(multi_version) + .unwrap_or_default(), dependency_metadata: DependencyMetadata::from_entries( args.dependency_metadata .combine(dependency_metadata) @@ -2549,6 +2567,7 @@ impl<'a> From> for ResolverSettingsRef<'a> { allow_insecure_host: settings.allow_insecure_host, resolution: settings.resolution, prerelease: settings.prerelease, + multi_version: settings.multi_version, dependency_metadata: settings.dependency_metadata, config_setting: settings.config_setting, no_build_isolation: settings.no_build_isolation, @@ -2600,7 +2619,7 @@ pub(crate) struct PublishSettings { } impl PublishSettings { - /// Resolve the [`crate::settings::PublishSettings`] from the CLI and filesystem configuration. + /// Resolve the [`PublishSettings`] from the CLI and filesystem configuration. pub(crate) fn resolve(args: PublishArgs, filesystem: Option) -> Self { let Options { publish, top_level, .. diff --git a/crates/uv/tests/it/lock.rs b/crates/uv/tests/it/lock.rs index 5ea020f6f53d7..1c4e03bdb796f 100644 --- a/crates/uv/tests/it/lock.rs +++ b/crates/uv/tests/it/lock.rs @@ -15939,6 +15939,270 @@ fn lock_keyring_credentials() -> Result<()> { Ok(()) } +#[test] +fn lock_latest() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.8" + dependencies = [ + "numpy ; python_version >= '3.8'", + "numpy ; python_version >= '3.9'", + "numpy ; python_version >= '3.10'", + "numpy ; python_version >= '3.11'", + "numpy ; python_version >= '3.12'", + ] + + [tool.uv] + constraint-dependencies = ["numpy <= 2.1.2"] + + [build-system] + requires = ["setuptools>=42"] + build-backend = "setuptools.build_meta" + "#, + )?; + + uv_snapshot!(context.filters(), context.lock().arg("--multi-version").arg("latest").env_remove("UV_EXCLUDE_NEWER"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 4 packages in [TIME] + "###); + + let lock = context.read("uv.lock"); + + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + lock, @r###" + version = 1 + requires-python = ">=3.8" + resolution-markers = [ + "python_full_version < '3.9'", + "python_full_version == '3.9.*'", + "python_full_version == '3.10.*'", + "python_full_version == '3.11.*'", + "python_full_version >= '3.12'", + ] + + [options] + multi-version-mode = "latest" + + [manifest] + constraints = [{ name = "numpy", specifier = "<=2.1.2" }] + + [[package]] + name = "numpy" + version = "1.24.4" + source = { registry = "https://pypi.org/simple" } + resolution-markers = [ + "python_full_version < '3.9'", + ] + sdist = { url = "https://files.pythonhosted.org/packages/a4/9b/027bec52c633f6556dba6b722d9a0befb40498b9ceddd29cbe67a45a127c/numpy-1.24.4.tar.gz", hash = "sha256:80f5e3a4e498641401868df4208b74581206afbee7cf7b8329daae82676d9463", size = 10911229 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/80/6cdfb3e275d95155a34659163b83c09e3a3ff9f1456880bec6cc63d71083/numpy-1.24.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0bfb52d2169d58c1cdb8cc1f16989101639b34c7d3ce60ed70b19c63eba0b64", size = 19789140 }, + { url = "https://files.pythonhosted.org/packages/64/5f/3f01d753e2175cfade1013eea08db99ba1ee4bdb147ebcf3623b75d12aa7/numpy-1.24.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ed094d4f0c177b1b8e7aa9cba7d6ceed51c0e569a5318ac0ca9a090680a6a1b1", size = 13854297 }, + { url = "https://files.pythonhosted.org/packages/5a/b3/2f9c21d799fa07053ffa151faccdceeb69beec5a010576b8991f614021f7/numpy-1.24.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79fc682a374c4a8ed08b331bef9c5f582585d1048fa6d80bc6c35bc384eee9b4", size = 13995611 }, + { url = "https://files.pythonhosted.org/packages/10/be/ae5bf4737cb79ba437879915791f6f26d92583c738d7d960ad94e5c36adf/numpy-1.24.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ffe43c74893dbf38c2b0a1f5428760a1a9c98285553c89e12d70a96a7f3a4d6", size = 17282357 }, + { url = "https://files.pythonhosted.org/packages/c0/64/908c1087be6285f40e4b3e79454552a701664a079321cff519d8c7051d06/numpy-1.24.4-cp310-cp310-win32.whl", hash = "sha256:4c21decb6ea94057331e111a5bed9a79d335658c27ce2adb580fb4d54f2ad9bc", size = 12429222 }, + { url = "https://files.pythonhosted.org/packages/22/55/3d5a7c1142e0d9329ad27cece17933b0e2ab4e54ddc5c1861fbfeb3f7693/numpy-1.24.4-cp310-cp310-win_amd64.whl", hash = "sha256:b4bea75e47d9586d31e892a7401f76e909712a0fd510f58f5337bea9572c571e", size = 14841514 }, + { url = "https://files.pythonhosted.org/packages/a9/cc/5ed2280a27e5dab12994c884f1f4d8c3bd4d885d02ae9e52a9d213a6a5e2/numpy-1.24.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f136bab9c2cfd8da131132c2cf6cc27331dd6fae65f95f69dcd4ae3c3639c810", size = 19775508 }, + { url = "https://files.pythonhosted.org/packages/c0/bc/77635c657a3668cf652806210b8662e1aff84b818a55ba88257abf6637a8/numpy-1.24.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2926dac25b313635e4d6cf4dc4e51c8c0ebfed60b801c799ffc4c32bf3d1254", size = 13840033 }, + { url = "https://files.pythonhosted.org/packages/a7/4c/96cdaa34f54c05e97c1c50f39f98d608f96f0677a6589e64e53104e22904/numpy-1.24.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:222e40d0e2548690405b0b3c7b21d1169117391c2e82c378467ef9ab4c8f0da7", size = 13991951 }, + { url = "https://files.pythonhosted.org/packages/22/97/dfb1a31bb46686f09e68ea6ac5c63fdee0d22d7b23b8f3f7ea07712869ef/numpy-1.24.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7215847ce88a85ce39baf9e89070cb860c98fdddacbaa6c0da3ffb31b3350bd5", size = 17278923 }, + { url = "https://files.pythonhosted.org/packages/35/e2/76a11e54139654a324d107da1d98f99e7aa2a7ef97cfd7c631fba7dbde71/numpy-1.24.4-cp311-cp311-win32.whl", hash = "sha256:4979217d7de511a8d57f4b4b5b2b965f707768440c17cb70fbf254c4b225238d", size = 12422446 }, + { url = "https://files.pythonhosted.org/packages/d8/ec/ebef2f7d7c28503f958f0f8b992e7ce606fb74f9e891199329d5f5f87404/numpy-1.24.4-cp311-cp311-win_amd64.whl", hash = "sha256:b7b1fc9864d7d39e28f41d089bfd6353cb5f27ecd9905348c24187a768c79694", size = 14834466 }, + { url = "https://files.pythonhosted.org/packages/11/10/943cfb579f1a02909ff96464c69893b1d25be3731b5d3652c2e0cf1281ea/numpy-1.24.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1452241c290f3e2a312c137a9999cdbf63f78864d63c79039bda65ee86943f61", size = 19780722 }, + { url = "https://files.pythonhosted.org/packages/a7/ae/f53b7b265fdc701e663fbb322a8e9d4b14d9cb7b2385f45ddfabfc4327e4/numpy-1.24.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:04640dab83f7c6c85abf9cd729c5b65f1ebd0ccf9de90b270cd61935eef0197f", size = 13843102 }, + { url = "https://files.pythonhosted.org/packages/25/6f/2586a50ad72e8dbb1d8381f837008a0321a3516dfd7cb57fc8cf7e4bb06b/numpy-1.24.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5425b114831d1e77e4b5d812b69d11d962e104095a5b9c3b641a218abcc050e", size = 14039616 }, + { url = "https://files.pythonhosted.org/packages/98/5d/5738903efe0ecb73e51eb44feafba32bdba2081263d40c5043568ff60faf/numpy-1.24.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd80e219fd4c71fc3699fc1dadac5dcf4fd882bfc6f7ec53d30fa197b8ee22dc", size = 17316263 }, + { url = "https://files.pythonhosted.org/packages/d1/57/8d328f0b91c733aa9aa7ee540dbc49b58796c862b4fbcb1146c701e888da/numpy-1.24.4-cp38-cp38-win32.whl", hash = "sha256:4602244f345453db537be5314d3983dbf5834a9701b7723ec28923e2889e0bb2", size = 12455660 }, + { url = "https://files.pythonhosted.org/packages/69/65/0d47953afa0ad569d12de5f65d964321c208492064c38fe3b0b9744f8d44/numpy-1.24.4-cp38-cp38-win_amd64.whl", hash = "sha256:692f2e0f55794943c5bfff12b3f56f99af76f902fc47487bdfe97856de51a706", size = 14868112 }, + { url = "https://files.pythonhosted.org/packages/9a/cd/d5b0402b801c8a8b56b04c1e85c6165efab298d2f0ab741c2406516ede3a/numpy-1.24.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2541312fbf09977f3b3ad449c4e5f4bb55d0dbf79226d7724211acc905049400", size = 19816549 }, + { url = "https://files.pythonhosted.org/packages/14/27/638aaa446f39113a3ed38b37a66243e21b38110d021bfcb940c383e120f2/numpy-1.24.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9667575fb6d13c95f1b36aca12c5ee3356bf001b714fc354eb5465ce1609e62f", size = 13879950 }, + { url = "https://files.pythonhosted.org/packages/8f/27/91894916e50627476cff1a4e4363ab6179d01077d71b9afed41d9e1f18bf/numpy-1.24.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3a86ed21e4f87050382c7bc96571755193c4c1392490744ac73d660e8f564a9", size = 14030228 }, + { url = "https://files.pythonhosted.org/packages/7a/7c/d7b2a0417af6428440c0ad7cb9799073e507b1a465f827d058b826236964/numpy-1.24.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d11efb4dbecbdf22508d55e48d9c8384db795e1b7b51ea735289ff96613ff74d", size = 17311170 }, + { url = "https://files.pythonhosted.org/packages/18/9d/e02ace5d7dfccee796c37b995c63322674daf88ae2f4a4724c5dd0afcc91/numpy-1.24.4-cp39-cp39-win32.whl", hash = "sha256:6620c0acd41dbcb368610bb2f4d83145674040025e5536954782467100aa8835", size = 12454918 }, + { url = "https://files.pythonhosted.org/packages/63/38/6cc19d6b8bfa1d1a459daf2b3fe325453153ca7019976274b6f33d8b5663/numpy-1.24.4-cp39-cp39-win_amd64.whl", hash = "sha256:befe2bf740fd8373cf56149a5c23a0f601e82869598d41f8e188a0e9869926f8", size = 14867441 }, + { url = "https://files.pythonhosted.org/packages/a4/fd/8dff40e25e937c94257455c237b9b6bf5a30d42dd1cc11555533be099492/numpy-1.24.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:31f13e25b4e304632a4619d0e0777662c2ffea99fcae2029556b17d8ff958aef", size = 19156590 }, + { url = "https://files.pythonhosted.org/packages/42/e7/4bf953c6e05df90c6d351af69966384fed8e988d0e8c54dad7103b59f3ba/numpy-1.24.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95f7ac6540e95bc440ad77f56e520da5bf877f87dca58bd095288dce8940532a", size = 16705744 }, + { url = "https://files.pythonhosted.org/packages/fc/dd/9106005eb477d022b60b3817ed5937a43dad8fd1f20b0610ea8a32fcb407/numpy-1.24.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e98f220aa76ca2a977fe435f5b04d7b3470c0a2e6312907b37ba6068f26787f2", size = 14734290 }, + ] + + [[package]] + name = "numpy" + version = "2.0.2" + source = { registry = "https://pypi.org/simple" } + resolution-markers = [ + "python_full_version == '3.9.*'", + ] + sdist = { url = "https://files.pythonhosted.org/packages/a9/75/10dd1f8116a8b796cb2c737b674e02d02e80454bda953fa7e65d8c12b016/numpy-2.0.2.tar.gz", hash = "sha256:883c987dee1880e2a864ab0dc9892292582510604156762362d9326444636e78", size = 18902015 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/21/91/3495b3237510f79f5d81f2508f9f13fea78ebfdf07538fc7444badda173d/numpy-2.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:51129a29dbe56f9ca83438b706e2e69a39892b5eda6cedcb6b0c9fdc9b0d3ece", size = 21165245 }, + { url = "https://files.pythonhosted.org/packages/05/33/26178c7d437a87082d11019292dce6d3fe6f0e9026b7b2309cbf3e489b1d/numpy-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f15975dfec0cf2239224d80e32c3170b1d168335eaedee69da84fbe9f1f9cd04", size = 13738540 }, + { url = "https://files.pythonhosted.org/packages/ec/31/cc46e13bf07644efc7a4bf68df2df5fb2a1a88d0cd0da9ddc84dc0033e51/numpy-2.0.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8c5713284ce4e282544c68d1c3b2c7161d38c256d2eefc93c1d683cf47683e66", size = 5300623 }, + { url = "https://files.pythonhosted.org/packages/6e/16/7bfcebf27bb4f9d7ec67332ffebee4d1bf085c84246552d52dbb548600e7/numpy-2.0.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:becfae3ddd30736fe1889a37f1f580e245ba79a5855bff5f2a29cb3ccc22dd7b", size = 6901774 }, + { url = "https://files.pythonhosted.org/packages/f9/a3/561c531c0e8bf082c5bef509d00d56f82e0ea7e1e3e3a7fc8fa78742a6e5/numpy-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2da5960c3cf0df7eafefd806d4e612c5e19358de82cb3c343631188991566ccd", size = 13907081 }, + { url = "https://files.pythonhosted.org/packages/fa/66/f7177ab331876200ac7563a580140643d1179c8b4b6a6b0fc9838de2a9b8/numpy-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:496f71341824ed9f3d2fd36cf3ac57ae2e0165c143b55c3a035ee219413f3318", size = 19523451 }, + { url = "https://files.pythonhosted.org/packages/25/7f/0b209498009ad6453e4efc2c65bcdf0ae08a182b2b7877d7ab38a92dc542/numpy-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a61ec659f68ae254e4d237816e33171497e978140353c0c2038d46e63282d0c8", size = 19927572 }, + { url = "https://files.pythonhosted.org/packages/3e/df/2619393b1e1b565cd2d4c4403bdd979621e2c4dea1f8532754b2598ed63b/numpy-2.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d731a1c6116ba289c1e9ee714b08a8ff882944d4ad631fd411106a30f083c326", size = 14400722 }, + { url = "https://files.pythonhosted.org/packages/22/ad/77e921b9f256d5da36424ffb711ae79ca3f451ff8489eeca544d0701d74a/numpy-2.0.2-cp310-cp310-win32.whl", hash = "sha256:984d96121c9f9616cd33fbd0618b7f08e0cfc9600a7ee1d6fd9b239186d19d97", size = 6472170 }, + { url = "https://files.pythonhosted.org/packages/10/05/3442317535028bc29cf0c0dd4c191a4481e8376e9f0db6bcf29703cadae6/numpy-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:c7b0be4ef08607dd04da4092faee0b86607f111d5ae68036f16cc787e250a131", size = 15905558 }, + { url = "https://files.pythonhosted.org/packages/8b/cf/034500fb83041aa0286e0fb16e7c76e5c8b67c0711bb6e9e9737a717d5fe/numpy-2.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:49ca4decb342d66018b01932139c0961a8f9ddc7589611158cb3c27cbcf76448", size = 21169137 }, + { url = "https://files.pythonhosted.org/packages/4a/d9/32de45561811a4b87fbdee23b5797394e3d1504b4a7cf40c10199848893e/numpy-2.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:11a76c372d1d37437857280aa142086476136a8c0f373b2e648ab2c8f18fb195", size = 13703552 }, + { url = "https://files.pythonhosted.org/packages/c1/ca/2f384720020c7b244d22508cb7ab23d95f179fcfff33c31a6eeba8d6c512/numpy-2.0.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:807ec44583fd708a21d4a11d94aedf2f4f3c3719035c76a2bbe1fe8e217bdc57", size = 5298957 }, + { url = "https://files.pythonhosted.org/packages/0e/78/a3e4f9fb6aa4e6fdca0c5428e8ba039408514388cf62d89651aade838269/numpy-2.0.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8cafab480740e22f8d833acefed5cc87ce276f4ece12fdaa2e8903db2f82897a", size = 6905573 }, + { url = "https://files.pythonhosted.org/packages/a0/72/cfc3a1beb2caf4efc9d0b38a15fe34025230da27e1c08cc2eb9bfb1c7231/numpy-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a15f476a45e6e5a3a79d8a14e62161d27ad897381fecfa4a09ed5322f2085669", size = 13914330 }, + { url = "https://files.pythonhosted.org/packages/ba/a8/c17acf65a931ce551fee11b72e8de63bf7e8a6f0e21add4c937c83563538/numpy-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13e689d772146140a252c3a28501da66dfecd77490b498b168b501835041f951", size = 19534895 }, + { url = "https://files.pythonhosted.org/packages/ba/86/8767f3d54f6ae0165749f84648da9dcc8cd78ab65d415494962c86fac80f/numpy-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9ea91dfb7c3d1c56a0e55657c0afb38cf1eeae4544c208dc465c3c9f3a7c09f9", size = 19937253 }, + { url = "https://files.pythonhosted.org/packages/df/87/f76450e6e1c14e5bb1eae6836478b1028e096fd02e85c1c37674606ab752/numpy-2.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c1c9307701fec8f3f7a1e6711f9089c06e6284b3afbbcd259f7791282d660a15", size = 14414074 }, + { url = "https://files.pythonhosted.org/packages/5c/ca/0f0f328e1e59f73754f06e1adfb909de43726d4f24c6a3f8805f34f2b0fa/numpy-2.0.2-cp311-cp311-win32.whl", hash = "sha256:a392a68bd329eafac5817e5aefeb39038c48b671afd242710b451e76090e81f4", size = 6470640 }, + { url = "https://files.pythonhosted.org/packages/eb/57/3a3f14d3a759dcf9bf6e9eda905794726b758819df4663f217d658a58695/numpy-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:286cd40ce2b7d652a6f22efdfc6d1edf879440e53e76a75955bc0c826c7e64dc", size = 15910230 }, + { url = "https://files.pythonhosted.org/packages/45/40/2e117be60ec50d98fa08c2f8c48e09b3edea93cfcabd5a9ff6925d54b1c2/numpy-2.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:df55d490dea7934f330006d0f81e8551ba6010a5bf035a249ef61a94f21c500b", size = 20895803 }, + { url = "https://files.pythonhosted.org/packages/46/92/1b8b8dee833f53cef3e0a3f69b2374467789e0bb7399689582314df02651/numpy-2.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8df823f570d9adf0978347d1f926b2a867d5608f434a7cff7f7908c6570dcf5e", size = 13471835 }, + { url = "https://files.pythonhosted.org/packages/7f/19/e2793bde475f1edaea6945be141aef6c8b4c669b90c90a300a8954d08f0a/numpy-2.0.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9a92ae5c14811e390f3767053ff54eaee3bf84576d99a2456391401323f4ec2c", size = 5038499 }, + { url = "https://files.pythonhosted.org/packages/e3/ff/ddf6dac2ff0dd50a7327bcdba45cb0264d0e96bb44d33324853f781a8f3c/numpy-2.0.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:a842d573724391493a97a62ebbb8e731f8a5dcc5d285dfc99141ca15a3302d0c", size = 6633497 }, + { url = "https://files.pythonhosted.org/packages/72/21/67f36eac8e2d2cd652a2e69595a54128297cdcb1ff3931cfc87838874bd4/numpy-2.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05e238064fc0610c840d1cf6a13bf63d7e391717d247f1bf0318172e759e692", size = 13621158 }, + { url = "https://files.pythonhosted.org/packages/39/68/e9f1126d757653496dbc096cb429014347a36b228f5a991dae2c6b6cfd40/numpy-2.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0123ffdaa88fa4ab64835dcbde75dcdf89c453c922f18dced6e27c90d1d0ec5a", size = 19236173 }, + { url = "https://files.pythonhosted.org/packages/d1/e9/1f5333281e4ebf483ba1c888b1d61ba7e78d7e910fdd8e6499667041cc35/numpy-2.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:96a55f64139912d61de9137f11bf39a55ec8faec288c75a54f93dfd39f7eb40c", size = 19634174 }, + { url = "https://files.pythonhosted.org/packages/71/af/a469674070c8d8408384e3012e064299f7a2de540738a8e414dcfd639996/numpy-2.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec9852fb39354b5a45a80bdab5ac02dd02b15f44b3804e9f00c556bf24b4bded", size = 14099701 }, + { url = "https://files.pythonhosted.org/packages/d0/3d/08ea9f239d0e0e939b6ca52ad403c84a2bce1bde301a8eb4888c1c1543f1/numpy-2.0.2-cp312-cp312-win32.whl", hash = "sha256:671bec6496f83202ed2d3c8fdc486a8fc86942f2e69ff0e986140339a63bcbe5", size = 6174313 }, + { url = "https://files.pythonhosted.org/packages/b2/b5/4ac39baebf1fdb2e72585c8352c56d063b6126be9fc95bd2bb5ef5770c20/numpy-2.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:cfd41e13fdc257aa5778496b8caa5e856dc4896d4ccf01841daee1d96465467a", size = 15606179 }, + { url = "https://files.pythonhosted.org/packages/43/c1/41c8f6df3162b0c6ffd4437d729115704bd43363de0090c7f913cfbc2d89/numpy-2.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9059e10581ce4093f735ed23f3b9d283b9d517ff46009ddd485f1747eb22653c", size = 21169942 }, + { url = "https://files.pythonhosted.org/packages/39/bc/fd298f308dcd232b56a4031fd6ddf11c43f9917fbc937e53762f7b5a3bb1/numpy-2.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:423e89b23490805d2a5a96fe40ec507407b8ee786d66f7328be214f9679df6dd", size = 13711512 }, + { url = "https://files.pythonhosted.org/packages/96/ff/06d1aa3eeb1c614eda245c1ba4fb88c483bee6520d361641331872ac4b82/numpy-2.0.2-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:2b2955fa6f11907cf7a70dab0d0755159bca87755e831e47932367fc8f2f2d0b", size = 5306976 }, + { url = "https://files.pythonhosted.org/packages/2d/98/121996dcfb10a6087a05e54453e28e58694a7db62c5a5a29cee14c6e047b/numpy-2.0.2-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:97032a27bd9d8988b9a97a8c4d2c9f2c15a81f61e2f21404d7e8ef00cb5be729", size = 6906494 }, + { url = "https://files.pythonhosted.org/packages/15/31/9dffc70da6b9bbf7968f6551967fc21156207366272c2a40b4ed6008dc9b/numpy-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e795a8be3ddbac43274f18588329c72939870a16cae810c2b73461c40718ab1", size = 13912596 }, + { url = "https://files.pythonhosted.org/packages/b9/14/78635daab4b07c0930c919d451b8bf8c164774e6a3413aed04a6d95758ce/numpy-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b258c385842546006213344c50655ff1555a9338e2e5e02a0756dc3e803dd", size = 19526099 }, + { url = "https://files.pythonhosted.org/packages/26/4c/0eeca4614003077f68bfe7aac8b7496f04221865b3a5e7cb230c9d055afd/numpy-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fec9451a7789926bcf7c2b8d187292c9f93ea30284802a0ab3f5be8ab36865d", size = 19932823 }, + { url = "https://files.pythonhosted.org/packages/f1/46/ea25b98b13dccaebddf1a803f8c748680d972e00507cd9bc6dcdb5aa2ac1/numpy-2.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9189427407d88ff25ecf8f12469d4d39d35bee1db5d39fc5c168c6f088a6956d", size = 14404424 }, + { url = "https://files.pythonhosted.org/packages/c8/a6/177dd88d95ecf07e722d21008b1b40e681a929eb9e329684d449c36586b2/numpy-2.0.2-cp39-cp39-win32.whl", hash = "sha256:905d16e0c60200656500c95b6b8dca5d109e23cb24abc701d41c02d74c6b3afa", size = 6476809 }, + { url = "https://files.pythonhosted.org/packages/ea/2b/7fc9f4e7ae5b507c1a3a21f0f15ed03e794c1242ea8a242ac158beb56034/numpy-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:a3f4ab0caa7f053f6797fcd4e1e25caee367db3112ef2b6ef82d749530768c73", size = 15911314 }, + { url = "https://files.pythonhosted.org/packages/8f/3b/df5a870ac6a3be3a86856ce195ef42eec7ae50d2a202be1f5a4b3b340e14/numpy-2.0.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7f0a0c6f12e07fa94133c8a67404322845220c06a9e80e85999afe727f7438b8", size = 21025288 }, + { url = "https://files.pythonhosted.org/packages/2c/97/51af92f18d6f6f2d9ad8b482a99fb74e142d71372da5d834b3a2747a446e/numpy-2.0.2-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:312950fdd060354350ed123c0e25a71327d3711584beaef30cdaa93320c392d4", size = 6762793 }, + { url = "https://files.pythonhosted.org/packages/12/46/de1fbd0c1b5ccaa7f9a005b66761533e2f6a3e560096682683a223631fe9/numpy-2.0.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26df23238872200f63518dd2aa984cfca675d82469535dc7162dc2ee52d9dd5c", size = 19334885 }, + { url = "https://files.pythonhosted.org/packages/cc/dc/d330a6faefd92b446ec0f0dfea4c3207bb1fef3c4771d19cf4543efd2c78/numpy-2.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a46288ec55ebbd58947d31d72be2c63cbf839f0a63b49cb755022310792a3385", size = 15828784 }, + ] + + [[package]] + name = "numpy" + version = "2.1.2" + source = { registry = "https://pypi.org/simple" } + resolution-markers = [ + "python_full_version == '3.10.*'", + "python_full_version == '3.11.*'", + "python_full_version >= '3.12'", + ] + sdist = { url = "https://files.pythonhosted.org/packages/4b/d1/8a730ea07f4a37d94f9172f4ce1d81064b7a64766b460378be278952de75/numpy-2.1.2.tar.gz", hash = "sha256:13532a088217fa624c99b843eeb54640de23b3414b14aa66d023805eb731066c", size = 18878063 } + wheels = [ + { url = "https://files.pythonhosted.org/packages/1c/a2/40a76d357f168e9f9f06d6cc2c8e22dd5fb2bfbe63fe2c433057258c145a/numpy-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:30d53720b726ec36a7f88dc873f0eec8447fbc93d93a8f079dfac2629598d6ee", size = 21150947 }, + { url = "https://files.pythonhosted.org/packages/b5/d0/ba271ea9108d7278d3889a7eb38d77370a88713fb94339964e71ac184d4a/numpy-2.1.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e8d3ca0a72dd8846eb6f7dfe8f19088060fcb76931ed592d29128e0219652884", size = 13758184 }, + { url = "https://files.pythonhosted.org/packages/7c/b9/5c6507439cd756201010f7937bf90712c2469052ae094584af14557dd64f/numpy-2.1.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:fc44e3c68ff00fd991b59092a54350e6e4911152682b4782f68070985aa9e648", size = 5354091 }, + { url = "https://files.pythonhosted.org/packages/60/21/7938cf724d9e84e45fb886f3fc794ab431d71facfebc261e3e9f19f3233a/numpy-2.1.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:7c1c60328bd964b53f8b835df69ae8198659e2b9302ff9ebb7de4e5a5994db3d", size = 6887169 }, + { url = "https://files.pythonhosted.org/packages/09/8d/42a124657f5d31902fca73921b25a0d022cead2b32ce7e6975762cd2995a/numpy-2.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6cdb606a7478f9ad91c6283e238544451e3a95f30fb5467fbf715964341a8a86", size = 13888165 }, + { url = "https://files.pythonhosted.org/packages/fb/25/ba023652a39a2c127200e85aed975fc6119b421e2c348e5d0171e2046edb/numpy-2.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d666cb72687559689e9906197e3bec7b736764df6a2e58ee265e360663e9baf7", size = 16326954 }, + { url = "https://files.pythonhosted.org/packages/34/58/23e6b07fad492b7c47cf09cd8bad6983658f0f925b6c535fd008e3e86274/numpy-2.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c6eef7a2dbd0abfb0d9eaf78b73017dbfd0b54051102ff4e6a7b2980d5ac1a03", size = 16702916 }, + { url = "https://files.pythonhosted.org/packages/91/24/37b5cf2dc7d385ac97f7b7fe50cba312abb70a2a5eac74c23af028811f73/numpy-2.1.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:12edb90831ff481f7ef5f6bc6431a9d74dc0e5ff401559a71e5e4611d4f2d466", size = 14384372 }, + { url = "https://files.pythonhosted.org/packages/ea/ec/0f6d471058a01d1a05a50d2793898de1549280fa715a8537987ee866b5d9/numpy-2.1.2-cp310-cp310-win32.whl", hash = "sha256:a65acfdb9c6ebb8368490dbafe83c03c7e277b37e6857f0caeadbbc56e12f4fb", size = 6535361 }, + { url = "https://files.pythonhosted.org/packages/c2/3d/293cc5927f916a7bc6bf74da8f6defab63d1b13f0959d7e21878ad8a20d8/numpy-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:860ec6e63e2c5c2ee5e9121808145c7bf86c96cca9ad396c0bd3e0f2798ccbe2", size = 12865501 }, + { url = "https://files.pythonhosted.org/packages/aa/9c/9a6ec3ae89cd0648d419781284308f2956d2a61d932b5ac9682c956a171b/numpy-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b42a1a511c81cc78cbc4539675713bbcf9d9c3913386243ceff0e9429ca892fe", size = 21154845 }, + { url = "https://files.pythonhosted.org/packages/02/69/9f05c4ecc75fabf297b17743996371b4c3dfc4d92e15c5c38d8bb3db8d74/numpy-2.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:faa88bc527d0f097abdc2c663cddf37c05a1c2f113716601555249805cf573f1", size = 13789409 }, + { url = "https://files.pythonhosted.org/packages/34/4e/f95c99217bf77bbfaaf660d693c10bd0dc03b6032d19316d316088c9e479/numpy-2.1.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:c82af4b2ddd2ee72d1fc0c6695048d457e00b3582ccde72d8a1c991b808bb20f", size = 5352097 }, + { url = "https://files.pythonhosted.org/packages/06/13/f5d87a497c16658e9af8920449b0b5692b469586b8231340c672962071c5/numpy-2.1.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:13602b3174432a35b16c4cfb5de9a12d229727c3dd47a6ce35111f2ebdf66ff4", size = 6891195 }, + { url = "https://files.pythonhosted.org/packages/6c/89/691ac07429ac061b344d5e37fa8e94be51a6017734aea15f2d9d7c6d119a/numpy-2.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ebec5fd716c5a5b3d8dfcc439be82a8407b7b24b230d0ad28a81b61c2f4659a", size = 13895153 }, + { url = "https://files.pythonhosted.org/packages/23/69/538317f0d925095537745f12aced33be1570bbdc4acde49b33748669af96/numpy-2.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2b49c3c0804e8ecb05d59af8386ec2f74877f7ca8fd9c1e00be2672e4d399b1", size = 16338306 }, + { url = "https://files.pythonhosted.org/packages/af/03/863fe7062c2106d3c151f7df9353f2ae2237c1dd6900f127a3eb1f24cb1b/numpy-2.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2cbba4b30bf31ddbe97f1c7205ef976909a93a66bb1583e983adbd155ba72ac2", size = 16710893 }, + { url = "https://files.pythonhosted.org/packages/70/77/0ad9efe25482009873f9660d29a40a8c41a6f0e8b541195e3c95c70684c5/numpy-2.1.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8e00ea6fc82e8a804433d3e9cedaa1051a1422cb6e443011590c14d2dea59146", size = 14398048 }, + { url = "https://files.pythonhosted.org/packages/3e/0f/e785fe75544db9f2b0bb1c181e13ceff349ce49753d807fd9672916aa06d/numpy-2.1.2-cp311-cp311-win32.whl", hash = "sha256:5006b13a06e0b38d561fab5ccc37581f23c9511879be7693bd33c7cd15ca227c", size = 6533458 }, + { url = "https://files.pythonhosted.org/packages/d4/96/450054662295125af861d48d2c4bc081dadcf1974a879b2104613157aa62/numpy-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:f1eb068ead09f4994dec71c24b2844f1e4e4e013b9629f812f292f04bd1510d9", size = 12870896 }, + { url = "https://files.pythonhosted.org/packages/a0/7d/554a6838f37f3ada5a55f25173c619d556ae98092a6e01afb6e710501d70/numpy-2.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7bf0a4f9f15b32b5ba53147369e94296f5fffb783db5aacc1be15b4bf72f43b", size = 20848077 }, + { url = "https://files.pythonhosted.org/packages/b0/29/cb48a402ea879e645b16218718f3f7d9588a77d674a9dcf22e4c43487636/numpy-2.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b1d0fcae4f0949f215d4632be684a539859b295e2d0cb14f78ec231915d644db", size = 13493242 }, + { url = "https://files.pythonhosted.org/packages/56/44/f899b0581766c230da42f751b7b8896d096640b19b312164c267e48d36cb/numpy-2.1.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:f751ed0a2f250541e19dfca9f1eafa31a392c71c832b6bb9e113b10d050cb0f1", size = 5089219 }, + { url = "https://files.pythonhosted.org/packages/79/8f/b987070d45161a7a4504afc67ed38544ed2c0ed5576263599a0402204a9c/numpy-2.1.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:bd33f82e95ba7ad632bc57837ee99dba3d7e006536200c4e9124089e1bf42426", size = 6620167 }, + { url = "https://files.pythonhosted.org/packages/c4/a7/af3329fda3c3ec31d9b650e42bbcd3422fc62a765cbb1405fde4177a0996/numpy-2.1.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b8cde4f11f0a975d1fd59373b32e2f5a562ade7cde4f85b7137f3de8fbb29a0", size = 13604905 }, + { url = "https://files.pythonhosted.org/packages/9b/b4/e3c7e6fab0f77fff6194afa173d1f2342073d91b1d3b4b30b17c3fb4407a/numpy-2.1.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d95f286b8244b3649b477ac066c6906fbb2905f8ac19b170e2175d3d799f4df", size = 16041825 }, + { url = "https://files.pythonhosted.org/packages/e9/50/6828e66a78aa03147c111f84d55f33ce2dde547cb578d6744a3b06a0124b/numpy-2.1.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ab4754d432e3ac42d33a269c8567413bdb541689b02d93788af4131018cbf366", size = 16409541 }, + { url = "https://files.pythonhosted.org/packages/bf/72/66af7916d9c3c6dbfbc8acdd4930c65461e1953374a2bc43d00f948f004a/numpy-2.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e585c8ae871fd38ac50598f4763d73ec5497b0de9a0ab4ef5b69f01c6a046142", size = 14081134 }, + { url = "https://files.pythonhosted.org/packages/dc/5a/59a67d84f33fe00ae74f0b5b69dd4f93a586a4aba7f7e19b54b2133db038/numpy-2.1.2-cp312-cp312-win32.whl", hash = "sha256:9c6c754df29ce6a89ed23afb25550d1c2d5fdb9901d9c67a16e0b16eaf7e2550", size = 6237784 }, + { url = "https://files.pythonhosted.org/packages/4c/79/73735a6a5dad6059c085f240a4e74c9270feccd2bc66e4d31b5ca01d329c/numpy-2.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:456e3b11cb79ac9946c822a56346ec80275eaf2950314b249b512896c0d2505e", size = 12568254 }, + { url = "https://files.pythonhosted.org/packages/16/72/716fa1dbe92395a9a623d5049203ff8ddb0cfce65b9df9117c3696ccc011/numpy-2.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a84498e0d0a1174f2b3ed769b67b656aa5460c92c9554039e11f20a05650f00d", size = 20834690 }, + { url = "https://files.pythonhosted.org/packages/1e/fb/3e85a39511586053b5c6a59a643879e376fae22230ebfef9cfabb0e032e2/numpy-2.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4d6ec0d4222e8ffdab1744da2560f07856421b367928026fb540e1945f2eeeaf", size = 13507474 }, + { url = "https://files.pythonhosted.org/packages/35/eb/5677556d9ba13436dab51e129f98d4829d95cd1b6bd0e199c14485a4bdb9/numpy-2.1.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:259ec80d54999cc34cd1eb8ded513cb053c3bf4829152a2e00de2371bd406f5e", size = 5074742 }, + { url = "https://files.pythonhosted.org/packages/3e/c5/6c5ef5ba41b65a7e51bed50dbf3e1483eb578055633dd013e811a28e96a1/numpy-2.1.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:675c741d4739af2dc20cd6c6a5c4b7355c728167845e3c6b0e824e4e5d36a6c3", size = 6606787 }, + { url = "https://files.pythonhosted.org/packages/08/ac/f2f29dd4fd325b379c7dc932a0ebab22f0e031dbe80b2f6019b291a3a544/numpy-2.1.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05b2d4e667895cc55e3ff2b56077e4c8a5604361fc21a042845ea3ad67465aa8", size = 13601333 }, + { url = "https://files.pythonhosted.org/packages/44/26/63f5f4e5089654dfb858f4892215ed968cd1a68e6f4a83f9961f84f855cb/numpy-2.1.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43cca367bf94a14aca50b89e9bc2061683116cfe864e56740e083392f533ce7a", size = 16038090 }, + { url = "https://files.pythonhosted.org/packages/1d/21/015e0594de9c3a8d5edd24943d2bd23f102ec71aec026083f822f86497e2/numpy-2.1.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:76322dcdb16fccf2ac56f99048af32259dcc488d9b7e25b51e5eca5147a3fb98", size = 16410865 }, + { url = "https://files.pythonhosted.org/packages/df/01/c1bcf9e6025d79077fbf3f3ee503b50aa7bfabfcd8f4b54f5829f4c00f3f/numpy-2.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:32e16a03138cabe0cb28e1007ee82264296ac0983714094380b408097a418cfe", size = 14078077 }, + { url = "https://files.pythonhosted.org/packages/ba/06/db9d127d63bd11591770ba9f3d960f8041e0f895184b9351d4b1b5b56983/numpy-2.1.2-cp313-cp313-win32.whl", hash = "sha256:242b39d00e4944431a3cd2db2f5377e15b5785920421993770cddb89992c3f3a", size = 6234904 }, + { url = "https://files.pythonhosted.org/packages/a9/96/9f61f8f95b6e0ea0aa08633b704c75d1882bdcb331bdf8bfd63263b25b00/numpy-2.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:f2ded8d9b6f68cc26f8425eda5d3877b47343e68ca23d0d0846f4d312ecaa445", size = 12561910 }, + { url = "https://files.pythonhosted.org/packages/36/b8/033f627821784a48e8f75c218033471eebbaacdd933f8979c79637a1b44b/numpy-2.1.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2ffef621c14ebb0188a8633348504a35c13680d6da93ab5cb86f4e54b7e922b5", size = 20857719 }, + { url = "https://files.pythonhosted.org/packages/96/46/af5726fde5b74ed83f2f17a73386d399319b7ed4d51279fb23b721d0816d/numpy-2.1.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ad369ed238b1959dfbade9018a740fb9392c5ac4f9b5173f420bd4f37ba1f7a0", size = 13518826 }, + { url = "https://files.pythonhosted.org/packages/db/6e/8ce677edf36da1c4dae80afe5529f47690697eb55b4864673af260ccea7b/numpy-2.1.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d82075752f40c0ddf57e6e02673a17f6cb0f8eb3f587f63ca1eaab5594da5b17", size = 5115036 }, + { url = "https://files.pythonhosted.org/packages/6a/ba/3cce44fb1b8438042c11847048812a776f75ee0e7070179c22e4cfbf420c/numpy-2.1.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:1600068c262af1ca9580a527d43dc9d959b0b1d8e56f8a05d830eea39b7c8af6", size = 6628641 }, + { url = "https://files.pythonhosted.org/packages/59/c8/e722998720ccbd35ffbcf1d1b8ed0aa2304af88d3f1c38e06ebf983599b3/numpy-2.1.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a26ae94658d3ba3781d5e103ac07a876b3e9b29db53f68ed7df432fd033358a8", size = 13574803 }, + { url = "https://files.pythonhosted.org/packages/7c/8e/fc1fdd83a55476765329ac2913321c4aed5b082a7915095628c4ca30ea72/numpy-2.1.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13311c2db4c5f7609b462bc0f43d3c465424d25c626d95040f073e30f7570e35", size = 16021174 }, + { url = "https://files.pythonhosted.org/packages/2a/b6/a790742aa88067adb4bd6c89a946778c1417d4deaeafce3ca928f26d4c52/numpy-2.1.2-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:2abbf905a0b568706391ec6fa15161fad0fb5d8b68d73c461b3c1bab6064dd62", size = 16400117 }, + { url = "https://files.pythonhosted.org/packages/48/6f/129e3c17e3befe7fefdeaa6890f4c4df3f3cf0831aa053802c3862da67aa/numpy-2.1.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ef444c57d664d35cac4e18c298c47d7b504c66b17c2ea91312e979fcfbdfb08a", size = 14066202 }, + { url = "https://files.pythonhosted.org/packages/73/c9/3e1d6bbe6d3d2e2c5a9483b24b2f29a229b323f62054278a3bba7fee11e5/numpy-2.1.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:bdd407c40483463898b84490770199d5714dcc9dd9b792f6c6caccc523c00952", size = 20981945 }, + { url = "https://files.pythonhosted.org/packages/6e/62/989c4988bde1a8e08117fccc3bab73d2886421fb98cde597168714f3c54e/numpy-2.1.2-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:da65fb46d4cbb75cb417cddf6ba5e7582eb7bb0b47db4b99c9fe5787ce5d91f5", size = 6750558 }, + { url = "https://files.pythonhosted.org/packages/53/b1/00ef9f30975f1312a53257f68e57b4513d14d537e03d507e2773a684b1e8/numpy-2.1.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c193d0b0238638e6fc5f10f1b074a6993cb13b0b431f64079a509d63d3aa8b7", size = 16141552 }, + { url = "https://files.pythonhosted.org/packages/c0/ec/0c04903b48dfea6be1d7b47ba70f98709fb7198fd970784a1400c391d522/numpy-2.1.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a7d80b2e904faa63068ead63107189164ca443b42dd1930299e0d1cb041cec2e", size = 12789924 }, + ] + + [[package]] + name = "project" + version = "0.1.0" + source = { editable = "." } + dependencies = [ + { name = "numpy", version = "1.24.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "numpy", version = "2.1.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + ] + + [package.metadata] + requires-dist = [ + { name = "numpy", marker = "python_full_version >= '3.8'" }, + { name = "numpy", marker = "python_full_version >= '3.9'" }, + { name = "numpy", marker = "python_full_version >= '3.10'" }, + { name = "numpy", marker = "python_full_version >= '3.11'" }, + { name = "numpy", marker = "python_full_version >= '3.12'" }, + ] + "### + ); + }); + + // Re-run with `--locked`. + uv_snapshot!(context.filters(), context.lock().arg("--locked").arg("--multi-version").arg("latest").env_remove("UV_EXCLUDE_NEWER"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 4 packages in [TIME] + "###); + + // Re-run with `--offline`. We shouldn't need a network connection to validate an + // already-correct lockfile with immutable metadata. + uv_snapshot!(context.filters(), context.lock().arg("--locked").arg("--offline").arg("--no-cache").arg("--multi-version").arg("latest").env_remove("UV_EXCLUDE_NEWER"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 4 packages in [TIME] + "###); + + Ok(()) +} + #[test] fn lock_multiple_sources() -> Result<()> { let context = TestContext::new("3.12"); diff --git a/crates/uv/tests/it/pip_install.rs b/crates/uv/tests/it/pip_install.rs index 1862e80e77597..739c21a5bd5cf 100644 --- a/crates/uv/tests/it/pip_install.rs +++ b/crates/uv/tests/it/pip_install.rs @@ -191,7 +191,7 @@ fn invalid_pyproject_toml_option_unknown_field() -> Result<()> { | 2 | unknown = "field" | ^^^^^^^ - unknown field `unknown`, expected one of `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`, `allow-insecure-host`, `resolution`, `prerelease`, `dependency-metadata`, `config-settings`, `no-build-isolation`, `no-build-isolation-package`, `exclude-newer`, `link-mode`, `compile-bytecode`, `no-sources`, `upgrade`, `upgrade-package`, `reinstall`, `reinstall-package`, `no-build`, `no-build-package`, `no-binary`, `no-binary-package`, `publish-url`, `trusted-publishing`, `pip`, `cache-keys`, `override-dependencies`, `constraint-dependencies`, `environments`, `workspace`, `sources`, `managed`, `package`, `default-groups`, `dev-dependencies` + unknown field `unknown`, expected one of `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`, `allow-insecure-host`, `resolution`, `prerelease`, `multi-version`, `dependency-metadata`, `config-settings`, `no-build-isolation`, `no-build-isolation-package`, `exclude-newer`, `link-mode`, `compile-bytecode`, `no-sources`, `upgrade`, `upgrade-package`, `reinstall`, `reinstall-package`, `no-build`, `no-build-package`, `no-binary`, `no-binary-package`, `publish-url`, `trusted-publishing`, `pip`, `cache-keys`, `override-dependencies`, `constraint-dependencies`, `environments`, `workspace`, `sources`, `managed`, `package`, `default-groups`, `dev-dependencies` Resolved in [TIME] Audited in [TIME] diff --git a/crates/uv/tests/it/show_settings.rs b/crates/uv/tests/it/show_settings.rs index 4275db0ac491c..a7a3044e702ad 100644 --- a/crates/uv/tests/it/show_settings.rs +++ b/crates/uv/tests/it/show_settings.rs @@ -141,6 +141,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> { dependency_mode: Transitive, resolution: LowestDirect, prerelease: IfNecessaryOrExplicit, + multi_version: Fewest, dependency_metadata: DependencyMetadata( {}, ), @@ -288,6 +289,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> { dependency_mode: Transitive, resolution: Highest, prerelease: IfNecessaryOrExplicit, + multi_version: Fewest, dependency_metadata: DependencyMetadata( {}, ), @@ -436,6 +438,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> { dependency_mode: Transitive, resolution: Highest, prerelease: IfNecessaryOrExplicit, + multi_version: Fewest, dependency_metadata: DependencyMetadata( {}, ), @@ -616,6 +619,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> { dependency_mode: Transitive, resolution: LowestDirect, prerelease: IfNecessaryOrExplicit, + multi_version: Fewest, dependency_metadata: DependencyMetadata( {}, ), @@ -736,6 +740,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> { dependency_mode: Transitive, resolution: Highest, prerelease: IfNecessaryOrExplicit, + multi_version: Fewest, dependency_metadata: DependencyMetadata( {}, ), @@ -894,6 +899,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> { dependency_mode: Transitive, resolution: LowestDirect, prerelease: IfNecessaryOrExplicit, + multi_version: Fewest, dependency_metadata: DependencyMetadata( {}, ), @@ -1094,6 +1100,7 @@ fn resolve_index_url() -> anyhow::Result<()> { dependency_mode: Transitive, resolution: Highest, prerelease: IfNecessaryOrExplicit, + multi_version: Fewest, dependency_metadata: DependencyMetadata( {}, ), @@ -1301,6 +1308,7 @@ fn resolve_index_url() -> anyhow::Result<()> { dependency_mode: Transitive, resolution: Highest, prerelease: IfNecessaryOrExplicit, + multi_version: Fewest, dependency_metadata: DependencyMetadata( {}, ), @@ -1473,6 +1481,7 @@ fn resolve_find_links() -> anyhow::Result<()> { dependency_mode: Transitive, resolution: Highest, prerelease: IfNecessaryOrExplicit, + multi_version: Fewest, dependency_metadata: DependencyMetadata( {}, ), @@ -1615,6 +1624,7 @@ fn resolve_top_level() -> anyhow::Result<()> { dependency_mode: Transitive, resolution: LowestDirect, prerelease: IfNecessaryOrExplicit, + multi_version: Fewest, dependency_metadata: DependencyMetadata( {}, ), @@ -1807,6 +1817,7 @@ fn resolve_top_level() -> anyhow::Result<()> { dependency_mode: Transitive, resolution: Highest, prerelease: IfNecessaryOrExplicit, + multi_version: Fewest, dependency_metadata: DependencyMetadata( {}, ), @@ -1982,6 +1993,7 @@ fn resolve_top_level() -> anyhow::Result<()> { dependency_mode: Transitive, resolution: LowestDirect, prerelease: IfNecessaryOrExplicit, + multi_version: Fewest, dependency_metadata: DependencyMetadata( {}, ), @@ -2124,6 +2136,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> { dependency_mode: Transitive, resolution: LowestDirect, prerelease: IfNecessaryOrExplicit, + multi_version: Fewest, dependency_metadata: DependencyMetadata( {}, ), @@ -2249,6 +2262,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> { dependency_mode: Transitive, resolution: LowestDirect, prerelease: IfNecessaryOrExplicit, + multi_version: Fewest, dependency_metadata: DependencyMetadata( {}, ), @@ -2374,6 +2388,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> { dependency_mode: Transitive, resolution: Highest, prerelease: IfNecessaryOrExplicit, + multi_version: Fewest, dependency_metadata: DependencyMetadata( {}, ), @@ -2501,6 +2516,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> { dependency_mode: Transitive, resolution: LowestDirect, prerelease: IfNecessaryOrExplicit, + multi_version: Fewest, dependency_metadata: DependencyMetadata( {}, ), @@ -2629,6 +2645,7 @@ fn resolve_tool() -> anyhow::Result<()> { LowestDirect, ), prerelease: None, + multi_version: None, dependency_metadata: None, config_settings: None, no_build_isolation: None, @@ -2663,6 +2680,7 @@ fn resolve_tool() -> anyhow::Result<()> { allow_insecure_host: [], resolution: LowestDirect, prerelease: IfNecessaryOrExplicit, + multi_version: Fewest, dependency_metadata: DependencyMetadata( {}, ), @@ -2805,6 +2823,7 @@ fn resolve_poetry_toml() -> anyhow::Result<()> { dependency_mode: Transitive, resolution: LowestDirect, prerelease: IfNecessaryOrExplicit, + multi_version: Fewest, dependency_metadata: DependencyMetadata( {}, ), @@ -2987,6 +3006,7 @@ fn resolve_both() -> anyhow::Result<()> { dependency_mode: Transitive, resolution: LowestDirect, prerelease: IfNecessaryOrExplicit, + multi_version: Fewest, dependency_metadata: DependencyMetadata( {}, ), @@ -3161,6 +3181,7 @@ fn resolve_config_file() -> anyhow::Result<()> { dependency_mode: Transitive, resolution: LowestDirect, prerelease: IfNecessaryOrExplicit, + multi_version: Fewest, dependency_metadata: DependencyMetadata( {}, ), @@ -3230,7 +3251,7 @@ fn resolve_config_file() -> anyhow::Result<()> { | 1 | [project] | ^^^^^^^ - unknown field `project`, expected one of `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`, `allow-insecure-host`, `resolution`, `prerelease`, `dependency-metadata`, `config-settings`, `no-build-isolation`, `no-build-isolation-package`, `exclude-newer`, `link-mode`, `compile-bytecode`, `no-sources`, `upgrade`, `upgrade-package`, `reinstall`, `reinstall-package`, `no-build`, `no-build-package`, `no-binary`, `no-binary-package`, `publish-url`, `trusted-publishing`, `pip`, `cache-keys`, `override-dependencies`, `constraint-dependencies`, `environments`, `workspace`, `sources`, `managed`, `package`, `default-groups`, `dev-dependencies` + unknown field `project`, expected one of `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`, `allow-insecure-host`, `resolution`, `prerelease`, `multi-version`, `dependency-metadata`, `config-settings`, `no-build-isolation`, `no-build-isolation-package`, `exclude-newer`, `link-mode`, `compile-bytecode`, `no-sources`, `upgrade`, `upgrade-package`, `reinstall`, `reinstall-package`, `no-build`, `no-build-package`, `no-binary`, `no-binary-package`, `publish-url`, `trusted-publishing`, `pip`, `cache-keys`, `override-dependencies`, `constraint-dependencies`, `environments`, `workspace`, `sources`, `managed`, `package`, `default-groups`, `dev-dependencies` "### ); @@ -3380,6 +3401,7 @@ fn resolve_skip_empty() -> anyhow::Result<()> { dependency_mode: Transitive, resolution: LowestDirect, prerelease: IfNecessaryOrExplicit, + multi_version: Fewest, dependency_metadata: DependencyMetadata( {}, ), @@ -3508,6 +3530,7 @@ fn resolve_skip_empty() -> anyhow::Result<()> { dependency_mode: Transitive, resolution: Highest, prerelease: IfNecessaryOrExplicit, + multi_version: Fewest, dependency_metadata: DependencyMetadata( {}, ), @@ -3655,6 +3678,7 @@ fn allow_insecure_host() -> anyhow::Result<()> { dependency_mode: Transitive, resolution: Highest, prerelease: IfNecessaryOrExplicit, + multi_version: Fewest, dependency_metadata: DependencyMetadata( {}, ), @@ -3853,6 +3877,7 @@ fn index_priority() -> anyhow::Result<()> { dependency_mode: Transitive, resolution: Highest, prerelease: IfNecessaryOrExplicit, + multi_version: Fewest, dependency_metadata: DependencyMetadata( {}, ), @@ -4030,6 +4055,7 @@ fn index_priority() -> anyhow::Result<()> { dependency_mode: Transitive, resolution: Highest, prerelease: IfNecessaryOrExplicit, + multi_version: Fewest, dependency_metadata: DependencyMetadata( {}, ), @@ -4213,6 +4239,7 @@ fn index_priority() -> anyhow::Result<()> { dependency_mode: Transitive, resolution: Highest, prerelease: IfNecessaryOrExplicit, + multi_version: Fewest, dependency_metadata: DependencyMetadata( {}, ), @@ -4391,6 +4418,7 @@ fn index_priority() -> anyhow::Result<()> { dependency_mode: Transitive, resolution: Highest, prerelease: IfNecessaryOrExplicit, + multi_version: Fewest, dependency_metadata: DependencyMetadata( {}, ), @@ -4576,6 +4604,7 @@ fn index_priority() -> anyhow::Result<()> { dependency_mode: Transitive, resolution: Highest, prerelease: IfNecessaryOrExplicit, + multi_version: Fewest, dependency_metadata: DependencyMetadata( {}, ), @@ -4754,6 +4783,7 @@ fn index_priority() -> anyhow::Result<()> { dependency_mode: Transitive, resolution: Highest, prerelease: IfNecessaryOrExplicit, + multi_version: Fewest, dependency_metadata: DependencyMetadata( {}, ), diff --git a/docs/reference/cli.md b/docs/reference/cli.md index a32e7ede4c3c7..7a0f78d10087a 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -246,6 +246,18 @@ uv run [OPTIONS] [COMMAND]

Equivalent to python -m <module>.

+
--multi-version multi-version

The strategy to use when selecting multiple versions of a given package across Python versions and platforms.

+ +

By default, uv will minimize the number of versions selected for each package (fewest), to minimize differences between environments. Under latest, uv will select the latest compatible version for each environment, even if it results in more versions being selected.

+ +

May also be set with the UV_MULTI_VERSION environment variable.

+

Possible values:

+ +
    +
  • fewest: Resolve the highest compatible version of each package
  • + +
  • latest: Resolve the lowest compatible version of each package
  • +
--native-tls

Whether to load TLS certificates from the platform’s native certificate store.

By default, uv loads certificates from the bundled webpki-roots crate. The webpki-roots are a reliable set of trust roots from Mozilla, and including them in uv improves portability and performance (especially on macOS).

@@ -853,6 +865,18 @@ uv add [OPTIONS] >

Requires that the lockfile is up-to-date. If the lockfile is missing or needs to be updated, uv will exit with an error.

May also be set with the UV_LOCKED environment variable.

+
--multi-version multi-version

The strategy to use when selecting multiple versions of a given package across Python versions and platforms.

+ +

By default, uv will minimize the number of versions selected for each package (fewest), to minimize differences between environments. Under latest, uv will select the latest compatible version for each environment, even if it results in more versions being selected.

+ +

May also be set with the UV_MULTI_VERSION environment variable.

+

Possible values:

+ +
    +
  • fewest: Resolve the highest compatible version of each package
  • + +
  • latest: Resolve the lowest compatible version of each package
  • +
--native-tls

Whether to load TLS certificates from the platform’s native certificate store.

By default, uv loads certificates from the bundled webpki-roots crate. The webpki-roots are a reliable set of trust roots from Mozilla, and including them in uv improves portability and performance (especially on macOS).

@@ -1194,6 +1218,18 @@ uv remove [OPTIONS] ...

Requires that the lockfile is up-to-date. If the lockfile is missing or needs to be updated, uv will exit with an error.

May also be set with the UV_LOCKED environment variable.

+
--multi-version multi-version

The strategy to use when selecting multiple versions of a given package across Python versions and platforms.

+ +

By default, uv will minimize the number of versions selected for each package (fewest), to minimize differences between environments. Under latest, uv will select the latest compatible version for each environment, even if it results in more versions being selected.

+ +

May also be set with the UV_MULTI_VERSION environment variable.

+

Possible values:

+ +
    +
  • fewest: Resolve the highest compatible version of each package
  • + +
  • latest: Resolve the lowest compatible version of each package
  • +
--native-tls

Whether to load TLS certificates from the platform’s native certificate store.

By default, uv loads certificates from the bundled webpki-roots crate. The webpki-roots are a reliable set of trust roots from Mozilla, and including them in uv improves portability and performance (especially on macOS).

@@ -1527,6 +1563,18 @@ uv sync [OPTIONS]

Requires that the lockfile is up-to-date. If the lockfile is missing or needs to be updated, uv will exit with an error.

May also be set with the UV_LOCKED environment variable.

+
--multi-version multi-version

The strategy to use when selecting multiple versions of a given package across Python versions and platforms.

+ +

By default, uv will minimize the number of versions selected for each package (fewest), to minimize differences between environments. Under latest, uv will select the latest compatible version for each environment, even if it results in more versions being selected.

+ +

May also be set with the UV_MULTI_VERSION environment variable.

+

Possible values:

+ +
    +
  • fewest: Resolve the highest compatible version of each package
  • + +
  • latest: Resolve the lowest compatible version of each package
  • +
--native-tls

Whether to load TLS certificates from the platform’s native certificate store.

By default, uv loads certificates from the bundled webpki-roots crate. The webpki-roots are a reliable set of trust roots from Mozilla, and including them in uv improves portability and performance (especially on macOS).

@@ -1864,6 +1912,18 @@ uv lock [OPTIONS]

Requires that the lockfile is up-to-date. If the lockfile is missing or needs to be updated, uv will exit with an error.

May also be set with the UV_LOCKED environment variable.

+
--multi-version multi-version

The strategy to use when selecting multiple versions of a given package across Python versions and platforms.

+ +

By default, uv will minimize the number of versions selected for each package (fewest), to minimize differences between environments. Under latest, uv will select the latest compatible version for each environment, even if it results in more versions being selected.

+ +

May also be set with the UV_MULTI_VERSION environment variable.

+

Possible values:

+ +
    +
  • fewest: Resolve the highest compatible version of each package
  • + +
  • latest: Resolve the lowest compatible version of each package
  • +
--native-tls

Whether to load TLS certificates from the platform’s native certificate store.

By default, uv loads certificates from the bundled webpki-roots crate. The webpki-roots are a reliable set of trust roots from Mozilla, and including them in uv improves portability and performance (especially on macOS).

@@ -2179,6 +2239,18 @@ uv export [OPTIONS]

Requires that the lockfile is up-to-date. If the lockfile is missing or needs to be updated, uv will exit with an error.

May also be set with the UV_LOCKED environment variable.

+
--multi-version multi-version

The strategy to use when selecting multiple versions of a given package across Python versions and platforms.

+ +

By default, uv will minimize the number of versions selected for each package (fewest), to minimize differences between environments. Under latest, uv will select the latest compatible version for each environment, even if it results in more versions being selected.

+ +

May also be set with the UV_MULTI_VERSION environment variable.

+

Possible values:

+ +
    +
  • fewest: Resolve the highest compatible version of each package
  • + +
  • latest: Resolve the lowest compatible version of each package
  • +
--native-tls

Whether to load TLS certificates from the platform’s native certificate store.

By default, uv loads certificates from the bundled webpki-roots crate. The webpki-roots are a reliable set of trust roots from Mozilla, and including them in uv improves portability and performance (especially on macOS).

@@ -2519,6 +2591,18 @@ uv tree [OPTIONS]

Requires that the lockfile is up-to-date. If the lockfile is missing or needs to be updated, uv will exit with an error.

May also be set with the UV_LOCKED environment variable.

+
--multi-version multi-version

The strategy to use when selecting multiple versions of a given package across Python versions and platforms.

+ +

By default, uv will minimize the number of versions selected for each package (fewest), to minimize differences between environments. Under latest, uv will select the latest compatible version for each environment, even if it results in more versions being selected.

+ +

May also be set with the UV_MULTI_VERSION environment variable.

+

Possible values:

+ +
    +
  • fewest: Resolve the highest compatible version of each package
  • + +
  • latest: Resolve the lowest compatible version of each package
  • +
--native-tls

Whether to load TLS certificates from the platform’s native certificate store.

By default, uv loads certificates from the bundled webpki-roots crate. The webpki-roots are a reliable set of trust roots from Mozilla, and including them in uv improves portability and performance (especially on macOS).

@@ -2922,6 +3006,18 @@ uv tool run [OPTIONS] [COMMAND]
  • symlink: Symbolically link packages from the wheel into the site-packages directory
  • +
    --multi-version multi-version

    The strategy to use when selecting multiple versions of a given package across Python versions and platforms.

    + +

    By default, uv will minimize the number of versions selected for each package (fewest), to minimize differences between environments. Under latest, uv will select the latest compatible version for each environment, even if it results in more versions being selected.

    + +

    May also be set with the UV_MULTI_VERSION environment variable.

    +

    Possible values:

    + +
      +
    • fewest: Resolve the highest compatible version of each package
    • + +
    • latest: Resolve the lowest compatible version of each package
    • +
    --native-tls

    Whether to load TLS certificates from the platform’s native certificate store.

    By default, uv loads certificates from the bundled webpki-roots crate. The webpki-roots are a reliable set of trust roots from Mozilla, and including them in uv improves portability and performance (especially on macOS).

    @@ -3226,6 +3322,18 @@ uv tool install [OPTIONS]
  • symlink: Symbolically link packages from the wheel into the site-packages directory
  • +
    --multi-version multi-version

    The strategy to use when selecting multiple versions of a given package across Python versions and platforms.

    + +

    By default, uv will minimize the number of versions selected for each package (fewest), to minimize differences between environments. Under latest, uv will select the latest compatible version for each environment, even if it results in more versions being selected.

    + +

    May also be set with the UV_MULTI_VERSION environment variable.

    +

    Possible values:

    + +
      +
    • fewest: Resolve the highest compatible version of each package
    • + +
    • latest: Resolve the lowest compatible version of each package
    • +
    --native-tls

    Whether to load TLS certificates from the platform’s native certificate store.

    By default, uv loads certificates from the bundled webpki-roots crate. The webpki-roots are a reliable set of trust roots from Mozilla, and including them in uv improves portability and performance (especially on macOS).

    @@ -3526,6 +3634,18 @@ uv tool upgrade [OPTIONS] ...
  • symlink: Symbolically link packages from the wheel into the site-packages directory
  • +
    --multi-version multi-version

    The strategy to use when selecting multiple versions of a given package across Python versions and platforms.

    + +

    By default, uv will minimize the number of versions selected for each package (fewest), to minimize differences between environments. Under latest, uv will select the latest compatible version for each environment, even if it results in more versions being selected.

    + +

    May also be set with the UV_MULTI_VERSION environment variable.

    +

    Possible values:

    + +
      +
    • fewest: Resolve the highest compatible version of each package
    • + +
    • latest: Resolve the lowest compatible version of each package
    • +
    --native-tls

    Whether to load TLS certificates from the platform’s native certificate store.

    By default, uv loads certificates from the bundled webpki-roots crate. The webpki-roots are a reliable set of trust roots from Mozilla, and including them in uv improves portability and performance (especially on macOS).

    @@ -5142,6 +5262,18 @@ uv pip compile [OPTIONS] ...
  • symlink: Symbolically link packages from the wheel into the site-packages directory
  • +
    --multi-version multi-version

    The strategy to use when selecting multiple versions of a given package across Python versions and platforms.

    + +

    By default, uv will minimize the number of versions selected for each package (fewest), to minimize differences between environments. Under latest, uv will select the latest compatible version for each environment, even if it results in more versions being selected.

    + +

    May also be set with the UV_MULTI_VERSION environment variable.

    +

    Possible values:

    + +
      +
    • fewest: Resolve the highest compatible version of each package
    • + +
    • latest: Resolve the lowest compatible version of each package
    • +
    --native-tls

    Whether to load TLS certificates from the platform’s native certificate store.

    By default, uv loads certificates from the bundled webpki-roots crate. The webpki-roots are a reliable set of trust roots from Mozilla, and including them in uv improves portability and performance (especially on macOS).

    @@ -5908,6 +6040,18 @@ uv pip install [OPTIONS] |--editable symlink: Symbolically link packages from the wheel into the site-packages directory +
    --multi-version multi-version

    The strategy to use when selecting multiple versions of a given package across Python versions and platforms.

    + +

    By default, uv will minimize the number of versions selected for each package (fewest), to minimize differences between environments. Under latest, uv will select the latest compatible version for each environment, even if it results in more versions being selected.

    + +

    May also be set with the UV_MULTI_VERSION environment variable.

    +

    Possible values:

    + +
      +
    • fewest: Resolve the highest compatible version of each package
    • + +
    • latest: Resolve the lowest compatible version of each package
    • +
    --native-tls

    Whether to load TLS certificates from the platform’s native certificate store.

    By default, uv loads certificates from the bundled webpki-roots crate. The webpki-roots are a reliable set of trust roots from Mozilla, and including them in uv improves portability and performance (especially on macOS).

    @@ -7382,6 +7526,18 @@ uv build [OPTIONS] [SRC]
  • symlink: Symbolically link packages from the wheel into the site-packages directory
  • +
    --multi-version multi-version

    The strategy to use when selecting multiple versions of a given package across Python versions and platforms.

    + +

    By default, uv will minimize the number of versions selected for each package (fewest), to minimize differences between environments. Under latest, uv will select the latest compatible version for each environment, even if it results in more versions being selected.

    + +

    May also be set with the UV_MULTI_VERSION environment variable.

    +

    Possible values:

    + +
      +
    • fewest: Resolve the highest compatible version of each package
    • + +
    • latest: Resolve the lowest compatible version of each package
    • +
    --native-tls

    Whether to load TLS certificates from the platform’s native certificate store.

    By default, uv loads certificates from the bundled webpki-roots crate. The webpki-roots are a reliable set of trust roots from Mozilla, and including them in uv improves portability and performance (especially on macOS).

    diff --git a/docs/reference/settings.md b/docs/reference/settings.md index 222eed8be5240..08c4aeab36953 100644 --- a/docs/reference/settings.md +++ b/docs/reference/settings.md @@ -854,6 +854,38 @@ Windows. --- +### [`multi-version`](#multi-version) {: #multi-version } + +The strategy to use when selecting multiple versions of a given package across Python +versions and platforms. + +By default, uv will minimize the number of versions selected for each package (`fewest`), +to minimize differences between environments. Under `latest`, uv will select the latest +compatible version for each environment, even if it results in more versions being selected. + +**Default value**: `"fewest"` + +**Possible values**: + +- `"fewest"`: Resolve the highest compatible version of each package +- `"latest"`: Resolve the lowest compatible version of each package + +**Example usage**: + +=== "pyproject.toml" + + ```toml + [tool.uv] + resolution = "latest" + ``` +=== "uv.toml" + + ```toml + resolution = "latest" + ``` + +--- + ### [`native-tls`](#native-tls) {: #native-tls } Whether to load TLS certificates from the platform's native certificate store. @@ -2149,6 +2181,40 @@ Windows. --- +#### [`multi-version`](#pip_multi-version) {: #pip_multi-version } + + +The strategy to use when selecting multiple versions of a given package across Python +versions and platforms. + +By default, uv will minimize the number of versions selected for each package (`fewest`), +to minimize differences between environments. Under `latest`, uv will select the latest +compatible version for each environment, even if it results in more versions being selected. + +**Default value**: `"fewest"` + +**Possible values**: + +- `"fewest"`: Resolve the highest compatible version of each package +- `"latest"`: Resolve the lowest compatible version of each package + +**Example usage**: + +=== "pyproject.toml" + + ```toml + [tool.uv.pip] + resolution = "latest" + ``` +=== "uv.toml" + + ```toml + [pip] + resolution = "latest" + ``` + +--- + #### [`no-annotate`](#pip_no-annotate) {: #pip_no-annotate } diff --git a/uv.schema.json b/uv.schema.json index 15ed7a0e03af3..641a6b5186b35 100644 --- a/uv.schema.json +++ b/uv.schema.json @@ -218,6 +218,17 @@ "null" ] }, + "multi-version": { + "description": "The strategy to use when selecting multiple versions of a given package across Python versions and platforms.\n\nBy default, uv will minimize the number of versions selected for each package (`fewest`), to minimize differences between environments. Under `latest`, uv will select the latest compatible version for each environment, even if it results in more versions being selected.", + "anyOf": [ + { + "$ref": "#/definitions/MultiVersionMode" + }, + { + "type": "null" + } + ] + }, "native-tls": { "description": "Whether to load TLS certificates from the platform's native certificate store.\n\nBy default, uv loads certificates from the bundled `webpki-roots` crate. The `webpki-roots` are a reliable set of trust roots from Mozilla, and including them in uv improves portability and performance (especially on macOS).\n\nHowever, in some cases, you may want to use the platform's native certificate store, especially if you're relying on a corporate trust root (e.g., for a mandatory proxy) that's included in your system's certificate store.", "type": [ @@ -718,6 +729,24 @@ "description": "A PEP 508-compliant marker expression, e.g., `sys_platform == 'Darwin'`", "type": "string" }, + "MultiVersionMode": { + "oneOf": [ + { + "description": "Resolve the highest compatible version of each package.", + "type": "string", + "enum": [ + "fewest" + ] + }, + { + "description": "Resolve the lowest compatible version of each package.", + "type": "string", + "enum": [ + "latest" + ] + } + ] + }, "PackageName": { "description": "The normalized name of a package.\n\nConverts the name to lowercase and collapses runs of `-`, `_`, and `.` down to a single `-`. For example, `---`, `.`, and `__` are all converted to a single `-`.\n\nSee: ", "type": "string" @@ -935,6 +964,17 @@ } ] }, + "multi-version": { + "description": "The strategy to use when selecting multiple versions of a given package across Python versions and platforms.\n\nBy default, uv will minimize the number of versions selected for each package (`fewest`), to minimize differences between environments. Under `latest`, uv will select the latest compatible version for each environment, even if it results in more versions being selected.", + "anyOf": [ + { + "$ref": "#/definitions/MultiVersionMode" + }, + { + "type": "null" + } + ] + }, "no-annotate": { "description": "Exclude comment annotations indicating the source of each package from the output file generated by `uv pip compile`.", "type": [