Tear miette out of the uv venv command (#14546)

This has some changes to the user-facing output, but makes it more
consistent with the rest of uv.
This commit is contained in:
Zanie Blue 2025-07-11 07:47:06 -05:00
parent dff9ced40a
commit dbaec0537a
3 changed files with 62 additions and 168 deletions

View file

@ -4,9 +4,7 @@ use std::str::FromStr;
use std::sync::Arc; use std::sync::Arc;
use std::vec; use std::vec;
use anstream::eprint;
use anyhow::Result; use anyhow::Result;
use miette::{Diagnostic, IntoDiagnostic};
use owo_colors::OwoColorize; use owo_colors::OwoColorize;
use thiserror::Error; use thiserror::Error;
@ -42,6 +40,21 @@ use crate::settings::NetworkSettings;
use super::project::default_dependency_groups; use super::project::default_dependency_groups;
#[derive(Error, Debug)]
enum VenvError {
#[error("Failed to create virtual environment")]
Creation(#[source] uv_virtualenv::Error),
#[error("Failed to install seed packages into virtual environment")]
Seed(#[source] AnyErrorBuild),
#[error("Failed to extract interpreter tags for installing seed packages")]
Tags(#[source] uv_platform_tags::TagsError),
#[error("Failed to resolve `--find-links` entry")]
FlatIndex(#[source] uv_client::FlatIndexError),
}
/// Create a virtual environment. /// Create a virtual environment.
#[allow(clippy::unnecessary_wraps, clippy::fn_params_excessive_bools)] #[allow(clippy::unnecessary_wraps, clippy::fn_params_excessive_bools)]
pub(crate) async fn venv( pub(crate) async fn venv(
@ -70,89 +83,6 @@ pub(crate) async fn venv(
relocatable: bool, relocatable: bool,
preview: PreviewMode, preview: PreviewMode,
) -> Result<ExitStatus> { ) -> Result<ExitStatus> {
match venv_impl(
project_dir,
path,
python_request,
install_mirrors,
link_mode,
index_locations,
index_strategy,
dependency_metadata,
keyring_provider,
network_settings,
prompt,
system_site_packages,
seed,
python_preference,
python_downloads,
allow_existing,
exclude_newer,
concurrency,
no_config,
no_project,
cache,
printer,
relocatable,
preview,
)
.await
{
Ok(status) => Ok(status),
Err(err) => {
eprint!("{err:?}");
Ok(ExitStatus::Failure)
}
}
}
#[derive(Error, Debug, Diagnostic)]
enum VenvError {
#[error("Failed to create virtualenv")]
#[diagnostic(code(uv::venv::creation))]
Creation(#[source] uv_virtualenv::Error),
#[error("Failed to install seed packages")]
#[diagnostic(code(uv::venv::seed))]
Seed(#[source] AnyErrorBuild),
#[error("Failed to extract interpreter tags")]
#[diagnostic(code(uv::venv::tags))]
Tags(#[source] uv_platform_tags::TagsError),
#[error("Failed to resolve `--find-links` entry")]
#[diagnostic(code(uv::venv::flat_index))]
FlatIndex(#[source] uv_client::FlatIndexError),
}
/// Create a virtual environment.
#[allow(clippy::fn_params_excessive_bools)]
async fn venv_impl(
project_dir: &Path,
path: Option<PathBuf>,
python_request: Option<PythonRequest>,
install_mirrors: PythonInstallMirrors,
link_mode: LinkMode,
index_locations: &IndexLocations,
index_strategy: IndexStrategy,
dependency_metadata: DependencyMetadata,
keyring_provider: KeyringProviderType,
network_settings: &NetworkSettings,
prompt: uv_virtualenv::Prompt,
system_site_packages: bool,
seed: bool,
python_preference: PythonPreference,
python_downloads: PythonDownloads,
allow_existing: bool,
exclude_newer: Option<ExcludeNewer>,
concurrency: Concurrency,
no_config: bool,
no_project: bool,
cache: &Cache,
printer: Printer,
relocatable: bool,
preview: PreviewMode,
) -> miette::Result<ExitStatus> {
let workspace_cache = WorkspaceCache::default(); let workspace_cache = WorkspaceCache::default();
let project = if no_project { let project = if no_project {
None None
@ -206,7 +136,7 @@ async fn venv_impl(
// If the default dependency-groups demand a higher requires-python // If the default dependency-groups demand a higher requires-python
// we should bias an empty venv to that to avoid churn. // we should bias an empty venv to that to avoid churn.
let default_groups = match &project { let default_groups = match &project {
Some(project) => default_dependency_groups(project.pyproject_toml()).into_diagnostic()?, Some(project) => default_dependency_groups(project.pyproject_toml())?,
None => DefaultGroups::default(), None => DefaultGroups::default(),
}; };
let groups = DependencyGroups::default().with_defaults(default_groups); let groups = DependencyGroups::default().with_defaults(default_groups);
@ -221,8 +151,7 @@ async fn venv_impl(
project_dir, project_dir,
no_config, no_config,
) )
.await .await?;
.into_diagnostic()?;
// Locate the Python interpreter to use in the environment // Locate the Python interpreter to use in the environment
let interpreter = { let interpreter = {
@ -239,9 +168,8 @@ async fn venv_impl(
install_mirrors.python_downloads_json_url.as_deref(), install_mirrors.python_downloads_json_url.as_deref(),
preview, preview,
) )
.await .await?;
.into_diagnostic()?; report_interpreter(&python, false, printer)?;
report_interpreter(&python, false, printer).into_diagnostic()?;
python.into_interpreter() python.into_interpreter()
}; };
@ -268,8 +196,7 @@ async fn venv_impl(
"Creating virtual environment {}at: {}", "Creating virtual environment {}at: {}",
if seed { "with seed packages " } else { "" }, if seed { "with seed packages " } else { "" },
path.user_display().cyan() path.user_display().cyan()
) )?;
.into_diagnostic()?;
let upgradeable = preview.is_enabled() let upgradeable = preview.is_enabled()
&& python_request && python_request
@ -307,8 +234,7 @@ async fn venv_impl(
} }
// Instantiate a client. // Instantiate a client.
let client = RegistryClientBuilder::try_from(client_builder) let client = RegistryClientBuilder::try_from(client_builder)?
.into_diagnostic()?
.cache(cache.clone()) .cache(cache.clone())
.index_locations(index_locations) .index_locations(index_locations)
.index_strategy(index_strategy) .index_strategy(index_strategy)
@ -400,9 +326,7 @@ async fn venv_impl(
.map_err(|err| VenvError::Seed(err.into()))?; .map_err(|err| VenvError::Seed(err.into()))?;
let changelog = Changelog::from_installed(installed); let changelog = Changelog::from_installed(installed);
DefaultInstallLogger DefaultInstallLogger.on_complete(&changelog, printer)?;
.on_complete(&changelog, printer)
.into_diagnostic()?;
} }
// Determine the appropriate activation command. // Determine the appropriate activation command.
@ -431,7 +355,7 @@ async fn venv_impl(
Some(Shell::Cmd) => Some(shlex_windows(venv.scripts().join("activate"), Shell::Cmd)), Some(Shell::Cmd) => Some(shlex_windows(venv.scripts().join("activate"), Shell::Cmd)),
}; };
if let Some(act) = activation { if let Some(act) = activation {
writeln!(printer.stderr(), "Activate with: {}", act.green()).into_diagnostic()?; writeln!(printer.stderr(), "Activate with: {}", act.green())?;
} }
Ok(ExitStatus::Success) Ok(ExitStatus::Success)

View file

@ -17411,11 +17411,11 @@ fn compile_broken_active_venv() -> Result<()> {
.arg(&broken_system_python) .arg(&broken_system_python)
.arg("venv2"), @r" .arg("venv2"), @r"
success: false success: false
exit_code: 1 exit_code: 2
----- stdout ----- ----- stdout -----
----- stderr ----- ----- stderr -----
× No interpreter found at path `python3.14159` error: No interpreter found at path `python3.14159`
"); ");
// Simulate a removed Python interpreter // Simulate a removed Python interpreter

View file

@ -656,13 +656,13 @@ fn create_venv_respects_group_requires_python() -> Result<()> {
uv_snapshot!(context.filters(), context.venv().arg("--python").arg("3.11"), @r" uv_snapshot!(context.filters(), context.venv().arg("--python").arg("3.11"), @r"
success: false success: false
exit_code: 1 exit_code: 2
----- stdout ----- ----- stdout -----
----- stderr ----- ----- stderr -----
× Found conflicting Python requirements: error: Found conflicting Python requirements:
- foo: <3.12 - foo: <3.12
- foo:dev: >=3.12 - foo:dev: >=3.12
" "
); );
@ -808,7 +808,7 @@ fn seed_older_python_version() {
#[test] #[test]
fn create_venv_unknown_python_minor() { fn create_venv_unknown_python_minor() {
let context = TestContext::new_with_versions(&["3.12"]); let context = TestContext::new_with_versions(&["3.12"]).with_filtered_python_sources();
let mut command = context.venv(); let mut command = context.venv();
command command
@ -819,34 +819,22 @@ fn create_venv_unknown_python_minor() {
// Unset this variable to force what the user would see // Unset this variable to force what the user would see
.env_remove(EnvVars::UV_TEST_PYTHON_PATH); .env_remove(EnvVars::UV_TEST_PYTHON_PATH);
if cfg!(windows) { uv_snapshot!(context.filters(), &mut command, @r"
uv_snapshot!(&mut command, @r###" success: false
success: false exit_code: 2
exit_code: 1 ----- stdout -----
----- stdout -----
----- stderr ----- ----- stderr -----
× No interpreter found for Python 3.100 in managed installations, search path, or registry error: No interpreter found for Python 3.100 in [PYTHON SOURCES]
"### "
); );
} else {
uv_snapshot!(&mut command, @r###"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No interpreter found for Python 3.100 in managed installations or search path
"###
);
}
context.venv.assert(predicates::path::missing()); context.venv.assert(predicates::path::missing());
} }
#[test] #[test]
fn create_venv_unknown_python_patch() { fn create_venv_unknown_python_patch() {
let context = TestContext::new_with_versions(&["3.12"]); let context = TestContext::new_with_versions(&["3.12"]).with_filtered_python_sources();
let mut command = context.venv(); let mut command = context.venv();
command command
@ -857,27 +845,15 @@ fn create_venv_unknown_python_patch() {
// Unset this variable to force what the user would see // Unset this variable to force what the user would see
.env_remove(EnvVars::UV_TEST_PYTHON_PATH); .env_remove(EnvVars::UV_TEST_PYTHON_PATH);
if cfg!(windows) { uv_snapshot!(context.filters(), &mut command, @r"
uv_snapshot!(&mut command, @r###" success: false
success: false exit_code: 2
exit_code: 1 ----- stdout -----
----- stdout -----
----- stderr ----- ----- stderr -----
× No interpreter found for Python 3.12.100 in managed installations, search path, or registry error: No interpreter found for Python 3.12.[X] in [PYTHON SOURCES]
"### "
); );
} else {
uv_snapshot!(&mut command, @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No interpreter found for Python 3.12.100 in managed installations or search path
"
);
}
context.venv.assert(predicates::path::missing()); context.venv.assert(predicates::path::missing());
} }
@ -915,19 +891,17 @@ fn file_exists() -> Result<()> {
uv_snapshot!(context.filters(), context.venv() uv_snapshot!(context.filters(), context.venv()
.arg(context.venv.as_os_str()) .arg(context.venv.as_os_str())
.arg("--python") .arg("--python")
.arg("3.12"), @r###" .arg("3.12"), @r"
success: false success: false
exit_code: 1 exit_code: 2
----- stdout ----- ----- stdout -----
----- stderr ----- ----- stderr -----
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
Creating virtual environment at: .venv Creating virtual environment at: .venv
uv::venv::creation error: Failed to create virtual environment
Caused by: File exists at `.venv`
× Failed to create virtualenv "
File exists at `.venv`
"###
); );
Ok(()) Ok(())
@ -970,19 +944,17 @@ fn non_empty_dir_exists() -> Result<()> {
uv_snapshot!(context.filters(), context.venv() uv_snapshot!(context.filters(), context.venv()
.arg(context.venv.as_os_str()) .arg(context.venv.as_os_str())
.arg("--python") .arg("--python")
.arg("3.12"), @r###" .arg("3.12"), @r"
success: false success: false
exit_code: 1 exit_code: 2
----- stdout ----- ----- stdout -----
----- stderr ----- ----- stderr -----
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
Creating virtual environment at: .venv Creating virtual environment at: .venv
uv::venv::creation error: Failed to create virtual environment
Caused by: The directory `.venv` exists, but it's not a virtual environment
× Failed to create virtualenv "
The directory `.venv` exists, but it's not a virtual environment
"###
); );
Ok(()) Ok(())
@ -1000,19 +972,17 @@ fn non_empty_dir_exists_allow_existing() -> Result<()> {
uv_snapshot!(context.filters(), context.venv() uv_snapshot!(context.filters(), context.venv()
.arg(context.venv.as_os_str()) .arg(context.venv.as_os_str())
.arg("--python") .arg("--python")
.arg("3.12"), @r###" .arg("3.12"), @r"
success: false success: false
exit_code: 1 exit_code: 2
----- stdout ----- ----- stdout -----
----- stderr ----- ----- stderr -----
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
Creating virtual environment at: .venv Creating virtual environment at: .venv
uv::venv::creation error: Failed to create virtual environment
Caused by: The directory `.venv` exists, but it's not a virtual environment
× Failed to create virtualenv "
The directory `.venv` exists, but it's not a virtual environment
"###
); );
uv_snapshot!(context.filters(), context.venv() uv_snapshot!(context.filters(), context.venv()