mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 21:35:00 +00:00
Allow constraints to be provided in --upgrade-package
(#4952)
## Summary Allows, e.g., `--upgrade-package flask<3.0.0`. Closes https://github.com/astral-sh/uv/issues/1964.
This commit is contained in:
parent
5b6dffe522
commit
23eb42deed
15 changed files with 175 additions and 34 deletions
3
Cargo.lock
generated
3
Cargo.lock
generated
|
@ -2457,6 +2457,7 @@ dependencies = [
|
||||||
"pyo3",
|
"pyo3",
|
||||||
"pyo3-log",
|
"pyo3-log",
|
||||||
"regex",
|
"regex",
|
||||||
|
"schemars",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"testing_logger",
|
"testing_logger",
|
||||||
|
@ -4598,6 +4599,8 @@ dependencies = [
|
||||||
"fs-err",
|
"fs-err",
|
||||||
"insta",
|
"insta",
|
||||||
"install-wheel-rs",
|
"install-wheel-rs",
|
||||||
|
"pep508_rs",
|
||||||
|
"pypi-types",
|
||||||
"serde",
|
"serde",
|
||||||
"url",
|
"url",
|
||||||
"uv-cache",
|
"uv-cache",
|
||||||
|
|
|
@ -26,6 +26,7 @@ pep440_rs = { workspace = true }
|
||||||
pyo3 = { workspace = true, optional = true, features = ["abi3", "extension-module"] }
|
pyo3 = { workspace = true, optional = true, features = ["abi3", "extension-module"] }
|
||||||
pyo3-log = { workspace = true, optional = true }
|
pyo3-log = { workspace = true, optional = true }
|
||||||
regex = { workspace = true }
|
regex = { workspace = true }
|
||||||
|
schemars = { workspace = true, optional = true }
|
||||||
serde = { workspace = true, features = ["derive", "rc"] }
|
serde = { workspace = true, features = ["derive", "rc"] }
|
||||||
serde_json = { workspace = true, optional = true }
|
serde_json = { workspace = true, optional = true }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
|
@ -44,6 +45,7 @@ testing_logger = { version = "0.1.1" }
|
||||||
[features]
|
[features]
|
||||||
pyo3 = ["dep:pyo3", "pep440_rs/pyo3", "pyo3-log", "tracing", "tracing/log"]
|
pyo3 = ["dep:pyo3", "pep440_rs/pyo3", "pyo3-log", "tracing", "tracing/log"]
|
||||||
tracing = ["dep:tracing", "pep440_rs/tracing"]
|
tracing = ["dep:tracing", "pep440_rs/tracing"]
|
||||||
|
schemars = ["dep:schemars"]
|
||||||
# PEP 508 allows only URLs such as `foo @ https://example.org/foo` or `foo @ file:///home/ferris/foo`, and
|
# PEP 508 allows only URLs such as `foo @ https://example.org/foo` or `foo @ file:///home/ferris/foo`, and
|
||||||
# arguably does not allow relative paths in file URLs (`foo @ file://./foo`,
|
# arguably does not allow relative paths in file URLs (`foo @ file://./foo`,
|
||||||
# `foo @ file:foo-3.0.0-py3-none-any.whl`, `foo @ file://foo-3.0.0-py3-none-any.whl`), as they are not part of the
|
# `foo @ file:foo-3.0.0-py3-none-any.whl`, `foo @ file://foo-3.0.0-py3-none-any.whl`), as they are not part of the
|
||||||
|
|
|
@ -33,6 +33,8 @@ use pyo3::{
|
||||||
create_exception, exceptions::PyNotImplementedError, pyclass, pyclass::CompareOp, pymethods,
|
create_exception, exceptions::PyNotImplementedError, pyclass, pyclass::CompareOp, pymethods,
|
||||||
pymodule, types::PyModule, IntoPy, PyObject, PyResult, Python,
|
pymodule, types::PyModule, IntoPy, PyObject, PyResult, Python,
|
||||||
};
|
};
|
||||||
|
use schemars::gen::SchemaGenerator;
|
||||||
|
use schemars::schema::Schema;
|
||||||
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
@ -489,6 +491,25 @@ impl Reporter for TracingReporter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "schemars")]
|
||||||
|
impl<T: Pep508Url> schemars::JsonSchema for Requirement<T> {
|
||||||
|
fn schema_name() -> String {
|
||||||
|
"Requirement".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn json_schema(_gen: &mut SchemaGenerator) -> Schema {
|
||||||
|
schemars::schema::SchemaObject {
|
||||||
|
instance_type: Some(schemars::schema::InstanceType::String.into()),
|
||||||
|
metadata: Some(Box::new(schemars::schema::Metadata {
|
||||||
|
description: Some("A PEP 508 dependency specifier".to_string()),
|
||||||
|
..schemars::schema::Metadata::default()
|
||||||
|
})),
|
||||||
|
..schemars::schema::SchemaObject::default()
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: Pep508Url> FromStr for Requirement<T> {
|
impl<T: Pep508Url> FromStr for Requirement<T> {
|
||||||
type Err = Pep508Error<T>;
|
type Err = Pep508Error<T>;
|
||||||
|
|
||||||
|
|
|
@ -16,12 +16,14 @@ workspace = true
|
||||||
[dependencies]
|
[dependencies]
|
||||||
distribution-types = { workspace = true }
|
distribution-types = { workspace = true }
|
||||||
install-wheel-rs = { workspace = true, features = ["clap"], default-features = false }
|
install-wheel-rs = { workspace = true, features = ["clap"], default-features = false }
|
||||||
|
pep508_rs = { workspace = true }
|
||||||
|
pypi-types = { workspace = true }
|
||||||
uv-cache = { workspace = true, features = ["clap"] }
|
uv-cache = { workspace = true, features = ["clap"] }
|
||||||
uv-configuration = { workspace = true, features = ["clap"] }
|
uv-configuration = { workspace = true, features = ["clap"] }
|
||||||
uv-normalize = { workspace = true }
|
uv-normalize = { workspace = true }
|
||||||
|
uv-python = { workspace = true, features = ["clap", "schemars"]}
|
||||||
uv-resolver = { workspace = true, features = ["clap"] }
|
uv-resolver = { workspace = true, features = ["clap"] }
|
||||||
uv-settings = { workspace = true, features = ["schemars"] }
|
uv-settings = { workspace = true, features = ["schemars"] }
|
||||||
uv-python = { workspace = true, features = ["clap", "schemars"]}
|
|
||||||
uv-version = { workspace = true }
|
uv-version = { workspace = true }
|
||||||
uv-warnings = { workspace = true }
|
uv-warnings = { workspace = true }
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,8 @@ use anyhow::{anyhow, Result};
|
||||||
use clap::{Args, Parser, Subcommand};
|
use clap::{Args, Parser, Subcommand};
|
||||||
|
|
||||||
use distribution_types::{FlatIndexLocation, IndexUrl};
|
use distribution_types::{FlatIndexLocation, IndexUrl};
|
||||||
|
use pep508_rs::Requirement;
|
||||||
|
use pypi_types::VerbatimParsedUrl;
|
||||||
use uv_cache::CacheArgs;
|
use uv_cache::CacheArgs;
|
||||||
use uv_configuration::{
|
use uv_configuration::{
|
||||||
ConfigSettingEntry, IndexStrategy, KeyringProviderType, PackageNameSpecifier, TargetTriple,
|
ConfigSettingEntry, IndexStrategy, KeyringProviderType, PackageNameSpecifier, TargetTriple,
|
||||||
|
@ -300,7 +302,7 @@ pub enum PipCommand {
|
||||||
after_help = "Use `uv help pip sync` for more details.",
|
after_help = "Use `uv help pip sync` for more details.",
|
||||||
after_long_help = ""
|
after_long_help = ""
|
||||||
)]
|
)]
|
||||||
Sync(PipSyncArgs),
|
Sync(Box<PipSyncArgs>),
|
||||||
/// Install packages into an environment.
|
/// Install packages into an environment.
|
||||||
#[command(
|
#[command(
|
||||||
after_help = "Use `uv help pip install` for more details.",
|
after_help = "Use `uv help pip install` for more details.",
|
||||||
|
@ -2408,7 +2410,7 @@ pub struct ResolverArgs {
|
||||||
/// Allow upgrades for a specific package, ignoring pinned versions in any existing output
|
/// Allow upgrades for a specific package, ignoring pinned versions in any existing output
|
||||||
/// file.
|
/// file.
|
||||||
#[arg(long, short = 'P')]
|
#[arg(long, short = 'P')]
|
||||||
pub upgrade_package: Vec<PackageName>,
|
pub upgrade_package: Vec<Requirement<VerbatimParsedUrl>>,
|
||||||
|
|
||||||
/// The strategy to use when resolving against multiple index URLs.
|
/// The strategy to use when resolving against multiple index URLs.
|
||||||
///
|
///
|
||||||
|
@ -2484,7 +2486,7 @@ pub struct ResolverInstallerArgs {
|
||||||
/// Allow upgrades for a specific package, ignoring pinned versions in any existing output
|
/// Allow upgrades for a specific package, ignoring pinned versions in any existing output
|
||||||
/// file.
|
/// file.
|
||||||
#[arg(long, short = 'P')]
|
#[arg(long, short = 'P')]
|
||||||
pub upgrade_package: Vec<PackageName>,
|
pub upgrade_package: Vec<Requirement<VerbatimParsedUrl>>,
|
||||||
|
|
||||||
/// Reinstall all packages, regardless of whether they're already installed.
|
/// Reinstall all packages, regardless of whether they're already installed.
|
||||||
#[arg(long, alias = "force-reinstall", overrides_with("no_reinstall"))]
|
#[arg(long, alias = "force-reinstall", overrides_with("no_reinstall"))]
|
||||||
|
|
|
@ -13,7 +13,7 @@ license = { workspace = true }
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
pep508_rs = { workspace = true }
|
pep508_rs = { workspace = true, features = ["schemars"] }
|
||||||
platform-tags = { workspace = true }
|
platform-tags = { workspace = true }
|
||||||
pypi-types = { workspace = true }
|
pypi-types = { workspace = true }
|
||||||
uv-auth = { workspace = true }
|
uv-auth = { workspace = true }
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
use either::Either;
|
|
||||||
use pep508_rs::MarkerTree;
|
|
||||||
use pypi_types::Requirement;
|
|
||||||
use rustc_hash::{FxBuildHasher, FxHashMap};
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
use either::Either;
|
||||||
|
use rustc_hash::FxHashMap;
|
||||||
|
|
||||||
|
use pep508_rs::MarkerTree;
|
||||||
|
use pypi_types::{Requirement, RequirementSource};
|
||||||
use uv_normalize::PackageName;
|
use uv_normalize::PackageName;
|
||||||
|
|
||||||
/// A set of constraints for a set of requirements.
|
/// A set of constraints for a set of requirements.
|
||||||
|
@ -11,10 +13,16 @@ pub struct Constraints(FxHashMap<PackageName, Vec<Requirement>>);
|
||||||
|
|
||||||
impl Constraints {
|
impl Constraints {
|
||||||
/// Create a new set of constraints from a set of requirements.
|
/// Create a new set of constraints from a set of requirements.
|
||||||
pub fn from_requirements(requirements: Vec<Requirement>) -> Self {
|
pub fn from_requirements(requirements: impl Iterator<Item = Requirement>) -> Self {
|
||||||
let mut constraints: FxHashMap<PackageName, Vec<Requirement>> =
|
let mut constraints: FxHashMap<PackageName, Vec<Requirement>> = FxHashMap::default();
|
||||||
FxHashMap::with_capacity_and_hasher(requirements.len(), FxBuildHasher);
|
|
||||||
for requirement in requirements {
|
for requirement in requirements {
|
||||||
|
// Skip empty constraints.
|
||||||
|
if let RequirementSource::Registry { specifier, .. } = &requirement.source {
|
||||||
|
if specifier.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
constraints
|
constraints
|
||||||
.entry(requirement.name.clone())
|
.entry(requirement.name.clone())
|
||||||
.or_default()
|
.or_default()
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
|
use either::Either;
|
||||||
use pep508_rs::PackageName;
|
use pep508_rs::PackageName;
|
||||||
|
|
||||||
use rustc_hash::FxHashSet;
|
use pypi_types::Requirement;
|
||||||
|
use rustc_hash::FxHashMap;
|
||||||
|
|
||||||
/// Whether to reinstall packages.
|
/// Whether to reinstall packages.
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
|
@ -54,12 +56,12 @@ pub enum Upgrade {
|
||||||
All,
|
All,
|
||||||
|
|
||||||
/// Allow package upgrades, but only for the specified packages.
|
/// Allow package upgrades, but only for the specified packages.
|
||||||
Packages(FxHashSet<PackageName>),
|
Packages(FxHashMap<PackageName, Vec<Requirement>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Upgrade {
|
impl Upgrade {
|
||||||
/// Determine the upgrade strategy from the command-line arguments.
|
/// Determine the upgrade strategy from the command-line arguments.
|
||||||
pub fn from_args(upgrade: Option<bool>, upgrade_package: Vec<PackageName>) -> Self {
|
pub fn from_args(upgrade: Option<bool>, upgrade_package: Vec<Requirement>) -> Self {
|
||||||
match upgrade {
|
match upgrade {
|
||||||
Some(true) => Self::All,
|
Some(true) => Self::All,
|
||||||
Some(false) => Self::None,
|
Some(false) => Self::None,
|
||||||
|
@ -67,7 +69,15 @@ impl Upgrade {
|
||||||
if upgrade_package.is_empty() {
|
if upgrade_package.is_empty() {
|
||||||
Self::None
|
Self::None
|
||||||
} else {
|
} else {
|
||||||
Self::Packages(upgrade_package.into_iter().collect())
|
Self::Packages(upgrade_package.into_iter().fold(
|
||||||
|
FxHashMap::default(),
|
||||||
|
|mut map, requirement| {
|
||||||
|
map.entry(requirement.name.clone())
|
||||||
|
.or_default()
|
||||||
|
.push(requirement);
|
||||||
|
map
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -82,4 +92,28 @@ impl Upgrade {
|
||||||
pub fn is_all(&self) -> bool {
|
pub fn is_all(&self) -> bool {
|
||||||
matches!(self, Self::All)
|
matches!(self, Self::All)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the specified package should be upgraded.
|
||||||
|
pub fn contains(&self, package_name: &PackageName) -> bool {
|
||||||
|
match &self {
|
||||||
|
Self::None => false,
|
||||||
|
Self::All => true,
|
||||||
|
Self::Packages(packages) => packages.contains_key(package_name),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an iterator over the constraints.
|
||||||
|
///
|
||||||
|
/// When upgrading, users can provide bounds on the upgrade (e.g., `--upgrade-package flask<3`).
|
||||||
|
pub fn constraints(&self) -> impl Iterator<Item = &Requirement> {
|
||||||
|
if let Self::Packages(packages) = self {
|
||||||
|
Either::Right(
|
||||||
|
packages
|
||||||
|
.values()
|
||||||
|
.flat_map(|requirements| requirements.iter()),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Either::Left(std::iter::empty())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,7 +56,7 @@ pub async fn read_requirements_txt(
|
||||||
// Ignore pinned versions for the specified packages.
|
// Ignore pinned versions for the specified packages.
|
||||||
Upgrade::Packages(packages) => preferences
|
Upgrade::Packages(packages) => preferences
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|preference| !packages.contains(preference.name()))
|
.filter(|preference| !packages.contains_key(preference.name()))
|
||||||
.collect(),
|
.collect(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -68,11 +68,7 @@ pub fn read_lock_requirements(lock: &Lock, upgrade: &Upgrade) -> LockedRequireme
|
||||||
|
|
||||||
for dist in lock.distributions() {
|
for dist in lock.distributions() {
|
||||||
// Skip the distribution if it's not included in the upgrade strategy.
|
// Skip the distribution if it's not included in the upgrade strategy.
|
||||||
if match upgrade {
|
if upgrade.contains(dist.name()) {
|
||||||
Upgrade::None => false,
|
|
||||||
Upgrade::All => true,
|
|
||||||
Upgrade::Packages(packages) => packages.contains(dist.name()),
|
|
||||||
} {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ impl Exclusions {
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Upgrade::Packages(packages) = upgrade {
|
if let Upgrade::Packages(packages) = upgrade {
|
||||||
exclusions.extend(packages);
|
exclusions.extend(packages.into_keys());
|
||||||
};
|
};
|
||||||
|
|
||||||
if exclusions.is_empty() {
|
if exclusions.is_empty() {
|
||||||
|
|
|
@ -4,6 +4,7 @@ use serde::Deserialize;
|
||||||
|
|
||||||
use distribution_types::{FlatIndexLocation, IndexUrl};
|
use distribution_types::{FlatIndexLocation, IndexUrl};
|
||||||
use install_wheel_rs::linker::LinkMode;
|
use install_wheel_rs::linker::LinkMode;
|
||||||
|
use pep508_rs::Requirement;
|
||||||
use pypi_types::VerbatimParsedUrl;
|
use pypi_types::VerbatimParsedUrl;
|
||||||
use uv_configuration::{
|
use uv_configuration::{
|
||||||
ConfigSettings, IndexStrategy, KeyringProviderType, PackageNameSpecifier, TargetTriple,
|
ConfigSettings, IndexStrategy, KeyringProviderType, PackageNameSpecifier, TargetTriple,
|
||||||
|
@ -105,7 +106,7 @@ pub struct ResolverOptions {
|
||||||
pub exclude_newer: Option<ExcludeNewer>,
|
pub exclude_newer: Option<ExcludeNewer>,
|
||||||
pub link_mode: Option<LinkMode>,
|
pub link_mode: Option<LinkMode>,
|
||||||
pub upgrade: Option<bool>,
|
pub upgrade: Option<bool>,
|
||||||
pub upgrade_package: Option<Vec<PackageName>>,
|
pub upgrade_package: Option<Vec<Requirement<VerbatimParsedUrl>>>,
|
||||||
pub no_build: Option<bool>,
|
pub no_build: Option<bool>,
|
||||||
pub no_build_package: Option<Vec<PackageName>>,
|
pub no_build_package: Option<Vec<PackageName>>,
|
||||||
pub no_binary: Option<bool>,
|
pub no_binary: Option<bool>,
|
||||||
|
@ -132,7 +133,7 @@ pub struct ResolverInstallerOptions {
|
||||||
pub link_mode: Option<LinkMode>,
|
pub link_mode: Option<LinkMode>,
|
||||||
pub compile_bytecode: Option<bool>,
|
pub compile_bytecode: Option<bool>,
|
||||||
pub upgrade: Option<bool>,
|
pub upgrade: Option<bool>,
|
||||||
pub upgrade_package: Option<Vec<PackageName>>,
|
pub upgrade_package: Option<Vec<Requirement<VerbatimParsedUrl>>>,
|
||||||
pub reinstall: Option<bool>,
|
pub reinstall: Option<bool>,
|
||||||
pub reinstall_package: Option<Vec<PackageName>>,
|
pub reinstall_package: Option<Vec<PackageName>>,
|
||||||
pub no_build: Option<bool>,
|
pub no_build: Option<bool>,
|
||||||
|
@ -193,7 +194,7 @@ pub struct PipOptions {
|
||||||
pub compile_bytecode: Option<bool>,
|
pub compile_bytecode: Option<bool>,
|
||||||
pub require_hashes: Option<bool>,
|
pub require_hashes: Option<bool>,
|
||||||
pub upgrade: Option<bool>,
|
pub upgrade: Option<bool>,
|
||||||
pub upgrade_package: Option<Vec<PackageName>>,
|
pub upgrade_package: Option<Vec<Requirement<VerbatimParsedUrl>>>,
|
||||||
pub reinstall: Option<bool>,
|
pub reinstall: Option<bool>,
|
||||||
pub reinstall_package: Option<Vec<PackageName>>,
|
pub reinstall_package: Option<Vec<PackageName>>,
|
||||||
pub concurrent_downloads: Option<NonZeroUsize>,
|
pub concurrent_downloads: Option<NonZeroUsize>,
|
||||||
|
|
|
@ -180,7 +180,11 @@ pub(crate) async fn resolve<InstalledPackages: InstalledPackagesProvider>(
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Collect constraints and overrides.
|
// Collect constraints and overrides.
|
||||||
let constraints = Constraints::from_requirements(constraints);
|
let constraints = Constraints::from_requirements(
|
||||||
|
constraints
|
||||||
|
.into_iter()
|
||||||
|
.chain(upgrade.constraints().cloned()),
|
||||||
|
);
|
||||||
let overrides = Overrides::from_requirements(overrides);
|
let overrides = Overrides::from_requirements(overrides);
|
||||||
let preferences = Preferences::from_iter(preferences, markers);
|
let preferences = Preferences::from_iter(preferences, markers);
|
||||||
|
|
||||||
|
|
|
@ -798,7 +798,7 @@ pub(crate) struct PipSyncSettings {
|
||||||
|
|
||||||
impl PipSyncSettings {
|
impl PipSyncSettings {
|
||||||
/// Resolve the [`PipSyncSettings`] from the CLI and filesystem configuration.
|
/// Resolve the [`PipSyncSettings`] from the CLI and filesystem configuration.
|
||||||
pub(crate) fn resolve(args: PipSyncArgs, filesystem: Option<FilesystemOptions>) -> Self {
|
pub(crate) fn resolve(args: Box<PipSyncArgs>, filesystem: Option<FilesystemOptions>) -> Self {
|
||||||
let PipSyncArgs {
|
let PipSyncArgs {
|
||||||
src_file,
|
src_file,
|
||||||
constraint,
|
constraint,
|
||||||
|
@ -829,7 +829,7 @@ impl PipSyncSettings {
|
||||||
no_strict,
|
no_strict,
|
||||||
dry_run,
|
dry_run,
|
||||||
compat_args: _,
|
compat_args: _,
|
||||||
} = args;
|
} = *args;
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
src_file,
|
src_file,
|
||||||
|
@ -1384,7 +1384,10 @@ impl ResolverSettings {
|
||||||
args.upgrade.combine(upgrade),
|
args.upgrade.combine(upgrade),
|
||||||
args.upgrade_package
|
args.upgrade_package
|
||||||
.combine(upgrade_package)
|
.combine(upgrade_package)
|
||||||
.unwrap_or_default(),
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.map(Requirement::from)
|
||||||
|
.collect(),
|
||||||
),
|
),
|
||||||
build_options: BuildOptions::new(
|
build_options: BuildOptions::new(
|
||||||
NoBinary::from_args(
|
NoBinary::from_args(
|
||||||
|
@ -1522,7 +1525,10 @@ impl ResolverInstallerSettings {
|
||||||
args.upgrade.combine(upgrade),
|
args.upgrade.combine(upgrade),
|
||||||
args.upgrade_package
|
args.upgrade_package
|
||||||
.combine(upgrade_package)
|
.combine(upgrade_package)
|
||||||
.unwrap_or_default(),
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.map(Requirement::from)
|
||||||
|
.collect(),
|
||||||
),
|
),
|
||||||
reinstall: Reinstall::from_args(
|
reinstall: Reinstall::from_args(
|
||||||
args.reinstall.combine(reinstall),
|
args.reinstall.combine(reinstall),
|
||||||
|
@ -1841,7 +1847,10 @@ impl PipSettings {
|
||||||
args.upgrade.combine(upgrade),
|
args.upgrade.combine(upgrade),
|
||||||
args.upgrade_package
|
args.upgrade_package
|
||||||
.combine(upgrade_package)
|
.combine(upgrade_package)
|
||||||
.unwrap_or_default(),
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.map(Requirement::from)
|
||||||
|
.collect(),
|
||||||
),
|
),
|
||||||
reinstall: Reinstall::from_args(
|
reinstall: Reinstall::from_args(
|
||||||
args.reinstall.combine(reinstall),
|
args.reinstall.combine(reinstall),
|
||||||
|
|
|
@ -4770,6 +4770,61 @@ fn upgrade_package() -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Upgrade a package with a constraint on the allowed upgrade.
|
||||||
|
#[test]
|
||||||
|
fn upgrade_constraint() -> Result<()> {
|
||||||
|
let context = TestContext::new("3.12");
|
||||||
|
let requirements_in = context.temp_dir.child("requirements.in");
|
||||||
|
requirements_in.write_str("iniconfig")?;
|
||||||
|
|
||||||
|
let requirements_txt = context.temp_dir.child("requirements.txt");
|
||||||
|
requirements_txt.write_str(indoc! {r"
|
||||||
|
# This file was autogenerated by uv via the following command:
|
||||||
|
# uv pip compile requirements.in --python-version 3.12 --cache-dir [CACHE_DIR]
|
||||||
|
iniconfig==1.0.0
|
||||||
|
"})?;
|
||||||
|
|
||||||
|
uv_snapshot!(context.pip_compile()
|
||||||
|
.arg("requirements.in")
|
||||||
|
.arg("--output-file")
|
||||||
|
.arg("requirements.txt")
|
||||||
|
.arg("--upgrade-package")
|
||||||
|
.arg("iniconfig<2"), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
# This file was autogenerated by uv via the following command:
|
||||||
|
# uv pip compile --cache-dir [CACHE_DIR] requirements.in --output-file requirements.txt --upgrade-package iniconfig<2
|
||||||
|
iniconfig==1.1.1
|
||||||
|
# via -r requirements.in
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 1 package in [TIME]
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
|
||||||
|
uv_snapshot!(context.pip_compile()
|
||||||
|
.arg("requirements.in")
|
||||||
|
.arg("--output-file")
|
||||||
|
.arg("requirements.txt")
|
||||||
|
.arg("--upgrade-package")
|
||||||
|
.arg("iniconfig"), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
# This file was autogenerated by uv via the following command:
|
||||||
|
# uv pip compile --cache-dir [CACHE_DIR] requirements.in --output-file requirements.txt --upgrade-package iniconfig
|
||||||
|
iniconfig==2.0.0
|
||||||
|
# via -r requirements.in
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 1 package in [TIME]
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Attempt to resolve a requirement at a path that doesn't exist.
|
/// Attempt to resolve a requirement at a path that doesn't exist.
|
||||||
#[test]
|
#[test]
|
||||||
fn missing_path_requirement() -> Result<()> {
|
fn missing_path_requirement() -> Result<()> {
|
||||||
|
|
8
uv.schema.json
generated
8
uv.schema.json
generated
|
@ -267,7 +267,7 @@
|
||||||
"null"
|
"null"
|
||||||
],
|
],
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/PackageName"
|
"$ref": "#/definitions/Requirement"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"workspace": {
|
"workspace": {
|
||||||
|
@ -826,7 +826,7 @@
|
||||||
"null"
|
"null"
|
||||||
],
|
],
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/PackageName"
|
"$ref": "#/definitions/Requirement"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -933,6 +933,10 @@
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "^3\\.\\d+(\\.\\d+)?$"
|
"pattern": "^3\\.\\d+(\\.\\d+)?$"
|
||||||
},
|
},
|
||||||
|
"Requirement": {
|
||||||
|
"description": "A PEP 508 dependency specifier",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"ResolutionMode": {
|
"ResolutionMode": {
|
||||||
"oneOf": [
|
"oneOf": [
|
||||||
{
|
{
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue