mirror of https://github.com/astral-sh/uv
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 
This commit is contained in:
parent
da9e85cc6a
commit
71d5661bd8
|
|
@ -4440,6 +4440,7 @@ dependencies = [
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"cache-key",
|
"cache-key",
|
||||||
"clap",
|
"clap",
|
||||||
|
"console",
|
||||||
"ctrlc",
|
"ctrlc",
|
||||||
"distribution-filename",
|
"distribution-filename",
|
||||||
"distribution-types",
|
"distribution-types",
|
||||||
|
|
@ -4488,6 +4489,7 @@ dependencies = [
|
||||||
"uv-cli",
|
"uv-cli",
|
||||||
"uv-client",
|
"uv-client",
|
||||||
"uv-configuration",
|
"uv-configuration",
|
||||||
|
"uv-console",
|
||||||
"uv-dispatch",
|
"uv-dispatch",
|
||||||
"uv-distribution",
|
"uv-distribution",
|
||||||
"uv-extract",
|
"uv-extract",
|
||||||
|
|
@ -4707,6 +4709,14 @@ dependencies = [
|
||||||
"which",
|
"which",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "uv-console"
|
||||||
|
version = "0.0.1"
|
||||||
|
dependencies = [
|
||||||
|
"console",
|
||||||
|
"ctrlc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uv-dev"
|
name = "uv-dev"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
|
|
@ -5078,7 +5088,6 @@ dependencies = [
|
||||||
"cache-key",
|
"cache-key",
|
||||||
"configparser",
|
"configparser",
|
||||||
"console",
|
"console",
|
||||||
"ctrlc",
|
|
||||||
"distribution-filename",
|
"distribution-filename",
|
||||||
"distribution-types",
|
"distribution-types",
|
||||||
"fs-err",
|
"fs-err",
|
||||||
|
|
@ -5094,6 +5103,7 @@ dependencies = [
|
||||||
"url",
|
"url",
|
||||||
"uv-client",
|
"uv-client",
|
||||||
"uv-configuration",
|
"uv-configuration",
|
||||||
|
"uv-console",
|
||||||
"uv-distribution",
|
"uv-distribution",
|
||||||
"uv-fs",
|
"uv-fs",
|
||||||
"uv-git",
|
"uv-git",
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ uv-cache-info = { path = "crates/uv-cache-info" }
|
||||||
uv-cli = { path = "crates/uv-cli" }
|
uv-cli = { path = "crates/uv-cli" }
|
||||||
uv-client = { path = "crates/uv-client" }
|
uv-client = { path = "crates/uv-client" }
|
||||||
uv-configuration = { path = "crates/uv-configuration" }
|
uv-configuration = { path = "crates/uv-configuration" }
|
||||||
|
uv-console = { path = "crates/uv-console" }
|
||||||
uv-dispatch = { path = "crates/uv-dispatch" }
|
uv-dispatch = { path = "crates/uv-dispatch" }
|
||||||
uv-distribution = { path = "crates/uv-distribution" }
|
uv-distribution = { path = "crates/uv-distribution" }
|
||||||
uv-extract = { path = "crates/uv-extract" }
|
uv-extract = { path = "crates/uv-extract" }
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
[package]
|
||||||
|
name = "uv-console"
|
||||||
|
version = "0.0.1"
|
||||||
|
edition = "2021"
|
||||||
|
description = "Utilities for interacting with the terminal"
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
ctrlc = { workspace = true }
|
||||||
|
console = { workspace = true }
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
use anyhow::Result;
|
|
||||||
use console::{style, Key, Term};
|
use console::{style, Key, Term};
|
||||||
|
|
||||||
/// Prompt the user for confirmation in the given [`Term`].
|
/// Prompt the user for confirmation in the given [`Term`].
|
||||||
///
|
///
|
||||||
/// This is a slimmed-down version of `dialoguer::Confirm`, with the post-confirmation report
|
/// This is a slimmed-down version of `dialoguer::Confirm`, with the post-confirmation report
|
||||||
/// enabled.
|
/// enabled.
|
||||||
pub(crate) fn confirm(message: &str, term: &Term, default: bool) -> Result<bool> {
|
pub fn confirm(message: &str, term: &Term, default: bool) -> std::io::Result<bool> {
|
||||||
// Set the Ctrl-C handler to exit the process.
|
// Set the Ctrl-C handler to exit the process.
|
||||||
let result = ctrlc::set_handler(move || {
|
let result = ctrlc::set_handler(move || {
|
||||||
let term = Term::stderr();
|
let term = Term::stderr();
|
||||||
|
|
@ -26,7 +25,7 @@ pub(crate) fn confirm(message: &str, term: &Term, default: bool) -> Result<bool>
|
||||||
// If multiple handlers were set, we assume that the existing handler is our
|
// If multiple handlers were set, we assume that the existing handler is our
|
||||||
// confirmation handler, and continue.
|
// confirmation handler, and continue.
|
||||||
}
|
}
|
||||||
Err(e) => return Err(e.into()),
|
Err(err) => return Err(std::io::Error::new(std::io::ErrorKind::Other, err)),
|
||||||
}
|
}
|
||||||
|
|
||||||
let prompt = format!(
|
let prompt = format!(
|
||||||
|
|
@ -21,6 +21,7 @@ pypi-types = { workspace = true }
|
||||||
requirements-txt = { workspace = true, features = ["http"] }
|
requirements-txt = { workspace = true, features = ["http"] }
|
||||||
uv-client = { workspace = true }
|
uv-client = { workspace = true }
|
||||||
uv-configuration = { workspace = true }
|
uv-configuration = { workspace = true }
|
||||||
|
uv-console = { workspace = true }
|
||||||
uv-distribution = { workspace = true }
|
uv-distribution = { workspace = true }
|
||||||
uv-fs = { workspace = true }
|
uv-fs = { workspace = true }
|
||||||
uv-git = { workspace = true }
|
uv-git = { workspace = true }
|
||||||
|
|
@ -33,7 +34,6 @@ uv-workspace = { workspace = true }
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
configparser = { workspace = true }
|
configparser = { workspace = true }
|
||||||
console = { workspace = true }
|
console = { workspace = true }
|
||||||
ctrlc = { workspace = true }
|
|
||||||
fs-err = { workspace = true, features = ["tokio"] }
|
fs-err = { workspace = true, features = ["tokio"] }
|
||||||
futures = { workspace = true }
|
futures = { workspace = true }
|
||||||
rustc-hash = { workspace = true }
|
rustc-hash = { workspace = true }
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ pub use crate::sources::*;
|
||||||
pub use crate::specification::*;
|
pub use crate::specification::*;
|
||||||
pub use crate::unnamed::*;
|
pub use crate::unnamed::*;
|
||||||
|
|
||||||
mod confirm;
|
|
||||||
mod lookahead;
|
mod lookahead;
|
||||||
mod source_tree;
|
mod source_tree;
|
||||||
mod sources;
|
mod sources;
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,6 @@ use console::Term;
|
||||||
use uv_fs::Simplified;
|
use uv_fs::Simplified;
|
||||||
use uv_warnings::warn_user;
|
use uv_warnings::warn_user;
|
||||||
|
|
||||||
use crate::confirm;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum RequirementsSource {
|
pub enum RequirementsSource {
|
||||||
/// A package was provided on the command line (e.g., `pip install flask`).
|
/// A package was provided on the command line (e.g., `pip install flask`).
|
||||||
|
|
@ -96,7 +94,7 @@ impl RequirementsSource {
|
||||||
let prompt = format!(
|
let prompt = format!(
|
||||||
"`{name}` looks like a local requirements file but was passed as a package name. Did you mean `-r {name}`?"
|
"`{name}` looks like a local requirements file but was passed as a package name. Did you mean `-r {name}`?"
|
||||||
);
|
);
|
||||||
let confirmation = confirm::confirm(&prompt, &term, true).unwrap();
|
let confirmation = uv_console::confirm(&prompt, &term, true).unwrap();
|
||||||
if confirmation {
|
if confirmation {
|
||||||
return Self::from_requirements_file(name.into());
|
return Self::from_requirements_file(name.into());
|
||||||
}
|
}
|
||||||
|
|
@ -113,7 +111,7 @@ impl RequirementsSource {
|
||||||
let prompt = format!(
|
let prompt = format!(
|
||||||
"`{name}` looks like a local metadata file but was passed as a package name. Did you mean `-r {name}`?"
|
"`{name}` looks like a local metadata file but was passed as a package name. Did you mean `-r {name}`?"
|
||||||
);
|
);
|
||||||
let confirmation = confirm::confirm(&prompt, &term, true).unwrap();
|
let confirmation = uv_console::confirm(&prompt, &term, true).unwrap();
|
||||||
if confirmation {
|
if confirmation {
|
||||||
return Self::from_requirements_file(name.into());
|
return Self::from_requirements_file(name.into());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ uv-cache-info = { workspace = true }
|
||||||
uv-cli = { workspace = true }
|
uv-cli = { workspace = true }
|
||||||
uv-client = { workspace = true }
|
uv-client = { workspace = true }
|
||||||
uv-configuration = { workspace = true }
|
uv-configuration = { workspace = true }
|
||||||
|
uv-console = { workspace = true }
|
||||||
uv-dispatch = { workspace = true }
|
uv-dispatch = { workspace = true }
|
||||||
uv-distribution = { workspace = true }
|
uv-distribution = { workspace = true }
|
||||||
uv-extract = { workspace = true }
|
uv-extract = { workspace = true }
|
||||||
|
|
@ -57,6 +58,7 @@ axoupdater = { workspace = true, features = [
|
||||||
"tokio",
|
"tokio",
|
||||||
], optional = true }
|
], optional = true }
|
||||||
clap = { workspace = true, features = ["derive", "string", "wrap_help"] }
|
clap = { workspace = true, features = ["derive", "string", "wrap_help"] }
|
||||||
|
console = { workspace = true }
|
||||||
ctrlc = { workspace = true }
|
ctrlc = { workspace = true }
|
||||||
flate2 = { workspace = true, default-features = false }
|
flate2 = { workspace = true, default-features = false }
|
||||||
fs-err = { workspace = true, features = ["tokio"] }
|
fs-err = { workspace = true, features = ["tokio"] }
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
use std::collections::hash_map::Entry;
|
|
||||||
use std::fmt::Write;
|
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
|
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{bail, Context, Result};
|
||||||
|
use console::Term;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use owo_colors::OwoColorize;
|
use owo_colors::OwoColorize;
|
||||||
use rustc_hash::{FxBuildHasher, FxHashMap};
|
use rustc_hash::{FxBuildHasher, FxHashMap};
|
||||||
|
use std::collections::hash_map::Entry;
|
||||||
|
use std::fmt::Write;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::sync::LazyLock;
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
use cache_key::RepositoryUrl;
|
use cache_key::RepositoryUrl;
|
||||||
|
|
@ -48,6 +50,18 @@ use crate::commands::{pip, project, ExitStatus, SharedState};
|
||||||
use crate::printer::Printer;
|
use crate::printer::Printer;
|
||||||
use crate::settings::{ResolverInstallerSettings, ResolverInstallerSettingsRef};
|
use crate::settings::{ResolverInstallerSettings, ResolverInstallerSettingsRef};
|
||||||
|
|
||||||
|
static CORRECTIONS: LazyLock<FxHashMap<PackageName, PackageName>> = LazyLock::new(|| {
|
||||||
|
[("dotenv", "python-dotenv"), ("sklearn", "scikit-learn")]
|
||||||
|
.iter()
|
||||||
|
.map(|(k, v)| {
|
||||||
|
(
|
||||||
|
PackageName::from_str(k).unwrap(),
|
||||||
|
PackageName::from_str(v).unwrap(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
});
|
||||||
|
|
||||||
/// Add one or more packages to the project requirements.
|
/// Add one or more packages to the project requirements.
|
||||||
#[allow(clippy::fn_params_excessive_bools)]
|
#[allow(clippy::fn_params_excessive_bools)]
|
||||||
pub(crate) async fn add(
|
pub(crate) async fn add(
|
||||||
|
|
@ -371,6 +385,23 @@ pub(crate) async fn add(
|
||||||
}?;
|
}?;
|
||||||
let mut edits = Vec::<DependencyEdit>::with_capacity(requirements.len());
|
let mut edits = Vec::<DependencyEdit>::with_capacity(requirements.len());
|
||||||
for mut requirement in requirements {
|
for mut requirement in requirements {
|
||||||
|
// If the user requested a package that is often confused for another package, prompt them.
|
||||||
|
if let Some(correction) = CORRECTIONS.get(&requirement.name) {
|
||||||
|
let term = Term::stderr();
|
||||||
|
if term.is_term() {
|
||||||
|
let prompt = format!(
|
||||||
|
"`{}` is often confused for `{}`. Did you mean `{}`?",
|
||||||
|
requirement.name.cyan(),
|
||||||
|
correction.cyan(),
|
||||||
|
format!("uv add {correction}").green()
|
||||||
|
);
|
||||||
|
let confirmation = uv_console::confirm(&prompt, &term, true)?;
|
||||||
|
if confirmation {
|
||||||
|
requirement.name = correction.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Add the specified extras.
|
// Add the specified extras.
|
||||||
requirement.extras.extend(extras.iter().cloned());
|
requirement.extras.extend(extras.iter().cloned());
|
||||||
requirement.extras.sort_unstable();
|
requirement.extras.sort_unstable();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue