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:
Charlie Marsh
2024-05-22 12:30:54 -04:00
committed by GitHub
parent 7afc3f6eb0
commit b92321bd2d
5 changed files with 114 additions and 5 deletions
+10
View File
@@ -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,
+3 -3
View File
@@ -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
View File
@@ -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,
+6
View File
@@ -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,
+85
View File
@@ -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<()> {