Skip to content

Commit dbaec05

Browse files
committed
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.
1 parent dff9ced commit dbaec05

File tree

3 files changed

+56
-162
lines changed

3 files changed

+56
-162
lines changed

crates/uv/src/commands/venv.rs

Lines changed: 17 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@ use std::str::FromStr;
44
use std::sync::Arc;
55
use std::vec;
66

7-
use anstream::eprint;
87
use anyhow::Result;
9-
use miette::{Diagnostic, IntoDiagnostic};
108
use owo_colors::OwoColorize;
119
use thiserror::Error;
1210

@@ -42,96 +40,30 @@ use crate::settings::NetworkSettings;
4240

4341
use super::project::default_dependency_groups;
4442

45-
/// Create a virtual environment.
46-
#[allow(clippy::unnecessary_wraps, clippy::fn_params_excessive_bools)]
47-
pub(crate) async fn venv(
48-
project_dir: &Path,
49-
path: Option<PathBuf>,
50-
python_request: Option<PythonRequest>,
51-
install_mirrors: PythonInstallMirrors,
52-
python_preference: PythonPreference,
53-
python_downloads: PythonDownloads,
54-
link_mode: LinkMode,
55-
index_locations: &IndexLocations,
56-
index_strategy: IndexStrategy,
57-
dependency_metadata: DependencyMetadata,
58-
keyring_provider: KeyringProviderType,
59-
network_settings: &NetworkSettings,
60-
prompt: uv_virtualenv::Prompt,
61-
system_site_packages: bool,
62-
seed: bool,
63-
allow_existing: bool,
64-
exclude_newer: Option<ExcludeNewer>,
65-
concurrency: Concurrency,
66-
no_config: bool,
67-
no_project: bool,
68-
cache: &Cache,
69-
printer: Printer,
70-
relocatable: bool,
71-
preview: PreviewMode,
72-
) -> Result<ExitStatus> {
73-
match venv_impl(
74-
project_dir,
75-
path,
76-
python_request,
77-
install_mirrors,
78-
link_mode,
79-
index_locations,
80-
index_strategy,
81-
dependency_metadata,
82-
keyring_provider,
83-
network_settings,
84-
prompt,
85-
system_site_packages,
86-
seed,
87-
python_preference,
88-
python_downloads,
89-
allow_existing,
90-
exclude_newer,
91-
concurrency,
92-
no_config,
93-
no_project,
94-
cache,
95-
printer,
96-
relocatable,
97-
preview,
98-
)
99-
.await
100-
{
101-
Ok(status) => Ok(status),
102-
Err(err) => {
103-
eprint!("{err:?}");
104-
Ok(ExitStatus::Failure)
105-
}
106-
}
107-
}
108-
109-
#[derive(Error, Debug, Diagnostic)]
43+
#[derive(Error, Debug)]
11044
enum VenvError {
111-
#[error("Failed to create virtualenv")]
112-
#[diagnostic(code(uv::venv::creation))]
45+
#[error("Failed to create virtual environment")]
11346
Creation(#[source] uv_virtualenv::Error),
11447

115-
#[error("Failed to install seed packages")]
116-
#[diagnostic(code(uv::venv::seed))]
48+
#[error("Failed to install seed packages into virtual environment")]
11749
Seed(#[source] AnyErrorBuild),
11850

119-
#[error("Failed to extract interpreter tags")]
120-
#[diagnostic(code(uv::venv::tags))]
51+
#[error("Failed to extract interpreter tags for installing seed packages")]
12152
Tags(#[source] uv_platform_tags::TagsError),
12253

12354
#[error("Failed to resolve `--find-links` entry")]
124-
#[diagnostic(code(uv::venv::flat_index))]
12555
FlatIndex(#[source] uv_client::FlatIndexError),
12656
}
12757

12858
/// Create a virtual environment.
129-
#[allow(clippy::fn_params_excessive_bools)]
130-
async fn venv_impl(
59+
#[allow(clippy::unnecessary_wraps, clippy::fn_params_excessive_bools)]
60+
pub(crate) async fn venv(
13161
project_dir: &Path,
13262
path: Option<PathBuf>,
13363
python_request: Option<PythonRequest>,
13464
install_mirrors: PythonInstallMirrors,
65+
python_preference: PythonPreference,
66+
python_downloads: PythonDownloads,
13567
link_mode: LinkMode,
13668
index_locations: &IndexLocations,
13769
index_strategy: IndexStrategy,
@@ -141,8 +73,6 @@ async fn venv_impl(
14173
prompt: uv_virtualenv::Prompt,
14274
system_site_packages: bool,
14375
seed: bool,
144-
python_preference: PythonPreference,
145-
python_downloads: PythonDownloads,
14676
allow_existing: bool,
14777
exclude_newer: Option<ExcludeNewer>,
14878
concurrency: Concurrency,
@@ -152,7 +82,7 @@ async fn venv_impl(
15282
printer: Printer,
15383
relocatable: bool,
15484
preview: PreviewMode,
155-
) -> miette::Result<ExitStatus> {
85+
) -> Result<ExitStatus> {
15686
let workspace_cache = WorkspaceCache::default();
15787
let project = if no_project {
15888
None
@@ -206,7 +136,7 @@ async fn venv_impl(
206136
// If the default dependency-groups demand a higher requires-python
207137
// we should bias an empty venv to that to avoid churn.
208138
let default_groups = match &project {
209-
Some(project) => default_dependency_groups(project.pyproject_toml()).into_diagnostic()?,
139+
Some(project) => default_dependency_groups(project.pyproject_toml())?,
210140
None => DefaultGroups::default(),
211141
};
212142
let groups = DependencyGroups::default().with_defaults(default_groups);
@@ -221,8 +151,7 @@ async fn venv_impl(
221151
project_dir,
222152
no_config,
223153
)
224-
.await
225-
.into_diagnostic()?;
154+
.await?;
226155

227156
// Locate the Python interpreter to use in the environment
228157
let interpreter = {
@@ -239,9 +168,8 @@ async fn venv_impl(
239168
install_mirrors.python_downloads_json_url.as_deref(),
240169
preview,
241170
)
242-
.await
243-
.into_diagnostic()?;
244-
report_interpreter(&python, false, printer).into_diagnostic()?;
171+
.await?;
172+
report_interpreter(&python, false, printer)?;
245173
python.into_interpreter()
246174
};
247175

@@ -268,8 +196,7 @@ async fn venv_impl(
268196
"Creating virtual environment {}at: {}",
269197
if seed { "with seed packages " } else { "" },
270198
path.user_display().cyan()
271-
)
272-
.into_diagnostic()?;
199+
)?;
273200

274201
let upgradeable = preview.is_enabled()
275202
&& python_request
@@ -307,8 +234,7 @@ async fn venv_impl(
307234
}
308235

309236
// Instantiate a client.
310-
let client = RegistryClientBuilder::try_from(client_builder)
311-
.into_diagnostic()?
237+
let client = RegistryClientBuilder::try_from(client_builder)?
312238
.cache(cache.clone())
313239
.index_locations(index_locations)
314240
.index_strategy(index_strategy)
@@ -400,9 +326,7 @@ async fn venv_impl(
400326
.map_err(|err| VenvError::Seed(err.into()))?;
401327

402328
let changelog = Changelog::from_installed(installed);
403-
DefaultInstallLogger
404-
.on_complete(&changelog, printer)
405-
.into_diagnostic()?;
329+
DefaultInstallLogger.on_complete(&changelog, printer)?;
406330
}
407331

408332
// Determine the appropriate activation command.
@@ -431,7 +355,7 @@ async fn venv_impl(
431355
Some(Shell::Cmd) => Some(shlex_windows(venv.scripts().join("activate"), Shell::Cmd)),
432356
};
433357
if let Some(act) = activation {
434-
writeln!(printer.stderr(), "Activate with: {}", act.green()).into_diagnostic()?;
358+
writeln!(printer.stderr(), "Activate with: {}", act.green())?;
435359
}
436360

437361
Ok(ExitStatus::Success)

crates/uv/tests/it/pip_compile.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17411,11 +17411,11 @@ fn compile_broken_active_venv() -> Result<()> {
1741117411
.arg(&broken_system_python)
1741217412
.arg("venv2"), @r"
1741317413
success: false
17414-
exit_code: 1
17414+
exit_code: 2
1741517415
----- stdout -----
1741617416

1741717417
----- stderr -----
17418-
× No interpreter found at path `python3.14159`
17418+
error: No interpreter found at path `python3.14159`
1741917419
");
1742017420

1742117421
// Simulate a removed Python interpreter

crates/uv/tests/it/venv.rs

Lines changed: 37 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -656,13 +656,13 @@ fn create_venv_respects_group_requires_python() -> Result<()> {
656656

657657
uv_snapshot!(context.filters(), context.venv().arg("--python").arg("3.11"), @r"
658658
success: false
659-
exit_code: 1
659+
exit_code: 2
660660
----- stdout -----
661661
662662
----- stderr -----
663-
× Found conflicting Python requirements:
664-
- foo: <3.12
665-
- foo:dev: >=3.12
663+
error: Found conflicting Python requirements:
664+
- foo: <3.12
665+
- foo:dev: >=3.12
666666
"
667667
);
668668

@@ -808,7 +808,7 @@ fn seed_older_python_version() {
808808

809809
#[test]
810810
fn create_venv_unknown_python_minor() {
811-
let context = TestContext::new_with_versions(&["3.12"]);
811+
let context = TestContext::new_with_versions(&["3.12"]).with_filtered_python_sources();
812812

813813
let mut command = context.venv();
814814
command
@@ -819,34 +819,22 @@ fn create_venv_unknown_python_minor() {
819819
// Unset this variable to force what the user would see
820820
.env_remove(EnvVars::UV_TEST_PYTHON_PATH);
821821

822-
if cfg!(windows) {
823-
uv_snapshot!(&mut command, @r###"
824-
success: false
825-
exit_code: 1
826-
----- stdout -----
827-
828-
----- stderr -----
829-
× No interpreter found for Python 3.100 in managed installations, search path, or registry
830-
"###
831-
);
832-
} else {
833-
uv_snapshot!(&mut command, @r###"
834-
success: false
835-
exit_code: 1
836-
----- stdout -----
822+
uv_snapshot!(context.filters(), &mut command, @r"
823+
success: false
824+
exit_code: 2
825+
----- stdout -----
837826
838-
----- stderr -----
839-
× No interpreter found for Python 3.100 in managed installations or search path
840-
"###
841-
);
842-
}
827+
----- stderr -----
828+
error: No interpreter found for Python 3.100 in [PYTHON SOURCES]
829+
"
830+
);
843831

844832
context.venv.assert(predicates::path::missing());
845833
}
846834

847835
#[test]
848836
fn create_venv_unknown_python_patch() {
849-
let context = TestContext::new_with_versions(&["3.12"]);
837+
let context = TestContext::new_with_versions(&["3.12"]).with_filtered_python_sources();
850838

851839
let mut command = context.venv();
852840
command
@@ -857,27 +845,15 @@ fn create_venv_unknown_python_patch() {
857845
// Unset this variable to force what the user would see
858846
.env_remove(EnvVars::UV_TEST_PYTHON_PATH);
859847

860-
if cfg!(windows) {
861-
uv_snapshot!(&mut command, @r###"
862-
success: false
863-
exit_code: 1
864-
----- stdout -----
865-
866-
----- stderr -----
867-
× No interpreter found for Python 3.12.100 in managed installations, search path, or registry
868-
"###
869-
);
870-
} else {
871-
uv_snapshot!(&mut command, @r"
872-
success: false
873-
exit_code: 1
874-
----- stdout -----
848+
uv_snapshot!(context.filters(), &mut command, @r"
849+
success: false
850+
exit_code: 2
851+
----- stdout -----
875852
876-
----- stderr -----
877-
× No interpreter found for Python 3.12.100 in managed installations or search path
878-
"
879-
);
880-
}
853+
----- stderr -----
854+
error: No interpreter found for Python 3.12.[X] in [PYTHON SOURCES]
855+
"
856+
);
881857

882858
context.venv.assert(predicates::path::missing());
883859
}
@@ -915,19 +891,17 @@ fn file_exists() -> Result<()> {
915891
uv_snapshot!(context.filters(), context.venv()
916892
.arg(context.venv.as_os_str())
917893
.arg("--python")
918-
.arg("3.12"), @r###"
894+
.arg("3.12"), @r"
919895
success: false
920-
exit_code: 1
896+
exit_code: 2
921897
----- stdout -----
922898
923899
----- stderr -----
924900
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
925901
Creating virtual environment at: .venv
926-
uv::venv::creation
927-
928-
× Failed to create virtualenv
929-
╰─▶ File exists at `.venv`
930-
"###
902+
error: Failed to create virtual environment
903+
Caused by: File exists at `.venv`
904+
"
931905
);
932906

933907
Ok(())
@@ -970,19 +944,17 @@ fn non_empty_dir_exists() -> Result<()> {
970944
uv_snapshot!(context.filters(), context.venv()
971945
.arg(context.venv.as_os_str())
972946
.arg("--python")
973-
.arg("3.12"), @r###"
947+
.arg("3.12"), @r"
974948
success: false
975-
exit_code: 1
949+
exit_code: 2
976950
----- stdout -----
977951
978952
----- stderr -----
979953
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
980954
Creating virtual environment at: .venv
981-
uv::venv::creation
982-
983-
× Failed to create virtualenv
984-
╰─▶ The directory `.venv` exists, but it's not a virtual environment
985-
"###
955+
error: Failed to create virtual environment
956+
Caused by: The directory `.venv` exists, but it's not a virtual environment
957+
"
986958
);
987959

988960
Ok(())
@@ -1000,19 +972,17 @@ fn non_empty_dir_exists_allow_existing() -> Result<()> {
1000972
uv_snapshot!(context.filters(), context.venv()
1001973
.arg(context.venv.as_os_str())
1002974
.arg("--python")
1003-
.arg("3.12"), @r###"
975+
.arg("3.12"), @r"
1004976
success: false
1005-
exit_code: 1
977+
exit_code: 2
1006978
----- stdout -----
1007979
1008980
----- stderr -----
1009981
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
1010982
Creating virtual environment at: .venv
1011-
uv::venv::creation
1012-
1013-
× Failed to create virtualenv
1014-
╰─▶ The directory `.venv` exists, but it's not a virtual environment
1015-
"###
983+
error: Failed to create virtual environment
984+
Caused by: The directory `.venv` exists, but it's not a virtual environment
985+
"
1016986
);
1017987

1018988
uv_snapshot!(context.filters(), context.venv()

0 commit comments

Comments
 (0)