Skip to content

Commit 71d5661

Browse files
Add a custom suggestion for uv add dotenv (#7799)
## Summary This was brought up on Twitter recently. `dotenv` hasn't been updated in years and doesn't build successfully anymore. Users almost always mean to install `python-dotenv`. I think we can add helpful hints here to point users in the right direction. ## Test Plan ![Screenshot 2024-09-29 at 9 27 27 PM](https://github.com/user-attachments/assets/72585860-9d98-4478-9eac-2c17ac06178b)
1 parent da9e85c commit 71d5661

9 files changed

Lines changed: 66 additions & 14 deletions

File tree

Cargo.lock

Lines changed: 11 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ uv-cache-info = { path = "crates/uv-cache-info" }
3838
uv-cli = { path = "crates/uv-cli" }
3939
uv-client = { path = "crates/uv-client" }
4040
uv-configuration = { path = "crates/uv-configuration" }
41+
uv-console = { path = "crates/uv-console" }
4142
uv-dispatch = { path = "crates/uv-dispatch" }
4243
uv-distribution = { path = "crates/uv-distribution" }
4344
uv-extract = { path = "crates/uv-extract" }

crates/uv-console/Cargo.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[package]
2+
name = "uv-console"
3+
version = "0.0.1"
4+
edition = "2021"
5+
description = "Utilities for interacting with the terminal"
6+
7+
[lints]
8+
workspace = true
9+
10+
[dependencies]
11+
ctrlc = { workspace = true }
12+
console = { workspace = true }
Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
use anyhow::Result;
21
use console::{style, Key, Term};
32

43
/// Prompt the user for confirmation in the given [`Term`].
54
///
65
/// This is a slimmed-down version of `dialoguer::Confirm`, with the post-confirmation report
76
/// enabled.
8-
pub(crate) fn confirm(message: &str, term: &Term, default: bool) -> Result<bool> {
7+
pub fn confirm(message: &str, term: &Term, default: bool) -> std::io::Result<bool> {
98
// Set the Ctrl-C handler to exit the process.
109
let result = ctrlc::set_handler(move || {
1110
let term = Term::stderr();
@@ -26,7 +25,7 @@ pub(crate) fn confirm(message: &str, term: &Term, default: bool) -> Result<bool>
2625
// If multiple handlers were set, we assume that the existing handler is our
2726
// confirmation handler, and continue.
2827
}
29-
Err(e) => return Err(e.into()),
28+
Err(err) => return Err(std::io::Error::new(std::io::ErrorKind::Other, err)),
3029
}
3130

3231
let prompt = format!(

crates/uv-requirements/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ pypi-types = { workspace = true }
2121
requirements-txt = { workspace = true, features = ["http"] }
2222
uv-client = { workspace = true }
2323
uv-configuration = { workspace = true }
24+
uv-console = { workspace = true }
2425
uv-distribution = { workspace = true }
2526
uv-fs = { workspace = true }
2627
uv-git = { workspace = true }
@@ -33,7 +34,6 @@ uv-workspace = { workspace = true }
3334
anyhow = { workspace = true }
3435
configparser = { workspace = true }
3536
console = { workspace = true }
36-
ctrlc = { workspace = true }
3737
fs-err = { workspace = true, features = ["tokio"] }
3838
futures = { workspace = true }
3939
rustc-hash = { workspace = true }

crates/uv-requirements/src/lib.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ pub use crate::sources::*;
44
pub use crate::specification::*;
55
pub use crate::unnamed::*;
66

7-
mod confirm;
87
mod lookahead;
98
mod source_tree;
109
mod sources;

crates/uv-requirements/src/sources.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ use console::Term;
55
use uv_fs::Simplified;
66
use uv_warnings::warn_user;
77

8-
use crate::confirm;
9-
108
#[derive(Debug, Clone)]
119
pub enum RequirementsSource {
1210
/// A package was provided on the command line (e.g., `pip install flask`).
@@ -96,7 +94,7 @@ impl RequirementsSource {
9694
let prompt = format!(
9795
"`{name}` looks like a local requirements file but was passed as a package name. Did you mean `-r {name}`?"
9896
);
99-
let confirmation = confirm::confirm(&prompt, &term, true).unwrap();
97+
let confirmation = uv_console::confirm(&prompt, &term, true).unwrap();
10098
if confirmation {
10199
return Self::from_requirements_file(name.into());
102100
}
@@ -113,7 +111,7 @@ impl RequirementsSource {
113111
let prompt = format!(
114112
"`{name}` looks like a local metadata file but was passed as a package name. Did you mean `-r {name}`?"
115113
);
116-
let confirmation = confirm::confirm(&prompt, &term, true).unwrap();
114+
let confirmation = uv_console::confirm(&prompt, &term, true).unwrap();
117115
if confirmation {
118116
return Self::from_requirements_file(name.into());
119117
}

crates/uv/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ uv-cache-info = { workspace = true }
2828
uv-cli = { workspace = true }
2929
uv-client = { workspace = true }
3030
uv-configuration = { workspace = true }
31+
uv-console = { workspace = true }
3132
uv-dispatch = { workspace = true }
3233
uv-distribution = { workspace = true }
3334
uv-extract = { workspace = true }
@@ -57,6 +58,7 @@ axoupdater = { workspace = true, features = [
5758
"tokio",
5859
], optional = true }
5960
clap = { workspace = true, features = ["derive", "string", "wrap_help"] }
61+
console = { workspace = true }
6062
ctrlc = { workspace = true }
6163
flate2 = { workspace = true, default-features = false }
6264
fs-err = { workspace = true, features = ["tokio"] }

crates/uv/src/commands/project/add.rs

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
use std::collections::hash_map::Entry;
2-
use std::fmt::Write;
3-
use std::path::{Path, PathBuf};
4-
51
use anyhow::{bail, Context, Result};
2+
use console::Term;
63
use itertools::Itertools;
74
use owo_colors::OwoColorize;
85
use rustc_hash::{FxBuildHasher, FxHashMap};
6+
use std::collections::hash_map::Entry;
7+
use std::fmt::Write;
8+
use std::path::{Path, PathBuf};
9+
use std::str::FromStr;
10+
use std::sync::LazyLock;
911
use tracing::debug;
1012

1113
use cache_key::RepositoryUrl;
@@ -48,6 +50,18 @@ use crate::commands::{pip, project, ExitStatus, SharedState};
4850
use crate::printer::Printer;
4951
use crate::settings::{ResolverInstallerSettings, ResolverInstallerSettingsRef};
5052

53+
static CORRECTIONS: LazyLock<FxHashMap<PackageName, PackageName>> = LazyLock::new(|| {
54+
[("dotenv", "python-dotenv"), ("sklearn", "scikit-learn")]
55+
.iter()
56+
.map(|(k, v)| {
57+
(
58+
PackageName::from_str(k).unwrap(),
59+
PackageName::from_str(v).unwrap(),
60+
)
61+
})
62+
.collect()
63+
});
64+
5165
/// Add one or more packages to the project requirements.
5266
#[allow(clippy::fn_params_excessive_bools)]
5367
pub(crate) async fn add(
@@ -371,6 +385,23 @@ pub(crate) async fn add(
371385
}?;
372386
let mut edits = Vec::<DependencyEdit>::with_capacity(requirements.len());
373387
for mut requirement in requirements {
388+
// If the user requested a package that is often confused for another package, prompt them.
389+
if let Some(correction) = CORRECTIONS.get(&requirement.name) {
390+
let term = Term::stderr();
391+
if term.is_term() {
392+
let prompt = format!(
393+
"`{}` is often confused for `{}`. Did you mean `{}`?",
394+
requirement.name.cyan(),
395+
correction.cyan(),
396+
format!("uv add {correction}").green()
397+
);
398+
let confirmation = uv_console::confirm(&prompt, &term, true)?;
399+
if confirmation {
400+
requirement.name = correction.clone();
401+
}
402+
}
403+
}
404+
374405
// Add the specified extras.
375406
requirement.extras.extend(extras.iter().cloned());
376407
requirement.extras.sort_unstable();

0 commit comments

Comments
 (0)