Allow --constraint files in pip sync (#3741)
## Summary Trivial now that this follows the same strategy and internals as `pip install`. Closes https://github.com/astral-sh/uv/issues/3438.
This commit is contained in:
@@ -639,6 +639,16 @@ pub(crate) struct PipSyncArgs {
|
||||
#[arg(required(true))]
|
||||
pub(crate) src_file: Vec<PathBuf>,
|
||||
|
||||
/// Constrain versions using the given requirements files.
|
||||
///
|
||||
/// Constraints files are `requirements.txt`-like files that only control the _version_ of a
|
||||
/// requirement that's installed. However, including a package in a constraints file will _not_
|
||||
/// trigger the installation of that package.
|
||||
///
|
||||
/// This is equivalent to pip's `--constraint` option.
|
||||
#[arg(long, short, env = "UV_CONSTRAINT", value_delimiter = ' ', value_parser = parse_file_path)]
|
||||
pub(crate) constraint: Vec<Maybe<PathBuf>>,
|
||||
|
||||
/// Reinstall all packages, regardless of whether they're already installed.
|
||||
#[arg(long, alias = "force-reinstall", overrides_with("no_reinstall"))]
|
||||
pub(crate) reinstall: bool,
|
||||
|
||||
@@ -42,7 +42,8 @@ use crate::printer::Printer;
|
||||
/// Install a set of locked requirements into the current Python environment.
|
||||
#[allow(clippy::too_many_arguments, clippy::fn_params_excessive_bools)]
|
||||
pub(crate) async fn pip_sync(
|
||||
sources: &[RequirementsSource],
|
||||
requirements: &[RequirementsSource],
|
||||
constraints: &[RequirementsSource],
|
||||
reinstall: &Reinstall,
|
||||
link_mode: LinkMode,
|
||||
compile: bool,
|
||||
@@ -77,7 +78,6 @@ pub(crate) async fn pip_sync(
|
||||
.keyring(keyring_provider);
|
||||
|
||||
// Initialize a few defaults.
|
||||
let constraints = &[];
|
||||
let overrides = &[];
|
||||
let extras = ExtrasSpecification::default();
|
||||
let upgrade = Upgrade::default();
|
||||
@@ -101,7 +101,7 @@ pub(crate) async fn pip_sync(
|
||||
no_build: specified_no_build,
|
||||
extras: _,
|
||||
} = operations::read_requirements(
|
||||
sources,
|
||||
requirements,
|
||||
constraints,
|
||||
overrides,
|
||||
&ExtrasSpecification::default(),
|
||||
|
||||
+10
-2
@@ -185,6 +185,7 @@ async fn run() -> Result<ExitStatus> {
|
||||
|
||||
// Initialize the cache.
|
||||
let cache = cache.init()?.with_refresh(args.refresh);
|
||||
|
||||
let requirements = args
|
||||
.src_file
|
||||
.into_iter()
|
||||
@@ -260,14 +261,21 @@ async fn run() -> Result<ExitStatus> {
|
||||
|
||||
// Initialize the cache.
|
||||
let cache = cache.init()?.with_refresh(args.refresh);
|
||||
let sources = args
|
||||
|
||||
let requirements = args
|
||||
.src_file
|
||||
.into_iter()
|
||||
.map(RequirementsSource::from_requirements_file)
|
||||
.collect::<Vec<_>>();
|
||||
let constraints = args
|
||||
.constraint
|
||||
.into_iter()
|
||||
.map(RequirementsSource::from_constraints_txt)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
commands::pip_sync(
|
||||
&sources,
|
||||
&requirements,
|
||||
&constraints,
|
||||
&args.reinstall,
|
||||
args.shared.link_mode,
|
||||
args.shared.compile_bytecode,
|
||||
|
||||
@@ -330,6 +330,7 @@ impl PipCompileSettings {
|
||||
pub(crate) struct PipSyncSettings {
|
||||
// CLI-only settings.
|
||||
pub(crate) src_file: Vec<PathBuf>,
|
||||
pub(crate) constraint: Vec<PathBuf>,
|
||||
pub(crate) reinstall: Reinstall,
|
||||
pub(crate) refresh: Refresh,
|
||||
pub(crate) dry_run: bool,
|
||||
@@ -343,6 +344,7 @@ impl PipSyncSettings {
|
||||
pub(crate) fn resolve(args: PipSyncArgs, workspace: Option<Workspace>) -> Self {
|
||||
let PipSyncArgs {
|
||||
src_file,
|
||||
constraint,
|
||||
reinstall,
|
||||
no_reinstall,
|
||||
reinstall_package,
|
||||
@@ -387,6 +389,10 @@ impl PipSyncSettings {
|
||||
Self {
|
||||
// CLI-only settings.
|
||||
src_file,
|
||||
constraint: constraint
|
||||
.into_iter()
|
||||
.filter_map(Maybe::into_option)
|
||||
.collect(),
|
||||
reinstall: Reinstall::from_args(flag(reinstall, no_reinstall), reinstall_package),
|
||||
refresh: Refresh::from_args(flag(refresh, no_refresh), refresh_package),
|
||||
dry_run,
|
||||
|
||||
@@ -2814,6 +2814,91 @@ fn offline() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Include a `constraints.txt` file with a compatible constraint.
|
||||
#[test]
|
||||
fn compatible_constraint() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
let requirements_txt = context.temp_dir.child("requirements.txt");
|
||||
requirements_txt.write_str("anyio==3.7.0")?;
|
||||
|
||||
let constraints_txt = context.temp_dir.child("constraints.txt");
|
||||
constraints_txt.write_str("anyio==3.7.0")?;
|
||||
|
||||
uv_snapshot!(command(&context)
|
||||
.arg("requirements.txt")
|
||||
.arg("--constraint")
|
||||
.arg("constraints.txt"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 1 package in [TIME]
|
||||
Downloaded 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ anyio==3.7.0
|
||||
"###
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Include a `constraints.txt` file with an incompatible constraint.
|
||||
#[test]
|
||||
fn incompatible_constraint() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
let requirements_txt = context.temp_dir.child("requirements.txt");
|
||||
requirements_txt.write_str("anyio==3.7.0")?;
|
||||
|
||||
let constraints_txt = context.temp_dir.child("constraints.txt");
|
||||
constraints_txt.write_str("anyio==3.6.0")?;
|
||||
|
||||
uv_snapshot!(command(&context)
|
||||
.arg("requirements.txt")
|
||||
.arg("--constraint")
|
||||
.arg("constraints.txt"), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
× No solution found when resolving dependencies:
|
||||
╰─▶ Because you require anyio==3.7.0 and anyio==3.6.0, we can conclude that the requirements are unsatisfiable.
|
||||
"###
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Include a `constraints.txt` file with an irrelevant constraint.
|
||||
#[test]
|
||||
fn irrelevant_constraint() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
let requirements_txt = context.temp_dir.child("requirements.txt");
|
||||
requirements_txt.write_str("anyio==3.7.0")?;
|
||||
|
||||
let constraints_txt = context.temp_dir.child("constraints.txt");
|
||||
constraints_txt.write_str("black==23.10.1")?;
|
||||
|
||||
uv_snapshot!(command(&context)
|
||||
.arg("requirements.txt")
|
||||
.arg("--constraint")
|
||||
.arg("constraints.txt"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 1 package in [TIME]
|
||||
Downloaded 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ anyio==3.7.0
|
||||
"###
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sync with a repeated `anyio` requirement.
|
||||
#[test]
|
||||
fn repeat_requirement_identical() -> Result<()> {
|
||||
|
||||
Reference in New Issue
Block a user