From 3ec1971e15bdcadcb120f1f4f907b0a7fda7b940 Mon Sep 17 00:00:00 2001 From: Simon Steinheber Date: Sun, 15 Dec 2024 19:10:09 +0100 Subject: [PATCH 01/28] Add functional draft --- Cargo.lock | 51 ++++++- crates/uv-auth/Cargo.toml | 7 +- crates/uv-auth/src/credentials.rs | 73 ++++++++++ crates/uv-auth/src/keyring.rs | 83 ++++++++++- crates/uv-auth/src/keyring_config.rs | 162 ++++++++++++++++++++++ crates/uv-auth/src/lib.rs | 3 + crates/uv-cli/src/lib.rs | 76 +++++++++- crates/uv-distribution-types/src/index.rs | 10 +- crates/uv/src/commands/build_frontend.rs | 2 +- crates/uv/src/commands/index.rs | 66 +++++++++ crates/uv/src/commands/mod.rs | 2 + crates/uv/src/commands/pip/compile.rs | 2 +- crates/uv/src/commands/pip/install.rs | 2 +- crates/uv/src/commands/pip/sync.rs | 3 +- crates/uv/src/commands/project/add.rs | 4 +- crates/uv/src/commands/project/lock.rs | 2 +- crates/uv/src/commands/project/mod.rs | 8 +- crates/uv/src/commands/project/sync.rs | 2 +- crates/uv/src/commands/venv.rs | 4 +- crates/uv/src/lib.rs | 34 ++++- crates/uv/src/settings.rs | 45 +++++- 21 files changed, 611 insertions(+), 30 deletions(-) create mode 100644 crates/uv-auth/src/keyring_config.rs create mode 100644 crates/uv/src/commands/index.rs diff --git a/Cargo.lock b/Cargo.lock index 04b9fe62f397..fc77c204c2b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -965,6 +965,12 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +[[package]] +name = "downcast" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" + [[package]] name = "dunce" version = "1.0.5" @@ -1160,6 +1166,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fragile" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" + [[package]] name = "fs-err" version = "2.11.0" @@ -1873,7 +1885,7 @@ checksum = "db69f08d4fb10524cacdb074c10b296299d71274ddbc830a8ee65666867002e9" dependencies = [ "jiff-tzdb-platform", "serde", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2196,6 +2208,32 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "mockall" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39a6bfcc6c8c7eed5ee98b9c3e33adc726054389233e201c95dab2d41a3839d2" +dependencies = [ + "cfg-if", + "downcast", + "fragile", + "mockall_derive", + "predicates", + "predicates-tree", +] + +[[package]] +name = "mockall_derive" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ca3004c2efe9011bd4e461bd8256445052b9615405b4f7ea43fc8ca5c20898" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "munge" version = "0.4.1" @@ -2742,7 +2780,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3747,7 +3785,7 @@ dependencies = [ "fastrand", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -4516,16 +4554,21 @@ dependencies = [ "futures", "http", "insta", + "mockall", "reqwest", "reqwest-middleware", "rust-netrc", "rustc-hash", + "serde", "tempfile", "test-log", + "thiserror 2.0.3", "tokio", + "toml", "tracing", "url", "urlencoding", + "uv-dirs", "uv-once-map", "uv-static", "wiremock", @@ -5898,7 +5941,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/crates/uv-auth/Cargo.toml b/crates/uv-auth/Cargo.toml index 24d75582b9f7..aa888d2491fa 100644 --- a/crates/uv-auth/Cargo.toml +++ b/crates/uv-auth/Cargo.toml @@ -27,10 +27,15 @@ url = { workspace = true } urlencoding = { workspace = true } uv-static = { workspace = true } +uv-dirs = { workspace = true } +toml.workspace = true +serde.workspace = true +thiserror.workspace = true [dev-dependencies] -tempfile = { workspace = true } +tempfile.workspace = true tokio = { workspace = true } wiremock = { workspace = true } insta = { version = "1.40.0" } test-log = { version = "0.2.16", features = ["trace"], default-features = false } +mockall = "0.13.1" diff --git a/crates/uv-auth/src/credentials.rs b/crates/uv-auth/src/credentials.rs index bb8f28d4a5d9..5a6e28904a7c 100644 --- a/crates/uv-auth/src/credentials.rs +++ b/crates/uv-auth/src/credentials.rs @@ -5,12 +5,20 @@ use base64::write::EncoderWriter; use netrc::Netrc; use reqwest::header::HeaderValue; use reqwest::Request; + use std::io::Read; use std::io::Write; +use futures::executor; + use url::Url; use uv_static::EnvVars; +use crate::KeyringProvider; +use crate::keyring_config::AuthConfig; +use crate::keyring_config::ConfigFile; + + #[derive(Clone, Debug, PartialEq)] pub struct Credentials { /// The name of the user for authentication. @@ -22,6 +30,7 @@ pub struct Credentials { #[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash, Default)] pub(crate) struct Username(Option); + impl Username { /// Create a new username. /// @@ -155,6 +164,34 @@ impl Credentials { } } + /// Extracte the [`Credentials`] from keyring, given a named source. + /// + /// Look up the username stored for the named source in the user-level config. + /// Load the credentials from keyring for the service and username. + pub fn from_keyring(name: String, url: &Url, keyring_provider: Option) -> Option { + if keyring_provider.is_none() { + return None + } + + let auth_config = match AuthConfig::load() { + Ok(auth_config) => auth_config, + Err(_) => { + eprintln!("Error loading auth config"); + return None; + } + }; + + let index = match auth_config.find_entry(&name) { + Some(i) => i, + None => { + eprintln!("Could not find entry for {name}"); + return None; + } + }; + + return executor::block_on(keyring_provider.unwrap().fetch(&url, &index.username)); + } + /// Parse [`Credentials`] from an HTTP request, if any. /// /// Only HTTP Basic Authentication is supported. @@ -247,6 +284,7 @@ impl Credentials { #[cfg(test)] mod tests { + use insta::assert_debug_snapshot; use super::*; @@ -353,4 +391,39 @@ mod tests { assert_debug_snapshot!(header, @r###""Basic dXNlcjpwYXNzd29yZD09""###); assert_eq!(Credentials::from_header_value(&header), Some(credentials)); } + + #[test] + fn from_keyring() { + // let service_name = "example.com"; + // let username = "user1"; + // let index = "index1"; + + // let child = Command::new("keyring") + // .arg("set") + // .arg(service_name) + // .arg(username) + // .stdin(Stdio::null()) + // .stdout(Stdio::piped()) + // .stderr(Stdio::inherit()) + // .spawn() + // .inspect_err(|err| eprint!("Failure running `keyring` command: {err}")) + // .ok()?; + + // let output = executor::block_on(child.wait_with_output()) + // .inspect_err(|err| eprintln!("Failed to wait for `keyring` output: {err}")) + // .ok()?; + + // assert!(output.status.success()); + + // let keyring_provider = KeyringProvider::subprocess(); + + // let mut auth_config = AuthConfig::load()?; + // auth_config.add_entry(index.to_string(), username.to_string()); + // auth_config.store(); + + // // Act + // let credentials = Credentials::from_keyring(index.to_string(), "example.com".to_string(), Some(keyring_provider)); + + + } } diff --git a/crates/uv-auth/src/keyring.rs b/crates/uv-auth/src/keyring.rs index e9b7fede70e4..bea54d06cb8f 100644 --- a/crates/uv-auth/src/keyring.rs +++ b/crates/uv-auth/src/keyring.rs @@ -1,6 +1,6 @@ use std::process::Stdio; -use tokio::process::Command; -use tracing::{instrument, trace, warn}; +use tokio::{io::AsyncWriteExt, process::Command}; +use tracing::{debug, instrument, trace, warn}; use url::Url; use crate::credentials::Credentials; @@ -114,6 +114,74 @@ impl KeyringProvider { } } + /// Set credentials for the given [`Url`] from the keyring. + #[instrument(skip_all, fields(url = % url.to_string(), username))] + pub async fn set(&self, url: &Url, username: &str, password: &str) { + // Validate the request + debug_assert!( + url.host_str().is_some(), + "Should only use keyring for urls with host" + ); + debug_assert!( + url.password().is_none(), + "Should only use keyring for urls without a password" + ); + debug_assert!( + !username.is_empty(), + "Should only use keyring with a username" + ); + + // Check the full URL first + // + trace!("Creating entry in keyring for URL {url} and username {username}"); + + match self.backend { + KeyringProviderBackend::Subprocess => self.set_subprocess(url.as_str(), username, password).await, + #[cfg(test)] + KeyringProviderBackend::Dummy(ref store) => { + let test = password; + None + } + }; + } + + #[instrument(skip(self))] + async fn set_subprocess(&self, service_name: &str, username: &str, password: &str) -> Option<()> { + let mut child = Command::new("keyring") + .arg("set") + .arg(service_name) + .arg(username) + .stdin(Stdio::piped()) // Allow writing to stdin + .stdout(Stdio::piped()) // Optionally capture stdout for debugging + .stderr(Stdio::piped()) // Capture stderr for debugging + .spawn() + .inspect_err(|err| warn!("Failure running `keyring` command: {err}")) + .ok()?; + + // If we successfully spawn the process, we can write to its stdin + if let Some(mut stdin) = child.stdin.take() { + // Write the password to the stdin of the keyring process + stdin.write(password.as_bytes()).await.inspect_err(|_| warn!("Failure providing the password to keyring!")).ok()?; + stdin.flush().await.inspect_err(|_| warn!("Failure flushing the password input to keyring")).ok()?; + } + + let output = child + .wait_with_output() + .await + .inspect_err(|err| warn!("Failed to wait for `keyring` output: {err}")) + .ok()?; + + if output.status.success() { + // On success, parse the newline terminated password + debug!("Password successfully saved"); + } else { + // On failure, no password was available + debug!("Could not save password in keyring"); + }; + + return None; + } + #[cfg(test)] fn fetch_dummy( store: &std::collections::HashMap<(String, &'static str), &'static str>, @@ -125,6 +193,17 @@ impl KeyringProvider { .map(|password| (*password).to_string()) } + #[cfg(test)] + fn set_dummy( + store: &mut std::collections::HashMap<(String, &str), &str>, + service_name: &str, + username: &str, + password: &str, + ) -> Option<()> { + // store.insert((service_name.to_string(), username), password); + None + } + /// Create a new provider with [`KeyringProviderBackend::Dummy`]. #[cfg(test)] pub fn dummy, T: IntoIterator>( diff --git a/crates/uv-auth/src/keyring_config.rs b/crates/uv-auth/src/keyring_config.rs new file mode 100644 index 000000000000..892dd490f47b --- /dev/null +++ b/crates/uv-auth/src/keyring_config.rs @@ -0,0 +1,162 @@ +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::fs::{self, File}; +use std::io::{self, Write}; +use std::path::PathBuf; +use toml; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ConfigError { + #[error("IO error: {0}")] + Io(#[from] io::Error), + + #[error("Serialization/Deserialization error: {0}")] + SerdeError(#[from] toml::de::Error), + + #[error("Invalid configuration path")] + InvalidPath, + + #[error("Serialization error while storing config: {0}")] + TomlSerializationError(#[from] toml::ser::Error), +} + + +pub trait ConfigFile { + fn path() -> Result; + + fn load() -> Result + where + Self: Sized; + + fn store(&self) -> Result<(), ConfigError>; +} + +impl ConfigFile for AuthConfig { + fn path() -> Result { + let cache_dir = uv_dirs::user_cache_dir().ok_or(ConfigError::InvalidPath)?; + Ok(cache_dir.join("auth.toml")) + } + + fn load() -> Result { + let path = AuthConfig::path()?; + return AuthConfig::load_from_path(&path); + } + + fn store(&self) -> Result<(), ConfigError> { + let path = AuthConfig::path()?; + return self.store_to_path(&path); + } +} + +#[derive(Deserialize, Serialize, PartialEq, Eq, Debug)] +pub struct AuthConfig { + pub indexes: HashMap, +} + +#[derive(Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord, Debug)] +pub struct Index { + pub username: String, +} + +impl AuthConfig { + pub fn add_entry(&mut self, index_name: String, username: String) { + self.indexes.entry(index_name).or_insert(Index { username }); + } + + pub fn find_entry(&self, index_name: &str) -> Option<&Index> { + self.indexes.get(index_name) + } + + pub fn load_from_path(path: &PathBuf) -> Result { + if !path.exists() { + return Ok(AuthConfig { + indexes: HashMap::new(), + }); + } + + let contents = fs::read_to_string(path)?; + let config: AuthConfig = toml::de::from_str(&contents)?; + Ok(config) + } + + pub fn store_to_path(&self, path: &PathBuf) -> Result<(), ConfigError> { + let contents = toml::to_string_pretty(self)?; + let mut file = File::create(path)?; + file.write_all(contents.as_bytes())?; + Ok(()) + } +} + + +#[cfg(test)] +mod tests { + use super::*; + use std::path::Path; + use std::fs; + + // Helper function to clean up a temporary file + fn remove_temp_file(path: &Path) -> io::Result<()> { + if path.exists() { + fs::remove_file(path)?; + } + Ok(()) + } + + #[test] + fn test_load_no_config_file() { + // Step 1: Create a temporary file using tempfile + // let temp_file = NamedTempFile::new().expect("Failed to create a temp file"); + + let path = Path::new("test_auth.toml"); + remove_temp_file(path).ok(); + + let config = AuthConfig::load_from_path(&path.to_path_buf()); + assert!(config.is_ok()); + let config = config.unwrap(); + assert_eq!(config.indexes.len(), 0); + } + + #[test] + fn test_store_no_config_file() { + // Prepare a fake file path for the test + let path = Path::new("test_auth.toml"); + remove_temp_file(path).ok(); + + let config = AuthConfig::load_from_path(&path.to_path_buf()); + assert!(config.is_ok()); + let mut config = config.unwrap(); + + config.add_entry("index1".to_string(), "user1".to_string()); + + let result = config.store_to_path(&path.to_path_buf()); + assert!(result.is_ok()); + + // Check if the file exists and contains the correct content + assert!(path.exists()); + + let contents = fs::read_to_string(path).expect("Failed to read config file"); + assert!(contents.contains("index1")); + assert!(contents.contains("user1")); + + // Clean up + remove_temp_file(path).ok(); + } + + #[test] + fn test_find_entry() { + let mut config = AuthConfig { + indexes: HashMap::new(), + }; + config.add_entry("index1".to_string(), "user1".to_string()); + + // Test finding an existing entry + let entry = config.find_entry("index1"); + assert!(entry.is_some()); + assert_eq!(entry.unwrap().username, "user1"); + + // Test finding a non-existing entry + let entry = config.find_entry("nonexistent"); + assert!(entry.is_none()); + } +} diff --git a/crates/uv-auth/src/lib.rs b/crates/uv-auth/src/lib.rs index 16f644418697..b15d03c8f8c8 100644 --- a/crates/uv-auth/src/lib.rs +++ b/crates/uv-auth/src/lib.rs @@ -7,6 +7,8 @@ use cache::CredentialsCache; pub use credentials::Credentials; pub use keyring::KeyringProvider; pub use middleware::AuthMiddleware; +pub use keyring_config::AuthConfig; +pub use keyring_config::ConfigFile; use realm::Realm; mod cache; @@ -14,6 +16,7 @@ mod credentials; mod keyring; mod middleware; mod realm; +mod keyring_config; // TODO(zanieb): Consider passing a cache explicitly throughout diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index e4f1f04df084..4d8f3e0268a4 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -5,7 +5,7 @@ use std::str::FromStr; use anyhow::{anyhow, Result}; use clap::builder::styling::{AnsiColor, Effects, Style}; -use clap::builder::Styles; +use clap::builder::{Str, Styles}; use clap::{Args, Parser, Subcommand}; use url::Url; @@ -461,6 +461,7 @@ pub enum Commands { ), )] Help(HelpArgs), + Index(IndexNamespace), } #[derive(Args, Debug)] @@ -5186,6 +5187,7 @@ pub struct PublishArgs { pub skip_existing: bool, } + /// See [PEP 517](https://peps.python.org/pep-0517/) and /// [PEP 660](https://peps.python.org/pep-0660/) for specifications of the parameters. #[derive(Subcommand)] @@ -5215,3 +5217,75 @@ pub enum BuildBackendCommand { /// PEP 660 hook `prepare_metadata_for_build_editable`. PrepareMetadataForBuildEditable { wheel_directory: PathBuf }, } + + + + +#[derive(Args)] +#[allow(clippy::struct_excessive_bools)] +pub struct IndexNamespace { + #[command(subcommand)] + pub command: IndexCommand, +} + +#[derive(Subcommand)] +pub enum IndexCommand { + /// Compile a `requirements.in` file to a `requirements.txt` file. + #[command( + after_help = "Use `uv help index add` for more details.", + after_long_help = "" + )] + Add(IndexSourceArgs), + /// Sync an environment with a `requirements.txt` file. + #[command( + after_help = "Use `uv help index list` for more details.", + after_long_help = "" + )] + List(IndexSourceArgs), + /// Install packages into an environment. + #[command( + after_help = "Use `uv help index delete` for more details.", + after_long_help = "" + )] + Delete(IndexSourceArgs), + + #[command(subcommand)] + Credentials(IndexCredentialsCommand), +} + +#[derive(Subcommand)] +pub enum IndexCredentialsCommand { + #[command( + after_help = "Use `uv help index credentials add` for more details.", + after_long_help = "" + )] + Add(IndexCredentialsArgs), +} + + +#[derive(Args)] +pub struct IndexSourceArgs { + /// The name of the index + #[arg(long)] + pub name: String, +} + +#[derive(Args)] +pub struct IndexCredentialsArgs { + /// The name of the index + #[arg(long)] + pub name: String, + + /// The username that should be used for the index + #[arg(long, required(false))] + pub username: Option, + + /// Attempt to use `keyring` for authentication for remote requirements files. + /// + /// At present, only `--keyring-provider subprocess` is supported, which configures uv to + /// use the `keyring` CLI to handle authentication. + /// + /// Defaults to `disabled`. + #[arg(long, value_enum, env = EnvVars::UV_KEYRING_PROVIDER)] + pub keyring_provider: Option, +} \ No newline at end of file diff --git a/crates/uv-distribution-types/src/index.rs b/crates/uv-distribution-types/src/index.rs index 3450757ab828..4322d6860ac8 100644 --- a/crates/uv-distribution-types/src/index.rs +++ b/crates/uv-distribution-types/src/index.rs @@ -3,12 +3,14 @@ use std::str::FromStr; use thiserror::Error; use url::Url; -use uv_auth::Credentials; +use uv_auth::{Credentials, KeyringProvider}; use crate::index_name::{IndexName, IndexNameError}; use crate::origin::Origin; use crate::{IndexUrl, IndexUrlError}; + + #[derive(Debug, Clone, Hash, Eq, PartialEq, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct Index { @@ -138,12 +140,16 @@ impl Index { } /// Retrieve the credentials for the index, either from the environment, or from the URL itself. - pub fn credentials(&self) -> Option { + pub fn credentials(&self, keyring_provider: Option) -> Option { // If the index is named, and credentials are provided via the environment, prefer those. if let Some(name) = self.name.as_ref() { if let Some(credentials) = Credentials::from_env(name.to_env_var()) { return Some(credentials); } + + if let Some(credentials) = Credentials::from_keyring(name.to_string(), self.url.url(), keyring_provider) { + return Some(credentials); + } } // Otherwise, extract the credentials from the URL. diff --git a/crates/uv/src/commands/build_frontend.rs b/crates/uv/src/commands/build_frontend.rs index 2fa5f1d5c780..54bfd1ac522c 100644 --- a/crates/uv/src/commands/build_frontend.rs +++ b/crates/uv/src/commands/build_frontend.rs @@ -492,7 +492,7 @@ async fn build_package( // Add all authenticated sources to the cache. for index in index_locations.allowed_indexes() { - if let Some(credentials) = index.credentials() { + if let Some(credentials) = index.credentials(None) { store_credentials(index.raw_url(), credentials); } } diff --git a/crates/uv/src/commands/index.rs b/crates/uv/src/commands/index.rs new file mode 100644 index 000000000000..27fa1af041e1 --- /dev/null +++ b/crates/uv/src/commands/index.rs @@ -0,0 +1,66 @@ +use console::Term; +use uv_configuration::KeyringProviderType; +use uv_distribution_types::Index; +use uv_auth::{AuthConfig, ConfigFile}; +use tracing::{debug, warn}; +use anyhow::{Context, Result}; + +/// Add one or more packages to the project requirements. +#[allow(clippy::fn_params_excessive_bools)] +pub(crate) async fn add_credentials(name: String, username: Option, keyring_provider: KeyringProviderType, index: Vec) -> Result<()> { + let index_for_name = index.iter().find( + |idx| idx.name.as_ref().map(|n| n.to_string() == name).unwrap_or(false) + ); + + let index_for_name = match index_for_name { + Some(obj) => obj, + None => panic!("No index found with the name '{}'", name), + }; + + + let username = match username { + Some(n) => n, + None => match prompt_username_input()? { + Some(n) => n, + None => panic!("No username provided and could not read username from input.") + }, + }; + + let password = match prompt_password_input()? { + Some(p) => p, + None => panic!("Could not read password from user input") + }; + + let url = index_for_name.raw_url(); + debug!("Will store password for index {} with URL {} and user {} in keyring", name, url, username); + keyring_provider.to_provider().expect("Keyring Provider is not available").set(&url, &username, &password).await; + + debug!("Will add index {} and user {} to index auth config in {:?}", name, url, AuthConfig::path()?); + let mut auth_config = AuthConfig::load().inspect_err(|err| warn!("Could not load auth config due to: {err}"))?; + auth_config.add_entry(name, username); + auth_config.store()?; + + Ok(()) +} + +fn prompt_username_input() -> Result> { + let term = Term::stderr(); + if !term.is_term() { + return Ok(None); + } + let username_prompt = "Enter username: "; + + let username = uv_console::input(username_prompt, &term).context("Failed to read username")?; + Ok(Some(username)) +} + +fn prompt_password_input() -> Result> { + let term = Term::stderr(); + if !term.is_term() { + return Ok(None); + } + let password_prompt = "Enter password: "; + let password = + uv_console::password(password_prompt, &term).context("Failed to read password")?; + Ok(Some(password)) +} \ No newline at end of file diff --git a/crates/uv/src/commands/mod.rs b/crates/uv/src/commands/mod.rs index a3fb800b7249..0ea316c0ba6b 100644 --- a/crates/uv/src/commands/mod.rs +++ b/crates/uv/src/commands/mod.rs @@ -12,6 +12,7 @@ pub(crate) use cache_clean::cache_clean; pub(crate) use cache_dir::cache_dir; pub(crate) use cache_prune::cache_prune; pub(crate) use help::help; +pub(crate) use index::add_credentials; pub(crate) use pip::check::pip_check; pub(crate) use pip::compile::pip_compile; pub(crate) use pip::freeze::pip_freeze; @@ -64,6 +65,7 @@ mod cache_dir; mod cache_prune; mod diagnostics; mod help; +mod index; pub(crate) mod pip; mod project; mod publish; diff --git a/crates/uv/src/commands/pip/compile.rs b/crates/uv/src/commands/pip/compile.rs index 65b437be0d05..7808b8cbcda8 100644 --- a/crates/uv/src/commands/pip/compile.rs +++ b/crates/uv/src/commands/pip/compile.rs @@ -287,7 +287,7 @@ pub(crate) async fn pip_compile( // Add all authenticated sources to the cache. for index in index_locations.allowed_indexes() { - if let Some(credentials) = index.credentials() { + if let Some(credentials) = index.credentials(None) { uv_auth::store_credentials(index.raw_url(), credentials); } } diff --git a/crates/uv/src/commands/pip/install.rs b/crates/uv/src/commands/pip/install.rs index e156bf81d671..f23e1a5554ce 100644 --- a/crates/uv/src/commands/pip/install.rs +++ b/crates/uv/src/commands/pip/install.rs @@ -306,7 +306,7 @@ pub(crate) async fn pip_install( // Add all authenticated sources to the cache. for index in index_locations.allowed_indexes() { - if let Some(credentials) = index.credentials() { + if let Some(credentials) = index.credentials(None) { uv_auth::store_credentials(index.raw_url(), credentials); } } diff --git a/crates/uv/src/commands/pip/sync.rs b/crates/uv/src/commands/pip/sync.rs index 21f9322775f7..434431cd8c8b 100644 --- a/crates/uv/src/commands/pip/sync.rs +++ b/crates/uv/src/commands/pip/sync.rs @@ -35,6 +35,7 @@ use crate::commands::pip::operations::{report_interpreter, report_target_environ use crate::commands::pip::{operations, resolution_markers, resolution_tags}; use crate::commands::{diagnostics, ExitStatus}; use crate::printer::Printer; +use crate::settings; /// Install a set of locked requirements into the current Python environment. #[allow(clippy::fn_params_excessive_bools)] @@ -246,7 +247,7 @@ pub(crate) async fn pip_sync( // Add all authenticated sources to the cache. for index in index_locations.allowed_indexes() { - if let Some(credentials) = index.credentials() { + if let Some(credentials) = index.credentials(None) { uv_auth::store_credentials(index.raw_url(), credentials); } } diff --git a/crates/uv/src/commands/project/add.rs b/crates/uv/src/commands/project/add.rs index c0bb80796880..da23615ee55f 100644 --- a/crates/uv/src/commands/project/add.rs +++ b/crates/uv/src/commands/project/add.rs @@ -282,9 +282,11 @@ pub(crate) async fn add( let hasher = HashStrategy::default(); let sources = SourceStrategy::Enabled; + + // Add all authenticated sources to the cache. for index in settings.index_locations.allowed_indexes() { - if let Some(credentials) = index.credentials() { + if let Some(credentials) = index.credentials(settings.keyring_provider.to_provider()) { uv_auth::store_credentials(index.raw_url(), credentials); } } diff --git a/crates/uv/src/commands/project/lock.rs b/crates/uv/src/commands/project/lock.rs index f7afd49b8138..22f6a5e3e353 100644 --- a/crates/uv/src/commands/project/lock.rs +++ b/crates/uv/src/commands/project/lock.rs @@ -436,7 +436,7 @@ async fn do_lock( // Add all authenticated sources to the cache. for index in index_locations.allowed_indexes() { - if let Some(credentials) = index.credentials() { + if let Some(credentials) = index.credentials(settings.keyring_provider.to_provider()) { uv_auth::store_credentials(index.raw_url(), credentials); } } diff --git a/crates/uv/src/commands/project/mod.rs b/crates/uv/src/commands/project/mod.rs index 99ef9e6aa2de..e64c2787d83c 100644 --- a/crates/uv/src/commands/project/mod.rs +++ b/crates/uv/src/commands/project/mod.rs @@ -936,7 +936,7 @@ pub(crate) async fn resolve_names( // Add all authenticated sources to the cache. for index in index_locations.allowed_indexes() { - if let Some(credentials) = index.credentials() { + if let Some(credentials) = index.credentials(settings.keyring_provider.to_provider()) { uv_auth::store_credentials(index.raw_url(), credentials); } } @@ -1085,7 +1085,7 @@ pub(crate) async fn resolve_environment<'a>( // Add all authenticated sources to the cache. for index in index_locations.allowed_indexes() { - if let Some(credentials) = index.credentials() { + if let Some(credentials) = index.credentials(settings.keyring_provider.to_provider()) { uv_auth::store_credentials(index.raw_url(), credentials); } } @@ -1248,7 +1248,7 @@ pub(crate) async fn sync_environment( // Add all authenticated sources to the cache. for index in index_locations.allowed_indexes() { - if let Some(credentials) = index.credentials() { + if let Some(credentials) = index.credentials(settings.keyring_provider.to_provider()) { uv_auth::store_credentials(index.raw_url(), credentials); } } @@ -1442,7 +1442,7 @@ pub(crate) async fn update_environment( // Add all authenticated sources to the cache. for index in index_locations.allowed_indexes() { - if let Some(credentials) = index.credentials() { + if let Some(credentials) = index.credentials(settings.keyring_provider.to_provider()) { uv_auth::store_credentials(index.raw_url(), credentials); } } diff --git a/crates/uv/src/commands/project/sync.rs b/crates/uv/src/commands/project/sync.rs index f1e385dc7b26..362290b0e6d7 100644 --- a/crates/uv/src/commands/project/sync.rs +++ b/crates/uv/src/commands/project/sync.rs @@ -361,7 +361,7 @@ pub(super) async fn do_sync( // Add all authenticated sources to the cache. for index in index_locations.allowed_indexes() { - if let Some(credentials) = index.credentials() { + if let Some(credentials) = index.credentials(settings.keyring_provider.to_provider()) { store_credentials(index.raw_url(), credentials); } } diff --git a/crates/uv/src/commands/venv.rs b/crates/uv/src/commands/venv.rs index f9f6b1698dfd..9ed45839b125 100644 --- a/crates/uv/src/commands/venv.rs +++ b/crates/uv/src/commands/venv.rs @@ -224,7 +224,7 @@ async fn venv_impl( // Add all authenticated sources to the cache. for index in index_locations.allowed_indexes() { - if let Some(credentials) = index.credentials() { + if let Some(credentials) = index.credentials(None) { uv_auth::store_credentials(index.raw_url(), credentials); } } @@ -271,7 +271,7 @@ async fn venv_impl( // Add all authenticated sources to the cache. for index in index_locations.allowed_indexes() { - if let Some(credentials) = index.credentials() { + if let Some(credentials) = index.credentials(None) { uv_auth::store_credentials(index.raw_url(), credentials); } } diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index a27ce472234c..8ab44ea7d2a1 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -11,8 +11,9 @@ use anstream::eprintln; use anyhow::{bail, Result}; use clap::error::{ContextKind, ContextValue}; use clap::{CommandFactory, Parser}; +use commands::add_credentials; use owo_colors::OwoColorize; -use settings::PipTreeSettings; +use settings::{IndexSettings, PipTreeSettings}; use tokio::task::spawn_blocking; use tracing::{debug, instrument}; use uv_cache::{Cache, Refresh}; @@ -21,7 +22,7 @@ use uv_cli::{ compat::CompatArgs, BuildBackendCommand, CacheCommand, CacheNamespace, Cli, Commands, PipCommand, PipNamespace, ProjectCommand, }; -use uv_cli::{PythonCommand, PythonNamespace, ToolCommand, ToolNamespace, TopLevelArgs}; +use uv_cli::{IndexCommand, IndexCredentialsCommand, IndexNamespace, PythonCommand, PythonNamespace, ToolCommand, ToolNamespace, TopLevelArgs}; #[cfg(feature = "self-update")] use uv_cli::{SelfCommand, SelfNamespace, SelfUpdateArgs}; use uv_fs::CWD; @@ -1258,6 +1259,35 @@ async fn run(mut cli: Cli) -> Result { }) .await .expect("tokio threadpool exited unexpectedly"), + Commands::Index (IndexNamespace { + command: IndexCommand::Add(args) + }) => { + println!("Add an index with name {}", args.name); + return Ok(ExitStatus::Success); + } + Commands::Index (IndexNamespace { + command: IndexCommand::List(_) + }) => { + println!("List all indexes"); + return Ok(ExitStatus::Success); + } + Commands::Index (IndexNamespace { + command: IndexCommand::Delete(args) + }) => { + println!("Delete index {}", args.name); + return Ok(ExitStatus::Success); + } + Commands::Index(IndexNamespace { + command: IndexCommand::Credentials(IndexCredentialsCommand::Add(args)), + }) => { + let IndexSettings { + name, username, keyring_provider, index + } = IndexSettings::resolve(args, filesystem); + + let _ = add_credentials(name, username, keyring_provider, index).await; + return Ok(ExitStatus::Success); + } + }; result } diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index b2ddf1376f65..0bc55642986f 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -13,11 +13,7 @@ use uv_cli::{ ToolUpgradeArgs, }; use uv_cli::{ - AddArgs, ColorChoice, ExternalCommand, GlobalArgs, InitArgs, ListFormat, LockArgs, Maybe, - PipCheckArgs, PipCompileArgs, PipFreezeArgs, PipInstallArgs, PipListArgs, PipShowArgs, - PipSyncArgs, PipTreeArgs, PipUninstallArgs, PythonFindArgs, PythonInstallArgs, PythonListArgs, - PythonPinArgs, PythonUninstallArgs, RemoveArgs, RunArgs, SyncArgs, ToolDirArgs, - ToolInstallArgs, ToolListArgs, ToolRunArgs, ToolUninstallArgs, TreeArgs, VenvArgs, + AddArgs, ColorChoice, ExternalCommand, GlobalArgs, IndexCredentialsArgs, InitArgs, ListFormat, LockArgs, Maybe, PipCheckArgs, PipCompileArgs, PipFreezeArgs, PipInstallArgs, PipListArgs, PipShowArgs, PipSyncArgs, PipTreeArgs, PipUninstallArgs, PythonFindArgs, PythonInstallArgs, PythonListArgs, PythonPinArgs, PythonUninstallArgs, RemoveArgs, RunArgs, SyncArgs, ToolDirArgs, ToolInstallArgs, ToolListArgs, ToolRunArgs, ToolUninstallArgs, TreeArgs, VenvArgs }; use uv_client::Connectivity; use uv_configuration::{ @@ -2882,6 +2878,45 @@ impl PublishSettings { } } + +pub(crate) struct IndexSettings { + // CLI only settings + pub(crate) name: String, + pub(crate) username: Option, + + // CLI and Filesystem settings + pub(crate) keyring_provider: KeyringProviderType, + + // Filesystem only settings + pub(crate) index: Vec, +} + +impl IndexSettings { + /// Resolve the [`IndexSettings`] from the CLI and filesystem configuration. + pub(crate) fn resolve(args: IndexCredentialsArgs, filesystem: Option) -> Self { + let Options { + top_level, .. + } = filesystem + .map(FilesystemOptions::into_options) + .unwrap_or_default(); + + let ResolverInstallerOptions { + keyring_provider, + index,.. + } = top_level; + + Self { + name: args.name, + username: args.username, + keyring_provider: args. + keyring_provider + .combine(keyring_provider) + .unwrap_or_default(), + index: index.unwrap_or_default() + } + } +} + // Environment variables that are not exposed as CLI arguments. mod env { use uv_static::EnvVars; From f70f431bf8905a533c577a0754bdec6c0dab6f54 Mon Sep 17 00:00:00 2001 From: Simon Steinheber Date: Sun, 15 Dec 2024 19:14:24 +0100 Subject: [PATCH 02/28] Format code --- crates/uv-auth/src/credentials.rs | 22 ++++----- crates/uv-auth/src/keyring.rs | 27 ++++++++--- crates/uv-auth/src/keyring_config.rs | 10 ++-- crates/uv-auth/src/lib.rs | 4 +- crates/uv-cli/src/lib.rs | 9 +--- crates/uv-distribution-types/src/index.rs | 6 +-- crates/uv/src/commands/index.rs | 56 +++++++++++++++-------- crates/uv/src/commands/project/add.rs | 2 - crates/uv/src/lib.rs | 23 ++++++---- crates/uv/src/settings.rs | 27 ++++++----- 10 files changed, 111 insertions(+), 75 deletions(-) diff --git a/crates/uv-auth/src/credentials.rs b/crates/uv-auth/src/credentials.rs index 5a6e28904a7c..0bbea348427d 100644 --- a/crates/uv-auth/src/credentials.rs +++ b/crates/uv-auth/src/credentials.rs @@ -6,18 +6,17 @@ use netrc::Netrc; use reqwest::header::HeaderValue; use reqwest::Request; +use futures::executor; use std::io::Read; use std::io::Write; -use futures::executor; use url::Url; use uv_static::EnvVars; -use crate::KeyringProvider; use crate::keyring_config::AuthConfig; use crate::keyring_config::ConfigFile; - +use crate::KeyringProvider; #[derive(Clone, Debug, PartialEq)] pub struct Credentials { @@ -30,7 +29,6 @@ pub struct Credentials { #[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash, Default)] pub(crate) struct Username(Option); - impl Username { /// Create a new username. /// @@ -165,14 +163,18 @@ impl Credentials { } /// Extracte the [`Credentials`] from keyring, given a named source. - /// + /// /// Look up the username stored for the named source in the user-level config. /// Load the credentials from keyring for the service and username. - pub fn from_keyring(name: String, url: &Url, keyring_provider: Option) -> Option { + pub fn from_keyring( + name: String, + url: &Url, + keyring_provider: Option, + ) -> Option { if keyring_provider.is_none() { - return None + return None; } - + let auth_config = match AuthConfig::load() { Ok(auth_config) => auth_config, Err(_) => { @@ -189,7 +191,7 @@ impl Credentials { } }; - return executor::block_on(keyring_provider.unwrap().fetch(&url, &index.username)); + return executor::block_on(keyring_provider.unwrap().fetch(&url, &index.username)); } /// Parse [`Credentials`] from an HTTP request, if any. @@ -423,7 +425,5 @@ mod tests { // // Act // let credentials = Credentials::from_keyring(index.to_string(), "example.com".to_string(), Some(keyring_provider)); - - } } diff --git a/crates/uv-auth/src/keyring.rs b/crates/uv-auth/src/keyring.rs index bea54d06cb8f..59cc46c58113 100644 --- a/crates/uv-auth/src/keyring.rs +++ b/crates/uv-auth/src/keyring.rs @@ -136,7 +136,9 @@ impl KeyringProvider { trace!("Creating entry in keyring for URL {url} and username {username}"); match self.backend { - KeyringProviderBackend::Subprocess => self.set_subprocess(url.as_str(), username, password).await, + KeyringProviderBackend::Subprocess => { + self.set_subprocess(url.as_str(), username, password).await + } #[cfg(test)] KeyringProviderBackend::Dummy(ref store) => { let test = password; @@ -146,12 +148,17 @@ impl KeyringProvider { } #[instrument(skip(self))] - async fn set_subprocess(&self, service_name: &str, username: &str, password: &str) -> Option<()> { + async fn set_subprocess( + &self, + service_name: &str, + username: &str, + password: &str, + ) -> Option<()> { let mut child = Command::new("keyring") .arg("set") .arg(service_name) .arg(username) - .stdin(Stdio::piped()) // Allow writing to stdin + .stdin(Stdio::piped()) // Allow writing to stdin .stdout(Stdio::piped()) // Optionally capture stdout for debugging .stderr(Stdio::piped()) // Capture stderr for debugging .spawn() @@ -161,9 +168,17 @@ impl KeyringProvider { // If we successfully spawn the process, we can write to its stdin if let Some(mut stdin) = child.stdin.take() { // Write the password to the stdin of the keyring process - stdin.write(password.as_bytes()).await.inspect_err(|_| warn!("Failure providing the password to keyring!")).ok()?; - stdin.flush().await.inspect_err(|_| warn!("Failure flushing the password input to keyring")).ok()?; - } + stdin + .write(password.as_bytes()) + .await + .inspect_err(|_| warn!("Failure providing the password to keyring!")) + .ok()?; + stdin + .flush() + .await + .inspect_err(|_| warn!("Failure flushing the password input to keyring")) + .ok()?; + } let output = child .wait_with_output() diff --git a/crates/uv-auth/src/keyring_config.rs b/crates/uv-auth/src/keyring_config.rs index 892dd490f47b..0f85c204c72f 100644 --- a/crates/uv-auth/src/keyring_config.rs +++ b/crates/uv-auth/src/keyring_config.rs @@ -3,17 +3,17 @@ use std::collections::HashMap; use std::fs::{self, File}; use std::io::{self, Write}; use std::path::PathBuf; -use toml; use thiserror::Error; +use toml; #[derive(Error, Debug)] pub enum ConfigError { #[error("IO error: {0}")] Io(#[from] io::Error), - + #[error("Serialization/Deserialization error: {0}")] SerdeError(#[from] toml::de::Error), - + #[error("Invalid configuration path")] InvalidPath, @@ -21,7 +21,6 @@ pub enum ConfigError { TomlSerializationError(#[from] toml::ser::Error), } - pub trait ConfigFile { fn path() -> Result; @@ -88,12 +87,11 @@ impl AuthConfig { } } - #[cfg(test)] mod tests { use super::*; - use std::path::Path; use std::fs; + use std::path::Path; // Helper function to clean up a temporary file fn remove_temp_file(path: &Path) -> io::Result<()> { diff --git a/crates/uv-auth/src/lib.rs b/crates/uv-auth/src/lib.rs index b15d03c8f8c8..7b71a6f95052 100644 --- a/crates/uv-auth/src/lib.rs +++ b/crates/uv-auth/src/lib.rs @@ -6,17 +6,17 @@ use url::Url; use cache::CredentialsCache; pub use credentials::Credentials; pub use keyring::KeyringProvider; -pub use middleware::AuthMiddleware; pub use keyring_config::AuthConfig; pub use keyring_config::ConfigFile; +pub use middleware::AuthMiddleware; use realm::Realm; mod cache; mod credentials; mod keyring; +mod keyring_config; mod middleware; mod realm; -mod keyring_config; // TODO(zanieb): Consider passing a cache explicitly throughout diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 4d8f3e0268a4..de5bd639a300 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -5187,7 +5187,6 @@ pub struct PublishArgs { pub skip_existing: bool, } - /// See [PEP 517](https://peps.python.org/pep-0517/) and /// [PEP 660](https://peps.python.org/pep-0660/) for specifications of the parameters. #[derive(Subcommand)] @@ -5218,9 +5217,6 @@ pub enum BuildBackendCommand { PrepareMetadataForBuildEditable { wheel_directory: PathBuf }, } - - - #[derive(Args)] #[allow(clippy::struct_excessive_bools)] pub struct IndexNamespace { @@ -5262,7 +5258,6 @@ pub enum IndexCredentialsCommand { Add(IndexCredentialsArgs), } - #[derive(Args)] pub struct IndexSourceArgs { /// The name of the index @@ -5271,7 +5266,7 @@ pub struct IndexSourceArgs { } #[derive(Args)] -pub struct IndexCredentialsArgs { +pub struct IndexCredentialsArgs { /// The name of the index #[arg(long)] pub name: String, @@ -5288,4 +5283,4 @@ pub struct IndexCredentialsArgs { /// Defaults to `disabled`. #[arg(long, value_enum, env = EnvVars::UV_KEYRING_PROVIDER)] pub keyring_provider: Option, -} \ No newline at end of file +} diff --git a/crates/uv-distribution-types/src/index.rs b/crates/uv-distribution-types/src/index.rs index 4322d6860ac8..d0a318407961 100644 --- a/crates/uv-distribution-types/src/index.rs +++ b/crates/uv-distribution-types/src/index.rs @@ -9,8 +9,6 @@ use crate::index_name::{IndexName, IndexNameError}; use crate::origin::Origin; use crate::{IndexUrl, IndexUrlError}; - - #[derive(Debug, Clone, Hash, Eq, PartialEq, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct Index { @@ -147,7 +145,9 @@ impl Index { return Some(credentials); } - if let Some(credentials) = Credentials::from_keyring(name.to_string(), self.url.url(), keyring_provider) { + if let Some(credentials) = + Credentials::from_keyring(name.to_string(), self.url.url(), keyring_provider) + { return Some(credentials); } } diff --git a/crates/uv/src/commands/index.rs b/crates/uv/src/commands/index.rs index 27fa1af041e1..083eb38aec0e 100644 --- a/crates/uv/src/commands/index.rs +++ b/crates/uv/src/commands/index.rs @@ -1,42 +1,62 @@ +use anyhow::{Context, Result}; use console::Term; +use tracing::{debug, warn}; +use uv_auth::{AuthConfig, ConfigFile}; use uv_configuration::KeyringProviderType; use uv_distribution_types::Index; -use uv_auth::{AuthConfig, ConfigFile}; -use tracing::{debug, warn}; -use anyhow::{Context, Result}; /// Add one or more packages to the project requirements. #[allow(clippy::fn_params_excessive_bools)] -pub(crate) async fn add_credentials(name: String, username: Option, keyring_provider: KeyringProviderType, index: Vec) -> Result<()> { - let index_for_name = index.iter().find( - |idx| idx.name.as_ref().map(|n| n.to_string() == name).unwrap_or(false) - ); +pub(crate) async fn add_credentials( + name: String, + username: Option, + keyring_provider: KeyringProviderType, + index: Vec, +) -> Result<()> { + let index_for_name = index.iter().find(|idx| { + idx.name + .as_ref() + .map(|n| n.to_string() == name) + .unwrap_or(false) + }); let index_for_name = match index_for_name { Some(obj) => obj, None => panic!("No index found with the name '{}'", name), }; - - + let username = match username { Some(n) => n, None => match prompt_username_input()? { Some(n) => n, - None => panic!("No username provided and could not read username from input.") + None => panic!("No username provided and could not read username from input."), }, - }; + }; let password = match prompt_password_input()? { Some(p) => p, - None => panic!("Could not read password from user input") + None => panic!("Could not read password from user input"), }; let url = index_for_name.raw_url(); - debug!("Will store password for index {} with URL {} and user {} in keyring", name, url, username); - keyring_provider.to_provider().expect("Keyring Provider is not available").set(&url, &username, &password).await; + debug!( + "Will store password for index {} with URL {} and user {} in keyring", + name, url, username + ); + keyring_provider + .to_provider() + .expect("Keyring Provider is not available") + .set(&url, &username, &password) + .await; - debug!("Will add index {} and user {} to index auth config in {:?}", name, url, AuthConfig::path()?); - let mut auth_config = AuthConfig::load().inspect_err(|err| warn!("Could not load auth config due to: {err}"))?; + debug!( + "Will add index {} and user {} to index auth config in {:?}", + name, + url, + AuthConfig::path()? + ); + let mut auth_config = + AuthConfig::load().inspect_err(|err| warn!("Could not load auth config due to: {err}"))?; auth_config.add_entry(name, username); auth_config.store()?; @@ -49,7 +69,7 @@ fn prompt_username_input() -> Result> { return Ok(None); } let username_prompt = "Enter username: "; - + let username = uv_console::input(username_prompt, &term).context("Failed to read username")?; Ok(Some(username)) } @@ -63,4 +83,4 @@ fn prompt_password_input() -> Result> { let password = uv_console::password(password_prompt, &term).context("Failed to read password")?; Ok(Some(password)) -} \ No newline at end of file +} diff --git a/crates/uv/src/commands/project/add.rs b/crates/uv/src/commands/project/add.rs index da23615ee55f..61d9046e6e01 100644 --- a/crates/uv/src/commands/project/add.rs +++ b/crates/uv/src/commands/project/add.rs @@ -282,8 +282,6 @@ pub(crate) async fn add( let hasher = HashStrategy::default(); let sources = SourceStrategy::Enabled; - - // Add all authenticated sources to the cache. for index in settings.index_locations.allowed_indexes() { if let Some(credentials) = index.credentials(settings.keyring_provider.to_provider()) { diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index 8ab44ea7d2a1..1b46bc201421 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -22,7 +22,10 @@ use uv_cli::{ compat::CompatArgs, BuildBackendCommand, CacheCommand, CacheNamespace, Cli, Commands, PipCommand, PipNamespace, ProjectCommand, }; -use uv_cli::{IndexCommand, IndexCredentialsCommand, IndexNamespace, PythonCommand, PythonNamespace, ToolCommand, ToolNamespace, TopLevelArgs}; +use uv_cli::{ + IndexCommand, IndexCredentialsCommand, IndexNamespace, PythonCommand, PythonNamespace, + ToolCommand, ToolNamespace, TopLevelArgs, +}; #[cfg(feature = "self-update")] use uv_cli::{SelfCommand, SelfNamespace, SelfUpdateArgs}; use uv_fs::CWD; @@ -1259,20 +1262,20 @@ async fn run(mut cli: Cli) -> Result { }) .await .expect("tokio threadpool exited unexpectedly"), - Commands::Index (IndexNamespace { - command: IndexCommand::Add(args) + Commands::Index(IndexNamespace { + command: IndexCommand::Add(args), }) => { println!("Add an index with name {}", args.name); return Ok(ExitStatus::Success); } - Commands::Index (IndexNamespace { - command: IndexCommand::List(_) + Commands::Index(IndexNamespace { + command: IndexCommand::List(_), }) => { println!("List all indexes"); return Ok(ExitStatus::Success); } - Commands::Index (IndexNamespace { - command: IndexCommand::Delete(args) + Commands::Index(IndexNamespace { + command: IndexCommand::Delete(args), }) => { println!("Delete index {}", args.name); return Ok(ExitStatus::Success); @@ -1281,13 +1284,15 @@ async fn run(mut cli: Cli) -> Result { command: IndexCommand::Credentials(IndexCredentialsCommand::Add(args)), }) => { let IndexSettings { - name, username, keyring_provider, index + name, + username, + keyring_provider, + index, } = IndexSettings::resolve(args, filesystem); let _ = add_credentials(name, username, keyring_provider, index).await; return Ok(ExitStatus::Success); } - }; result } diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index 0bc55642986f..c086d4feeabc 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -13,7 +13,11 @@ use uv_cli::{ ToolUpgradeArgs, }; use uv_cli::{ - AddArgs, ColorChoice, ExternalCommand, GlobalArgs, IndexCredentialsArgs, InitArgs, ListFormat, LockArgs, Maybe, PipCheckArgs, PipCompileArgs, PipFreezeArgs, PipInstallArgs, PipListArgs, PipShowArgs, PipSyncArgs, PipTreeArgs, PipUninstallArgs, PythonFindArgs, PythonInstallArgs, PythonListArgs, PythonPinArgs, PythonUninstallArgs, RemoveArgs, RunArgs, SyncArgs, ToolDirArgs, ToolInstallArgs, ToolListArgs, ToolRunArgs, ToolUninstallArgs, TreeArgs, VenvArgs + AddArgs, ColorChoice, ExternalCommand, GlobalArgs, IndexCredentialsArgs, InitArgs, ListFormat, + LockArgs, Maybe, PipCheckArgs, PipCompileArgs, PipFreezeArgs, PipInstallArgs, PipListArgs, + PipShowArgs, PipSyncArgs, PipTreeArgs, PipUninstallArgs, PythonFindArgs, PythonInstallArgs, + PythonListArgs, PythonPinArgs, PythonUninstallArgs, RemoveArgs, RunArgs, SyncArgs, ToolDirArgs, + ToolInstallArgs, ToolListArgs, ToolRunArgs, ToolUninstallArgs, TreeArgs, VenvArgs, }; use uv_client::Connectivity; use uv_configuration::{ @@ -2878,7 +2882,6 @@ impl PublishSettings { } } - pub(crate) struct IndexSettings { // CLI only settings pub(crate) name: String, @@ -2893,26 +2896,28 @@ pub(crate) struct IndexSettings { impl IndexSettings { /// Resolve the [`IndexSettings`] from the CLI and filesystem configuration. - pub(crate) fn resolve(args: IndexCredentialsArgs, filesystem: Option) -> Self { - let Options { - top_level, .. - } = filesystem + pub(crate) fn resolve( + args: IndexCredentialsArgs, + filesystem: Option, + ) -> Self { + let Options { top_level, .. } = filesystem .map(FilesystemOptions::into_options) .unwrap_or_default(); let ResolverInstallerOptions { - keyring_provider, - index,.. + keyring_provider, + index, + .. } = top_level; Self { name: args.name, username: args.username, - keyring_provider: args. - keyring_provider + keyring_provider: args + .keyring_provider .combine(keyring_provider) .unwrap_or_default(), - index: index.unwrap_or_default() + index: index.unwrap_or_default(), } } } From 336ff34a7481a8aedd4ef9c371781fa4891a04a5 Mon Sep 17 00:00:00 2001 From: Simon Steinheber Date: Mon, 30 Dec 2024 11:33:44 +0100 Subject: [PATCH 03/28] Add tests to keyring --- Cargo.lock | 39 ------------------ crates/uv-auth/Cargo.toml | 1 - crates/uv-auth/src/keyring.rs | 64 +++++++++++++++++++++++------- crates/uv/src/commands/pip/sync.rs | 1 - 4 files changed, 50 insertions(+), 55 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fc77c204c2b6..baee05ab64f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -965,12 +965,6 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" -[[package]] -name = "downcast" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" - [[package]] name = "dunce" version = "1.0.5" @@ -1166,12 +1160,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "fragile" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" - [[package]] name = "fs-err" version = "2.11.0" @@ -2208,32 +2196,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "mockall" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39a6bfcc6c8c7eed5ee98b9c3e33adc726054389233e201c95dab2d41a3839d2" -dependencies = [ - "cfg-if", - "downcast", - "fragile", - "mockall_derive", - "predicates", - "predicates-tree", -] - -[[package]] -name = "mockall_derive" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25ca3004c2efe9011bd4e461bd8256445052b9615405b4f7ea43fc8ca5c20898" -dependencies = [ - "cfg-if", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "munge" version = "0.4.1" @@ -4554,7 +4516,6 @@ dependencies = [ "futures", "http", "insta", - "mockall", "reqwest", "reqwest-middleware", "rust-netrc", diff --git a/crates/uv-auth/Cargo.toml b/crates/uv-auth/Cargo.toml index aa888d2491fa..fcf508574873 100644 --- a/crates/uv-auth/Cargo.toml +++ b/crates/uv-auth/Cargo.toml @@ -38,4 +38,3 @@ tokio = { workspace = true } wiremock = { workspace = true } insta = { version = "1.40.0" } test-log = { version = "0.2.16", features = ["trace"], default-features = false } -mockall = "0.13.1" diff --git a/crates/uv-auth/src/keyring.rs b/crates/uv-auth/src/keyring.rs index 59cc46c58113..6e71eb7938e8 100644 --- a/crates/uv-auth/src/keyring.rs +++ b/crates/uv-auth/src/keyring.rs @@ -116,7 +116,7 @@ impl KeyringProvider { /// Set credentials for the given [`Url`] from the keyring. #[instrument(skip_all, fields(url = % url.to_string(), username))] - pub async fn set(&self, url: &Url, username: &str, password: &str) { + pub async fn set(&mut self, url: &Url, username: &str, password: &str) { // Validate the request debug_assert!( url.host_str().is_some(), @@ -131,18 +131,19 @@ impl KeyringProvider { "Should only use keyring with a username" ); - // Check the full URL first - // - trace!("Creating entry in keyring for URL {url} and username {username}"); + let host = url.host().expect("Url should contain a host!"); + trace!("Creating entry in keyring for host {host} (from url {url}) and username {username}"); - match self.backend { + match &mut self.backend { KeyringProviderBackend::Subprocess => { - self.set_subprocess(url.as_str(), username, password).await + self.set_subprocess(&host.to_string(), &username, &password).await } #[cfg(test)] - KeyringProviderBackend::Dummy(ref store) => { - let test = password; - None + KeyringProviderBackend::Dummy(ref mut store) => { + let username_static: &'static str = Box::leak(username.to_owned().into_boxed_str()); + let password_static: &'static str = Box::leak(password.to_owned().into_boxed_str()); + + Self::set_dummy(store, &host.to_string(), &username_static, &password_static) } }; } @@ -210,13 +211,13 @@ impl KeyringProvider { #[cfg(test)] fn set_dummy( - store: &mut std::collections::HashMap<(String, &str), &str>, + store: &mut std::collections::HashMap<(String, &'static str), &'static str>, service_name: &str, - username: &str, - password: &str, + username: &'static str, + password: &'static str, ) -> Option<()> { - // store.insert((service_name.to_string(), username), password); - None + store.insert((service_name.to_string(), username), password); + return None; } /// Create a new provider with [`KeyringProviderBackend::Dummy`]. @@ -246,6 +247,7 @@ impl KeyringProvider { #[cfg(test)] mod tests { + use super::*; use futures::FutureExt; @@ -374,4 +376,38 @@ mod tests { let credentials = keyring.fetch(&url, "bar").await; assert_eq!(credentials, None); } + + #[tokio::test] + async fn set_url() { + let url = Url::parse("https://example.com").unwrap(); + let mut keyring = KeyringProvider::dummy([((url.host_str().unwrap(), "user"), "password")]); + + keyring.set(&url, "foo", "password").await; + + let credentials = keyring.fetch(&url, "foo").await; + assert_eq!( + credentials, + Some(Credentials::new( + Some("foo".to_string()), + Some("password".to_string()) + )) + ); + } + + #[tokio::test] + async fn set_url_with_path() { + let url = Url::parse("https://example.com").unwrap(); + let mut keyring = KeyringProvider::dummy([((url.host_str().unwrap(), "user"), "password")]); + + keyring.set(&url.join("test").unwrap(), "foo", "password").await; + + let credentials = keyring.fetch(&url, "foo").await; + assert_eq!( + credentials, + Some(Credentials::new( + Some("foo".to_string()), + Some("password".to_string()) + )) + ); + } } diff --git a/crates/uv/src/commands/pip/sync.rs b/crates/uv/src/commands/pip/sync.rs index 434431cd8c8b..56c0df59bb6e 100644 --- a/crates/uv/src/commands/pip/sync.rs +++ b/crates/uv/src/commands/pip/sync.rs @@ -35,7 +35,6 @@ use crate::commands::pip::operations::{report_interpreter, report_target_environ use crate::commands::pip::{operations, resolution_markers, resolution_tags}; use crate::commands::{diagnostics, ExitStatus}; use crate::printer::Printer; -use crate::settings; /// Install a set of locked requirements into the current Python environment. #[allow(clippy::fn_params_excessive_bools)] From 200857469cf7769bf0e9dbf41333a0cf9e7f8954 Mon Sep 17 00:00:00 2001 From: Simon Steinheber Date: Mon, 30 Dec 2024 11:59:19 +0100 Subject: [PATCH 04/28] Add password option to index command --- crates/uv-cli/src/lib.rs | 6 +++++- crates/uv/src/commands/index.rs | 23 ++++++++++++----------- crates/uv/src/lib.rs | 3 ++- crates/uv/src/settings.rs | 2 ++ 4 files changed, 21 insertions(+), 13 deletions(-) diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index de5bd639a300..55eff15a9141 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -5,7 +5,7 @@ use std::str::FromStr; use anyhow::{anyhow, Result}; use clap::builder::styling::{AnsiColor, Effects, Style}; -use clap::builder::{Str, Styles}; +use clap::builder::Styles; use clap::{Args, Parser, Subcommand}; use url::Url; @@ -5275,6 +5275,10 @@ pub struct IndexCredentialsArgs { #[arg(long, required(false))] pub username: Option, + /// The password that should be user for the index + #[arg(long, required(false))] + pub password: Option, + /// Attempt to use `keyring` for authentication for remote requirements files. /// /// At present, only `--keyring-provider subprocess` is supported, which configures uv to diff --git a/crates/uv/src/commands/index.rs b/crates/uv/src/commands/index.rs index 083eb38aec0e..968cf36dfcf5 100644 --- a/crates/uv/src/commands/index.rs +++ b/crates/uv/src/commands/index.rs @@ -10,17 +10,18 @@ use uv_distribution_types::Index; pub(crate) async fn add_credentials( name: String, username: Option, + password: Option, keyring_provider: KeyringProviderType, - index: Vec, + indexes: Vec, ) -> Result<()> { - let index_for_name = index.iter().find(|idx| { + let index = indexes.iter().find(|idx| { idx.name .as_ref() .map(|n| n.to_string() == name) .unwrap_or(false) }); - let index_for_name = match index_for_name { + let index = match index { Some(obj) => obj, None => panic!("No index found with the name '{}'", name), }; @@ -33,15 +34,17 @@ pub(crate) async fn add_credentials( }, }; - let password = match prompt_password_input()? { + let password = match password { Some(p) => p, - None => panic!("Could not read password from user input"), + None => match prompt_password_input()? { + Some(p) => p, + None => panic!("Could not read password from user input"), + }, }; - let url = index_for_name.raw_url(); + let url = index.raw_url(); debug!( - "Will store password for index {} with URL {} and user {} in keyring", - name, url, username + "Will store password for index {name} with URL {url} and user {username} in keyring" ); keyring_provider .to_provider() @@ -50,9 +53,7 @@ pub(crate) async fn add_credentials( .await; debug!( - "Will add index {} and user {} to index auth config in {:?}", - name, - url, + "Will add index {name} and user {username} to index auth config in {:?}", AuthConfig::path()? ); let mut auth_config = diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index 1b46bc201421..a5fb02b53b70 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -1286,11 +1286,12 @@ async fn run(mut cli: Cli) -> Result { let IndexSettings { name, username, + password, keyring_provider, index, } = IndexSettings::resolve(args, filesystem); - let _ = add_credentials(name, username, keyring_provider, index).await; + let _ = add_credentials(name, username, password, keyring_provider, index).await; return Ok(ExitStatus::Success); } }; diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index c086d4feeabc..b16207041b56 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -2886,6 +2886,7 @@ pub(crate) struct IndexSettings { // CLI only settings pub(crate) name: String, pub(crate) username: Option, + pub(crate) password: Option, // CLI and Filesystem settings pub(crate) keyring_provider: KeyringProviderType, @@ -2913,6 +2914,7 @@ impl IndexSettings { Self { name: args.name, username: args.username, + password: args.password, keyring_provider: args .keyring_provider .combine(keyring_provider) From f9bce4369a7a50857808de4229a9a102d3d6e1bf Mon Sep 17 00:00:00 2001 From: Simon Steinheber Date: Mon, 30 Dec 2024 12:21:57 +0100 Subject: [PATCH 05/28] Clean up --- crates/uv-auth/src/credentials.rs | 11 +++++++---- crates/uv-auth/src/keyring.rs | 15 ++++++++++----- crates/uv-distribution-types/src/index.rs | 5 ++++- crates/uv/src/commands/build_frontend.rs | 2 +- crates/uv/src/commands/index.rs | 4 +--- crates/uv/src/commands/pip/compile.rs | 2 +- crates/uv/src/commands/pip/install.rs | 2 +- crates/uv/src/commands/pip/sync.rs | 2 +- crates/uv/src/commands/venv.rs | 4 ++-- 9 files changed, 28 insertions(+), 19 deletions(-) diff --git a/crates/uv-auth/src/credentials.rs b/crates/uv-auth/src/credentials.rs index 0bbea348427d..a1c35555d83d 100644 --- a/crates/uv-auth/src/credentials.rs +++ b/crates/uv-auth/src/credentials.rs @@ -9,6 +9,7 @@ use reqwest::Request; use futures::executor; use std::io::Read; use std::io::Write; +use tracing::{debug, error, trace, warn}; use url::Url; @@ -162,7 +163,7 @@ impl Credentials { } } - /// Extracte the [`Credentials`] from keyring, given a named source. + /// Extract the [`Credentials`] from keyring, given a named source. /// /// Look up the username stored for the named source in the user-level config. /// Load the credentials from keyring for the service and username. @@ -171,14 +172,16 @@ impl Credentials { url: &Url, keyring_provider: Option, ) -> Option { + debug!("Trying to read credentials for index {name} with url {url}"); if keyring_provider.is_none() { + trace!("No keyring provider available"); return None; } let auth_config = match AuthConfig::load() { Ok(auth_config) => auth_config, - Err(_) => { - eprintln!("Error loading auth config"); + Err(e) => { + error!("Error loading auth config: {e}"); return None; } }; @@ -186,7 +189,7 @@ impl Credentials { let index = match auth_config.find_entry(&name) { Some(i) => i, None => { - eprintln!("Could not find entry for {name}"); + warn!("Could not find entry for {name}"); return None; } }; diff --git a/crates/uv-auth/src/keyring.rs b/crates/uv-auth/src/keyring.rs index 6e71eb7938e8..0a40d6a6292e 100644 --- a/crates/uv-auth/src/keyring.rs +++ b/crates/uv-auth/src/keyring.rs @@ -132,11 +132,14 @@ impl KeyringProvider { ); let host = url.host().expect("Url should contain a host!"); - trace!("Creating entry in keyring for host {host} (from url {url}) and username {username}"); + trace!( + "Creating entry in keyring for host {host} (from url {url}) and username {username}" + ); match &mut self.backend { KeyringProviderBackend::Subprocess => { - self.set_subprocess(&host.to_string(), &username, &password).await + self.set_subprocess(&host.to_string(), &username, &password) + .await } #[cfg(test)] KeyringProviderBackend::Dummy(ref mut store) => { @@ -386,7 +389,7 @@ mod tests { let credentials = keyring.fetch(&url, "foo").await; assert_eq!( - credentials, + credentials, Some(Credentials::new( Some("foo".to_string()), Some("password".to_string()) @@ -399,11 +402,13 @@ mod tests { let url = Url::parse("https://example.com").unwrap(); let mut keyring = KeyringProvider::dummy([((url.host_str().unwrap(), "user"), "password")]); - keyring.set(&url.join("test").unwrap(), "foo", "password").await; + keyring + .set(&url.join("test").unwrap(), "foo", "password") + .await; let credentials = keyring.fetch(&url, "foo").await; assert_eq!( - credentials, + credentials, Some(Credentials::new( Some("foo".to_string()), Some("password".to_string()) diff --git a/crates/uv-distribution-types/src/index.rs b/crates/uv-distribution-types/src/index.rs index d0a318407961..150de7ebb5f5 100644 --- a/crates/uv-distribution-types/src/index.rs +++ b/crates/uv-distribution-types/src/index.rs @@ -139,12 +139,15 @@ impl Index { /// Retrieve the credentials for the index, either from the environment, or from the URL itself. pub fn credentials(&self, keyring_provider: Option) -> Option { - // If the index is named, and credentials are provided via the environment, prefer those. + // If the index is named, try to load credentials for the named index. + if let Some(name) = self.name.as_ref() { + // If credentials are provided via the environment, prefer those. if let Some(credentials) = Credentials::from_env(name.to_env_var()) { return Some(credentials); } + // Otherwise try to read the credentials from keyring. if let Some(credentials) = Credentials::from_keyring(name.to_string(), self.url.url(), keyring_provider) { diff --git a/crates/uv/src/commands/build_frontend.rs b/crates/uv/src/commands/build_frontend.rs index 54bfd1ac522c..338f28172aee 100644 --- a/crates/uv/src/commands/build_frontend.rs +++ b/crates/uv/src/commands/build_frontend.rs @@ -492,7 +492,7 @@ async fn build_package( // Add all authenticated sources to the cache. for index in index_locations.allowed_indexes() { - if let Some(credentials) = index.credentials(None) { + if let Some(credentials) = index.credentials(keyring_provider.to_provider()) { store_credentials(index.raw_url(), credentials); } } diff --git a/crates/uv/src/commands/index.rs b/crates/uv/src/commands/index.rs index 968cf36dfcf5..193e468d000a 100644 --- a/crates/uv/src/commands/index.rs +++ b/crates/uv/src/commands/index.rs @@ -43,9 +43,7 @@ pub(crate) async fn add_credentials( }; let url = index.raw_url(); - debug!( - "Will store password for index {name} with URL {url} and user {username} in keyring" - ); + debug!("Will store password for index {name} with URL {url} and user {username} in keyring"); keyring_provider .to_provider() .expect("Keyring Provider is not available") diff --git a/crates/uv/src/commands/pip/compile.rs b/crates/uv/src/commands/pip/compile.rs index 7808b8cbcda8..b6b715af52ed 100644 --- a/crates/uv/src/commands/pip/compile.rs +++ b/crates/uv/src/commands/pip/compile.rs @@ -287,7 +287,7 @@ pub(crate) async fn pip_compile( // Add all authenticated sources to the cache. for index in index_locations.allowed_indexes() { - if let Some(credentials) = index.credentials(None) { + if let Some(credentials) = index.credentials(keyring_provider.to_provider()) { uv_auth::store_credentials(index.raw_url(), credentials); } } diff --git a/crates/uv/src/commands/pip/install.rs b/crates/uv/src/commands/pip/install.rs index f23e1a5554ce..2bb91d4b356b 100644 --- a/crates/uv/src/commands/pip/install.rs +++ b/crates/uv/src/commands/pip/install.rs @@ -306,7 +306,7 @@ pub(crate) async fn pip_install( // Add all authenticated sources to the cache. for index in index_locations.allowed_indexes() { - if let Some(credentials) = index.credentials(None) { + if let Some(credentials) = index.credentials(keyring_provider.to_provider()) { uv_auth::store_credentials(index.raw_url(), credentials); } } diff --git a/crates/uv/src/commands/pip/sync.rs b/crates/uv/src/commands/pip/sync.rs index 56c0df59bb6e..292c5c9ea394 100644 --- a/crates/uv/src/commands/pip/sync.rs +++ b/crates/uv/src/commands/pip/sync.rs @@ -246,7 +246,7 @@ pub(crate) async fn pip_sync( // Add all authenticated sources to the cache. for index in index_locations.allowed_indexes() { - if let Some(credentials) = index.credentials(None) { + if let Some(credentials) = index.credentials(keyring_provider.to_provider()) { uv_auth::store_credentials(index.raw_url(), credentials); } } diff --git a/crates/uv/src/commands/venv.rs b/crates/uv/src/commands/venv.rs index 9ed45839b125..03b31c777690 100644 --- a/crates/uv/src/commands/venv.rs +++ b/crates/uv/src/commands/venv.rs @@ -224,7 +224,7 @@ async fn venv_impl( // Add all authenticated sources to the cache. for index in index_locations.allowed_indexes() { - if let Some(credentials) = index.credentials(None) { + if let Some(credentials) = index.credentials(keyring_provider.to_provider()) { uv_auth::store_credentials(index.raw_url(), credentials); } } @@ -271,7 +271,7 @@ async fn venv_impl( // Add all authenticated sources to the cache. for index in index_locations.allowed_indexes() { - if let Some(credentials) = index.credentials(None) { + if let Some(credentials) = index.credentials(keyring_provider.to_provider()) { uv_auth::store_credentials(index.raw_url(), credentials); } } From cacf2e2d13fd62358523e36341f906ba7ce62dc8 Mon Sep 17 00:00:00 2001 From: Simon Steinheber Date: Mon, 30 Dec 2024 13:21:31 +0100 Subject: [PATCH 06/28] Test loading creadentials from keyring --- crates/uv-auth/src/credentials.rs | 59 ++++++++++++++-------------- crates/uv-auth/src/keyring_config.rs | 25 ++++++++++++ 2 files changed, 55 insertions(+), 29 deletions(-) diff --git a/crates/uv-auth/src/credentials.rs b/crates/uv-auth/src/credentials.rs index a1c35555d83d..3579e5a7252e 100644 --- a/crates/uv-auth/src/credentials.rs +++ b/crates/uv-auth/src/credentials.rs @@ -291,6 +291,9 @@ impl Credentials { mod tests { use insta::assert_debug_snapshot; + use tempfile::tempdir; + + use crate::keyring_config::{reset_config_path, set_test_config_path}; use super::*; @@ -399,34 +402,32 @@ mod tests { #[test] fn from_keyring() { - // let service_name = "example.com"; - // let username = "user1"; - // let index = "index1"; - - // let child = Command::new("keyring") - // .arg("set") - // .arg(service_name) - // .arg(username) - // .stdin(Stdio::null()) - // .stdout(Stdio::piped()) - // .stderr(Stdio::inherit()) - // .spawn() - // .inspect_err(|err| eprint!("Failure running `keyring` command: {err}")) - // .ok()?; - - // let output = executor::block_on(child.wait_with_output()) - // .inspect_err(|err| eprintln!("Failed to wait for `keyring` output: {err}")) - // .ok()?; - - // assert!(output.status.success()); - - // let keyring_provider = KeyringProvider::subprocess(); - - // let mut auth_config = AuthConfig::load()?; - // auth_config.add_entry(index.to_string(), username.to_string()); - // auth_config.store(); - - // // Act - // let credentials = Credentials::from_keyring(index.to_string(), "example.com".to_string(), Some(keyring_provider)); + let username = "user"; + let password = "password"; + let index = "test_index"; + + let url = Url::parse("https://example.com").unwrap(); + let keyring = KeyringProvider::dummy([((url.host_str().unwrap(), username), password)]); + let temp_dir = tempdir().unwrap(); + let auth_config_path = temp_dir.into_path().join("test_auth.toml"); + + set_test_config_path(auth_config_path); + + let mut auth_config = AuthConfig::load().unwrap(); + auth_config.add_entry(index.to_string(), username.to_string()); + auth_config.store().unwrap(); + + // Act + let credentials = Credentials::from_keyring(index.to_string(), &url, Some(keyring)); + + assert_eq!( + credentials, + Some(Credentials::new( + Some(username.to_string()), + Some(password.to_string()) + )) + ); + + reset_config_path(); } } diff --git a/crates/uv-auth/src/keyring_config.rs b/crates/uv-auth/src/keyring_config.rs index 0f85c204c72f..f4c3773d1b08 100644 --- a/crates/uv-auth/src/keyring_config.rs +++ b/crates/uv-auth/src/keyring_config.rs @@ -3,6 +3,7 @@ use std::collections::HashMap; use std::fs::{self, File}; use std::io::{self, Write}; use std::path::PathBuf; +use std::sync::Mutex; use thiserror::Error; use toml; @@ -21,6 +22,9 @@ pub enum ConfigError { TomlSerializationError(#[from] toml::ser::Error), } +#[cfg(test)] +static CONFIG_PATH: Mutex> = Mutex::new(None); + pub trait ConfigFile { fn path() -> Result; @@ -33,6 +37,15 @@ pub trait ConfigFile { impl ConfigFile for AuthConfig { fn path() -> Result { + #[cfg(test)] + { + // Lock the mutex safely and access the path + let path_guard = CONFIG_PATH.lock().unwrap(); + if let Some(ref path) = *path_guard { + return Ok(path.clone()); + } + } + let cache_dir = uv_dirs::user_cache_dir().ok_or(ConfigError::InvalidPath)?; Ok(cache_dir.join("auth.toml")) } @@ -87,6 +100,18 @@ impl AuthConfig { } } +#[cfg(test)] +pub(crate) fn set_test_config_path(path: PathBuf) { + let mut path_guard = CONFIG_PATH.lock().unwrap(); + *path_guard = Some(path); +} + +#[cfg(test)] +pub(crate) fn reset_config_path() { + let mut path_guard = CONFIG_PATH.lock().unwrap(); + *path_guard = None; +} + #[cfg(test)] mod tests { use super::*; From 258f8fd2c835ab19c80456c1bff9bc497dfd0f26 Mon Sep 17 00:00:00 2001 From: Simon Steinheber Date: Mon, 30 Dec 2024 13:33:25 +0100 Subject: [PATCH 07/28] Fix issues after merging new state of uv --- Cargo.lock | 64 ++++++++++++++++----------- crates/uv/src/commands/project/add.rs | 3 +- 2 files changed, 39 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f99d4da60e13..1424020e9fc5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -502,9 +502,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.5" +version = "1.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31a0499c1dc64f458ad13872de75c0eb7e3fdb0e67964610c914b034fc5956e" +checksum = "8d6dbb628b8f8555f86d0323c2eb39e3ec81901f4b83e091db8a6a76d316a333" dependencies = [ "jobserver", "libc", @@ -580,7 +580,7 @@ dependencies = [ "anstyle", "clap_lex", "strsim", - "terminal_size 0.4.1", + "terminal_size", ] [[package]] @@ -672,7 +672,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -1026,7 +1026,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1509,9 +1509,9 @@ dependencies = [ [[package]] name = "http-content-range" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91314cc9d86f625097a3365cab4e4b6f190eac231650f8f41c1edd8080cea1d0" +checksum = "b4aa8e0a9f1496d70bdd43b1e30ff373857c952609ad64b89f50569cfb8cbfca" [[package]] name = "httparse" @@ -2116,9 +2116,9 @@ dependencies = [ [[package]] name = "miette" -version = "7.2.0" +version = "7.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4edc8853320c2a0dab800fbda86253c8938f6ea88510dc92c5f1ed20e794afc1" +checksum = "317f146e2eb7021892722af37cf1b971f0a70c8406f487e24952667616192c64" dependencies = [ "cfg-if", "miette-derive", @@ -2126,7 +2126,7 @@ dependencies = [ "supports-color", "supports-hyperlinks", "supports-unicode", - "terminal_size 0.3.0", + "terminal_size", "textwrap", "thiserror 1.0.69", "unicode-width 0.1.14", @@ -2134,9 +2134,9 @@ dependencies = [ [[package]] name = "miette-derive" -version = "7.2.0" +version = "7.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf09caffaac8068c346b6df2a7fc27a177fd20b39421a39ce0a211bde679a6c" +checksum = "23c9b935fbe1d6cbd1dac857b54a688145e2d93f48db36010514d0f612d0ad67" dependencies = [ "proc-macro2", "quote", @@ -2931,9 +2931,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.12.9" +version = "0.12.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" +checksum = "7fe060fe50f524be480214aba758c71f99f90ee8c83c5a36b5e9e1d568eb4eb3" dependencies = [ "async-compression", "base64 0.22.1", @@ -2969,6 +2969,7 @@ dependencies = [ "tokio-rustls", "tokio-socks", "tokio-util", + "tower", "tower-service", "url", "wasm-bindgen", @@ -3180,7 +3181,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3751,16 +3752,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "terminal_size" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" -dependencies = [ - "rustix", - "windows-sys 0.48.0", -] - [[package]] name = "terminal_size" version = "0.4.1" @@ -4088,6 +4079,27 @@ dependencies = [ "winnow", ] +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + [[package]] name = "tower-service" version = "0.3.3" @@ -4525,7 +4537,7 @@ dependencies = [ "serde", "tempfile", "test-log", - "thiserror 2.0.3", + "thiserror 2.0.9", "tokio", "toml", "tracing", diff --git a/crates/uv/src/commands/project/add.rs b/crates/uv/src/commands/project/add.rs index 275f93ce753c..dabf7167bc70 100644 --- a/crates/uv/src/commands/project/add.rs +++ b/crates/uv/src/commands/project/add.rs @@ -253,7 +253,6 @@ pub(crate) async fn add( let RequirementsSpecification { requirements, .. } = RequirementsSpecification::from_simple_sources(&requirements, &client_builder).await?; - // TODO(charlie): These are all default values. We should consider whether we want to make them // optional on the downstream APIs. let bounds = LowerBound::default(); @@ -323,7 +322,7 @@ pub(crate) async fn add( // Add all authenticated sources to the cache. for index in settings.index_locations.allowed_indexes() { - if let Some(credentials) = index.credentials() { + if let Some(credentials) = index.credentials(settings.keyring_provider.to_provider()) { uv_auth::store_credentials(index.raw_url(), credentials); } } From adf6969c8a23c560633afeac5c99d637beaaf473 Mon Sep 17 00:00:00 2001 From: Simon Steinheber Date: Mon, 30 Dec 2024 13:34:49 +0100 Subject: [PATCH 08/28] Format code --- crates/uv/src/commands/project/add.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/uv/src/commands/project/add.rs b/crates/uv/src/commands/project/add.rs index dabf7167bc70..9e6ca27b57a0 100644 --- a/crates/uv/src/commands/project/add.rs +++ b/crates/uv/src/commands/project/add.rs @@ -322,7 +322,9 @@ pub(crate) async fn add( // Add all authenticated sources to the cache. for index in settings.index_locations.allowed_indexes() { - if let Some(credentials) = index.credentials(settings.keyring_provider.to_provider()) { + if let Some(credentials) = + index.credentials(settings.keyring_provider.to_provider()) + { uv_auth::store_credentials(index.raw_url(), credentials); } } From e134ae8ca976251e7a53bf7f135fc1c9a751bccd Mon Sep 17 00:00:00 2001 From: Simon Steinheber Date: Mon, 30 Dec 2024 13:57:19 +0100 Subject: [PATCH 09/28] Fix errors from clippy --- crates/uv-auth/src/credentials.rs | 15 ++++++--------- crates/uv-auth/src/keyring.rs | 6 +++--- crates/uv-auth/src/keyring_config.rs | 8 +++----- crates/uv-distribution-types/src/index.rs | 2 +- crates/uv/src/commands/project/add.rs | 4 ++-- 5 files changed, 15 insertions(+), 20 deletions(-) diff --git a/crates/uv-auth/src/credentials.rs b/crates/uv-auth/src/credentials.rs index d401ea4b8853..96f7b8c95244 100644 --- a/crates/uv-auth/src/credentials.rs +++ b/crates/uv-auth/src/credentials.rs @@ -168,7 +168,7 @@ impl Credentials { /// Look up the username stored for the named source in the user-level config. /// Load the credentials from keyring for the service and username. pub fn from_keyring( - name: String, + name: &str, url: &Url, keyring_provider: Option, ) -> Option { @@ -186,15 +186,12 @@ impl Credentials { } }; - let index = match auth_config.find_entry(&name) { - Some(i) => i, - None => { - warn!("Could not find entry for {name}"); - return None; - } + let index = if let Some(i) = auth_config.find_entry(&name) { i } else { + warn!("Could not find entry for {name}"); + return None; }; - return executor::block_on(keyring_provider.unwrap().fetch(&url, &index.username)); + executor::block_on(keyring_provider.unwrap().fetch(url, &index.username)) } /// Parse [`Credentials`] from an HTTP request, if any. @@ -418,7 +415,7 @@ mod tests { auth_config.store().unwrap(); // Act - let credentials = Credentials::from_keyring(index.to_string(), &url, Some(keyring)); + let credentials = Credentials::from_keyring(index, &url, Some(keyring)); assert_eq!( credentials, diff --git a/crates/uv-auth/src/keyring.rs b/crates/uv-auth/src/keyring.rs index 0a40d6a6292e..d6ba37ec3b8e 100644 --- a/crates/uv-auth/src/keyring.rs +++ b/crates/uv-auth/src/keyring.rs @@ -146,7 +146,7 @@ impl KeyringProvider { let username_static: &'static str = Box::leak(username.to_owned().into_boxed_str()); let password_static: &'static str = Box::leak(password.to_owned().into_boxed_str()); - Self::set_dummy(store, &host.to_string(), &username_static, &password_static) + Self::set_dummy(store, &host.to_string(), username_static, password_static) } }; } @@ -198,7 +198,7 @@ impl KeyringProvider { debug!("Could not save password in keyring"); }; - return None; + None } #[cfg(test)] @@ -220,7 +220,7 @@ impl KeyringProvider { password: &'static str, ) -> Option<()> { store.insert((service_name.to_string(), username), password); - return None; + None } /// Create a new provider with [`KeyringProviderBackend::Dummy`]. diff --git a/crates/uv-auth/src/keyring_config.rs b/crates/uv-auth/src/keyring_config.rs index f4c3773d1b08..744ca467ee79 100644 --- a/crates/uv-auth/src/keyring_config.rs +++ b/crates/uv-auth/src/keyring_config.rs @@ -3,9 +3,7 @@ use std::collections::HashMap; use std::fs::{self, File}; use std::io::{self, Write}; use std::path::PathBuf; -use std::sync::Mutex; use thiserror::Error; -use toml; #[derive(Error, Debug)] pub enum ConfigError { @@ -23,7 +21,7 @@ pub enum ConfigError { } #[cfg(test)] -static CONFIG_PATH: Mutex> = Mutex::new(None); +static CONFIG_PATH: std::sync::Mutex> = std::sync::Mutex::new(None); pub trait ConfigFile { fn path() -> Result; @@ -52,12 +50,12 @@ impl ConfigFile for AuthConfig { fn load() -> Result { let path = AuthConfig::path()?; - return AuthConfig::load_from_path(&path); + AuthConfig::load_from_path(&path) } fn store(&self) -> Result<(), ConfigError> { let path = AuthConfig::path()?; - return self.store_to_path(&path); + self.store_to_path(&path) } } diff --git a/crates/uv-distribution-types/src/index.rs b/crates/uv-distribution-types/src/index.rs index 980c1125c4aa..af75d6c82198 100644 --- a/crates/uv-distribution-types/src/index.rs +++ b/crates/uv-distribution-types/src/index.rs @@ -166,7 +166,7 @@ impl Index { // Otherwise try to read the credentials from keyring. if let Some(credentials) = - Credentials::from_keyring(name.to_string(), self.url.url(), keyring_provider) + Credentials::from_keyring(name, self.url.url(), keyring_provider) { return Some(credentials); } diff --git a/crates/uv/src/commands/project/add.rs b/crates/uv/src/commands/project/add.rs index 9e6ca27b57a0..fb195a835f25 100644 --- a/crates/uv/src/commands/project/add.rs +++ b/crates/uv/src/commands/project/add.rs @@ -269,7 +269,7 @@ pub(crate) async fn add( } // Initialize the registry client. - let client = RegistryClientBuilder::try_from(client_builder)? + let client = RegistryClientBuilder::try_from(client_builder.clone())? .index_urls(settings.index_locations.index_urls()) .index_strategy(settings.index_strategy) .markers(target.interpreter().markers()) @@ -330,7 +330,7 @@ pub(crate) async fn add( } // Initialize the registry client. - let client = RegistryClientBuilder::try_from(client_builder)? + let client = RegistryClientBuilder::try_from(client_builder.clone())? .index_urls(settings.index_locations.index_urls()) .index_strategy(settings.index_strategy) .markers(target.interpreter().markers()) From 1d6f656b12247d66584df4907465c5dc55c330fe Mon Sep 17 00:00:00 2001 From: Simon Steinheber Date: Mon, 30 Dec 2024 14:03:32 +0100 Subject: [PATCH 10/28] Format code --- crates/uv-auth/src/credentials.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/uv-auth/src/credentials.rs b/crates/uv-auth/src/credentials.rs index 96f7b8c95244..422798d25726 100644 --- a/crates/uv-auth/src/credentials.rs +++ b/crates/uv-auth/src/credentials.rs @@ -186,7 +186,9 @@ impl Credentials { } }; - let index = if let Some(i) = auth_config.find_entry(&name) { i } else { + let index = if let Some(i) = auth_config.find_entry(&name) { + i + } else { warn!("Could not find entry for {name}"); return None; }; From e8df313ff55151eb5bd33c231bcc260e85c86a9e Mon Sep 17 00:00:00 2001 From: Simon Steinheber Date: Mon, 30 Dec 2024 14:07:46 +0100 Subject: [PATCH 11/28] Fix reference errors --- crates/uv-auth/src/credentials.rs | 2 +- crates/uv-auth/src/keyring.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/uv-auth/src/credentials.rs b/crates/uv-auth/src/credentials.rs index 422798d25726..3a6604e37aed 100644 --- a/crates/uv-auth/src/credentials.rs +++ b/crates/uv-auth/src/credentials.rs @@ -186,7 +186,7 @@ impl Credentials { } }; - let index = if let Some(i) = auth_config.find_entry(&name) { + let index = if let Some(i) = auth_config.find_entry(name) { i } else { warn!("Could not find entry for {name}"); diff --git a/crates/uv-auth/src/keyring.rs b/crates/uv-auth/src/keyring.rs index d6ba37ec3b8e..553c4eecb551 100644 --- a/crates/uv-auth/src/keyring.rs +++ b/crates/uv-auth/src/keyring.rs @@ -138,7 +138,7 @@ impl KeyringProvider { match &mut self.backend { KeyringProviderBackend::Subprocess => { - self.set_subprocess(&host.to_string(), &username, &password) + self.set_subprocess(&host.to_string(), username, password) .await } #[cfg(test)] From 8782357eff898d68a978a2426a9ead274e277f42 Mon Sep 17 00:00:00 2001 From: Simon Steinheber Date: Sun, 12 Jan 2025 17:07:53 +0100 Subject: [PATCH 12/28] Use tokio instead of `std::fs` --- crates/uv-auth/src/keyring_config.rs | 62 +++++++++++++++------------- 1 file changed, 34 insertions(+), 28 deletions(-) diff --git a/crates/uv-auth/src/keyring_config.rs b/crates/uv-auth/src/keyring_config.rs index 744ca467ee79..38cda00c392b 100644 --- a/crates/uv-auth/src/keyring_config.rs +++ b/crates/uv-auth/src/keyring_config.rs @@ -1,9 +1,10 @@ use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use std::fs::{self, File}; -use std::io::{self, Write}; +use std::io; use std::path::PathBuf; use thiserror::Error; +use tokio; +use tokio::io::AsyncWriteExt; #[derive(Error, Debug)] pub enum ConfigError { @@ -26,11 +27,11 @@ static CONFIG_PATH: std::sync::Mutex> = std::sync::Mutex::new(No pub trait ConfigFile { fn path() -> Result; - fn load() -> Result + async fn load() -> Result where Self: Sized; - fn store(&self) -> Result<(), ConfigError>; + async fn store(&self) -> Result<(), ConfigError>; } impl ConfigFile for AuthConfig { @@ -44,18 +45,18 @@ impl ConfigFile for AuthConfig { } } - let cache_dir = uv_dirs::user_cache_dir().ok_or(ConfigError::InvalidPath)?; + let cache_dir = uv_dirs::user_state_dir().ok_or(ConfigError::InvalidPath)?; Ok(cache_dir.join("auth.toml")) } - fn load() -> Result { + async fn load() -> Result { let path = AuthConfig::path()?; - AuthConfig::load_from_path(&path) + AuthConfig::load_from_path(&path).await } - fn store(&self) -> Result<(), ConfigError> { + async fn store(&self) -> Result<(), ConfigError> { let path = AuthConfig::path()?; - self.store_to_path(&path) + self.store_to_path(&path).await } } @@ -78,22 +79,26 @@ impl AuthConfig { self.indexes.get(index_name) } - pub fn load_from_path(path: &PathBuf) -> Result { + pub fn delete_entry(&mut self, index_name: &str) { + self.indexes.remove(index_name); + } + + pub async fn load_from_path(path: &PathBuf) -> Result { if !path.exists() { return Ok(AuthConfig { indexes: HashMap::new(), }); } - let contents = fs::read_to_string(path)?; + let contents = tokio::fs::read_to_string(path).await?; let config: AuthConfig = toml::de::from_str(&contents)?; Ok(config) } - pub fn store_to_path(&self, path: &PathBuf) -> Result<(), ConfigError> { + pub async fn store_to_path(&self, path: &PathBuf) -> Result<(), ConfigError> { let contents = toml::to_string_pretty(self)?; - let mut file = File::create(path)?; - file.write_all(contents.as_bytes())?; + let mut file = tokio::fs::File::create(path).await?; + file.write_all(contents.as_bytes()).await?; Ok(()) } } @@ -113,55 +118,56 @@ pub(crate) fn reset_config_path() { #[cfg(test)] mod tests { use super::*; - use std::fs; use std::path::Path; // Helper function to clean up a temporary file - fn remove_temp_file(path: &Path) -> io::Result<()> { + async fn remove_temp_file(path: &Path) -> io::Result<()> { if path.exists() { - fs::remove_file(path)?; + tokio::fs::remove_file(path).await?; } Ok(()) } - #[test] - fn test_load_no_config_file() { + #[tokio::test] + async fn test_load_no_config_file() { // Step 1: Create a temporary file using tempfile // let temp_file = NamedTempFile::new().expect("Failed to create a temp file"); let path = Path::new("test_auth.toml"); - remove_temp_file(path).ok(); + remove_temp_file(path).await.ok(); - let config = AuthConfig::load_from_path(&path.to_path_buf()); + let config = AuthConfig::load_from_path(&path.to_path_buf()).await; assert!(config.is_ok()); let config = config.unwrap(); assert_eq!(config.indexes.len(), 0); } - #[test] - fn test_store_no_config_file() { + #[tokio::test] + async fn test_store_no_config_file() { // Prepare a fake file path for the test let path = Path::new("test_auth.toml"); - remove_temp_file(path).ok(); + remove_temp_file(path).await.ok(); - let config = AuthConfig::load_from_path(&path.to_path_buf()); + let config = AuthConfig::load_from_path(&path.to_path_buf()).await; assert!(config.is_ok()); let mut config = config.unwrap(); config.add_entry("index1".to_string(), "user1".to_string()); - let result = config.store_to_path(&path.to_path_buf()); + let result = config.store_to_path(&path.to_path_buf()).await; assert!(result.is_ok()); // Check if the file exists and contains the correct content assert!(path.exists()); - let contents = fs::read_to_string(path).expect("Failed to read config file"); + let contents = tokio::fs::read_to_string(path) + .await + .expect("Failed to read config file"); assert!(contents.contains("index1")); assert!(contents.contains("user1")); // Clean up - remove_temp_file(path).ok(); + remove_temp_file(path).await.ok(); } #[test] From c168bb9a16330b5c1bc8383132b74f00a8c266e0 Mon Sep 17 00:00:00 2001 From: Simon Steinheber Date: Sun, 12 Jan 2025 17:08:07 +0100 Subject: [PATCH 13/28] Add `list` and `unset` commands for index credentials --- crates/uv-auth/src/credentials.rs | 16 +++--- crates/uv-auth/src/keyring.rs | 72 +++++++++++++++++++++++++ crates/uv-cli/src/lib.rs | 48 ++++++++++++++++- crates/uv/src/commands/index.rs | 83 ++++++++++++++++++++++++++-- crates/uv/src/commands/mod.rs | 2 +- crates/uv/src/lib.rs | 35 ++++++++++-- crates/uv/src/settings.rs | 89 ++++++++++++++++++++++++++++--- 7 files changed, 319 insertions(+), 26 deletions(-) diff --git a/crates/uv-auth/src/credentials.rs b/crates/uv-auth/src/credentials.rs index 3a6604e37aed..c002c397a6b7 100644 --- a/crates/uv-auth/src/credentials.rs +++ b/crates/uv-auth/src/credentials.rs @@ -178,7 +178,9 @@ impl Credentials { return None; } - let auth_config = match AuthConfig::load() { + let auth_config = executor::block_on(AuthConfig::load()); + + let auth_config = match auth_config { Ok(auth_config) => auth_config, Err(e) => { error!("Error loading auth config: {e}"); @@ -186,9 +188,7 @@ impl Credentials { } }; - let index = if let Some(i) = auth_config.find_entry(name) { - i - } else { + let Some(index) = auth_config.find_entry(name) else { warn!("Could not find entry for {name}"); return None; }; @@ -399,8 +399,8 @@ mod tests { assert_eq!(Credentials::from_header_value(&header), Some(credentials)); } - #[test] - fn from_keyring() { + #[tokio::test] + async fn from_keyring() { let username = "user"; let password = "password"; let index = "test_index"; @@ -412,9 +412,9 @@ mod tests { set_test_config_path(auth_config_path); - let mut auth_config = AuthConfig::load().unwrap(); + let mut auth_config = AuthConfig::load().await.unwrap(); auth_config.add_entry(index.to_string(), username.to_string()); - auth_config.store().unwrap(); + auth_config.store().await.unwrap(); // Act let credentials = Credentials::from_keyring(index, &url, Some(keyring)); diff --git a/crates/uv-auth/src/keyring.rs b/crates/uv-auth/src/keyring.rs index 553c4eecb551..70a68a04d308 100644 --- a/crates/uv-auth/src/keyring.rs +++ b/crates/uv-auth/src/keyring.rs @@ -201,6 +201,68 @@ impl KeyringProvider { None } + /// Set credentials for the given [`Url`] from the keyring. + #[instrument(skip_all, fields(url = % url.to_string(), username))] + pub async fn unset(&mut self, url: &Url, username: &str) { + debug_assert!( + url.host_str().is_some(), + "Should only use keyring for urls with host" + ); + debug_assert!( + url.password().is_none(), + "Should only use keyring for urls without a password" + ); + debug_assert!( + !username.is_empty(), + "Should only use keyring with a username" + ); + + let host = url.host().expect("Url should contain a host!"); + trace!( + "Deleting entry in keyring for host {host} (from url {url}) and username {username}" + ); + + match &mut self.backend { + KeyringProviderBackend::Subprocess => { + self.unset_subprocess(&host.to_string(), username).await + } + #[cfg(test)] + KeyringProviderBackend::Dummy(ref mut store) => { + let username_static: &'static str = Box::leak(username.to_owned().into_boxed_str()); + + Self::unset_dummy(store, &host.to_string(), username_static) + } + }; + } + + #[instrument(skip(self))] + async fn unset_subprocess(&self, service_name: &str, username: &str) -> Option<()> { + let child = Command::new("keyring") + .arg("del") + .arg(service_name) + .arg(username) + .stdin(Stdio::piped()) // Allow writing to stdin + .stdout(Stdio::piped()) // Optionally capture stdout for debugging + .stderr(Stdio::piped()) // Capture stderr for debugging + .spawn() + .inspect_err(|err| warn!("Failure running `keyring` command: {err}")) + .ok()?; + + let output = child + .wait_with_output() + .await + .inspect_err(|err| warn!("Failed to wait for `keyring` output: {err}")) + .ok()?; + + if output.status.success() { + debug!("Keyring entry successfully removed"); + } else { + debug!("Could not remove entry in keyring"); + }; + + None + } + #[cfg(test)] fn fetch_dummy( store: &std::collections::HashMap<(String, &'static str), &'static str>, @@ -223,6 +285,16 @@ impl KeyringProvider { None } + #[cfg(test)] + fn unset_dummy( + store: &mut std::collections::HashMap<(String, &'static str), &'static str>, + service_name: &str, + username: &'static str, + ) -> Option<()> { + store.remove(&(service_name.to_string(), username)); + None + } + /// Create a new provider with [`KeyringProviderBackend::Dummy`]. #[cfg(test)] pub fn dummy, T: IntoIterator>( diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index f346af8114d9..2de0546be85c 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -5414,7 +5414,19 @@ pub enum IndexCredentialsCommand { after_help = "Use `uv help index credentials add` for more details.", after_long_help = "" )] - Add(IndexCredentialsArgs), + Add(IndexAddCredentialsArgs), + + #[command( + after_help = "Use `uv help index credentials list` for more details.", + after_long_help = "" + )] + List(IndexListCredentialsArgs), + + #[command( + after_help = "Use `uv help index credentials unset` for more details.", + after_long_help = "" + )] + Unset(IndexUnsetCredentialsArgs), } #[derive(Args)] @@ -5425,7 +5437,7 @@ pub struct IndexSourceArgs { } #[derive(Args)] -pub struct IndexCredentialsArgs { +pub struct IndexAddCredentialsArgs { /// The name of the index #[arg(long)] pub name: String, @@ -5447,3 +5459,35 @@ pub struct IndexCredentialsArgs { #[arg(long, value_enum, env = EnvVars::UV_KEYRING_PROVIDER)] pub keyring_provider: Option, } + +#[derive(Args)] +pub struct IndexListCredentialsArgs { + /// Attempt to use `keyring` for authentication for remote requirements files. + /// + /// At present, only `--keyring-provider subprocess` is supported, which configures uv to + /// use the `keyring` CLI to handle authentication. + /// + /// Defaults to `disabled`. + #[arg(long, value_enum, env = EnvVars::UV_KEYRING_PROVIDER)] + pub keyring_provider: Option, +} + +#[derive(Args)] +pub struct IndexUnsetCredentialsArgs { + /// The name of the index + #[arg(long)] + pub name: String, + + /// The username that should be used for the index + #[arg(long, required(false))] + pub username: Option, + + /// Attempt to use `keyring` for authentication for remote requirements files. + /// + /// At present, only `--keyring-provider subprocess` is supported, which configures uv to + /// use the `keyring` CLI to handle authentication. + /// + /// Defaults to `disabled`. + #[arg(long, value_enum, env = EnvVars::UV_KEYRING_PROVIDER)] + pub keyring_provider: Option, +} diff --git a/crates/uv/src/commands/index.rs b/crates/uv/src/commands/index.rs index 193e468d000a..dadcb621281c 100644 --- a/crates/uv/src/commands/index.rs +++ b/crates/uv/src/commands/index.rs @@ -1,4 +1,4 @@ -use anyhow::{Context, Result}; +use anyhow::{Context, Ok, Result}; use console::Term; use tracing::{debug, warn}; use uv_auth::{AuthConfig, ConfigFile}; @@ -54,10 +54,85 @@ pub(crate) async fn add_credentials( "Will add index {name} and user {username} to index auth config in {:?}", AuthConfig::path()? ); - let mut auth_config = - AuthConfig::load().inspect_err(|err| warn!("Could not load auth config due to: {err}"))?; + let mut auth_config = AuthConfig::load() + .await + .inspect_err(|err| warn!("Could not load auth config due to: {err}"))?; auth_config.add_entry(name, username); - auth_config.store()?; + auth_config.store().await?; + + Ok(()) +} + +pub(crate) async fn list_credentials( + keyring_provider_type: KeyringProviderType, + indexes: Vec, +) -> Result<()> { + let auth_config = AuthConfig::load() + .await + .inspect_err(|err| warn!("Could not load auth config due to: {err}"))?; + + let keyring_provider = keyring_provider_type + .to_provider() + .expect("Keyring Provider is not available"); + + for index in indexes { + if let Some(index_name) = index.name { + if let Some(auth_index) = auth_config.indexes.get(&index_name.to_string()) { + let username = auth_index.username.clone(); + let password = keyring_provider.fetch(&index.url, &username).await; + + match password { + Some(_) => println!( + "Index: '{}' authenticates with username '{}'.", + index_name, username + ), + None => println!("Index: '{}' has no credentials.", index_name), + } + } + } + } + + Ok(()) +} + +pub(crate) async fn unset_credentials( + name: String, + username: Option, + keyring_provider: KeyringProviderType, + indexes: Vec, +) -> Result<()> { + let index = indexes.iter().find(|idx| { + idx.name + .as_ref() + .map(|n| n.to_string() == name) + .unwrap_or(false) + }); + + let index = match index { + Some(obj) => obj, + None => panic!("No index found with the name '{}'", name), + }; + + let username = match username { + Some(n) => n, + None => match prompt_username_input()? { + Some(n) => n, + None => panic!("No username provided and could not read username from input."), + }, + }; + + keyring_provider + .to_provider() + .expect("Keyring Provider is not available") + .unset(&index.url, &username) + .await; + + let mut auth_config = AuthConfig::load() + .await + .inspect_err(|err| warn!("Could not load auth config due to: {err}"))?; + + auth_config.delete_entry(&name); + auth_config.store().await?; Ok(()) } diff --git a/crates/uv/src/commands/mod.rs b/crates/uv/src/commands/mod.rs index 0ea316c0ba6b..f5d26d56f2bf 100644 --- a/crates/uv/src/commands/mod.rs +++ b/crates/uv/src/commands/mod.rs @@ -12,7 +12,7 @@ pub(crate) use cache_clean::cache_clean; pub(crate) use cache_dir::cache_dir; pub(crate) use cache_prune::cache_prune; pub(crate) use help::help; -pub(crate) use index::add_credentials; +pub(crate) use index::{add_credentials, list_credentials, unset_credentials}; pub(crate) use pip::check::pip_check; pub(crate) use pip::compile::pip_compile; pub(crate) use pip::freeze::pip_freeze; diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index 2af3ba7ce1d1..4f06914eaf3a 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -10,9 +10,12 @@ use anstream::eprintln; use anyhow::{bail, Context, Result}; use clap::error::{ContextKind, ContextValue}; use clap::{CommandFactory, Parser}; -use commands::add_credentials; +use commands::{add_credentials, list_credentials, unset_credentials}; use owo_colors::OwoColorize; -use settings::{IndexSettings, PipTreeSettings}; +use settings::{ + IndexAddCredentialsSettings, IndexListCredentialsSettings, IndexUnsetCredentialsSettings, + PipTreeSettings, +}; use tokio::task::spawn_blocking; use tracing::{debug, instrument}; use uv_cache::{Cache, Refresh}; @@ -1338,17 +1341,41 @@ async fn run(mut cli: Cli) -> Result { Commands::Index(IndexNamespace { command: IndexCommand::Credentials(IndexCredentialsCommand::Add(args)), }) => { - let IndexSettings { + let IndexAddCredentialsSettings { name, username, password, keyring_provider, index, - } = IndexSettings::resolve(args, filesystem); + } = IndexAddCredentialsSettings::resolve(args, filesystem); let _ = add_credentials(name, username, password, keyring_provider, index).await; return Ok(ExitStatus::Success); } + Commands::Index(IndexNamespace { + command: IndexCommand::Credentials(IndexCredentialsCommand::List(args)), + }) => { + let IndexListCredentialsSettings { + keyring_provider, + index, + } = IndexListCredentialsSettings::resolve(args, filesystem); + + let _ = list_credentials(keyring_provider, index).await; + return Ok(ExitStatus::Success); + } + Commands::Index(IndexNamespace { + command: IndexCommand::Credentials(IndexCredentialsCommand::Unset(args)), + }) => { + let IndexUnsetCredentialsSettings { + name, + username, + keyring_provider, + index, + } = IndexUnsetCredentialsSettings::resolve(args, filesystem); + + let _ = unset_credentials(name, username, keyring_provider, index).await; + return Ok(ExitStatus::Success); + } }; result } diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index 1007bb71913e..e25da1800038 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -13,10 +13,11 @@ use uv_cli::{ ToolUpgradeArgs, }; use uv_cli::{ - AddArgs, ColorChoice, ExternalCommand, GlobalArgs, IndexCredentialsArgs, InitArgs, ListFormat, - LockArgs, Maybe, PipCheckArgs, PipCompileArgs, PipFreezeArgs, PipInstallArgs, PipListArgs, - PipShowArgs, PipSyncArgs, PipTreeArgs, PipUninstallArgs, PythonFindArgs, PythonInstallArgs, - PythonListArgs, PythonPinArgs, PythonUninstallArgs, RemoveArgs, RunArgs, SyncArgs, ToolDirArgs, + AddArgs, ColorChoice, ExternalCommand, GlobalArgs, IndexAddCredentialsArgs, + IndexListCredentialsArgs, IndexUnsetCredentialsArgs, InitArgs, ListFormat, LockArgs, Maybe, + PipCheckArgs, PipCompileArgs, PipFreezeArgs, PipInstallArgs, PipListArgs, PipShowArgs, + PipSyncArgs, PipTreeArgs, PipUninstallArgs, PythonFindArgs, PythonInstallArgs, PythonListArgs, + PythonPinArgs, PythonUninstallArgs, RemoveArgs, RunArgs, SyncArgs, ToolDirArgs, ToolInstallArgs, ToolListArgs, ToolRunArgs, ToolUninstallArgs, TreeArgs, VenvArgs, }; use uv_client::Connectivity; @@ -2959,7 +2960,7 @@ impl PublishSettings { } } -pub(crate) struct IndexSettings { +pub(crate) struct IndexAddCredentialsSettings { // CLI only settings pub(crate) name: String, pub(crate) username: Option, @@ -2972,10 +2973,10 @@ pub(crate) struct IndexSettings { pub(crate) index: Vec, } -impl IndexSettings { +impl IndexAddCredentialsSettings { /// Resolve the [`IndexSettings`] from the CLI and filesystem configuration. pub(crate) fn resolve( - args: IndexCredentialsArgs, + args: IndexAddCredentialsArgs, filesystem: Option, ) -> Self { let Options { top_level, .. } = filesystem @@ -3001,6 +3002,80 @@ impl IndexSettings { } } +pub(crate) struct IndexListCredentialsSettings { + // CLI and Filesystem settings + pub(crate) keyring_provider: KeyringProviderType, + + // Filesystem only settings + pub(crate) index: Vec, +} + +impl IndexListCredentialsSettings { + /// Resolve the [`IndexSettings`] from the CLI and filesystem configuration. + pub(crate) fn resolve( + args: IndexListCredentialsArgs, + filesystem: Option, + ) -> Self { + let Options { top_level, .. } = filesystem + .map(FilesystemOptions::into_options) + .unwrap_or_default(); + + let ResolverInstallerOptions { + keyring_provider, + index, + .. + } = top_level; + + Self { + keyring_provider: args + .keyring_provider + .combine(keyring_provider) + .unwrap_or_default(), + index: index.unwrap_or_default(), + } + } +} + +pub(crate) struct IndexUnsetCredentialsSettings { + // CLI only settings + pub(crate) name: String, + pub(crate) username: Option, + + // CLI and Filesystem settings + pub(crate) keyring_provider: KeyringProviderType, + + // Filesystem only settings + pub(crate) index: Vec, +} + +impl IndexUnsetCredentialsSettings { + /// Resolve the [`IndexSettings`] from the CLI and filesystem configuration. + pub(crate) fn resolve( + args: IndexUnsetCredentialsArgs, + filesystem: Option, + ) -> Self { + let Options { top_level, .. } = filesystem + .map(FilesystemOptions::into_options) + .unwrap_or_default(); + + let ResolverInstallerOptions { + keyring_provider, + index, + .. + } = top_level; + + Self { + name: args.name, + username: args.username, + keyring_provider: args + .keyring_provider + .combine(keyring_provider) + .unwrap_or_default(), + index: index.unwrap_or_default(), + } + } +} + // Environment variables that are not exposed as CLI arguments. mod env { use uv_static::EnvVars; From ec5e0fdf6c87b0531ab214a4e1427ebd3a1bc67b Mon Sep 17 00:00:00 2001 From: Simon Steinheber Date: Sun, 12 Jan 2025 19:23:34 +0100 Subject: [PATCH 14/28] Use `fs-err` instead of `tokio` --- Cargo.lock | 1 + crates/uv-auth/Cargo.toml | 1 + crates/uv-auth/src/credentials.rs | 14 ++++---- crates/uv-auth/src/keyring_config.rs | 54 ++++++++++++++-------------- crates/uv/src/commands/index.rs | 7 ++-- 5 files changed, 36 insertions(+), 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1424020e9fc5..6c4dffcb2598 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4527,6 +4527,7 @@ dependencies = [ "anyhow", "async-trait", "base64 0.22.1", + "fs-err 3.0.0", "futures", "http", "insta", diff --git a/crates/uv-auth/Cargo.toml b/crates/uv-auth/Cargo.toml index fcf508574873..02d76dd9cc94 100644 --- a/crates/uv-auth/Cargo.toml +++ b/crates/uv-auth/Cargo.toml @@ -31,6 +31,7 @@ uv-dirs = { workspace = true } toml.workspace = true serde.workspace = true thiserror.workspace = true +fs-err.workspace = true [dev-dependencies] tempfile.workspace = true diff --git a/crates/uv-auth/src/credentials.rs b/crates/uv-auth/src/credentials.rs index c002c397a6b7..a2ae9c309fcc 100644 --- a/crates/uv-auth/src/credentials.rs +++ b/crates/uv-auth/src/credentials.rs @@ -177,10 +177,8 @@ impl Credentials { trace!("No keyring provider available"); return None; } - - let auth_config = executor::block_on(AuthConfig::load()); - - let auth_config = match auth_config { + + let auth_config = match AuthConfig::load() { Ok(auth_config) => auth_config, Err(e) => { error!("Error loading auth config: {e}"); @@ -399,8 +397,8 @@ mod tests { assert_eq!(Credentials::from_header_value(&header), Some(credentials)); } - #[tokio::test] - async fn from_keyring() { + #[test] + fn from_keyring() { let username = "user"; let password = "password"; let index = "test_index"; @@ -412,9 +410,9 @@ mod tests { set_test_config_path(auth_config_path); - let mut auth_config = AuthConfig::load().await.unwrap(); + let mut auth_config = AuthConfig::load().unwrap(); auth_config.add_entry(index.to_string(), username.to_string()); - auth_config.store().await.unwrap(); + auth_config.store().unwrap(); // Act let credentials = Credentials::from_keyring(index, &url, Some(keyring)); diff --git a/crates/uv-auth/src/keyring_config.rs b/crates/uv-auth/src/keyring_config.rs index 38cda00c392b..a486a7beb104 100644 --- a/crates/uv-auth/src/keyring_config.rs +++ b/crates/uv-auth/src/keyring_config.rs @@ -1,10 +1,9 @@ use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use std::io; +use std::io::{self, Write}; use std::path::PathBuf; use thiserror::Error; -use tokio; -use tokio::io::AsyncWriteExt; +use fs_err as fs; #[derive(Error, Debug)] pub enum ConfigError { @@ -27,11 +26,11 @@ static CONFIG_PATH: std::sync::Mutex> = std::sync::Mutex::new(No pub trait ConfigFile { fn path() -> Result; - async fn load() -> Result + fn load() -> Result where Self: Sized; - async fn store(&self) -> Result<(), ConfigError>; + fn store(&self) -> Result<(), ConfigError>; } impl ConfigFile for AuthConfig { @@ -49,14 +48,14 @@ impl ConfigFile for AuthConfig { Ok(cache_dir.join("auth.toml")) } - async fn load() -> Result { + fn load() -> Result { let path = AuthConfig::path()?; - AuthConfig::load_from_path(&path).await + AuthConfig::load_from_path(&path) } - async fn store(&self) -> Result<(), ConfigError> { + fn store(&self) -> Result<(), ConfigError> { let path = AuthConfig::path()?; - self.store_to_path(&path).await + self.store_to_path(&path) } } @@ -83,22 +82,22 @@ impl AuthConfig { self.indexes.remove(index_name); } - pub async fn load_from_path(path: &PathBuf) -> Result { + pub fn load_from_path(path: &PathBuf) -> Result { if !path.exists() { return Ok(AuthConfig { indexes: HashMap::new(), }); } - let contents = tokio::fs::read_to_string(path).await?; + let contents = fs::read_to_string(path)?; let config: AuthConfig = toml::de::from_str(&contents)?; Ok(config) } - pub async fn store_to_path(&self, path: &PathBuf) -> Result<(), ConfigError> { + pub fn store_to_path(&self, path: &PathBuf) -> Result<(), ConfigError> { let contents = toml::to_string_pretty(self)?; - let mut file = tokio::fs::File::create(path).await?; - file.write_all(contents.as_bytes()).await?; + let mut file = fs::File::create(path)?; + file.write_all(contents.as_bytes())?; Ok(()) } } @@ -121,53 +120,52 @@ mod tests { use std::path::Path; // Helper function to clean up a temporary file - async fn remove_temp_file(path: &Path) -> io::Result<()> { + fn remove_temp_file(path: &Path) -> io::Result<()> { if path.exists() { - tokio::fs::remove_file(path).await?; + fs::remove_file(path)?; } Ok(()) } - #[tokio::test] - async fn test_load_no_config_file() { + #[test] + fn test_load_no_config_file() { // Step 1: Create a temporary file using tempfile // let temp_file = NamedTempFile::new().expect("Failed to create a temp file"); let path = Path::new("test_auth.toml"); - remove_temp_file(path).await.ok(); + remove_temp_file(path).ok(); - let config = AuthConfig::load_from_path(&path.to_path_buf()).await; + let config = AuthConfig::load_from_path(&path.to_path_buf()); assert!(config.is_ok()); let config = config.unwrap(); assert_eq!(config.indexes.len(), 0); } - #[tokio::test] - async fn test_store_no_config_file() { + #[test] + fn test_store_no_config_file() { // Prepare a fake file path for the test let path = Path::new("test_auth.toml"); - remove_temp_file(path).await.ok(); + remove_temp_file(path).ok(); - let config = AuthConfig::load_from_path(&path.to_path_buf()).await; + let config = AuthConfig::load_from_path(&path.to_path_buf()); assert!(config.is_ok()); let mut config = config.unwrap(); config.add_entry("index1".to_string(), "user1".to_string()); - let result = config.store_to_path(&path.to_path_buf()).await; + let result = config.store_to_path(&path.to_path_buf()); assert!(result.is_ok()); // Check if the file exists and contains the correct content assert!(path.exists()); - let contents = tokio::fs::read_to_string(path) - .await + let contents = fs::read_to_string(path) .expect("Failed to read config file"); assert!(contents.contains("index1")); assert!(contents.contains("user1")); // Clean up - remove_temp_file(path).await.ok(); + remove_temp_file(path).ok(); } #[test] diff --git a/crates/uv/src/commands/index.rs b/crates/uv/src/commands/index.rs index dadcb621281c..42f0d8fd19f1 100644 --- a/crates/uv/src/commands/index.rs +++ b/crates/uv/src/commands/index.rs @@ -55,10 +55,9 @@ pub(crate) async fn add_credentials( AuthConfig::path()? ); let mut auth_config = AuthConfig::load() - .await .inspect_err(|err| warn!("Could not load auth config due to: {err}"))?; auth_config.add_entry(name, username); - auth_config.store().await?; + auth_config.store()?; Ok(()) } @@ -68,7 +67,6 @@ pub(crate) async fn list_credentials( indexes: Vec, ) -> Result<()> { let auth_config = AuthConfig::load() - .await .inspect_err(|err| warn!("Could not load auth config due to: {err}"))?; let keyring_provider = keyring_provider_type @@ -128,11 +126,10 @@ pub(crate) async fn unset_credentials( .await; let mut auth_config = AuthConfig::load() - .await .inspect_err(|err| warn!("Could not load auth config due to: {err}"))?; auth_config.delete_entry(&name); - auth_config.store().await?; + auth_config.store()?; Ok(()) } From d5bdb2bf12bb360e573cb3121ac9c272a2eee458 Mon Sep 17 00:00:00 2001 From: Simon Steinheber Date: Mon, 13 Jan 2025 08:52:43 +0100 Subject: [PATCH 15/28] Format changed --- Cargo.lock | 2 +- crates/uv-auth/src/credentials.rs | 2 +- crates/uv-auth/src/keyring_config.rs | 5 ++--- crates/uv/src/commands/index.rs | 12 ++++++------ 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 62c4e6c20591..090f0776185d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4595,7 +4595,7 @@ dependencies = [ "serde", "tempfile", "test-log", - "thiserror 2.0.9", + "thiserror 2.0.11", "tokio", "toml", "tracing", diff --git a/crates/uv-auth/src/credentials.rs b/crates/uv-auth/src/credentials.rs index a2ae9c309fcc..e2874d756bfd 100644 --- a/crates/uv-auth/src/credentials.rs +++ b/crates/uv-auth/src/credentials.rs @@ -177,7 +177,7 @@ impl Credentials { trace!("No keyring provider available"); return None; } - + let auth_config = match AuthConfig::load() { Ok(auth_config) => auth_config, Err(e) => { diff --git a/crates/uv-auth/src/keyring_config.rs b/crates/uv-auth/src/keyring_config.rs index a486a7beb104..4ad53301e8b7 100644 --- a/crates/uv-auth/src/keyring_config.rs +++ b/crates/uv-auth/src/keyring_config.rs @@ -1,9 +1,9 @@ +use fs_err as fs; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::io::{self, Write}; use std::path::PathBuf; use thiserror::Error; -use fs_err as fs; #[derive(Error, Debug)] pub enum ConfigError { @@ -159,8 +159,7 @@ mod tests { // Check if the file exists and contains the correct content assert!(path.exists()); - let contents = fs::read_to_string(path) - .expect("Failed to read config file"); + let contents = fs::read_to_string(path).expect("Failed to read config file"); assert!(contents.contains("index1")); assert!(contents.contains("user1")); diff --git a/crates/uv/src/commands/index.rs b/crates/uv/src/commands/index.rs index 42f0d8fd19f1..f6166fb46484 100644 --- a/crates/uv/src/commands/index.rs +++ b/crates/uv/src/commands/index.rs @@ -54,8 +54,8 @@ pub(crate) async fn add_credentials( "Will add index {name} and user {username} to index auth config in {:?}", AuthConfig::path()? ); - let mut auth_config = AuthConfig::load() - .inspect_err(|err| warn!("Could not load auth config due to: {err}"))?; + let mut auth_config = + AuthConfig::load().inspect_err(|err| warn!("Could not load auth config due to: {err}"))?; auth_config.add_entry(name, username); auth_config.store()?; @@ -66,8 +66,8 @@ pub(crate) async fn list_credentials( keyring_provider_type: KeyringProviderType, indexes: Vec, ) -> Result<()> { - let auth_config = AuthConfig::load() - .inspect_err(|err| warn!("Could not load auth config due to: {err}"))?; + let auth_config = + AuthConfig::load().inspect_err(|err| warn!("Could not load auth config due to: {err}"))?; let keyring_provider = keyring_provider_type .to_provider() @@ -125,8 +125,8 @@ pub(crate) async fn unset_credentials( .unset(&index.url, &username) .await; - let mut auth_config = AuthConfig::load() - .inspect_err(|err| warn!("Could not load auth config due to: {err}"))?; + let mut auth_config = + AuthConfig::load().inspect_err(|err| warn!("Could not load auth config due to: {err}"))?; auth_config.delete_entry(&name); auth_config.store()?; From 6e649a4c9fff44498fed8c7276139a61ed3f7012 Mon Sep 17 00:00:00 2001 From: Simon Steinheber Date: Mon, 13 Jan 2025 08:57:22 +0100 Subject: [PATCH 16/28] Use `set` / `unset` commands for naming --- crates/uv-cli/src/lib.rs | 10 +++++----- crates/uv/src/commands/index.rs | 2 +- crates/uv/src/commands/mod.rs | 2 +- crates/uv/src/lib.rs | 17 ++++++++++------- crates/uv/src/settings.rs | 6 +++--- 5 files changed, 20 insertions(+), 17 deletions(-) diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index f6d6281bcee8..151f47ab19d2 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -5423,7 +5423,7 @@ pub enum IndexCommand { after_help = "Use `uv help index add` for more details.", after_long_help = "" )] - Add(IndexSourceArgs), + Set(IndexSourceArgs), /// Sync an environment with a `requirements.txt` file. #[command( after_help = "Use `uv help index list` for more details.", @@ -5435,7 +5435,7 @@ pub enum IndexCommand { after_help = "Use `uv help index delete` for more details.", after_long_help = "" )] - Delete(IndexSourceArgs), + Unset(IndexSourceArgs), #[command(subcommand)] Credentials(IndexCredentialsCommand), @@ -5444,10 +5444,10 @@ pub enum IndexCommand { #[derive(Subcommand)] pub enum IndexCredentialsCommand { #[command( - after_help = "Use `uv help index credentials add` for more details.", + after_help = "Use `uv help index credentials set` for more details.", after_long_help = "" )] - Add(IndexAddCredentialsArgs), + Set(IndexSetCredentialsArgs), #[command( after_help = "Use `uv help index credentials list` for more details.", @@ -5470,7 +5470,7 @@ pub struct IndexSourceArgs { } #[derive(Args)] -pub struct IndexAddCredentialsArgs { +pub struct IndexSetCredentialsArgs { /// The name of the index #[arg(long)] pub name: String, diff --git a/crates/uv/src/commands/index.rs b/crates/uv/src/commands/index.rs index f6166fb46484..cc394a82c701 100644 --- a/crates/uv/src/commands/index.rs +++ b/crates/uv/src/commands/index.rs @@ -7,7 +7,7 @@ use uv_distribution_types::Index; /// Add one or more packages to the project requirements. #[allow(clippy::fn_params_excessive_bools)] -pub(crate) async fn add_credentials( +pub(crate) async fn set_credentials( name: String, username: Option, password: Option, diff --git a/crates/uv/src/commands/mod.rs b/crates/uv/src/commands/mod.rs index f5d26d56f2bf..af79bd0cc1cd 100644 --- a/crates/uv/src/commands/mod.rs +++ b/crates/uv/src/commands/mod.rs @@ -12,7 +12,7 @@ pub(crate) use cache_clean::cache_clean; pub(crate) use cache_dir::cache_dir; pub(crate) use cache_prune::cache_prune; pub(crate) use help::help; -pub(crate) use index::{add_credentials, list_credentials, unset_credentials}; +pub(crate) use index::{list_credentials, set_credentials, unset_credentials}; pub(crate) use pip::check::pip_check; pub(crate) use pip::compile::pip_compile; pub(crate) use pip::freeze::pip_freeze; diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index aeabb69cde8c..a73ea01ca4a4 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -11,7 +11,7 @@ use anstream::eprintln; use anyhow::{bail, Context, Result}; use clap::error::{ContextKind, ContextValue}; use clap::{CommandFactory, Parser}; -use commands::{add_credentials, list_credentials, unset_credentials}; +use commands::{list_credentials, set_credentials, unset_credentials}; use futures::FutureExt; use owo_colors::OwoColorize; use settings::{ @@ -1351,25 +1351,28 @@ async fn run(mut cli: Cli) -> Result { .await .expect("tokio threadpool exited unexpectedly"), Commands::Index(IndexNamespace { - command: IndexCommand::Add(args), + command: IndexCommand::Set(args), }) => { - println!("Add an index with name {}", args.name); + println!("This is not implemented yet!"); + println!("Set an index with name {}", args.name); return Ok(ExitStatus::Success); } Commands::Index(IndexNamespace { command: IndexCommand::List(_), }) => { + println!("This is not implemented yet!"); println!("List all indexes"); return Ok(ExitStatus::Success); } Commands::Index(IndexNamespace { - command: IndexCommand::Delete(args), + command: IndexCommand::Unset(args), }) => { - println!("Delete index {}", args.name); + println!("This is not implemented yet!"); + println!("Unset index {}", args.name); return Ok(ExitStatus::Success); } Commands::Index(IndexNamespace { - command: IndexCommand::Credentials(IndexCredentialsCommand::Add(args)), + command: IndexCommand::Credentials(IndexCredentialsCommand::Set(args)), }) => { let IndexAddCredentialsSettings { name, @@ -1379,7 +1382,7 @@ async fn run(mut cli: Cli) -> Result { index, } = IndexAddCredentialsSettings::resolve(args, filesystem); - let _ = add_credentials(name, username, password, keyring_provider, index).await; + let _ = set_credentials(name, username, password, keyring_provider, index).await; return Ok(ExitStatus::Success); } Commands::Index(IndexNamespace { diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index a04f2e01c7d9..a372174d9498 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -14,8 +14,8 @@ use uv_cli::{ ToolUpgradeArgs, }; use uv_cli::{ - AddArgs, ColorChoice, ExternalCommand, GlobalArgs, IndexAddCredentialsArgs, - IndexListCredentialsArgs, IndexUnsetCredentialsArgs, InitArgs, ListFormat, LockArgs, Maybe, + AddArgs, ColorChoice, ExternalCommand, GlobalArgs, IndexListCredentialsArgs, + IndexSetCredentialsArgs, IndexUnsetCredentialsArgs, InitArgs, ListFormat, LockArgs, Maybe, PipCheckArgs, PipCompileArgs, PipFreezeArgs, PipInstallArgs, PipListArgs, PipShowArgs, PipSyncArgs, PipTreeArgs, PipUninstallArgs, PythonFindArgs, PythonInstallArgs, PythonListArgs, PythonPinArgs, PythonUninstallArgs, RemoveArgs, RunArgs, SyncArgs, ToolDirArgs, @@ -2995,7 +2995,7 @@ pub(crate) struct IndexAddCredentialsSettings { impl IndexAddCredentialsSettings { /// Resolve the [`IndexSettings`] from the CLI and filesystem configuration. pub(crate) fn resolve( - args: IndexAddCredentialsArgs, + args: IndexSetCredentialsArgs, filesystem: Option, ) -> Self { let Options { top_level, .. } = filesystem From aa9e1505c6fa99bb9841b7a4603586f9b305ec5d Mon Sep 17 00:00:00 2001 From: Simon Steinheber Date: Mon, 13 Jan 2025 09:10:33 +0100 Subject: [PATCH 17/28] Add explanation to each command --- crates/uv-cli/src/lib.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 151f47ab19d2..2b38ae0e7d3c 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -5418,43 +5418,47 @@ pub struct IndexNamespace { #[derive(Subcommand)] pub enum IndexCommand { - /// Compile a `requirements.in` file to a `requirements.txt` file. + /// Set a new index. This will be added to your pyproject.toml #[command( after_help = "Use `uv help index add` for more details.", after_long_help = "" )] Set(IndexSourceArgs), - /// Sync an environment with a `requirements.txt` file. + /// List all indexes set in your pyproject.toml #[command( after_help = "Use `uv help index list` for more details.", after_long_help = "" )] List(IndexSourceArgs), - /// Install packages into an environment. + /// Unset an existing index. This will be removed from your pyproject.toml #[command( after_help = "Use `uv help index delete` for more details.", after_long_help = "" )] Unset(IndexSourceArgs), + /// Manage credentials for the indexes configured in your pyproject.toml #[command(subcommand)] Credentials(IndexCredentialsCommand), } #[derive(Subcommand)] pub enum IndexCredentialsCommand { + /// Set credentials for an index #[command( after_help = "Use `uv help index credentials set` for more details.", after_long_help = "" )] Set(IndexSetCredentialsArgs), + /// List credentials for each index (Only username is shown). #[command( after_help = "Use `uv help index credentials list` for more details.", after_long_help = "" )] List(IndexListCredentialsArgs), + /// Unset the credentials for an index #[command( after_help = "Use `uv help index credentials unset` for more details.", after_long_help = "" From 8a72b8370d04cd8620ce91eabf8ed313dd816d12 Mon Sep 17 00:00:00 2001 From: Simon Steinheber Date: Fri, 31 Jan 2025 23:59:15 +0100 Subject: [PATCH 18/28] Integrate auth config into credentials from url --- crates/uv-auth/src/credentials.rs | 107 +++++++++------------- crates/uv-auth/src/keyring.rs | 10 +- crates/uv-auth/src/keyring_config.rs | 53 ++++++++--- crates/uv-distribution-types/src/index.rs | 11 +-- crates/uv/src/commands/build_frontend.rs | 2 +- crates/uv/src/commands/index.rs | 44 +++++---- crates/uv/src/commands/pip/compile.rs | 2 +- crates/uv/src/commands/pip/install.rs | 2 +- crates/uv/src/commands/pip/sync.rs | 2 +- crates/uv/src/commands/project/add.rs | 6 +- crates/uv/src/commands/project/lock.rs | 2 +- crates/uv/src/commands/project/mod.rs | 8 +- crates/uv/src/commands/project/sync.rs | 2 +- crates/uv/src/commands/venv.rs | 4 +- 14 files changed, 133 insertions(+), 122 deletions(-) diff --git a/crates/uv-auth/src/credentials.rs b/crates/uv-auth/src/credentials.rs index e2874d756bfd..c9e8330c3748 100644 --- a/crates/uv-auth/src/credentials.rs +++ b/crates/uv-auth/src/credentials.rs @@ -6,10 +6,9 @@ use netrc::Netrc; use reqwest::header::HeaderValue; use reqwest::Request; -use futures::executor; use std::io::Read; use std::io::Write; -use tracing::{debug, error, trace, warn}; +use tracing::{debug, error, warn}; use url::Url; @@ -17,7 +16,6 @@ use uv_static::EnvVars; use crate::keyring_config::AuthConfig; use crate::keyring_config::ConfigFile; -use crate::KeyringProvider; #[derive(Clone, Debug, PartialEq)] pub struct Credentials { @@ -126,7 +124,25 @@ impl Credentials { /// Returns [`None`] if both [`Url::username`] and [`Url::password`] are not populated. pub fn from_url(url: &Url) -> Option { if url.username().is_empty() && url.password().is_none() { - return None; + debug!("Trying to read username for url {url}"); + + let auth_config = match AuthConfig::load() { + Ok(auth_config) => auth_config, + Err(e) => { + error!("Error loading auth config: {e}"); + return None; + } + }; + + let Some(index) = auth_config.find_entry(url) else { + warn!("Could not find entry for {url}"); + return None; + }; + + return Some(Self { + username: Username::new(Some(index.username.clone())), + password: None, + }); } Some(Self { // Remove percent-encoding from URL credentials @@ -163,37 +179,6 @@ impl Credentials { } } - /// Extract the [`Credentials`] from keyring, given a named source. - /// - /// Look up the username stored for the named source in the user-level config. - /// Load the credentials from keyring for the service and username. - pub fn from_keyring( - name: &str, - url: &Url, - keyring_provider: Option, - ) -> Option { - debug!("Trying to read credentials for index {name} with url {url}"); - if keyring_provider.is_none() { - trace!("No keyring provider available"); - return None; - } - - let auth_config = match AuthConfig::load() { - Ok(auth_config) => auth_config, - Err(e) => { - error!("Error loading auth config: {e}"); - return None; - } - }; - - let Some(index) = auth_config.find_entry(name) else { - warn!("Could not find entry for {name}"); - return None; - }; - - executor::block_on(keyring_provider.unwrap().fetch(url, &index.username)) - } - /// Parse [`Credentials`] from an HTTP request, if any. /// /// Only HTTP Basic Authentication is supported. @@ -331,6 +316,27 @@ mod tests { assert_eq!(credentials.password(), None); } + #[test] + fn from_url_with_configured_user() { + let url = &Url::parse("https://example.com/simple/first/").unwrap(); + let username = "user"; + + let temp_dir = tempdir().unwrap(); + let auth_config_path = temp_dir.into_path().join("test_auth.toml"); + + set_test_config_path(auth_config_path); + + let mut auth_config = AuthConfig::load().unwrap(); + auth_config.add_entry(url, username.to_string()); + auth_config.store().unwrap(); + + let credentials = Credentials::from_url(&url).unwrap(); + assert_eq!(credentials.username(), Some(username)); + assert_eq!(credentials.password(), None); + + reset_config_path(); + } + #[test] fn authenticated_request_from_url() { let url = Url::parse("https://example.com/simple/first/").unwrap(); @@ -396,35 +402,4 @@ mod tests { assert_debug_snapshot!(header, @r###""Basic dXNlcjpwYXNzd29yZD09""###); assert_eq!(Credentials::from_header_value(&header), Some(credentials)); } - - #[test] - fn from_keyring() { - let username = "user"; - let password = "password"; - let index = "test_index"; - - let url = Url::parse("https://example.com").unwrap(); - let keyring = KeyringProvider::dummy([((url.host_str().unwrap(), username), password)]); - let temp_dir = tempdir().unwrap(); - let auth_config_path = temp_dir.into_path().join("test_auth.toml"); - - set_test_config_path(auth_config_path); - - let mut auth_config = AuthConfig::load().unwrap(); - auth_config.add_entry(index.to_string(), username.to_string()); - auth_config.store().unwrap(); - - // Act - let credentials = Credentials::from_keyring(index, &url, Some(keyring)); - - assert_eq!( - credentials, - Some(Credentials::new( - Some(username.to_string()), - Some(password.to_string()) - )) - ); - - reset_config_path(); - } } diff --git a/crates/uv-auth/src/keyring.rs b/crates/uv-auth/src/keyring.rs index 70a68a04d308..b20fa918114d 100644 --- a/crates/uv-auth/src/keyring.rs +++ b/crates/uv-auth/src/keyring.rs @@ -131,7 +131,15 @@ impl KeyringProvider { "Should only use keyring with a username" ); - let host = url.host().expect("Url should contain a host!"); + let host = if let Some(port) = url.port() { + format!( + "{}:{}", + url.host_str().expect("Url should have a host"), + port + ) + } else { + url.host_str().expect("Url should have a host").to_string() + }; trace!( "Creating entry in keyring for host {host} (from url {url}) and username {username}" ); diff --git a/crates/uv-auth/src/keyring_config.rs b/crates/uv-auth/src/keyring_config.rs index 4ad53301e8b7..042f305fc424 100644 --- a/crates/uv-auth/src/keyring_config.rs +++ b/crates/uv-auth/src/keyring_config.rs @@ -4,6 +4,8 @@ use std::collections::HashMap; use std::io::{self, Write}; use std::path::PathBuf; use thiserror::Error; +use tracing::debug; +use url::Url; #[derive(Error, Debug)] pub enum ConfigError { @@ -70,16 +72,19 @@ pub struct Index { } impl AuthConfig { - pub fn add_entry(&mut self, index_name: String, username: String) { - self.indexes.entry(index_name).or_insert(Index { username }); + pub fn add_entry(&mut self, index_url: &Url, username: String) { + let host = self.url_to_host(index_url); + self.indexes.entry(host).or_insert(Index { username }); } - pub fn find_entry(&self, index_name: &str) -> Option<&Index> { - self.indexes.get(index_name) + pub fn find_entry(&self, index_url: &Url) -> Option<&Index> { + let host = self.url_to_host(index_url); + self.indexes.get(&host) } - pub fn delete_entry(&mut self, index_name: &str) { - self.indexes.remove(index_name); + pub fn delete_entry(&mut self, index_url: &Url) { + let host = self.url_to_host(index_url); + self.indexes.remove(&host); } pub fn load_from_path(path: &PathBuf) -> Result { @@ -96,10 +101,32 @@ impl AuthConfig { pub fn store_to_path(&self, path: &PathBuf) -> Result<(), ConfigError> { let contents = toml::to_string_pretty(self)?; + let dir = path + .parent() + .expect("Path to auth config should have a parent directory!"); + + if !dir.exists() { + debug!("Creating directory {dir:?}"); + fs::create_dir_all(dir).unwrap(); + } + let mut file = fs::File::create(path)?; file.write_all(contents.as_bytes())?; Ok(()) } + + fn url_to_host(&self, url: &Url) -> String { + let host = if let Some(port) = url.port() { + format!( + "{}:{}", + url.host_str().expect("Url should have a host"), + port + ) + } else { + url.host_str().expect("Url should have a host").to_string() + }; + return host; + } } #[cfg(test)] @@ -144,6 +171,7 @@ mod tests { #[test] fn test_store_no_config_file() { // Prepare a fake file path for the test + let url = Url::parse("https://example.com/secure/pypi").unwrap(); let path = Path::new("test_auth.toml"); remove_temp_file(path).ok(); @@ -151,7 +179,7 @@ mod tests { assert!(config.is_ok()); let mut config = config.unwrap(); - config.add_entry("index1".to_string(), "user1".to_string()); + config.add_entry(&url, "user1".to_string()); let result = config.store_to_path(&path.to_path_buf()); assert!(result.is_ok()); @@ -160,7 +188,7 @@ mod tests { assert!(path.exists()); let contents = fs::read_to_string(path).expect("Failed to read config file"); - assert!(contents.contains("index1")); + assert!(contents.contains("example.com")); assert!(contents.contains("user1")); // Clean up @@ -169,18 +197,21 @@ mod tests { #[test] fn test_find_entry() { + let url = Url::parse("https://example.com/secure/pypi").unwrap(); + let url_not_existing = Url::parse("https://other-domain.com/secure/pypi").unwrap(); + let mut config = AuthConfig { indexes: HashMap::new(), }; - config.add_entry("index1".to_string(), "user1".to_string()); + config.add_entry(&url, "user1".to_string()); // Test finding an existing entry - let entry = config.find_entry("index1"); + let entry = config.find_entry(&url); assert!(entry.is_some()); assert_eq!(entry.unwrap().username, "user1"); // Test finding a non-existing entry - let entry = config.find_entry("nonexistent"); + let entry = config.find_entry(&url_not_existing); assert!(entry.is_none()); } } diff --git a/crates/uv-distribution-types/src/index.rs b/crates/uv-distribution-types/src/index.rs index af75d6c82198..59b7f5d7c45c 100644 --- a/crates/uv-distribution-types/src/index.rs +++ b/crates/uv-distribution-types/src/index.rs @@ -3,7 +3,7 @@ use std::str::FromStr; use thiserror::Error; use url::Url; -use uv_auth::{Credentials, KeyringProvider}; +use uv_auth::Credentials; use crate::index_name::{IndexName, IndexNameError}; use crate::origin::Origin; @@ -155,7 +155,7 @@ impl Index { } /// Retrieve the credentials for the index, either from the environment, or from the URL itself. - pub fn credentials(&self, keyring_provider: Option) -> Option { + pub fn credentials(&self) -> Option { // If the index is named, try to load credentials for the named index. if let Some(name) = self.name.as_ref() { @@ -163,13 +163,6 @@ impl Index { if let Some(credentials) = Credentials::from_env(name.to_env_var()) { return Some(credentials); } - - // Otherwise try to read the credentials from keyring. - if let Some(credentials) = - Credentials::from_keyring(name, self.url.url(), keyring_provider) - { - return Some(credentials); - } } // Otherwise, extract the credentials from the URL. diff --git a/crates/uv/src/commands/build_frontend.rs b/crates/uv/src/commands/build_frontend.rs index a9a9fa718107..0569b72f17a6 100644 --- a/crates/uv/src/commands/build_frontend.rs +++ b/crates/uv/src/commands/build_frontend.rs @@ -493,7 +493,7 @@ async fn build_package( // Add all authenticated sources to the cache. for index in index_locations.allowed_indexes() { - if let Some(credentials) = index.credentials(keyring_provider.to_provider()) { + if let Some(credentials) = index.credentials() { store_credentials(index.raw_url(), credentials); } } diff --git a/crates/uv/src/commands/index.rs b/crates/uv/src/commands/index.rs index cc394a82c701..098c1624d621 100644 --- a/crates/uv/src/commands/index.rs +++ b/crates/uv/src/commands/index.rs @@ -21,9 +21,8 @@ pub(crate) async fn set_credentials( .unwrap_or(false) }); - let index = match index { - Some(obj) => obj, - None => panic!("No index found with the name '{}'", name), + let Some(index) = index else { + panic!("No index found with the name '{name}'") }; let username = match username { @@ -47,7 +46,7 @@ pub(crate) async fn set_credentials( keyring_provider .to_provider() .expect("Keyring Provider is not available") - .set(&url, &username, &password) + .set(url, &username, &password) .await; debug!( @@ -56,8 +55,10 @@ pub(crate) async fn set_credentials( ); let mut auth_config = AuthConfig::load().inspect_err(|err| warn!("Could not load auth config due to: {err}"))?; - auth_config.add_entry(name, username); - auth_config.store()?; + auth_config.add_entry(index.raw_url(), username); + auth_config + .store() + .inspect_err(|err| warn!("Could not save auth config due to: {err}"))?; Ok(()) } @@ -73,20 +74,25 @@ pub(crate) async fn list_credentials( .to_provider() .expect("Keyring Provider is not available"); + let num_indexes = indexes.len(); + debug!("Found {num_indexes} indexes"); for index in indexes { - if let Some(index_name) = index.name { - if let Some(auth_index) = auth_config.indexes.get(&index_name.to_string()) { - let username = auth_index.username.clone(); - let password = keyring_provider.fetch(&index.url, &username).await; - - match password { - Some(_) => println!( - "Index: '{}' authenticates with username '{}'.", - index_name, username - ), - None => println!("Index: '{}' has no credentials.", index_name), - } + let index_url = index.raw_url(); + + if let Some(auth_index) = auth_config.find_entry(index_url) { + let username = auth_index.username.clone(); + let password = keyring_provider.fetch(&index.url, &username).await; + + let index_name = index.name.expect("Index should have a name").to_string(); + match password { + Some(_) => println!( + "Index: '{}' authenticates with username '{}'.", + index_name, username + ), + None => println!("Index: '{}' has no credentials.", index_name), } + } else { + debug!("Could not find the index with url {index_url} in auth config"); } } @@ -128,7 +134,7 @@ pub(crate) async fn unset_credentials( let mut auth_config = AuthConfig::load().inspect_err(|err| warn!("Could not load auth config due to: {err}"))?; - auth_config.delete_entry(&name); + auth_config.delete_entry(index.raw_url()); auth_config.store()?; Ok(()) diff --git a/crates/uv/src/commands/pip/compile.rs b/crates/uv/src/commands/pip/compile.rs index d259750fee77..e2398c44d20d 100644 --- a/crates/uv/src/commands/pip/compile.rs +++ b/crates/uv/src/commands/pip/compile.rs @@ -289,7 +289,7 @@ pub(crate) async fn pip_compile( // Add all authenticated sources to the cache. for index in index_locations.allowed_indexes() { - if let Some(credentials) = index.credentials(keyring_provider.to_provider()) { + if let Some(credentials) = index.credentials() { uv_auth::store_credentials(index.raw_url(), credentials); } } diff --git a/crates/uv/src/commands/pip/install.rs b/crates/uv/src/commands/pip/install.rs index 465f21a9cc08..6110d43a1bff 100644 --- a/crates/uv/src/commands/pip/install.rs +++ b/crates/uv/src/commands/pip/install.rs @@ -307,7 +307,7 @@ pub(crate) async fn pip_install( // Add all authenticated sources to the cache. for index in index_locations.allowed_indexes() { - if let Some(credentials) = index.credentials(keyring_provider.to_provider()) { + if let Some(credentials) = index.credentials() { uv_auth::store_credentials(index.raw_url(), credentials); } } diff --git a/crates/uv/src/commands/pip/sync.rs b/crates/uv/src/commands/pip/sync.rs index 597908ee2133..aa85bc89a77c 100644 --- a/crates/uv/src/commands/pip/sync.rs +++ b/crates/uv/src/commands/pip/sync.rs @@ -247,7 +247,7 @@ pub(crate) async fn pip_sync( // Add all authenticated sources to the cache. for index in index_locations.allowed_indexes() { - if let Some(credentials) = index.credentials(keyring_provider.to_provider()) { + if let Some(credentials) = index.credentials() { uv_auth::store_credentials(index.raw_url(), credentials); } } diff --git a/crates/uv/src/commands/project/add.rs b/crates/uv/src/commands/project/add.rs index 11a09b397e2e..a04c88611061 100644 --- a/crates/uv/src/commands/project/add.rs +++ b/crates/uv/src/commands/project/add.rs @@ -260,7 +260,7 @@ pub(crate) async fn add( // Add all authenticated sources to the cache. for index in settings.index_locations.allowed_indexes() { - if let Some(credentials) = index.credentials(settings.keyring_provider.to_provider()) { + if let Some(credentials) = index.credentials() { uv_auth::store_credentials(index.raw_url(), credentials); } } @@ -319,9 +319,7 @@ pub(crate) async fn add( // Add all authenticated sources to the cache. for index in settings.index_locations.allowed_indexes() { - if let Some(credentials) = - index.credentials(settings.keyring_provider.to_provider()) - { + if let Some(credentials) = index.credentials() { uv_auth::store_credentials(index.raw_url(), credentials); } } diff --git a/crates/uv/src/commands/project/lock.rs b/crates/uv/src/commands/project/lock.rs index a58d4c959f02..f33e24d29131 100644 --- a/crates/uv/src/commands/project/lock.rs +++ b/crates/uv/src/commands/project/lock.rs @@ -461,7 +461,7 @@ async fn do_lock( // Add all authenticated sources to the cache. for index in index_locations.allowed_indexes() { - if let Some(credentials) = index.credentials(settings.keyring_provider.to_provider()) { + if let Some(credentials) = index.credentials() { uv_auth::store_credentials(index.raw_url(), credentials); } } diff --git a/crates/uv/src/commands/project/mod.rs b/crates/uv/src/commands/project/mod.rs index c9c9dc0fc3f2..74cfbe5ffc09 100644 --- a/crates/uv/src/commands/project/mod.rs +++ b/crates/uv/src/commands/project/mod.rs @@ -1018,7 +1018,7 @@ pub(crate) async fn resolve_names( // Add all authenticated sources to the cache. for index in index_locations.allowed_indexes() { - if let Some(credentials) = index.credentials(settings.keyring_provider.to_provider()) { + if let Some(credentials) = index.credentials() { uv_auth::store_credentials(index.raw_url(), credentials); } } @@ -1168,7 +1168,7 @@ pub(crate) async fn resolve_environment( // Add all authenticated sources to the cache. for index in index_locations.allowed_indexes() { - if let Some(credentials) = index.credentials(settings.keyring_provider.to_provider()) { + if let Some(credentials) = index.credentials() { uv_auth::store_credentials(index.raw_url(), credentials); } } @@ -1333,7 +1333,7 @@ pub(crate) async fn sync_environment( // Add all authenticated sources to the cache. for index in index_locations.allowed_indexes() { - if let Some(credentials) = index.credentials(settings.keyring_provider.to_provider()) { + if let Some(credentials) = index.credentials() { uv_auth::store_credentials(index.raw_url(), credentials); } } @@ -1528,7 +1528,7 @@ pub(crate) async fn update_environment( // Add all authenticated sources to the cache. for index in index_locations.allowed_indexes() { - if let Some(credentials) = index.credentials(settings.keyring_provider.to_provider()) { + if let Some(credentials) = index.credentials() { uv_auth::store_credentials(index.raw_url(), credentials); } } diff --git a/crates/uv/src/commands/project/sync.rs b/crates/uv/src/commands/project/sync.rs index c4f062c6a28c..755a4314c3b7 100644 --- a/crates/uv/src/commands/project/sync.rs +++ b/crates/uv/src/commands/project/sync.rs @@ -358,7 +358,7 @@ pub(super) async fn do_sync( // Add all authenticated sources to the cache. for index in index_locations.allowed_indexes() { - if let Some(credentials) = index.credentials(settings.keyring_provider.to_provider()) { + if let Some(credentials) = index.credentials() { store_credentials(index.raw_url(), credentials); } } diff --git a/crates/uv/src/commands/venv.rs b/crates/uv/src/commands/venv.rs index 325f3cda5ff8..6b7c10f95f34 100644 --- a/crates/uv/src/commands/venv.rs +++ b/crates/uv/src/commands/venv.rs @@ -224,7 +224,7 @@ async fn venv_impl( // Add all authenticated sources to the cache. for index in index_locations.allowed_indexes() { - if let Some(credentials) = index.credentials(keyring_provider.to_provider()) { + if let Some(credentials) = index.credentials() { uv_auth::store_credentials(index.raw_url(), credentials); } } @@ -271,7 +271,7 @@ async fn venv_impl( // Add all authenticated sources to the cache. for index in index_locations.allowed_indexes() { - if let Some(credentials) = index.credentials(keyring_provider.to_provider()) { + if let Some(credentials) = index.credentials() { uv_auth::store_credentials(index.raw_url(), credentials); } } From b033ba8bd86414b08355b84cce856b8c9671f47d Mon Sep 17 00:00:00 2001 From: Simon Steinheber Date: Sun, 2 Feb 2025 18:02:29 +0100 Subject: [PATCH 19/28] Adjust documentation --- docs/configuration/indexes.md | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/docs/configuration/indexes.md b/docs/configuration/indexes.md index 7e9327d6fdb9..1b4ecd8846e9 100644 --- a/docs/configuration/indexes.md +++ b/docs/configuration/indexes.md @@ -140,7 +140,7 @@ While `unsafe-best-match` is the closest to pip's behavior, it exposes users to Most private registries require authentication to access packages, typically via a username and password (or access token). -To authenticate with a provide index, either provide credentials via environment variables or embed +To authenticate with a provided index, either provide credentials via environment variables, manage credentials via the CLI or embed them in the URL. For example, given an index named `internal-proxy` that requires a username (`public`) and password @@ -152,6 +152,7 @@ name = "internal-proxy" url = "https://example.com/simple" ``` +### Provide credentials via environment variables From there, you can set the `UV_INDEX_INTERNAL_PROXY_USERNAME` and `UV_INDEX_INTERNAL_PROXY_PASSWORD` environment variables, where `INTERNAL_PROXY` is the uppercase version of the index name, with non-alphanumeric characters replaced by underscores: @@ -164,6 +165,28 @@ export UV_INDEX_INTERNAL_PROXY_PASSWORD=koala By providing credentials via environment variables, you can avoid storing sensitive information in the plaintext `pyproject.toml` file. +### Use the `uv` cli to manage your credentials +An alternative to environment variables is using the cli to manage your credentials. + +To set credentials for the index from above use the following command: + +```sh +uv index credentials set --index-name="internal-proxy" --keyring-provider="subprocess" --username="public" --password "koala" +``` + +The username is linked to the index and stored locally. The password is saved securely with keyring. +This works [analogously to poetry](https://python-poetry.org/docs/repositories/#installing-from-private-package-sources). + +!!! tip + + `--username` and `--password` are optional. You can leave them out. The command will ask for the information interactively. + +!!! info + + The syntax varies compared to poetry. The syntax we chose opens up the possibility to add further commands for index management. + + +### Embed credentials in the url Alternatively, credentials can be embedded directly in the index definition: ```toml From 4a6632a8bd9d27bb8031733fb4b6950b73d4951d Mon Sep 17 00:00:00 2001 From: Simon Steinheber Date: Sun, 2 Feb 2025 18:13:40 +0100 Subject: [PATCH 20/28] Fix bugs after merge --- Cargo.lock | 25 ++++++++++++------------- crates/uv-auth/Cargo.toml | 2 -- crates/uv/src/commands/project/add.rs | 2 +- 3 files changed, 13 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1655c8cea08f..674b77d80dd3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -185,9 +185,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.85" +version = "0.1.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f934833b4b7233644e5848f235df3f57ed8c80f1528a26c3dfa13d2147fa056" +checksum = "644dd749086bf3771a2fbc5f256fdb982d53f011c7d5d560304eafeecebce79d" dependencies = [ "proc-macro2", "quote", @@ -976,9 +976,9 @@ checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] name = "dyn-clone" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" +checksum = "feeef44e73baff3a26d371801df019877a9866a8c493d315ab00177843314f35" [[package]] name = "either" @@ -1867,7 +1867,7 @@ checksum = "e19b23d53f35ce9f56aebc7d1bb4e6ac1e9c0db7ac85c8d1760c04379edced37" dependencies = [ "hermit-abi 0.4.0", "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1917,9 +1917,9 @@ checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "jiff" -version = "0.1.28" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c607c728e28764fecde611a2764a3a5db19ae21dcec46f292244f5cc5c085a81" +checksum = "c04ef77ae73f3cf50510712722f0c4e8b46f5aaa1bf5ffad2ae213e6495e78e5" dependencies = [ "jiff-tzdb-platform", "log", @@ -2691,9 +2691,9 @@ dependencies = [ [[package]] name = "priority-queue" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "714c75db297bc88a63783ffc6ab9f830698a6705aa0201416931759ef4c8183d" +checksum = "090ded312ed32a928fb49cb91ab4db6523ae3767225e61fbf6ceaaec3664ed26" dependencies = [ "autocfg", "equivalent", @@ -3746,9 +3746,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.96" +version = "2.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" +checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" dependencies = [ "proc-macro2", "quote", @@ -4600,7 +4600,7 @@ dependencies = [ "anyhow", "async-trait", "base64 0.22.1", - "fs-err 3.0.0", + "fs-err 3.1.0", "futures", "http", "insta", @@ -4617,7 +4617,6 @@ dependencies = [ "toml", "tracing", "url", - "urlencoding", "uv-dirs", "uv-once-map", "uv-static", diff --git a/crates/uv-auth/Cargo.toml b/crates/uv-auth/Cargo.toml index 6bf0908e0c94..2bd3d4427498 100644 --- a/crates/uv-auth/Cargo.toml +++ b/crates/uv-auth/Cargo.toml @@ -26,8 +26,6 @@ rustc-hash = { workspace = true } tokio = { workspace = true } tracing = { workspace = true } url = { workspace = true } -urlencoding = { workspace = true } -uv-static = { workspace = true } uv-dirs = { workspace = true } toml.workspace = true serde.workspace = true diff --git a/crates/uv/src/commands/project/add.rs b/crates/uv/src/commands/project/add.rs index 079059b6fe6e..e5a66b440dd8 100644 --- a/crates/uv/src/commands/project/add.rs +++ b/crates/uv/src/commands/project/add.rs @@ -262,7 +262,7 @@ pub(crate) async fn add( // Add all authenticated sources to the cache. for index in settings.index_locations.allowed_indexes() { if let Some(credentials) = index.credentials() { - uv_auth::store_credentials(index.raw_url(), credentials); + uv_auth::store_credentials(index.raw_url(), credentials.into()); } } From 0578961f6da467755824c55251abc8d0e309e731 Mon Sep 17 00:00:00 2001 From: Simon Steinheber Date: Sun, 2 Feb 2025 18:24:15 +0100 Subject: [PATCH 21/28] Format docs --- docs/configuration/indexes.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/docs/configuration/indexes.md b/docs/configuration/indexes.md index 1b4ecd8846e9..26891932c03e 100644 --- a/docs/configuration/indexes.md +++ b/docs/configuration/indexes.md @@ -140,8 +140,8 @@ While `unsafe-best-match` is the closest to pip's behavior, it exposes users to Most private registries require authentication to access packages, typically via a username and password (or access token). -To authenticate with a provided index, either provide credentials via environment variables, manage credentials via the CLI or embed -them in the URL. +To authenticate with a provided index, either provide credentials via environment variables, manage +credentials via the CLI or embed them in the URL. For example, given an index named `internal-proxy` that requires a username (`public`) and password (`koala`), define the index (without credentials) in your `pyproject.toml`: @@ -153,6 +153,7 @@ url = "https://example.com/simple" ``` ### Provide credentials via environment variables + From there, you can set the `UV_INDEX_INTERNAL_PROXY_USERNAME` and `UV_INDEX_INTERNAL_PROXY_PASSWORD` environment variables, where `INTERNAL_PROXY` is the uppercase version of the index name, with non-alphanumeric characters replaced by underscores: @@ -166,7 +167,8 @@ By providing credentials via environment variables, you can avoid storing sensit the plaintext `pyproject.toml` file. ### Use the `uv` cli to manage your credentials -An alternative to environment variables is using the cli to manage your credentials. + +An alternative to environment variables is using the cli to manage your credentials. To set credentials for the index from above use the following command: @@ -175,7 +177,8 @@ uv index credentials set --index-name="internal-proxy" --keyring-provider="subpr ``` The username is linked to the index and stored locally. The password is saved securely with keyring. -This works [analogously to poetry](https://python-poetry.org/docs/repositories/#installing-from-private-package-sources). +This works +[analogously to poetry](https://python-poetry.org/docs/repositories/#installing-from-private-package-sources). !!! tip @@ -185,8 +188,8 @@ This works [analogously to poetry](https://python-poetry.org/docs/repositories/# The syntax varies compared to poetry. The syntax we chose opens up the possibility to add further commands for index management. - ### Embed credentials in the url + Alternatively, credentials can be embedded directly in the index definition: ```toml From 566510fb1fc0cc8a5a70ddcd57c35c3397e1ddc7 Mon Sep 17 00:00:00 2001 From: Simon Steinheber Date: Sun, 2 Feb 2025 19:57:58 +0100 Subject: [PATCH 22/28] Fix errors from CI pipeline --- crates/uv-auth/src/keyring_config.rs | 18 +- crates/uv-cli/src/lib.rs | 1 + crates/uv/tests/it/help.rs | 35 +- crates/uv/tests/it/lock.rs | 10 +- docs/reference/cli.md | 802 ++++++++++++++++++++++++++- 5 files changed, 842 insertions(+), 24 deletions(-) diff --git a/crates/uv-auth/src/keyring_config.rs b/crates/uv-auth/src/keyring_config.rs index 042f305fc424..53dd1d721a0f 100644 --- a/crates/uv-auth/src/keyring_config.rs +++ b/crates/uv-auth/src/keyring_config.rs @@ -73,17 +73,17 @@ pub struct Index { impl AuthConfig { pub fn add_entry(&mut self, index_url: &Url, username: String) { - let host = self.url_to_host(index_url); + let host = AuthConfig::url_to_string(index_url); self.indexes.entry(host).or_insert(Index { username }); } pub fn find_entry(&self, index_url: &Url) -> Option<&Index> { - let host = self.url_to_host(index_url); + let host = AuthConfig::url_to_string(index_url); self.indexes.get(&host) } pub fn delete_entry(&mut self, index_url: &Url) { - let host = self.url_to_host(index_url); + let host = AuthConfig::url_to_string(index_url); self.indexes.remove(&host); } @@ -115,17 +115,21 @@ impl AuthConfig { Ok(()) } - fn url_to_host(&self, url: &Url) -> String { + fn url_to_string(url: &Url) -> String { + if !url.has_host() { + return url.as_str().to_string(); + } + let host = if let Some(port) = url.port() { format!( "{}:{}", - url.host_str().expect("Url should have a host"), + url.host_str().expect(&format!("Url {url:?} has no host")), port ) } else { - url.host_str().expect("Url should have a host").to_string() + url.host_str().expect(&format!("Url {url:?} has no host")).to_string() }; - return host; + host } } diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 8df8664c6011..4abdeb05fcff 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -489,6 +489,7 @@ pub enum Commands { ), )] Help(HelpArgs), + /// Manage uv's indexes Index(IndexNamespace), } diff --git a/crates/uv/tests/it/help.rs b/crates/uv/tests/it/help.rs index 93b6ece0251f..bd706f7e22bb 100644 --- a/crates/uv/tests/it/help.rs +++ b/crates/uv/tests/it/help.rs @@ -5,7 +5,7 @@ fn help() { let context = TestContext::new_with_versions(&[]); // The `uv help` command should show the long help message - uv_snapshot!(context.filters(), context.help(), @r###" + uv_snapshot!(context.filters(), context.help(), @r#" success: true exit_code: 0 ----- stdout ----- @@ -33,6 +33,7 @@ fn help() { version Display uv's version generate-shell-completion Generate shell completion help Display documentation for a command + index Manage uv's indexes Cache options: -n, --no-cache Avoid reading from or writing to the cache, instead using a temporary @@ -79,14 +80,14 @@ fn help() { ----- stderr ----- - "###); + "#); } #[test] fn help_flag() { let context = TestContext::new_with_versions(&[]); - uv_snapshot!(context.filters(), context.command().arg("--help"), @r###" + uv_snapshot!(context.filters(), context.command().arg("--help"), @r#" success: true exit_code: 0 ----- stdout ----- @@ -113,6 +114,7 @@ fn help_flag() { self Manage the uv executable version Display uv's version help Display documentation for a command + index Manage uv's indexes Cache options: -n, --no-cache Avoid reading from or writing to the cache, instead using a temporary @@ -158,14 +160,14 @@ fn help_flag() { Use `uv help` for more details. ----- stderr ----- - "###); + "#); } #[test] fn help_short_flag() { let context = TestContext::new_with_versions(&[]); - uv_snapshot!(context.filters(), context.command().arg("-h"), @r###" + uv_snapshot!(context.filters(), context.command().arg("-h"), @r#" success: true exit_code: 0 ----- stdout ----- @@ -192,6 +194,7 @@ fn help_short_flag() { self Manage the uv executable version Display uv's version help Display documentation for a command + index Manage uv's indexes Cache options: -n, --no-cache Avoid reading from or writing to the cache, instead using a temporary @@ -237,7 +240,7 @@ fn help_short_flag() { Use `uv help` for more details. ----- stderr ----- - "###); + "#); } #[test] @@ -832,7 +835,7 @@ fn help_flag_subsubcommand() { fn help_unknown_subcommand() { let context = TestContext::new_with_versions(&[]); - uv_snapshot!(context.filters(), context.help().arg("foobar"), @r###" + uv_snapshot!(context.filters(), context.help().arg("foobar"), @r" success: false exit_code: 2 ----- stdout ----- @@ -856,10 +859,11 @@ fn help_unknown_subcommand() { cache self version + index generate-shell-completion - "###); + "); - uv_snapshot!(context.filters(), context.help().arg("foo").arg("bar"), @r###" + uv_snapshot!(context.filters(), context.help().arg("foo").arg("bar"), @r" success: false exit_code: 2 ----- stdout ----- @@ -883,8 +887,9 @@ fn help_unknown_subcommand() { cache self version + index generate-shell-completion - "###); + "); } #[test] @@ -911,7 +916,7 @@ fn help_unknown_subsubcommand() { fn help_with_global_option() { let context = TestContext::new_with_versions(&[]); - uv_snapshot!(context.filters(), context.help().arg("--no-cache"), @r###" + uv_snapshot!(context.filters(), context.help().arg("--no-cache"), @r#" success: true exit_code: 0 ----- stdout ----- @@ -939,6 +944,7 @@ fn help_with_global_option() { version Display uv's version generate-shell-completion Generate shell completion help Display documentation for a command + index Manage uv's indexes Cache options: -n, --no-cache Avoid reading from or writing to the cache, instead using a temporary @@ -985,7 +991,7 @@ fn help_with_global_option() { ----- stderr ----- - "###); + "#); } #[test] @@ -1027,7 +1033,7 @@ fn help_with_no_pager() { // We can't really test whether the --no-pager option works with a snapshot test. // It's still nice to have a test for the option to confirm the option exists. - uv_snapshot!(context.filters(), context.help().arg("--no-pager"), @r###" + uv_snapshot!(context.filters(), context.help().arg("--no-pager"), @r#" success: true exit_code: 0 ----- stdout ----- @@ -1055,6 +1061,7 @@ fn help_with_no_pager() { version Display uv's version generate-shell-completion Generate shell completion help Display documentation for a command + index Manage uv's indexes Cache options: -n, --no-cache Avoid reading from or writing to the cache, instead using a temporary @@ -1101,5 +1108,5 @@ fn help_with_no_pager() { ----- stderr ----- - "###); + "#); } diff --git a/crates/uv/tests/it/lock.rs b/crates/uv/tests/it/lock.rs index e79139299325..b50e1e57fd38 100644 --- a/crates/uv/tests/it/lock.rs +++ b/crates/uv/tests/it/lock.rs @@ -14635,7 +14635,7 @@ fn lock_explicit_default_index() -> Result<()> { "#, )?; - uv_snapshot!(context.filters(), context.lock().arg("--verbose"), @r###" + uv_snapshot!(context.filters(), context.lock().arg("--verbose"), @r#" success: false exit_code: 1 ----- stdout ----- @@ -14647,6 +14647,12 @@ fn lock_explicit_default_index() -> Result<()> { DEBUG Using Python request `>=3.12` from `requires-python` metadata DEBUG Checking for Python environment at `.venv` DEBUG The virtual environment's Python version satisfies `>=3.12` + DEBUG Trying to read username for url https://test.pypi.org/simple + WARN Could not find entry for https://test.pypi.org/simple + DEBUG Trying to read username for url https://test.pypi.org/simple + WARN Could not find entry for https://test.pypi.org/simple + DEBUG Trying to read username for url https://test.pypi.org/simple + WARN Could not find entry for https://test.pypi.org/simple DEBUG Using request timeout of [TIME] DEBUG Found static `pyproject.toml` for: project @ file://[TEMP_DIR]/ DEBUG No workspace root found, using project root @@ -14667,7 +14673,7 @@ fn lock_explicit_default_index() -> Result<()> { ╰─▶ Because anyio was not found in the provided package locations and your project depends on anyio, we can conclude that your project's requirements are unsatisfiable. hint: Packages were unavailable because index lookups were disabled and no additional package locations were provided (try: `--find-links `) - "###); + "#); let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock")).unwrap(); diff --git a/docs/reference/cli.md b/docs/reference/cli.md index b1368141664a..e6481c1a54c0 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -48,7 +48,7 @@ uv [OPTIONS]
uv help

Display documentation for a command

- +
uv index
## uv run @@ -9479,3 +9479,803 @@ uv help [OPTIONS] [COMMAND]... +## uv index + +

Usage

+ +``` +uv index [OPTIONS] +``` + +

Commands

+ +
uv index set

Set a new index. This will be added to your pyproject.toml

+
+
uv index list

List all indexes set in your pyproject.toml

+
+
uv index unset

Unset an existing index. This will be removed from your pyproject.toml

+
+
uv index credentials

Manage credentials for the indexes configured in your pyproject.toml

+
+
+ +### uv index set + +Set a new index. This will be added to your pyproject.toml + +

Usage

+ +``` +uv index set [OPTIONS] --name +``` + +

Options

+ +
--allow-insecure-host allow-insecure-host

Allow insecure connections to a host.

+ +

Can be provided multiple times.

+ +

Expects to receive either a hostname (e.g., localhost), a host-port pair (e.g., localhost:8080), or a URL (e.g., https://localhost).

+ +

WARNING: Hosts included in this list will not be verified against the system’s certificate store. Only use --allow-insecure-host in a secure network with verified sources, as it bypasses SSL verification and could expose you to MITM attacks.

+ +

May also be set with the UV_INSECURE_HOST environment variable.

+
--cache-dir cache-dir

Path to the cache directory.

+ +

Defaults to $XDG_CACHE_HOME/uv or $HOME/.cache/uv on macOS and Linux, and %LOCALAPPDATA%\uv\cache on Windows.

+ +

To view the location of the cache directory, run uv cache dir.

+ +

May also be set with the UV_CACHE_DIR environment variable.

+
--color color-choice

Control the use of color in output.

+ +

By default, uv will automatically detect support for colors when writing to a terminal.

+ +

Possible values:

+ +
    +
  • auto: Enables colored output only when the output is going to a terminal or TTY with support
  • + +
  • always: Enables colored output regardless of the detected environment
  • + +
  • never: Disables colored output
  • +
+
--config-file config-file

The path to a uv.toml file to use for configuration.

+ +

While uv configuration can be included in a pyproject.toml file, it is not allowed in this context.

+ +

May also be set with the UV_CONFIG_FILE environment variable.

+
--directory directory

Change to the given directory prior to running the command.

+ +

Relative paths are resolved with the given directory as the base.

+ +

See --project to only change the project root directory.

+ +
--help, -h

Display the concise help for this command

+ +
--name name

The name of the index

+ +
--native-tls

Whether to load TLS certificates from the platform’s native certificate store.

+ +

By default, uv loads certificates from the bundled webpki-roots crate. The webpki-roots are a reliable set of trust roots from Mozilla, and including them in uv improves portability and performance (especially on macOS).

+ +

However, in some cases, you may want to use the platform’s native certificate store, especially if you’re relying on a corporate trust root (e.g., for a mandatory proxy) that’s included in your system’s certificate store.

+ +

May also be set with the UV_NATIVE_TLS environment variable.

+
--no-cache, -n

Avoid reading from or writing to the cache, instead using a temporary directory for the duration of the operation

+ +

May also be set with the UV_NO_CACHE environment variable.

+
--no-config

Avoid discovering configuration files (pyproject.toml, uv.toml).

+ +

Normally, configuration files are discovered in the current directory, parent directories, or user configuration directories.

+ +

May also be set with the UV_NO_CONFIG environment variable.

+
--no-progress

Hide all progress outputs.

+ +

For example, spinners or progress bars.

+ +

May also be set with the UV_NO_PROGRESS environment variable.

+
--no-python-downloads

Disable automatic downloads of Python.

+ +
--offline

Disable network access.

+ +

When disabled, uv will only use locally cached data and locally available files.

+ +

May also be set with the UV_OFFLINE environment variable.

+
--project project

Run the command within the given project directory.

+ +

All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project’s virtual environment (.venv).

+ +

Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

+ +

See --directory to change the working directory entirely.

+ +

This setting has no effect when used in the uv pip interface.

+ +
--python-preference python-preference

Whether to prefer uv-managed or system Python installations.

+ +

By default, uv prefers using Python versions it manages. However, it will use system Python installations if a uv-managed Python is not installed. This option allows prioritizing or ignoring system Python installations.

+ +

May also be set with the UV_PYTHON_PREFERENCE environment variable.

+

Possible values:

+ +
    +
  • only-managed: Only use managed Python installations; never use system Python installations
  • + +
  • managed: Prefer managed Python installations over system Python installations
  • + +
  • system: Prefer system Python installations over managed Python installations
  • + +
  • only-system: Only use system Python installations; never use managed Python installations
  • +
+
--quiet, -q

Do not print any output

+ +
--verbose, -v

Use verbose output.

+ +

You can configure fine-grained logging using the RUST_LOG environment variable. (<https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives>)

+ +
--version, -V

Display the uv version

+ +
+ +### uv index list + +List all indexes set in your pyproject.toml + +

Usage

+ +``` +uv index list [OPTIONS] --name +``` + +

Options

+ +
--allow-insecure-host allow-insecure-host

Allow insecure connections to a host.

+ +

Can be provided multiple times.

+ +

Expects to receive either a hostname (e.g., localhost), a host-port pair (e.g., localhost:8080), or a URL (e.g., https://localhost).

+ +

WARNING: Hosts included in this list will not be verified against the system’s certificate store. Only use --allow-insecure-host in a secure network with verified sources, as it bypasses SSL verification and could expose you to MITM attacks.

+ +

May also be set with the UV_INSECURE_HOST environment variable.

+
--cache-dir cache-dir

Path to the cache directory.

+ +

Defaults to $XDG_CACHE_HOME/uv or $HOME/.cache/uv on macOS and Linux, and %LOCALAPPDATA%\uv\cache on Windows.

+ +

To view the location of the cache directory, run uv cache dir.

+ +

May also be set with the UV_CACHE_DIR environment variable.

+
--color color-choice

Control the use of color in output.

+ +

By default, uv will automatically detect support for colors when writing to a terminal.

+ +

Possible values:

+ +
    +
  • auto: Enables colored output only when the output is going to a terminal or TTY with support
  • + +
  • always: Enables colored output regardless of the detected environment
  • + +
  • never: Disables colored output
  • +
+
--config-file config-file

The path to a uv.toml file to use for configuration.

+ +

While uv configuration can be included in a pyproject.toml file, it is not allowed in this context.

+ +

May also be set with the UV_CONFIG_FILE environment variable.

+
--directory directory

Change to the given directory prior to running the command.

+ +

Relative paths are resolved with the given directory as the base.

+ +

See --project to only change the project root directory.

+ +
--help, -h

Display the concise help for this command

+ +
--name name

The name of the index

+ +
--native-tls

Whether to load TLS certificates from the platform’s native certificate store.

+ +

By default, uv loads certificates from the bundled webpki-roots crate. The webpki-roots are a reliable set of trust roots from Mozilla, and including them in uv improves portability and performance (especially on macOS).

+ +

However, in some cases, you may want to use the platform’s native certificate store, especially if you’re relying on a corporate trust root (e.g., for a mandatory proxy) that’s included in your system’s certificate store.

+ +

May also be set with the UV_NATIVE_TLS environment variable.

+
--no-cache, -n

Avoid reading from or writing to the cache, instead using a temporary directory for the duration of the operation

+ +

May also be set with the UV_NO_CACHE environment variable.

+
--no-config

Avoid discovering configuration files (pyproject.toml, uv.toml).

+ +

Normally, configuration files are discovered in the current directory, parent directories, or user configuration directories.

+ +

May also be set with the UV_NO_CONFIG environment variable.

+
--no-progress

Hide all progress outputs.

+ +

For example, spinners or progress bars.

+ +

May also be set with the UV_NO_PROGRESS environment variable.

+
--no-python-downloads

Disable automatic downloads of Python.

+ +
--offline

Disable network access.

+ +

When disabled, uv will only use locally cached data and locally available files.

+ +

May also be set with the UV_OFFLINE environment variable.

+
--project project

Run the command within the given project directory.

+ +

All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project’s virtual environment (.venv).

+ +

Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

+ +

See --directory to change the working directory entirely.

+ +

This setting has no effect when used in the uv pip interface.

+ +
--python-preference python-preference

Whether to prefer uv-managed or system Python installations.

+ +

By default, uv prefers using Python versions it manages. However, it will use system Python installations if a uv-managed Python is not installed. This option allows prioritizing or ignoring system Python installations.

+ +

May also be set with the UV_PYTHON_PREFERENCE environment variable.

+

Possible values:

+ +
    +
  • only-managed: Only use managed Python installations; never use system Python installations
  • + +
  • managed: Prefer managed Python installations over system Python installations
  • + +
  • system: Prefer system Python installations over managed Python installations
  • + +
  • only-system: Only use system Python installations; never use managed Python installations
  • +
+
--quiet, -q

Do not print any output

+ +
--verbose, -v

Use verbose output.

+ +

You can configure fine-grained logging using the RUST_LOG environment variable. (<https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives>)

+ +
--version, -V

Display the uv version

+ +
+ +### uv index unset + +Unset an existing index. This will be removed from your pyproject.toml + +

Usage

+ +``` +uv index unset [OPTIONS] --name +``` + +

Options

+ +
--allow-insecure-host allow-insecure-host

Allow insecure connections to a host.

+ +

Can be provided multiple times.

+ +

Expects to receive either a hostname (e.g., localhost), a host-port pair (e.g., localhost:8080), or a URL (e.g., https://localhost).

+ +

WARNING: Hosts included in this list will not be verified against the system’s certificate store. Only use --allow-insecure-host in a secure network with verified sources, as it bypasses SSL verification and could expose you to MITM attacks.

+ +

May also be set with the UV_INSECURE_HOST environment variable.

+
--cache-dir cache-dir

Path to the cache directory.

+ +

Defaults to $XDG_CACHE_HOME/uv or $HOME/.cache/uv on macOS and Linux, and %LOCALAPPDATA%\uv\cache on Windows.

+ +

To view the location of the cache directory, run uv cache dir.

+ +

May also be set with the UV_CACHE_DIR environment variable.

+
--color color-choice

Control the use of color in output.

+ +

By default, uv will automatically detect support for colors when writing to a terminal.

+ +

Possible values:

+ +
    +
  • auto: Enables colored output only when the output is going to a terminal or TTY with support
  • + +
  • always: Enables colored output regardless of the detected environment
  • + +
  • never: Disables colored output
  • +
+
--config-file config-file

The path to a uv.toml file to use for configuration.

+ +

While uv configuration can be included in a pyproject.toml file, it is not allowed in this context.

+ +

May also be set with the UV_CONFIG_FILE environment variable.

+
--directory directory

Change to the given directory prior to running the command.

+ +

Relative paths are resolved with the given directory as the base.

+ +

See --project to only change the project root directory.

+ +
--help, -h

Display the concise help for this command

+ +
--name name

The name of the index

+ +
--native-tls

Whether to load TLS certificates from the platform’s native certificate store.

+ +

By default, uv loads certificates from the bundled webpki-roots crate. The webpki-roots are a reliable set of trust roots from Mozilla, and including them in uv improves portability and performance (especially on macOS).

+ +

However, in some cases, you may want to use the platform’s native certificate store, especially if you’re relying on a corporate trust root (e.g., for a mandatory proxy) that’s included in your system’s certificate store.

+ +

May also be set with the UV_NATIVE_TLS environment variable.

+
--no-cache, -n

Avoid reading from or writing to the cache, instead using a temporary directory for the duration of the operation

+ +

May also be set with the UV_NO_CACHE environment variable.

+
--no-config

Avoid discovering configuration files (pyproject.toml, uv.toml).

+ +

Normally, configuration files are discovered in the current directory, parent directories, or user configuration directories.

+ +

May also be set with the UV_NO_CONFIG environment variable.

+
--no-progress

Hide all progress outputs.

+ +

For example, spinners or progress bars.

+ +

May also be set with the UV_NO_PROGRESS environment variable.

+
--no-python-downloads

Disable automatic downloads of Python.

+ +
--offline

Disable network access.

+ +

When disabled, uv will only use locally cached data and locally available files.

+ +

May also be set with the UV_OFFLINE environment variable.

+
--project project

Run the command within the given project directory.

+ +

All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project’s virtual environment (.venv).

+ +

Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

+ +

See --directory to change the working directory entirely.

+ +

This setting has no effect when used in the uv pip interface.

+ +
--python-preference python-preference

Whether to prefer uv-managed or system Python installations.

+ +

By default, uv prefers using Python versions it manages. However, it will use system Python installations if a uv-managed Python is not installed. This option allows prioritizing or ignoring system Python installations.

+ +

May also be set with the UV_PYTHON_PREFERENCE environment variable.

+

Possible values:

+ +
    +
  • only-managed: Only use managed Python installations; never use system Python installations
  • + +
  • managed: Prefer managed Python installations over system Python installations
  • + +
  • system: Prefer system Python installations over managed Python installations
  • + +
  • only-system: Only use system Python installations; never use managed Python installations
  • +
+
--quiet, -q

Do not print any output

+ +
--verbose, -v

Use verbose output.

+ +

You can configure fine-grained logging using the RUST_LOG environment variable. (<https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives>)

+ +
--version, -V

Display the uv version

+ +
+ +### uv index credentials + +Manage credentials for the indexes configured in your pyproject.toml + +

Usage

+ +``` +uv index credentials [OPTIONS] +``` + +

Commands

+ +
uv index credentials set

Set credentials for an index

+
+
uv index credentials list

List credentials for each index (Only username is shown)

+
+
uv index credentials unset

Unset the credentials for an index

+
+
+ +#### uv index credentials set + +Set credentials for an index + +

Usage

+ +``` +uv index credentials set [OPTIONS] --name +``` + +

Options

+ +
--allow-insecure-host allow-insecure-host

Allow insecure connections to a host.

+ +

Can be provided multiple times.

+ +

Expects to receive either a hostname (e.g., localhost), a host-port pair (e.g., localhost:8080), or a URL (e.g., https://localhost).

+ +

WARNING: Hosts included in this list will not be verified against the system’s certificate store. Only use --allow-insecure-host in a secure network with verified sources, as it bypasses SSL verification and could expose you to MITM attacks.

+ +

May also be set with the UV_INSECURE_HOST environment variable.

+
--cache-dir cache-dir

Path to the cache directory.

+ +

Defaults to $XDG_CACHE_HOME/uv or $HOME/.cache/uv on macOS and Linux, and %LOCALAPPDATA%\uv\cache on Windows.

+ +

To view the location of the cache directory, run uv cache dir.

+ +

May also be set with the UV_CACHE_DIR environment variable.

+
--color color-choice

Control the use of color in output.

+ +

By default, uv will automatically detect support for colors when writing to a terminal.

+ +

Possible values:

+ +
    +
  • auto: Enables colored output only when the output is going to a terminal or TTY with support
  • + +
  • always: Enables colored output regardless of the detected environment
  • + +
  • never: Disables colored output
  • +
+
--config-file config-file

The path to a uv.toml file to use for configuration.

+ +

While uv configuration can be included in a pyproject.toml file, it is not allowed in this context.

+ +

May also be set with the UV_CONFIG_FILE environment variable.

+
--directory directory

Change to the given directory prior to running the command.

+ +

Relative paths are resolved with the given directory as the base.

+ +

See --project to only change the project root directory.

+ +
--help, -h

Display the concise help for this command

+ +
--keyring-provider keyring-provider

Attempt to use keyring for authentication for remote requirements files.

+ +

At present, only --keyring-provider subprocess is supported, which configures uv to use the keyring CLI to handle authentication.

+ +

Defaults to disabled.

+ +

May also be set with the UV_KEYRING_PROVIDER environment variable.

+

Possible values:

+ +
    +
  • disabled: Do not use keyring for credential lookup
  • + +
  • subprocess: Use the keyring command for credential lookup
  • +
+
--name name

The name of the index

+ +
--native-tls

Whether to load TLS certificates from the platform’s native certificate store.

+ +

By default, uv loads certificates from the bundled webpki-roots crate. The webpki-roots are a reliable set of trust roots from Mozilla, and including them in uv improves portability and performance (especially on macOS).

+ +

However, in some cases, you may want to use the platform’s native certificate store, especially if you’re relying on a corporate trust root (e.g., for a mandatory proxy) that’s included in your system’s certificate store.

+ +

May also be set with the UV_NATIVE_TLS environment variable.

+
--no-cache, -n

Avoid reading from or writing to the cache, instead using a temporary directory for the duration of the operation

+ +

May also be set with the UV_NO_CACHE environment variable.

+
--no-config

Avoid discovering configuration files (pyproject.toml, uv.toml).

+ +

Normally, configuration files are discovered in the current directory, parent directories, or user configuration directories.

+ +

May also be set with the UV_NO_CONFIG environment variable.

+
--no-progress

Hide all progress outputs.

+ +

For example, spinners or progress bars.

+ +

May also be set with the UV_NO_PROGRESS environment variable.

+
--no-python-downloads

Disable automatic downloads of Python.

+ +
--offline

Disable network access.

+ +

When disabled, uv will only use locally cached data and locally available files.

+ +

May also be set with the UV_OFFLINE environment variable.

+
--password password

The password that should be user for the index

+ +
--project project

Run the command within the given project directory.

+ +

All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project’s virtual environment (.venv).

+ +

Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

+ +

See --directory to change the working directory entirely.

+ +

This setting has no effect when used in the uv pip interface.

+ +
--python-preference python-preference

Whether to prefer uv-managed or system Python installations.

+ +

By default, uv prefers using Python versions it manages. However, it will use system Python installations if a uv-managed Python is not installed. This option allows prioritizing or ignoring system Python installations.

+ +

May also be set with the UV_PYTHON_PREFERENCE environment variable.

+

Possible values:

+ +
    +
  • only-managed: Only use managed Python installations; never use system Python installations
  • + +
  • managed: Prefer managed Python installations over system Python installations
  • + +
  • system: Prefer system Python installations over managed Python installations
  • + +
  • only-system: Only use system Python installations; never use managed Python installations
  • +
+
--quiet, -q

Do not print any output

+ +
--username username

The username that should be used for the index

+ +
--verbose, -v

Use verbose output.

+ +

You can configure fine-grained logging using the RUST_LOG environment variable. (<https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives>)

+ +
--version, -V

Display the uv version

+ +
+ +#### uv index credentials list + +List credentials for each index (Only username is shown) + +

Usage

+ +``` +uv index credentials list [OPTIONS] +``` + +

Options

+ +
--allow-insecure-host allow-insecure-host

Allow insecure connections to a host.

+ +

Can be provided multiple times.

+ +

Expects to receive either a hostname (e.g., localhost), a host-port pair (e.g., localhost:8080), or a URL (e.g., https://localhost).

+ +

WARNING: Hosts included in this list will not be verified against the system’s certificate store. Only use --allow-insecure-host in a secure network with verified sources, as it bypasses SSL verification and could expose you to MITM attacks.

+ +

May also be set with the UV_INSECURE_HOST environment variable.

+
--cache-dir cache-dir

Path to the cache directory.

+ +

Defaults to $XDG_CACHE_HOME/uv or $HOME/.cache/uv on macOS and Linux, and %LOCALAPPDATA%\uv\cache on Windows.

+ +

To view the location of the cache directory, run uv cache dir.

+ +

May also be set with the UV_CACHE_DIR environment variable.

+
--color color-choice

Control the use of color in output.

+ +

By default, uv will automatically detect support for colors when writing to a terminal.

+ +

Possible values:

+ +
    +
  • auto: Enables colored output only when the output is going to a terminal or TTY with support
  • + +
  • always: Enables colored output regardless of the detected environment
  • + +
  • never: Disables colored output
  • +
+
--config-file config-file

The path to a uv.toml file to use for configuration.

+ +

While uv configuration can be included in a pyproject.toml file, it is not allowed in this context.

+ +

May also be set with the UV_CONFIG_FILE environment variable.

+
--directory directory

Change to the given directory prior to running the command.

+ +

Relative paths are resolved with the given directory as the base.

+ +

See --project to only change the project root directory.

+ +
--help, -h

Display the concise help for this command

+ +
--keyring-provider keyring-provider

Attempt to use keyring for authentication for remote requirements files.

+ +

At present, only --keyring-provider subprocess is supported, which configures uv to use the keyring CLI to handle authentication.

+ +

Defaults to disabled.

+ +

May also be set with the UV_KEYRING_PROVIDER environment variable.

+

Possible values:

+ +
    +
  • disabled: Do not use keyring for credential lookup
  • + +
  • subprocess: Use the keyring command for credential lookup
  • +
+
--native-tls

Whether to load TLS certificates from the platform’s native certificate store.

+ +

By default, uv loads certificates from the bundled webpki-roots crate. The webpki-roots are a reliable set of trust roots from Mozilla, and including them in uv improves portability and performance (especially on macOS).

+ +

However, in some cases, you may want to use the platform’s native certificate store, especially if you’re relying on a corporate trust root (e.g., for a mandatory proxy) that’s included in your system’s certificate store.

+ +

May also be set with the UV_NATIVE_TLS environment variable.

+
--no-cache, -n

Avoid reading from or writing to the cache, instead using a temporary directory for the duration of the operation

+ +

May also be set with the UV_NO_CACHE environment variable.

+
--no-config

Avoid discovering configuration files (pyproject.toml, uv.toml).

+ +

Normally, configuration files are discovered in the current directory, parent directories, or user configuration directories.

+ +

May also be set with the UV_NO_CONFIG environment variable.

+
--no-progress

Hide all progress outputs.

+ +

For example, spinners or progress bars.

+ +

May also be set with the UV_NO_PROGRESS environment variable.

+
--no-python-downloads

Disable automatic downloads of Python.

+ +
--offline

Disable network access.

+ +

When disabled, uv will only use locally cached data and locally available files.

+ +

May also be set with the UV_OFFLINE environment variable.

+
--project project

Run the command within the given project directory.

+ +

All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project’s virtual environment (.venv).

+ +

Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

+ +

See --directory to change the working directory entirely.

+ +

This setting has no effect when used in the uv pip interface.

+ +
--python-preference python-preference

Whether to prefer uv-managed or system Python installations.

+ +

By default, uv prefers using Python versions it manages. However, it will use system Python installations if a uv-managed Python is not installed. This option allows prioritizing or ignoring system Python installations.

+ +

May also be set with the UV_PYTHON_PREFERENCE environment variable.

+

Possible values:

+ +
    +
  • only-managed: Only use managed Python installations; never use system Python installations
  • + +
  • managed: Prefer managed Python installations over system Python installations
  • + +
  • system: Prefer system Python installations over managed Python installations
  • + +
  • only-system: Only use system Python installations; never use managed Python installations
  • +
+
--quiet, -q

Do not print any output

+ +
--verbose, -v

Use verbose output.

+ +

You can configure fine-grained logging using the RUST_LOG environment variable. (<https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives>)

+ +
--version, -V

Display the uv version

+ +
+ +#### uv index credentials unset + +Unset the credentials for an index + +

Usage

+ +``` +uv index credentials unset [OPTIONS] --name +``` + +

Options

+ +
--allow-insecure-host allow-insecure-host

Allow insecure connections to a host.

+ +

Can be provided multiple times.

+ +

Expects to receive either a hostname (e.g., localhost), a host-port pair (e.g., localhost:8080), or a URL (e.g., https://localhost).

+ +

WARNING: Hosts included in this list will not be verified against the system’s certificate store. Only use --allow-insecure-host in a secure network with verified sources, as it bypasses SSL verification and could expose you to MITM attacks.

+ +

May also be set with the UV_INSECURE_HOST environment variable.

+
--cache-dir cache-dir

Path to the cache directory.

+ +

Defaults to $XDG_CACHE_HOME/uv or $HOME/.cache/uv on macOS and Linux, and %LOCALAPPDATA%\uv\cache on Windows.

+ +

To view the location of the cache directory, run uv cache dir.

+ +

May also be set with the UV_CACHE_DIR environment variable.

+
--color color-choice

Control the use of color in output.

+ +

By default, uv will automatically detect support for colors when writing to a terminal.

+ +

Possible values:

+ +
    +
  • auto: Enables colored output only when the output is going to a terminal or TTY with support
  • + +
  • always: Enables colored output regardless of the detected environment
  • + +
  • never: Disables colored output
  • +
+
--config-file config-file

The path to a uv.toml file to use for configuration.

+ +

While uv configuration can be included in a pyproject.toml file, it is not allowed in this context.

+ +

May also be set with the UV_CONFIG_FILE environment variable.

+
--directory directory

Change to the given directory prior to running the command.

+ +

Relative paths are resolved with the given directory as the base.

+ +

See --project to only change the project root directory.

+ +
--help, -h

Display the concise help for this command

+ +
--keyring-provider keyring-provider

Attempt to use keyring for authentication for remote requirements files.

+ +

At present, only --keyring-provider subprocess is supported, which configures uv to use the keyring CLI to handle authentication.

+ +

Defaults to disabled.

+ +

May also be set with the UV_KEYRING_PROVIDER environment variable.

+

Possible values:

+ +
    +
  • disabled: Do not use keyring for credential lookup
  • + +
  • subprocess: Use the keyring command for credential lookup
  • +
+
--name name

The name of the index

+ +
--native-tls

Whether to load TLS certificates from the platform’s native certificate store.

+ +

By default, uv loads certificates from the bundled webpki-roots crate. The webpki-roots are a reliable set of trust roots from Mozilla, and including them in uv improves portability and performance (especially on macOS).

+ +

However, in some cases, you may want to use the platform’s native certificate store, especially if you’re relying on a corporate trust root (e.g., for a mandatory proxy) that’s included in your system’s certificate store.

+ +

May also be set with the UV_NATIVE_TLS environment variable.

+
--no-cache, -n

Avoid reading from or writing to the cache, instead using a temporary directory for the duration of the operation

+ +

May also be set with the UV_NO_CACHE environment variable.

+
--no-config

Avoid discovering configuration files (pyproject.toml, uv.toml).

+ +

Normally, configuration files are discovered in the current directory, parent directories, or user configuration directories.

+ +

May also be set with the UV_NO_CONFIG environment variable.

+
--no-progress

Hide all progress outputs.

+ +

For example, spinners or progress bars.

+ +

May also be set with the UV_NO_PROGRESS environment variable.

+
--no-python-downloads

Disable automatic downloads of Python.

+ +
--offline

Disable network access.

+ +

When disabled, uv will only use locally cached data and locally available files.

+ +

May also be set with the UV_OFFLINE environment variable.

+
--project project

Run the command within the given project directory.

+ +

All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project’s virtual environment (.venv).

+ +

Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

+ +

See --directory to change the working directory entirely.

+ +

This setting has no effect when used in the uv pip interface.

+ +
--python-preference python-preference

Whether to prefer uv-managed or system Python installations.

+ +

By default, uv prefers using Python versions it manages. However, it will use system Python installations if a uv-managed Python is not installed. This option allows prioritizing or ignoring system Python installations.

+ +

May also be set with the UV_PYTHON_PREFERENCE environment variable.

+

Possible values:

+ +
    +
  • only-managed: Only use managed Python installations; never use system Python installations
  • + +
  • managed: Prefer managed Python installations over system Python installations
  • + +
  • system: Prefer system Python installations over managed Python installations
  • + +
  • only-system: Only use system Python installations; never use managed Python installations
  • +
+
--quiet, -q

Do not print any output

+ +
--username username

The username that should be used for the index

+ +
--verbose, -v

Use verbose output.

+ +

You can configure fine-grained logging using the RUST_LOG environment variable. (<https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives>)

+ +
--version, -V

Display the uv version

+ +
+ From 19a8d64954240aa1d8db40858c1f54cef590c6c2 Mon Sep 17 00:00:00 2001 From: Simon Steinheber Date: Sun, 2 Feb 2025 20:00:43 +0100 Subject: [PATCH 23/28] Format code --- crates/uv-auth/src/keyring_config.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/uv-auth/src/keyring_config.rs b/crates/uv-auth/src/keyring_config.rs index 53dd1d721a0f..6248f5312fa7 100644 --- a/crates/uv-auth/src/keyring_config.rs +++ b/crates/uv-auth/src/keyring_config.rs @@ -127,7 +127,9 @@ impl AuthConfig { port ) } else { - url.host_str().expect(&format!("Url {url:?} has no host")).to_string() + url.host_str() + .expect(&format!("Url {url:?} has no host")) + .to_string() }; host } From 77a845119b9653d2a0ce1389885911f9ef6cb696 Mon Sep 17 00:00:00 2001 From: Simon Steinheber Date: Sun, 2 Feb 2025 20:11:32 +0100 Subject: [PATCH 24/28] Fix failing CI jobs --- crates/uv-auth/src/keyring_config.rs | 5 +++-- docs/reference/cli.md | 6 +++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/crates/uv-auth/src/keyring_config.rs b/crates/uv-auth/src/keyring_config.rs index 6248f5312fa7..f1c7e917ab9d 100644 --- a/crates/uv-auth/src/keyring_config.rs +++ b/crates/uv-auth/src/keyring_config.rs @@ -123,12 +123,13 @@ impl AuthConfig { let host = if let Some(port) = url.port() { format!( "{}:{}", - url.host_str().expect(&format!("Url {url:?} has no host")), + url.host_str() + .unwrap_or_else(|| panic!("Url {url:?} has no host")), port ) } else { url.host_str() - .expect(&format!("Url {url:?} has no host")) + .unwrap_or_else(|| panic!("Url {url:?} has no host")) .to_string() }; host diff --git a/docs/reference/cli.md b/docs/reference/cli.md index e6481c1a54c0..6cdba695d12a 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -48,7 +48,9 @@ uv [OPTIONS]
uv help

Display documentation for a command

-
uv index
+
uv index

Manage uv’s indexes

+
+ ## uv run @@ -9481,6 +9483,8 @@ uv help [OPTIONS] [COMMAND]... ## uv index +Manage uv's indexes +

Usage

``` From 6b5e47c7b3287048719e19f6dd9db4b0093b69bd Mon Sep 17 00:00:00 2001 From: Simon Steinheber Date: Fri, 28 Feb 2025 15:14:20 +0100 Subject: [PATCH 25/28] Resolve clippy complaints --- crates/uv-auth/src/credentials.rs | 2 +- crates/uv-cli/src/lib.rs | 36 ++++++++++++------------ crates/uv/src/commands/index.rs | 30 ++++++++++++-------- crates/uv/src/lib.rs | 46 +++++++++++++++---------------- crates/uv/src/settings.rs | 2 +- 5 files changed, 62 insertions(+), 54 deletions(-) diff --git a/crates/uv-auth/src/credentials.rs b/crates/uv-auth/src/credentials.rs index cdb1e6a51130..09ad34e35d23 100644 --- a/crates/uv-auth/src/credentials.rs +++ b/crates/uv-auth/src/credentials.rs @@ -332,7 +332,7 @@ mod tests { auth_config.add_entry(url, username.to_string()); auth_config.store().unwrap(); - let credentials = Credentials::from_url(&url).unwrap(); + let credentials = Credentials::from_url(url).unwrap(); assert_eq!(credentials.username(), Some(username)); assert_eq!(credentials.password(), None); diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 4abdeb05fcff..d3003fc1fda7 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -5430,24 +5430,24 @@ pub struct IndexNamespace { #[derive(Subcommand)] pub enum IndexCommand { - /// Set a new index. This will be added to your pyproject.toml - #[command( - after_help = "Use `uv help index add` for more details.", - after_long_help = "" - )] - Set(IndexSourceArgs), - /// List all indexes set in your pyproject.toml - #[command( - after_help = "Use `uv help index list` for more details.", - after_long_help = "" - )] - List(IndexSourceArgs), - /// Unset an existing index. This will be removed from your pyproject.toml - #[command( - after_help = "Use `uv help index delete` for more details.", - after_long_help = "" - )] - Unset(IndexSourceArgs), + // /// Set a new index. This will be added to your pyproject.toml + // #[command( + // after_help = "Use `uv help index add` for more details.", + // after_long_help = "" + // )] + // Set(IndexSourceArgs), + // /// List all indexes set in your pyproject.toml + // #[command( + // after_help = "Use `uv help index list` for more details.", + // after_long_help = "" + // )] + // List(IndexSourceArgs), + // /// Unset an existing index. This will be removed from your pyproject.toml + // #[command( + // after_help = "Use `uv help index delete` for more details.", + // after_long_help = "" + // )] + // Unset(IndexSourceArgs), /// Manage credentials for the indexes configured in your pyproject.toml #[command(subcommand)] diff --git a/crates/uv/src/commands/index.rs b/crates/uv/src/commands/index.rs index 098c1624d621..89c7283749d6 100644 --- a/crates/uv/src/commands/index.rs +++ b/crates/uv/src/commands/index.rs @@ -4,6 +4,10 @@ use tracing::{debug, warn}; use uv_auth::{AuthConfig, ConfigFile}; use uv_configuration::KeyringProviderType; use uv_distribution_types::Index; +use std::fmt::Write; +use owo_colors::OwoColorize; + +use crate::printer::Printer; /// Add one or more packages to the project requirements. #[allow(clippy::fn_params_excessive_bools)] @@ -66,6 +70,7 @@ pub(crate) async fn set_credentials( pub(crate) async fn list_credentials( keyring_provider_type: KeyringProviderType, indexes: Vec, + printer: Printer ) -> Result<()> { let auth_config = AuthConfig::load().inspect_err(|err| warn!("Could not load auth config due to: {err}"))?; @@ -84,13 +89,19 @@ pub(crate) async fn list_credentials( let password = keyring_provider.fetch(&index.url, &username).await; let index_name = index.name.expect("Index should have a name").to_string(); - match password { - Some(_) => println!( - "Index: '{}' authenticates with username '{}'.", - index_name, username + let _ = match password { + Some(_) => writeln!( + printer.stderr(), + "{} authenticates with username {}", + format!("Index: {index_name}").bold(), + username, + ), + None => writeln!( + printer.stderr(), + "{} has no credentials.", + format!("Index: {index_name}").bold() ), - None => println!("Index: '{}' has no credentials.", index_name), - } + }; } else { debug!("Could not find the index with url {index_url} in auth config"); } @@ -112,11 +123,8 @@ pub(crate) async fn unset_credentials( .unwrap_or(false) }); - let index = match index { - Some(obj) => obj, - None => panic!("No index found with the name '{}'", name), - }; - + let Some(index) = index else {panic!("No index found with the name '{name}'")}; + let username = match username { Some(n) => n, None => match prompt_username_input()? { diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index 2b9709c04143..934ee1173147 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -1387,27 +1387,27 @@ async fn run(mut cli: Cli) -> Result { }) .await .expect("tokio threadpool exited unexpectedly"), - Commands::Index(IndexNamespace { - command: IndexCommand::Set(args), - }) => { - println!("This is not implemented yet!"); - println!("Set an index with name {}", args.name); - return Ok(ExitStatus::Success); - } - Commands::Index(IndexNamespace { - command: IndexCommand::List(_), - }) => { - println!("This is not implemented yet!"); - println!("List all indexes"); - return Ok(ExitStatus::Success); - } - Commands::Index(IndexNamespace { - command: IndexCommand::Unset(args), - }) => { - println!("This is not implemented yet!"); - println!("Unset index {}", args.name); - return Ok(ExitStatus::Success); - } + // Commands::Index(IndexNamespace { + // command: IndexCommand::Set(args), + // }) => { + // println!("This is not implemented yet!"); + // println!("Set an index with name {}", args.name); + // return Ok(ExitStatus::Success); + // } + // Commands::Index(IndexNamespace { + // command: IndexCommand::List(_), + // }) => { + // println!("This is not implemented yet!"); + // println!("List all indexes"); + // return Ok(ExitStatus::Success); + // } + // Commands::Index(IndexNamespace { + // command: IndexCommand::Unset(args), + // }) => { + // println!("This is not implemented yet!"); + // println!("Unset index {}", args.name); + // return Ok(ExitStatus::Success); + // } Commands::Index(IndexNamespace { command: IndexCommand::Credentials(IndexCredentialsCommand::Set(args)), }) => { @@ -1428,9 +1428,9 @@ async fn run(mut cli: Cli) -> Result { let IndexListCredentialsSettings { keyring_provider, index, - } = IndexListCredentialsSettings::resolve(args, filesystem); + } = IndexListCredentialsSettings::resolve(&args, filesystem); - let _ = list_credentials(keyring_provider, index).await; + let _ = list_credentials(keyring_provider, index, printer).await; return Ok(ExitStatus::Success); } Commands::Index(IndexNamespace { diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index c90d4ad32fdd..d419b20a208b 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -3087,7 +3087,7 @@ pub(crate) struct IndexListCredentialsSettings { impl IndexListCredentialsSettings { /// Resolve the [`IndexSettings`] from the CLI and filesystem configuration. pub(crate) fn resolve( - args: IndexListCredentialsArgs, + args: &IndexListCredentialsArgs, filesystem: Option, ) -> Self { let Options { top_level, .. } = filesystem From 7271466322b8b7bc44066689bccf9f517b97935a Mon Sep 17 00:00:00 2001 From: Simon Steinheber Date: Fri, 28 Feb 2025 15:17:13 +0100 Subject: [PATCH 26/28] Format changes --- Cargo.lock | 1 - crates/uv-cli/src/lib.rs | 1 - crates/uv/src/commands/index.rs | 12 +++++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0bdd6dc784d8..2288abe6ae7f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1945,7 +1945,6 @@ name = "jiff" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3590fea8e9e22d449600c9bbd481a8163bef223e4ff938e5f55899f8cf1adb93" - dependencies = [ "jiff-tzdb-platform", "log", diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 12a38651f75e..ad3649e94453 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -5599,7 +5599,6 @@ pub enum IndexCommand { // after_long_help = "" // )] // Unset(IndexSourceArgs), - /// Manage credentials for the indexes configured in your pyproject.toml #[command(subcommand)] Credentials(IndexCredentialsCommand), diff --git a/crates/uv/src/commands/index.rs b/crates/uv/src/commands/index.rs index 89c7283749d6..619d74ddb8f1 100644 --- a/crates/uv/src/commands/index.rs +++ b/crates/uv/src/commands/index.rs @@ -1,11 +1,11 @@ use anyhow::{Context, Ok, Result}; use console::Term; +use owo_colors::OwoColorize; +use std::fmt::Write; use tracing::{debug, warn}; use uv_auth::{AuthConfig, ConfigFile}; use uv_configuration::KeyringProviderType; use uv_distribution_types::Index; -use std::fmt::Write; -use owo_colors::OwoColorize; use crate::printer::Printer; @@ -70,7 +70,7 @@ pub(crate) async fn set_credentials( pub(crate) async fn list_credentials( keyring_provider_type: KeyringProviderType, indexes: Vec, - printer: Printer + printer: Printer, ) -> Result<()> { let auth_config = AuthConfig::load().inspect_err(|err| warn!("Could not load auth config due to: {err}"))?; @@ -123,8 +123,10 @@ pub(crate) async fn unset_credentials( .unwrap_or(false) }); - let Some(index) = index else {panic!("No index found with the name '{name}'")}; - + let Some(index) = index else { + panic!("No index found with the name '{name}'") + }; + let username = match username { Some(n) => n, None => match prompt_username_input()? { From 12654111c0c67608b6372bf720164bcc4fb14fae Mon Sep 17 00:00:00 2001 From: Simon Steinheber Date: Fri, 28 Feb 2025 15:21:19 +0100 Subject: [PATCH 27/28] Generate cli reference --- docs/reference/cli.md | 483 ++++++------------------------------------ 1 file changed, 60 insertions(+), 423 deletions(-) diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 0a7d984eb067..7a246a60abbe 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -9557,373 +9557,10 @@ uv index [OPTIONS]

Commands

-
uv index set

Set a new index. This will be added to your pyproject.toml

-
-
uv index list

List all indexes set in your pyproject.toml

-
-
uv index unset

Unset an existing index. This will be removed from your pyproject.toml

-
-
uv index credentials

Manage credentials for the indexes configured in your pyproject.toml

+
uv index credentials

Manage credentials for the indexes configured in your pyproject.toml

-### uv index set - -Set a new index. This will be added to your pyproject.toml - -

Usage

- -``` -uv index set [OPTIONS] --name -``` - -

Options

- -
--allow-insecure-host allow-insecure-host

Allow insecure connections to a host.

- -

Can be provided multiple times.

- -

Expects to receive either a hostname (e.g., localhost), a host-port pair (e.g., localhost:8080), or a URL (e.g., https://localhost).

- -

WARNING: Hosts included in this list will not be verified against the system’s certificate store. Only use --allow-insecure-host in a secure network with verified sources, as it bypasses SSL verification and could expose you to MITM attacks.

- -

May also be set with the UV_INSECURE_HOST environment variable.

-
--cache-dir cache-dir

Path to the cache directory.

- -

Defaults to $XDG_CACHE_HOME/uv or $HOME/.cache/uv on macOS and Linux, and %LOCALAPPDATA%\uv\cache on Windows.

- -

To view the location of the cache directory, run uv cache dir.

- -

May also be set with the UV_CACHE_DIR environment variable.

-
--color color-choice

Control the use of color in output.

- -

By default, uv will automatically detect support for colors when writing to a terminal.

- -

Possible values:

- -
    -
  • auto: Enables colored output only when the output is going to a terminal or TTY with support
  • - -
  • always: Enables colored output regardless of the detected environment
  • - -
  • never: Disables colored output
  • -
-
--config-file config-file

The path to a uv.toml file to use for configuration.

- -

While uv configuration can be included in a pyproject.toml file, it is not allowed in this context.

- -

May also be set with the UV_CONFIG_FILE environment variable.

-
--directory directory

Change to the given directory prior to running the command.

- -

Relative paths are resolved with the given directory as the base.

- -

See --project to only change the project root directory.

- -
--help, -h

Display the concise help for this command

- -
--name name

The name of the index

- -
--native-tls

Whether to load TLS certificates from the platform’s native certificate store.

- -

By default, uv loads certificates from the bundled webpki-roots crate. The webpki-roots are a reliable set of trust roots from Mozilla, and including them in uv improves portability and performance (especially on macOS).

- -

However, in some cases, you may want to use the platform’s native certificate store, especially if you’re relying on a corporate trust root (e.g., for a mandatory proxy) that’s included in your system’s certificate store.

- -

May also be set with the UV_NATIVE_TLS environment variable.

-
--no-cache, -n

Avoid reading from or writing to the cache, instead using a temporary directory for the duration of the operation

- -

May also be set with the UV_NO_CACHE environment variable.

-
--no-config

Avoid discovering configuration files (pyproject.toml, uv.toml).

- -

Normally, configuration files are discovered in the current directory, parent directories, or user configuration directories.

- -

May also be set with the UV_NO_CONFIG environment variable.

-
--no-progress

Hide all progress outputs.

- -

For example, spinners or progress bars.

- -

May also be set with the UV_NO_PROGRESS environment variable.

-
--no-python-downloads

Disable automatic downloads of Python.

- -
--offline

Disable network access.

- -

When disabled, uv will only use locally cached data and locally available files.

- -

May also be set with the UV_OFFLINE environment variable.

-
--project project

Run the command within the given project directory.

- -

All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project’s virtual environment (.venv).

- -

Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

- -

See --directory to change the working directory entirely.

- -

This setting has no effect when used in the uv pip interface.

- -
--python-preference python-preference

Whether to prefer uv-managed or system Python installations.

- -

By default, uv prefers using Python versions it manages. However, it will use system Python installations if a uv-managed Python is not installed. This option allows prioritizing or ignoring system Python installations.

- -

May also be set with the UV_PYTHON_PREFERENCE environment variable.

-

Possible values:

- -
    -
  • only-managed: Only use managed Python installations; never use system Python installations
  • - -
  • managed: Prefer managed Python installations over system Python installations
  • - -
  • system: Prefer system Python installations over managed Python installations
  • - -
  • only-system: Only use system Python installations; never use managed Python installations
  • -
-
--quiet, -q

Do not print any output

- -
--verbose, -v

Use verbose output.

- -

You can configure fine-grained logging using the RUST_LOG environment variable. (<https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives>)

- -
--version, -V

Display the uv version

- -
- -### uv index list - -List all indexes set in your pyproject.toml - -

Usage

- -``` -uv index list [OPTIONS] --name -``` - -

Options

- -
--allow-insecure-host allow-insecure-host

Allow insecure connections to a host.

- -

Can be provided multiple times.

- -

Expects to receive either a hostname (e.g., localhost), a host-port pair (e.g., localhost:8080), or a URL (e.g., https://localhost).

- -

WARNING: Hosts included in this list will not be verified against the system’s certificate store. Only use --allow-insecure-host in a secure network with verified sources, as it bypasses SSL verification and could expose you to MITM attacks.

- -

May also be set with the UV_INSECURE_HOST environment variable.

-
--cache-dir cache-dir

Path to the cache directory.

- -

Defaults to $XDG_CACHE_HOME/uv or $HOME/.cache/uv on macOS and Linux, and %LOCALAPPDATA%\uv\cache on Windows.

- -

To view the location of the cache directory, run uv cache dir.

- -

May also be set with the UV_CACHE_DIR environment variable.

-
--color color-choice

Control the use of color in output.

- -

By default, uv will automatically detect support for colors when writing to a terminal.

- -

Possible values:

- -
    -
  • auto: Enables colored output only when the output is going to a terminal or TTY with support
  • - -
  • always: Enables colored output regardless of the detected environment
  • - -
  • never: Disables colored output
  • -
-
--config-file config-file

The path to a uv.toml file to use for configuration.

- -

While uv configuration can be included in a pyproject.toml file, it is not allowed in this context.

- -

May also be set with the UV_CONFIG_FILE environment variable.

-
--directory directory

Change to the given directory prior to running the command.

- -

Relative paths are resolved with the given directory as the base.

- -

See --project to only change the project root directory.

- -
--help, -h

Display the concise help for this command

- -
--name name

The name of the index

- -
--native-tls

Whether to load TLS certificates from the platform’s native certificate store.

- -

By default, uv loads certificates from the bundled webpki-roots crate. The webpki-roots are a reliable set of trust roots from Mozilla, and including them in uv improves portability and performance (especially on macOS).

- -

However, in some cases, you may want to use the platform’s native certificate store, especially if you’re relying on a corporate trust root (e.g., for a mandatory proxy) that’s included in your system’s certificate store.

- -

May also be set with the UV_NATIVE_TLS environment variable.

-
--no-cache, -n

Avoid reading from or writing to the cache, instead using a temporary directory for the duration of the operation

- -

May also be set with the UV_NO_CACHE environment variable.

-
--no-config

Avoid discovering configuration files (pyproject.toml, uv.toml).

- -

Normally, configuration files are discovered in the current directory, parent directories, or user configuration directories.

- -

May also be set with the UV_NO_CONFIG environment variable.

-
--no-progress

Hide all progress outputs.

- -

For example, spinners or progress bars.

- -

May also be set with the UV_NO_PROGRESS environment variable.

-
--no-python-downloads

Disable automatic downloads of Python.

- -
--offline

Disable network access.

- -

When disabled, uv will only use locally cached data and locally available files.

- -

May also be set with the UV_OFFLINE environment variable.

-
--project project

Run the command within the given project directory.

- -

All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project’s virtual environment (.venv).

- -

Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

- -

See --directory to change the working directory entirely.

- -

This setting has no effect when used in the uv pip interface.

- -
--python-preference python-preference

Whether to prefer uv-managed or system Python installations.

- -

By default, uv prefers using Python versions it manages. However, it will use system Python installations if a uv-managed Python is not installed. This option allows prioritizing or ignoring system Python installations.

- -

May also be set with the UV_PYTHON_PREFERENCE environment variable.

-

Possible values:

- -
    -
  • only-managed: Only use managed Python installations; never use system Python installations
  • - -
  • managed: Prefer managed Python installations over system Python installations
  • - -
  • system: Prefer system Python installations over managed Python installations
  • - -
  • only-system: Only use system Python installations; never use managed Python installations
  • -
-
--quiet, -q

Do not print any output

- -
--verbose, -v

Use verbose output.

- -

You can configure fine-grained logging using the RUST_LOG environment variable. (<https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives>)

- -
--version, -V

Display the uv version

- -
- -### uv index unset - -Unset an existing index. This will be removed from your pyproject.toml - -

Usage

- -``` -uv index unset [OPTIONS] --name -``` - -

Options

- -
--allow-insecure-host allow-insecure-host

Allow insecure connections to a host.

- -

Can be provided multiple times.

- -

Expects to receive either a hostname (e.g., localhost), a host-port pair (e.g., localhost:8080), or a URL (e.g., https://localhost).

- -

WARNING: Hosts included in this list will not be verified against the system’s certificate store. Only use --allow-insecure-host in a secure network with verified sources, as it bypasses SSL verification and could expose you to MITM attacks.

- -

May also be set with the UV_INSECURE_HOST environment variable.

-
--cache-dir cache-dir

Path to the cache directory.

- -

Defaults to $XDG_CACHE_HOME/uv or $HOME/.cache/uv on macOS and Linux, and %LOCALAPPDATA%\uv\cache on Windows.

- -

To view the location of the cache directory, run uv cache dir.

- -

May also be set with the UV_CACHE_DIR environment variable.

-
--color color-choice

Control the use of color in output.

- -

By default, uv will automatically detect support for colors when writing to a terminal.

- -

Possible values:

- -
    -
  • auto: Enables colored output only when the output is going to a terminal or TTY with support
  • - -
  • always: Enables colored output regardless of the detected environment
  • - -
  • never: Disables colored output
  • -
-
--config-file config-file

The path to a uv.toml file to use for configuration.

- -

While uv configuration can be included in a pyproject.toml file, it is not allowed in this context.

- -

May also be set with the UV_CONFIG_FILE environment variable.

-
--directory directory

Change to the given directory prior to running the command.

- -

Relative paths are resolved with the given directory as the base.

- -

See --project to only change the project root directory.

- -
--help, -h

Display the concise help for this command

- -
--name name

The name of the index

- -
--native-tls

Whether to load TLS certificates from the platform’s native certificate store.

- -

By default, uv loads certificates from the bundled webpki-roots crate. The webpki-roots are a reliable set of trust roots from Mozilla, and including them in uv improves portability and performance (especially on macOS).

- -

However, in some cases, you may want to use the platform’s native certificate store, especially if you’re relying on a corporate trust root (e.g., for a mandatory proxy) that’s included in your system’s certificate store.

- -

May also be set with the UV_NATIVE_TLS environment variable.

-
--no-cache, -n

Avoid reading from or writing to the cache, instead using a temporary directory for the duration of the operation

- -

May also be set with the UV_NO_CACHE environment variable.

-
--no-config

Avoid discovering configuration files (pyproject.toml, uv.toml).

- -

Normally, configuration files are discovered in the current directory, parent directories, or user configuration directories.

- -

May also be set with the UV_NO_CONFIG environment variable.

-
--no-progress

Hide all progress outputs.

- -

For example, spinners or progress bars.

- -

May also be set with the UV_NO_PROGRESS environment variable.

-
--no-python-downloads

Disable automatic downloads of Python.

- -
--offline

Disable network access.

- -

When disabled, uv will only use locally cached data and locally available files.

- -

May also be set with the UV_OFFLINE environment variable.

-
--project project

Run the command within the given project directory.

- -

All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project’s virtual environment (.venv).

- -

Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

- -

See --directory to change the working directory entirely.

- -

This setting has no effect when used in the uv pip interface.

- -
--python-preference python-preference

Whether to prefer uv-managed or system Python installations.

- -

By default, uv prefers using Python versions it manages. However, it will use system Python installations if a uv-managed Python is not installed. This option allows prioritizing or ignoring system Python installations.

- -

May also be set with the UV_PYTHON_PREFERENCE environment variable.

-

Possible values:

- -
    -
  • only-managed: Only use managed Python installations; never use system Python installations
  • - -
  • managed: Prefer managed Python installations over system Python installations
  • - -
  • system: Prefer system Python installations over managed Python installations
  • - -
  • only-system: Only use system Python installations; never use managed Python installations
  • -
-
--quiet, -q

Do not print any output

- -
--verbose, -v

Use verbose output.

- -

You can configure fine-grained logging using the RUST_LOG environment variable. (<https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives>)

- -
--version, -V

Display the uv version

- -
- ### uv index credentials Manage credentials for the indexes configured in your pyproject.toml @@ -9956,7 +9593,7 @@ uv index credentials set [OPTIONS] --name

Options

-
--allow-insecure-host allow-insecure-host

Allow insecure connections to a host.

+
--allow-insecure-host allow-insecure-host

Allow insecure connections to a host.

Can be provided multiple times.

@@ -9965,14 +9602,14 @@ uv index credentials set [OPTIONS] --name

WARNING: Hosts included in this list will not be verified against the system’s certificate store. Only use --allow-insecure-host in a secure network with verified sources, as it bypasses SSL verification and could expose you to MITM attacks.

May also be set with the UV_INSECURE_HOST environment variable.

-
--cache-dir cache-dir

Path to the cache directory.

+
--cache-dir cache-dir

Path to the cache directory.

Defaults to $XDG_CACHE_HOME/uv or $HOME/.cache/uv on macOS and Linux, and %LOCALAPPDATA%\uv\cache on Windows.

To view the location of the cache directory, run uv cache dir.

May also be set with the UV_CACHE_DIR environment variable.

-
--color color-choice

Control the use of color in output.

+
--color color-choice

Control the use of color in output.

By default, uv will automatically detect support for colors when writing to a terminal.

@@ -9985,20 +9622,20 @@ uv index credentials set [OPTIONS] --name
  • never: Disables colored output
  • -
    --config-file config-file

    The path to a uv.toml file to use for configuration.

    +
    --config-file config-file

    The path to a uv.toml file to use for configuration.

    While uv configuration can be included in a pyproject.toml file, it is not allowed in this context.

    May also be set with the UV_CONFIG_FILE environment variable.

    -
    --directory directory

    Change to the given directory prior to running the command.

    +
    --directory directory

    Change to the given directory prior to running the command.

    Relative paths are resolved with the given directory as the base.

    See --project to only change the project root directory.

    -
    --help, -h

    Display the concise help for this command

    +
    --help, -h

    Display the concise help for this command

    -
    --keyring-provider keyring-provider

    Attempt to use keyring for authentication for remote requirements files.

    +
    --keyring-provider keyring-provider

    Attempt to use keyring for authentication for remote requirements files.

    At present, only --keyring-provider subprocess is supported, which configures uv to use the keyring CLI to handle authentication.

    @@ -10012,38 +9649,38 @@ uv index credentials set [OPTIONS] --name
  • subprocess: Use the keyring command for credential lookup
  • -
    --name name

    The name of the index

    +
    --name name

    The name of the index

    -
    --native-tls

    Whether to load TLS certificates from the platform’s native certificate store.

    +
    --native-tls

    Whether to load TLS certificates from the platform’s native certificate store.

    By default, uv loads certificates from the bundled webpki-roots crate. The webpki-roots are a reliable set of trust roots from Mozilla, and including them in uv improves portability and performance (especially on macOS).

    However, in some cases, you may want to use the platform’s native certificate store, especially if you’re relying on a corporate trust root (e.g., for a mandatory proxy) that’s included in your system’s certificate store.

    May also be set with the UV_NATIVE_TLS environment variable.

    -
    --no-cache, -n

    Avoid reading from or writing to the cache, instead using a temporary directory for the duration of the operation

    +
    --no-cache, -n

    Avoid reading from or writing to the cache, instead using a temporary directory for the duration of the operation

    May also be set with the UV_NO_CACHE environment variable.

    -
    --no-config

    Avoid discovering configuration files (pyproject.toml, uv.toml).

    +
    --no-config

    Avoid discovering configuration files (pyproject.toml, uv.toml).

    Normally, configuration files are discovered in the current directory, parent directories, or user configuration directories.

    May also be set with the UV_NO_CONFIG environment variable.

    -
    --no-progress

    Hide all progress outputs.

    +
    --no-progress

    Hide all progress outputs.

    For example, spinners or progress bars.

    May also be set with the UV_NO_PROGRESS environment variable.

    -
    --no-python-downloads

    Disable automatic downloads of Python.

    +
    --no-python-downloads

    Disable automatic downloads of Python.

    -
    --offline

    Disable network access.

    +
    --offline

    Disable network access.

    When disabled, uv will only use locally cached data and locally available files.

    May also be set with the UV_OFFLINE environment variable.

    -
    --password password

    The password that should be user for the index

    +
    --password password

    The password that should be user for the index

    -
    --project project

    Run the command within the given project directory.

    +
    --project project

    Run the command within the given project directory.

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project’s virtual environment (.venv).

    @@ -10053,7 +9690,7 @@ uv index credentials set [OPTIONS] --name

    This setting has no effect when used in the uv pip interface.

    -
    --python-preference python-preference

    Whether to prefer uv-managed or system Python installations.

    +
    --python-preference python-preference

    Whether to prefer uv-managed or system Python installations.

    By default, uv prefers using Python versions it manages. However, it will use system Python installations if a uv-managed Python is not installed. This option allows prioritizing or ignoring system Python installations.

    @@ -10069,15 +9706,15 @@ uv index credentials set [OPTIONS] --name
  • only-system: Only use system Python installations; never use managed Python installations
  • -
    --quiet, -q

    Do not print any output

    +
    --quiet, -q

    Do not print any output

    -
    --username username

    The username that should be used for the index

    +
    --username username

    The username that should be used for the index

    -
    --verbose, -v

    Use verbose output.

    +
    --verbose, -v

    Use verbose output.

    You can configure fine-grained logging using the RUST_LOG environment variable. (<https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives>)

    -
    --version, -V

    Display the uv version

    +
    --version, -V

    Display the uv version

    @@ -10093,7 +9730,7 @@ uv index credentials list [OPTIONS]

    Options

    -
    --allow-insecure-host allow-insecure-host

    Allow insecure connections to a host.

    +
    --allow-insecure-host allow-insecure-host

    Allow insecure connections to a host.

    Can be provided multiple times.

    @@ -10102,14 +9739,14 @@ uv index credentials list [OPTIONS]

    WARNING: Hosts included in this list will not be verified against the system’s certificate store. Only use --allow-insecure-host in a secure network with verified sources, as it bypasses SSL verification and could expose you to MITM attacks.

    May also be set with the UV_INSECURE_HOST environment variable.

    -
    --cache-dir cache-dir

    Path to the cache directory.

    +
    --cache-dir cache-dir

    Path to the cache directory.

    Defaults to $XDG_CACHE_HOME/uv or $HOME/.cache/uv on macOS and Linux, and %LOCALAPPDATA%\uv\cache on Windows.

    To view the location of the cache directory, run uv cache dir.

    May also be set with the UV_CACHE_DIR environment variable.

    -
    --color color-choice

    Control the use of color in output.

    +
    --color color-choice

    Control the use of color in output.

    By default, uv will automatically detect support for colors when writing to a terminal.

    @@ -10122,20 +9759,20 @@ uv index credentials list [OPTIONS]
  • never: Disables colored output
  • -
    --config-file config-file

    The path to a uv.toml file to use for configuration.

    +
    --config-file config-file

    The path to a uv.toml file to use for configuration.

    While uv configuration can be included in a pyproject.toml file, it is not allowed in this context.

    May also be set with the UV_CONFIG_FILE environment variable.

    -
    --directory directory

    Change to the given directory prior to running the command.

    +
    --directory directory

    Change to the given directory prior to running the command.

    Relative paths are resolved with the given directory as the base.

    See --project to only change the project root directory.

    -
    --help, -h

    Display the concise help for this command

    +
    --help, -h

    Display the concise help for this command

    -
    --keyring-provider keyring-provider

    Attempt to use keyring for authentication for remote requirements files.

    +
    --keyring-provider keyring-provider

    Attempt to use keyring for authentication for remote requirements files.

    At present, only --keyring-provider subprocess is supported, which configures uv to use the keyring CLI to handle authentication.

    @@ -10149,34 +9786,34 @@ uv index credentials list [OPTIONS]
  • subprocess: Use the keyring command for credential lookup
  • -
    --native-tls

    Whether to load TLS certificates from the platform’s native certificate store.

    +
    --native-tls

    Whether to load TLS certificates from the platform’s native certificate store.

    By default, uv loads certificates from the bundled webpki-roots crate. The webpki-roots are a reliable set of trust roots from Mozilla, and including them in uv improves portability and performance (especially on macOS).

    However, in some cases, you may want to use the platform’s native certificate store, especially if you’re relying on a corporate trust root (e.g., for a mandatory proxy) that’s included in your system’s certificate store.

    May also be set with the UV_NATIVE_TLS environment variable.

    -
    --no-cache, -n

    Avoid reading from or writing to the cache, instead using a temporary directory for the duration of the operation

    +
    --no-cache, -n

    Avoid reading from or writing to the cache, instead using a temporary directory for the duration of the operation

    May also be set with the UV_NO_CACHE environment variable.

    -
    --no-config

    Avoid discovering configuration files (pyproject.toml, uv.toml).

    +
    --no-config

    Avoid discovering configuration files (pyproject.toml, uv.toml).

    Normally, configuration files are discovered in the current directory, parent directories, or user configuration directories.

    May also be set with the UV_NO_CONFIG environment variable.

    -
    --no-progress

    Hide all progress outputs.

    +
    --no-progress

    Hide all progress outputs.

    For example, spinners or progress bars.

    May also be set with the UV_NO_PROGRESS environment variable.

    -
    --no-python-downloads

    Disable automatic downloads of Python.

    +
    --no-python-downloads

    Disable automatic downloads of Python.

    -
    --offline

    Disable network access.

    +
    --offline

    Disable network access.

    When disabled, uv will only use locally cached data and locally available files.

    May also be set with the UV_OFFLINE environment variable.

    -
    --project project

    Run the command within the given project directory.

    +
    --project project

    Run the command within the given project directory.

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project’s virtual environment (.venv).

    @@ -10186,7 +9823,7 @@ uv index credentials list [OPTIONS]

    This setting has no effect when used in the uv pip interface.

    -
    --python-preference python-preference

    Whether to prefer uv-managed or system Python installations.

    +
    --python-preference python-preference

    Whether to prefer uv-managed or system Python installations.

    By default, uv prefers using Python versions it manages. However, it will use system Python installations if a uv-managed Python is not installed. This option allows prioritizing or ignoring system Python installations.

    @@ -10202,13 +9839,13 @@ uv index credentials list [OPTIONS]
  • only-system: Only use system Python installations; never use managed Python installations
  • -
    --quiet, -q

    Do not print any output

    +
    --quiet, -q

    Do not print any output

    -
    --verbose, -v

    Use verbose output.

    +
    --verbose, -v

    Use verbose output.

    You can configure fine-grained logging using the RUST_LOG environment variable. (<https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives>)

    -
    --version, -V

    Display the uv version

    +
    --version, -V

    Display the uv version

    @@ -10224,7 +9861,7 @@ uv index credentials unset [OPTIONS] --name

    Options

    -
    --allow-insecure-host allow-insecure-host

    Allow insecure connections to a host.

    +
    --allow-insecure-host allow-insecure-host

    Allow insecure connections to a host.

    Can be provided multiple times.

    @@ -10233,14 +9870,14 @@ uv index credentials unset [OPTIONS] --name

    WARNING: Hosts included in this list will not be verified against the system’s certificate store. Only use --allow-insecure-host in a secure network with verified sources, as it bypasses SSL verification and could expose you to MITM attacks.

    May also be set with the UV_INSECURE_HOST environment variable.

    -
    --cache-dir cache-dir

    Path to the cache directory.

    +
    --cache-dir cache-dir

    Path to the cache directory.

    Defaults to $XDG_CACHE_HOME/uv or $HOME/.cache/uv on macOS and Linux, and %LOCALAPPDATA%\uv\cache on Windows.

    To view the location of the cache directory, run uv cache dir.

    May also be set with the UV_CACHE_DIR environment variable.

    -
    --color color-choice

    Control the use of color in output.

    +
    --color color-choice

    Control the use of color in output.

    By default, uv will automatically detect support for colors when writing to a terminal.

    @@ -10253,20 +9890,20 @@ uv index credentials unset [OPTIONS] --name
  • never: Disables colored output
  • -
    --config-file config-file

    The path to a uv.toml file to use for configuration.

    +
    --config-file config-file

    The path to a uv.toml file to use for configuration.

    While uv configuration can be included in a pyproject.toml file, it is not allowed in this context.

    May also be set with the UV_CONFIG_FILE environment variable.

    -
    --directory directory

    Change to the given directory prior to running the command.

    +
    --directory directory

    Change to the given directory prior to running the command.

    Relative paths are resolved with the given directory as the base.

    See --project to only change the project root directory.

    -
    --help, -h

    Display the concise help for this command

    +
    --help, -h

    Display the concise help for this command

    -
    --keyring-provider keyring-provider

    Attempt to use keyring for authentication for remote requirements files.

    +
    --keyring-provider keyring-provider

    Attempt to use keyring for authentication for remote requirements files.

    At present, only --keyring-provider subprocess is supported, which configures uv to use the keyring CLI to handle authentication.

    @@ -10280,36 +9917,36 @@ uv index credentials unset [OPTIONS] --name
  • subprocess: Use the keyring command for credential lookup
  • -
    --name name

    The name of the index

    +
    --name name

    The name of the index

    -
    --native-tls

    Whether to load TLS certificates from the platform’s native certificate store.

    +
    --native-tls

    Whether to load TLS certificates from the platform’s native certificate store.

    By default, uv loads certificates from the bundled webpki-roots crate. The webpki-roots are a reliable set of trust roots from Mozilla, and including them in uv improves portability and performance (especially on macOS).

    However, in some cases, you may want to use the platform’s native certificate store, especially if you’re relying on a corporate trust root (e.g., for a mandatory proxy) that’s included in your system’s certificate store.

    May also be set with the UV_NATIVE_TLS environment variable.

    -
    --no-cache, -n

    Avoid reading from or writing to the cache, instead using a temporary directory for the duration of the operation

    +
    --no-cache, -n

    Avoid reading from or writing to the cache, instead using a temporary directory for the duration of the operation

    May also be set with the UV_NO_CACHE environment variable.

    -
    --no-config

    Avoid discovering configuration files (pyproject.toml, uv.toml).

    +
    --no-config

    Avoid discovering configuration files (pyproject.toml, uv.toml).

    Normally, configuration files are discovered in the current directory, parent directories, or user configuration directories.

    May also be set with the UV_NO_CONFIG environment variable.

    -
    --no-progress

    Hide all progress outputs.

    +
    --no-progress

    Hide all progress outputs.

    For example, spinners or progress bars.

    May also be set with the UV_NO_PROGRESS environment variable.

    -
    --no-python-downloads

    Disable automatic downloads of Python.

    +
    --no-python-downloads

    Disable automatic downloads of Python.

    -
    --offline

    Disable network access.

    +
    --offline

    Disable network access.

    When disabled, uv will only use locally cached data and locally available files.

    May also be set with the UV_OFFLINE environment variable.

    -
    --project project

    Run the command within the given project directory.

    +
    --project project

    Run the command within the given project directory.

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project’s virtual environment (.venv).

    @@ -10319,7 +9956,7 @@ uv index credentials unset [OPTIONS] --name

    This setting has no effect when used in the uv pip interface.

    -
    --python-preference python-preference

    Whether to prefer uv-managed or system Python installations.

    +
    --python-preference python-preference

    Whether to prefer uv-managed or system Python installations.

    By default, uv prefers using Python versions it manages. However, it will use system Python installations if a uv-managed Python is not installed. This option allows prioritizing or ignoring system Python installations.

    @@ -10335,15 +9972,15 @@ uv index credentials unset [OPTIONS] --name
  • only-system: Only use system Python installations; never use managed Python installations
  • -
    --quiet, -q

    Do not print any output

    +
    --quiet, -q

    Do not print any output

    -
    --username username

    The username that should be used for the index

    +
    --username username

    The username that should be used for the index

    -
    --verbose, -v

    Use verbose output.

    +
    --verbose, -v

    Use verbose output.

    You can configure fine-grained logging using the RUST_LOG environment variable. (<https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives>)

    -
    --version, -V

    Display the uv version

    +
    --version, -V

    Display the uv version

    From 49cbe38e11df54105949ae226d6c4f222137003a Mon Sep 17 00:00:00 2001 From: Simon Steinheber Date: Fri, 28 Feb 2025 15:27:41 +0100 Subject: [PATCH 28/28] Fix merge errors --- crates/uv/src/commands/project/add.rs | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/crates/uv/src/commands/project/add.rs b/crates/uv/src/commands/project/add.rs index c22d92d21b79..9819b7668fbe 100644 --- a/crates/uv/src/commands/project/add.rs +++ b/crates/uv/src/commands/project/add.rs @@ -250,14 +250,6 @@ pub(crate) async fn add( let RequirementsSpecification { requirements, .. } = RequirementsSpecification::from_simple_sources(&requirements, &client_builder).await?; - // TODO(charlie): These are all default values. We should consider whether we want to make them - // optional on the downstream APIs. - let bounds = LowerBound::default(); - let build_constraints = Constraints::default(); - let build_hasher = HashStrategy::default(); - let hasher = HashStrategy::default(); - let sources = SourceStrategy::Enabled; - // Add all authenticated sources to the cache. for index in settings.index_locations.allowed_indexes() { if let Some(credentials) = index.credentials() { @@ -265,17 +257,9 @@ pub(crate) async fn add( } } - // Initialize the registry client. - let client = RegistryClientBuilder::try_from(client_builder.clone())? - .index_urls(settings.index_locations.index_urls()) - .index_strategy(settings.index_strategy) - .markers(target.interpreter().markers()) - .platform(target.interpreter().platform()) - .build(); - // Determine whether to enable build isolation. let environment; - let build_isolation = if settings.no_build_isolation { + let _build_isolation = if settings.no_build_isolation { environment = PythonEnvironment::from_interpreter(target.interpreter().clone()); BuildIsolation::Shared(&environment) } else if settings.no_build_isolation_package.is_empty() {