mirror of https://github.com/astral-sh/uv
Add `--dry-run` flag to `uv pip install` (#1436)
## What Adds a `--dry-run` flag that ejects out of the installation process early (but after resolution) and displays only what *would have* installed ## Closes Closes #1244 ## Out of Scope I think it may be nice to include a `dry-run` flag for `uninstall` even though `pip` doesn't implement this... thinking `Would uninstall X packages: ...` --------- Co-authored-by: Zanie Blue <contact@zanie.dev>
This commit is contained in:
parent
f3495d7cad
commit
15f6f9f448
|
|
@ -1,6 +1,5 @@
|
||||||
use std::fmt::Write;
|
|
||||||
use std::process::ExitCode;
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
use std::{fmt::Display, fmt::Write, process::ExitCode};
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use owo_colors::OwoColorize;
|
use owo_colors::OwoColorize;
|
||||||
|
|
@ -19,6 +18,7 @@ use uv_cache::Cache;
|
||||||
use uv_fs::Simplified;
|
use uv_fs::Simplified;
|
||||||
use uv_installer::compile_tree;
|
use uv_installer::compile_tree;
|
||||||
use uv_interpreter::PythonEnvironment;
|
use uv_interpreter::PythonEnvironment;
|
||||||
|
use uv_normalize::PackageName;
|
||||||
pub(crate) use venv::venv;
|
pub(crate) use venv::venv;
|
||||||
pub(crate) use version::version;
|
pub(crate) use version::version;
|
||||||
|
|
||||||
|
|
@ -89,6 +89,13 @@ pub(super) struct ChangeEvent<T: InstalledMetadata> {
|
||||||
kind: ChangeEventKind,
|
kind: ChangeEventKind,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(super) struct DryRunEvent<T: Display> {
|
||||||
|
name: PackageName,
|
||||||
|
version: T,
|
||||||
|
kind: ChangeEventKind,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, clap::ValueEnum)]
|
#[derive(Debug, Clone, Copy, clap::ValueEnum)]
|
||||||
pub(crate) enum VersionFormat {
|
pub(crate) enum VersionFormat {
|
||||||
Text,
|
Text,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
use anstream::eprint;
|
use anstream::eprint;
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
|
|
@ -11,7 +12,8 @@ use tempfile::tempdir_in;
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
use distribution_types::{
|
use distribution_types::{
|
||||||
IndexLocations, InstalledMetadata, LocalDist, LocalEditable, Name, Resolution,
|
DistributionMetadata, IndexLocations, InstalledMetadata, LocalDist, LocalEditable, Name,
|
||||||
|
Resolution,
|
||||||
};
|
};
|
||||||
use install_wheel_rs::linker::LinkMode;
|
use install_wheel_rs::linker::LinkMode;
|
||||||
use pep508_rs::{MarkerEnvironment, Requirement};
|
use pep508_rs::{MarkerEnvironment, Requirement};
|
||||||
|
|
@ -39,7 +41,7 @@ use crate::commands::{compile_bytecode, elapsed, ChangeEvent, ChangeEventKind, E
|
||||||
use crate::printer::Printer;
|
use crate::printer::Printer;
|
||||||
use crate::requirements::{ExtrasSpecification, RequirementsSource, RequirementsSpecification};
|
use crate::requirements::{ExtrasSpecification, RequirementsSource, RequirementsSpecification};
|
||||||
|
|
||||||
use super::Upgrade;
|
use super::{DryRunEvent, Upgrade};
|
||||||
|
|
||||||
/// Install packages into the current environment.
|
/// Install packages into the current environment.
|
||||||
#[allow(clippy::too_many_arguments, clippy::fn_params_excessive_bools)]
|
#[allow(clippy::too_many_arguments, clippy::fn_params_excessive_bools)]
|
||||||
|
|
@ -69,6 +71,7 @@ pub(crate) async fn pip_install(
|
||||||
break_system_packages: bool,
|
break_system_packages: bool,
|
||||||
native_tls: bool,
|
native_tls: bool,
|
||||||
cache: Cache,
|
cache: Cache,
|
||||||
|
dry_run: bool,
|
||||||
printer: Printer,
|
printer: Printer,
|
||||||
) -> Result<ExitStatus> {
|
) -> Result<ExitStatus> {
|
||||||
let start = std::time::Instant::now();
|
let start = std::time::Instant::now();
|
||||||
|
|
@ -164,6 +167,9 @@ pub(crate) async fn pip_install(
|
||||||
)
|
)
|
||||||
.dimmed()
|
.dimmed()
|
||||||
)?;
|
)?;
|
||||||
|
if dry_run {
|
||||||
|
writeln!(printer.stderr(), "Would make no changes")?;
|
||||||
|
}
|
||||||
return Ok(ExitStatus::Success);
|
return Ok(ExitStatus::Success);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -320,6 +326,7 @@ pub(crate) async fn pip_install(
|
||||||
&install_dispatch,
|
&install_dispatch,
|
||||||
&cache,
|
&cache,
|
||||||
&venv,
|
&venv,
|
||||||
|
dry_run,
|
||||||
printer,
|
printer,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
@ -392,7 +399,7 @@ async fn build_editables(
|
||||||
build_dispatch: &BuildDispatch<'_>,
|
build_dispatch: &BuildDispatch<'_>,
|
||||||
printer: Printer,
|
printer: Printer,
|
||||||
) -> Result<Vec<BuiltEditable>, Error> {
|
) -> Result<Vec<BuiltEditable>, Error> {
|
||||||
let start = std::time::Instant::now();
|
let start = Instant::now();
|
||||||
|
|
||||||
let downloader = Downloader::new(cache, tags, client, build_dispatch)
|
let downloader = Downloader::new(cache, tags, client, build_dispatch)
|
||||||
.with_reporter(DownloadReporter::from(printer).with_length(editables.len() as u64));
|
.with_reporter(DownloadReporter::from(printer).with_length(editables.len() as u64));
|
||||||
|
|
@ -558,6 +565,7 @@ async fn install(
|
||||||
build_dispatch: &BuildDispatch<'_>,
|
build_dispatch: &BuildDispatch<'_>,
|
||||||
cache: &Cache,
|
cache: &Cache,
|
||||||
venv: &PythonEnvironment,
|
venv: &PythonEnvironment,
|
||||||
|
dry_run: bool,
|
||||||
printer: Printer,
|
printer: Printer,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let start = std::time::Instant::now();
|
let start = std::time::Instant::now();
|
||||||
|
|
@ -572,12 +580,7 @@ async fn install(
|
||||||
|
|
||||||
// Partition into those that should be linked from the cache (`local`), those that need to be
|
// Partition into those that should be linked from the cache (`local`), those that need to be
|
||||||
// downloaded (`remote`), and those that should be removed (`extraneous`).
|
// downloaded (`remote`), and those that should be removed (`extraneous`).
|
||||||
let Plan {
|
let plan = Planner::with_requirements(&requirements)
|
||||||
local,
|
|
||||||
remote,
|
|
||||||
reinstalls,
|
|
||||||
extraneous: _,
|
|
||||||
} = Planner::with_requirements(&requirements)
|
|
||||||
.with_editable_requirements(&editables)
|
.with_editable_requirements(&editables)
|
||||||
.build(
|
.build(
|
||||||
site_packages,
|
site_packages,
|
||||||
|
|
@ -590,6 +593,17 @@ async fn install(
|
||||||
)
|
)
|
||||||
.context("Failed to determine installation plan")?;
|
.context("Failed to determine installation plan")?;
|
||||||
|
|
||||||
|
if dry_run {
|
||||||
|
return report_dry_run(resolution, plan, start, printer);
|
||||||
|
}
|
||||||
|
|
||||||
|
let Plan {
|
||||||
|
local,
|
||||||
|
remote,
|
||||||
|
reinstalls,
|
||||||
|
extraneous: _,
|
||||||
|
} = plan;
|
||||||
|
|
||||||
// Nothing to do.
|
// Nothing to do.
|
||||||
if remote.is_empty() && local.is_empty() && reinstalls.is_empty() {
|
if remote.is_empty() && local.is_empty() && reinstalls.is_empty() {
|
||||||
let s = if resolution.len() == 1 { "" } else { "s" };
|
let s = if resolution.len() == 1 { "" } else { "s" };
|
||||||
|
|
@ -603,7 +617,6 @@ async fn install(
|
||||||
)
|
)
|
||||||
.dimmed()
|
.dimmed()
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -622,7 +635,7 @@ async fn install(
|
||||||
let wheels = if remote.is_empty() {
|
let wheels = if remote.is_empty() {
|
||||||
vec![]
|
vec![]
|
||||||
} else {
|
} else {
|
||||||
let start = std::time::Instant::now();
|
let start = Instant::now();
|
||||||
|
|
||||||
let downloader = Downloader::new(cache, tags, client, build_dispatch)
|
let downloader = Downloader::new(cache, tags, client, build_dispatch)
|
||||||
.with_reporter(DownloadReporter::from(printer).with_length(remote.len() as u64));
|
.with_reporter(DownloadReporter::from(printer).with_length(remote.len() as u64));
|
||||||
|
|
@ -728,6 +741,135 @@ async fn install(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::items_after_statements)]
|
||||||
|
fn report_dry_run(
|
||||||
|
resolution: &Resolution,
|
||||||
|
plan: Plan,
|
||||||
|
start: Instant,
|
||||||
|
printer: Printer,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let Plan {
|
||||||
|
local,
|
||||||
|
remote,
|
||||||
|
reinstalls,
|
||||||
|
extraneous: _,
|
||||||
|
} = plan;
|
||||||
|
|
||||||
|
// Nothing to do.
|
||||||
|
if remote.is_empty() && local.is_empty() && reinstalls.is_empty() {
|
||||||
|
let s = if resolution.len() == 1 { "" } else { "s" };
|
||||||
|
writeln!(
|
||||||
|
printer.stderr(),
|
||||||
|
"{}",
|
||||||
|
format!(
|
||||||
|
"Audited {} in {}",
|
||||||
|
format!("{} package{}", resolution.len(), s).bold(),
|
||||||
|
elapsed(start.elapsed())
|
||||||
|
)
|
||||||
|
.dimmed()
|
||||||
|
)?;
|
||||||
|
writeln!(printer.stderr(), "Would make no changes")?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map any registry-based requirements back to those returned by the resolver.
|
||||||
|
let remote = remote
|
||||||
|
.iter()
|
||||||
|
.map(|dist| {
|
||||||
|
resolution
|
||||||
|
.get(&dist.name)
|
||||||
|
.cloned()
|
||||||
|
.expect("Resolution should contain all packages")
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// Download, build, and unzip any missing distributions.
|
||||||
|
let wheels = if remote.is_empty() {
|
||||||
|
vec![]
|
||||||
|
} else {
|
||||||
|
let s = if remote.len() == 1 { "" } else { "s" };
|
||||||
|
writeln!(
|
||||||
|
printer.stderr(),
|
||||||
|
"{}",
|
||||||
|
format!(
|
||||||
|
"Would download {}",
|
||||||
|
format!("{} package{}", remote.len(), s).bold(),
|
||||||
|
)
|
||||||
|
.dimmed()
|
||||||
|
)?;
|
||||||
|
remote
|
||||||
|
};
|
||||||
|
|
||||||
|
// Remove any existing installations.
|
||||||
|
if !reinstalls.is_empty() {
|
||||||
|
let s = if reinstalls.len() == 1 { "" } else { "s" };
|
||||||
|
writeln!(
|
||||||
|
printer.stderr(),
|
||||||
|
"{}",
|
||||||
|
format!(
|
||||||
|
"Would uninstall {}",
|
||||||
|
format!("{} package{}", reinstalls.len(), s).bold(),
|
||||||
|
)
|
||||||
|
.dimmed()
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Install the resolved distributions.
|
||||||
|
let installs = wheels.len() + local.len();
|
||||||
|
|
||||||
|
if installs > 0 {
|
||||||
|
let s = if installs == 1 { "" } else { "s" };
|
||||||
|
writeln!(
|
||||||
|
printer.stderr(),
|
||||||
|
"{}",
|
||||||
|
format!("Would install {}", format!("{installs} package{s}").bold()).dimmed()
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
for event in reinstalls
|
||||||
|
.into_iter()
|
||||||
|
.map(|distribution| DryRunEvent {
|
||||||
|
name: distribution.name().clone(),
|
||||||
|
version: distribution.installed_version().to_string(),
|
||||||
|
kind: ChangeEventKind::Removed,
|
||||||
|
})
|
||||||
|
.chain(wheels.into_iter().map(|distribution| DryRunEvent {
|
||||||
|
name: distribution.name().clone(),
|
||||||
|
version: distribution.version_or_url().to_string(),
|
||||||
|
kind: ChangeEventKind::Added,
|
||||||
|
}))
|
||||||
|
.chain(local.into_iter().map(|distribution| DryRunEvent {
|
||||||
|
name: distribution.name().clone(),
|
||||||
|
version: distribution.installed_version().to_string(),
|
||||||
|
kind: ChangeEventKind::Added,
|
||||||
|
}))
|
||||||
|
.sorted_unstable_by(|a, b| a.name.cmp(&b.name).then_with(|| a.kind.cmp(&b.kind)))
|
||||||
|
{
|
||||||
|
match event.kind {
|
||||||
|
ChangeEventKind::Added => {
|
||||||
|
writeln!(
|
||||||
|
printer.stderr(),
|
||||||
|
" {} {}{}",
|
||||||
|
"+".green(),
|
||||||
|
event.name.as_ref().bold(),
|
||||||
|
event.version.dimmed()
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
ChangeEventKind::Removed => {
|
||||||
|
writeln!(
|
||||||
|
printer.stderr(),
|
||||||
|
" {} {}{}",
|
||||||
|
"-".red(),
|
||||||
|
event.name.as_ref().bold(),
|
||||||
|
event.version.dimmed()
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
// TODO(konstin): Also check the cache whether any cached or installed dist is already known to
|
// TODO(konstin): Also check the cache whether any cached or installed dist is already known to
|
||||||
// have been yanked, we currently don't show this message on the second run anymore
|
// have been yanked, we currently don't show this message on the second run anymore
|
||||||
for dist in &remote {
|
for dist in &remote {
|
||||||
|
|
|
||||||
|
|
@ -891,6 +891,11 @@ struct PipInstallArgs {
|
||||||
/// format (e.g., `2006-12-02`).
|
/// format (e.g., `2006-12-02`).
|
||||||
#[arg(long, value_parser = date_or_datetime)]
|
#[arg(long, value_parser = date_or_datetime)]
|
||||||
exclude_newer: Option<DateTime<Utc>>,
|
exclude_newer: Option<DateTime<Utc>>,
|
||||||
|
|
||||||
|
/// Perform a dry run, i.e., don't actually install anything but resolve the dependencies and
|
||||||
|
/// print the resulting plan.
|
||||||
|
#[clap(long)]
|
||||||
|
dry_run: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Args)]
|
#[derive(Args)]
|
||||||
|
|
@ -1586,6 +1591,7 @@ async fn run() -> Result<ExitStatus> {
|
||||||
args.break_system_packages,
|
args.break_system_packages,
|
||||||
cli.native_tls,
|
cli.native_tls,
|
||||||
cache,
|
cache,
|
||||||
|
args.dry_run,
|
||||||
printer,
|
printer,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
|
||||||
|
|
@ -2414,3 +2414,272 @@ fn utf8_to_utf16_with_bom_be(s: &str) -> Vec<u8> {
|
||||||
byteorder::BigEndian::write_u16_into(&u16s, &mut u8s);
|
byteorder::BigEndian::write_u16_into(&u16s, &mut u8s);
|
||||||
u8s
|
u8s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dry_run_install() -> std::result::Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let context = TestContext::new("3.12");
|
||||||
|
let requirements_txt = context.temp_dir.child("requirements.txt");
|
||||||
|
requirements_txt.touch()?;
|
||||||
|
requirements_txt.write_str("httpx==0.25.1")?;
|
||||||
|
|
||||||
|
uv_snapshot!(command(&context)
|
||||||
|
.arg("-r")
|
||||||
|
.arg("requirements.txt")
|
||||||
|
.arg("--dry-run")
|
||||||
|
.arg("--strict"), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 7 packages in [TIME]
|
||||||
|
Would download 7 packages
|
||||||
|
Would install 7 packages
|
||||||
|
+ anyio==4.0.0
|
||||||
|
+ certifi==2023.11.17
|
||||||
|
+ h11==0.14.0
|
||||||
|
+ httpcore==1.0.2
|
||||||
|
+ httpx==0.25.1
|
||||||
|
+ idna==3.4
|
||||||
|
+ sniffio==1.3.0
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dry_run_install_url_dependency() -> std::result::Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let context = TestContext::new("3.12");
|
||||||
|
let requirements_txt = context.temp_dir.child("requirements.txt");
|
||||||
|
requirements_txt.touch()?;
|
||||||
|
requirements_txt.write_str("anyio @ https://files.pythonhosted.org/packages/2d/b8/7333d87d5f03247215d86a86362fd3e324111788c6cdd8d2e6196a6ba833/anyio-4.2.0.tar.gz")?;
|
||||||
|
|
||||||
|
uv_snapshot!(command(&context)
|
||||||
|
.arg("-r")
|
||||||
|
.arg("requirements.txt")
|
||||||
|
.arg("--dry-run")
|
||||||
|
.arg("--strict"), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 3 packages in [TIME]
|
||||||
|
Would download 3 packages
|
||||||
|
Would install 3 packages
|
||||||
|
+ anyio @ https://files.pythonhosted.org/packages/2d/b8/7333d87d5f03247215d86a86362fd3e324111788c6cdd8d2e6196a6ba833/anyio-4.2.0.tar.gz
|
||||||
|
+ idna==3.4
|
||||||
|
+ sniffio==1.3.0
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dry_run_uninstall_url_dependency() -> std::result::Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let context = TestContext::new("3.12");
|
||||||
|
let requirements_txt = context.temp_dir.child("requirements.txt");
|
||||||
|
requirements_txt.touch()?;
|
||||||
|
requirements_txt.write_str("anyio @ https://files.pythonhosted.org/packages/2d/b8/7333d87d5f03247215d86a86362fd3e324111788c6cdd8d2e6196a6ba833/anyio-4.2.0.tar.gz")?;
|
||||||
|
|
||||||
|
// Install the URL dependency
|
||||||
|
uv_snapshot!(command(&context)
|
||||||
|
.arg("-r")
|
||||||
|
.arg("requirements.txt")
|
||||||
|
.arg("--strict"), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 3 packages in [TIME]
|
||||||
|
Downloaded 3 packages in [TIME]
|
||||||
|
Installed 3 packages in [TIME]
|
||||||
|
+ anyio==4.2.0 (from https://files.pythonhosted.org/packages/2d/b8/7333d87d5f03247215d86a86362fd3e324111788c6cdd8d2e6196a6ba833/anyio-4.2.0.tar.gz)
|
||||||
|
+ idna==3.4
|
||||||
|
+ sniffio==1.3.0
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
|
||||||
|
// Then switch to a registry dependency
|
||||||
|
requirements_txt.write_str("anyio")?;
|
||||||
|
uv_snapshot!(command(&context)
|
||||||
|
.arg("-r")
|
||||||
|
.arg("requirements.txt")
|
||||||
|
.arg("--upgrade-package")
|
||||||
|
.arg("anyio")
|
||||||
|
.arg("--dry-run")
|
||||||
|
.arg("--strict"), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 3 packages in [TIME]
|
||||||
|
Would download 1 package
|
||||||
|
Would uninstall 1 package
|
||||||
|
Would install 1 package
|
||||||
|
- anyio==4.2.0 (from https://files.pythonhosted.org/packages/2d/b8/7333d87d5f03247215d86a86362fd3e324111788c6cdd8d2e6196a6ba833/anyio-4.2.0.tar.gz)
|
||||||
|
+ anyio==4.0.0
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dry_run_install_already_installed() -> std::result::Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let context = TestContext::new("3.12");
|
||||||
|
let requirements_txt = context.temp_dir.child("requirements.txt");
|
||||||
|
requirements_txt.touch()?;
|
||||||
|
requirements_txt.write_str("httpx==0.25.1")?;
|
||||||
|
|
||||||
|
// Install the package
|
||||||
|
uv_snapshot!(command(&context)
|
||||||
|
.arg("-r")
|
||||||
|
.arg("requirements.txt")
|
||||||
|
.arg("--strict"), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 7 packages in [TIME]
|
||||||
|
Downloaded 7 packages in [TIME]
|
||||||
|
Installed 7 packages in [TIME]
|
||||||
|
+ anyio==4.0.0
|
||||||
|
+ certifi==2023.11.17
|
||||||
|
+ h11==0.14.0
|
||||||
|
+ httpcore==1.0.2
|
||||||
|
+ httpx==0.25.1
|
||||||
|
+ idna==3.4
|
||||||
|
+ sniffio==1.3.0
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
|
||||||
|
// Install again with dry run enabled
|
||||||
|
uv_snapshot!(command(&context)
|
||||||
|
.arg("-r")
|
||||||
|
.arg("requirements.txt")
|
||||||
|
.arg("--dry-run")
|
||||||
|
.arg("--strict"), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Audited 1 package in [TIME]
|
||||||
|
Would make no changes
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dry_run_install_transitive_dependency_already_installed(
|
||||||
|
) -> std::result::Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let context = TestContext::new("3.12");
|
||||||
|
|
||||||
|
let requirements_txt = context.temp_dir.child("requirements.txt");
|
||||||
|
requirements_txt.touch()?;
|
||||||
|
requirements_txt.write_str("httpcore==1.0.2")?;
|
||||||
|
|
||||||
|
// Install a dependency of httpx
|
||||||
|
uv_snapshot!(command(&context)
|
||||||
|
.arg("-r")
|
||||||
|
.arg("requirements.txt")
|
||||||
|
.arg("--strict"), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 3 packages in [TIME]
|
||||||
|
Downloaded 3 packages in [TIME]
|
||||||
|
Installed 3 packages in [TIME]
|
||||||
|
+ certifi==2023.11.17
|
||||||
|
+ h11==0.14.0
|
||||||
|
+ httpcore==1.0.2
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
|
||||||
|
// Install it httpx with dry run enabled
|
||||||
|
requirements_txt.write_str("httpx==0.25.1")?;
|
||||||
|
uv_snapshot!(command(&context)
|
||||||
|
.arg("-r")
|
||||||
|
.arg("requirements.txt")
|
||||||
|
.arg("--dry-run")
|
||||||
|
.arg("--strict"), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 7 packages in [TIME]
|
||||||
|
Would download 4 packages
|
||||||
|
Would install 4 packages
|
||||||
|
+ anyio==4.0.0
|
||||||
|
+ httpx==0.25.1
|
||||||
|
+ idna==3.4
|
||||||
|
+ sniffio==1.3.0
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dry_run_install_then_upgrade() -> std::result::Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let context = TestContext::new("3.12");
|
||||||
|
let requirements_txt = context.temp_dir.child("requirements.txt");
|
||||||
|
requirements_txt.touch()?;
|
||||||
|
requirements_txt.write_str("httpx==0.25.0")?;
|
||||||
|
|
||||||
|
// Install the package
|
||||||
|
uv_snapshot!(command(&context)
|
||||||
|
.arg("-r")
|
||||||
|
.arg("requirements.txt")
|
||||||
|
.arg("--strict"), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 7 packages in [TIME]
|
||||||
|
Downloaded 7 packages in [TIME]
|
||||||
|
Installed 7 packages in [TIME]
|
||||||
|
+ anyio==4.0.0
|
||||||
|
+ certifi==2023.11.17
|
||||||
|
+ h11==0.14.0
|
||||||
|
+ httpcore==0.18.0
|
||||||
|
+ httpx==0.25.0
|
||||||
|
+ idna==3.4
|
||||||
|
+ sniffio==1.3.0
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
|
||||||
|
// Bump the version and install with dry run enabled
|
||||||
|
requirements_txt.write_str("httpx==0.25.1")?;
|
||||||
|
uv_snapshot!(command(&context)
|
||||||
|
.arg("-r")
|
||||||
|
.arg("requirements.txt")
|
||||||
|
.arg("--dry-run"), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 7 packages in [TIME]
|
||||||
|
Would download 1 package
|
||||||
|
Would uninstall 1 package
|
||||||
|
Would install 1 package
|
||||||
|
- httpx==0.25.0
|
||||||
|
+ httpx==0.25.1
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue