diff --git a/Cargo.lock b/Cargo.lock index 0a572895c..11d7a1c53 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5373,6 +5373,7 @@ dependencies = [ "uv-pep440", "uv-pep508", "uv-platform-tags", + "uv-preview", "uv-pypi-types", "uv-redacted", "uv-small-str", @@ -5409,8 +5410,10 @@ dependencies = [ "uv-pep440", "uv-pep508", "uv-platform-tags", + "uv-preview", "uv-redacted", "uv-static", + "uv-warnings", ] [[package]] @@ -6001,6 +6004,7 @@ dependencies = [ "uv-extract", "uv-fs", "uv-metadata", + "uv-preview", "uv-pypi-types", "uv-redacted", "uv-static", @@ -6139,6 +6143,7 @@ dependencies = [ "uv-git", "uv-normalize", "uv-pep508", + "uv-preview", "uv-pypi-types", "uv-redacted", "uv-requirements-txt", @@ -6175,6 +6180,7 @@ dependencies = [ "uv-fs", "uv-normalize", "uv-pep508", + "uv-preview", "uv-pypi-types", "uv-redacted", "uv-warnings", diff --git a/crates/uv-client/Cargo.toml b/crates/uv-client/Cargo.toml index cf1c603df..e1a933241 100644 --- a/crates/uv-client/Cargo.toml +++ b/crates/uv-client/Cargo.toml @@ -22,6 +22,7 @@ uv-normalize = { workspace = true } uv-pep440 = { workspace = true } uv-pep508 = { workspace = true } uv-platform-tags = { workspace = true } +uv-preview = { workspace = true } uv-pypi-types = { workspace = true } uv-small-str = { workspace = true } uv-redacted = { workspace = true } diff --git a/crates/uv-client/src/base_client.rs b/crates/uv-client/src/base_client.rs index 641d88498..9292b2743 100644 --- a/crates/uv-client/src/base_client.rs +++ b/crates/uv-client/src/base_client.rs @@ -33,6 +33,7 @@ use uv_configuration::{KeyringProviderType, TrustedHost}; use uv_fs::Simplified; use uv_pep508::MarkerEnvironment; use uv_platform_tags::Platform; +use uv_preview::Preview; use uv_redacted::DisplaySafeUrl; use uv_static::EnvVars; use uv_version::version; @@ -68,6 +69,7 @@ pub enum AuthIntegration { #[derive(Debug, Clone)] pub struct BaseClientBuilder<'a> { keyring: KeyringProviderType, + preview: Preview, allow_insecure_host: Vec, native_tls: bool, built_in_root_certs: bool, @@ -125,6 +127,7 @@ impl Default for BaseClientBuilder<'_> { fn default() -> Self { Self { keyring: KeyringProviderType::default(), + preview: Preview::default(), allow_insecure_host: vec![], native_tls: false, built_in_root_certs: false, @@ -486,13 +489,13 @@ impl<'a> BaseClientBuilder<'a> { AuthIntegration::Default => { let auth_middleware = AuthMiddleware::new() .with_indexes(self.indexes.clone()) - .with_keyring(self.keyring.to_provider()); + .with_keyring(self.keyring.to_provider(&self.preview)); client = client.with(auth_middleware); } AuthIntegration::OnlyAuthenticated => { let auth_middleware = AuthMiddleware::new() .with_indexes(self.indexes.clone()) - .with_keyring(self.keyring.to_provider()) + .with_keyring(self.keyring.to_provider(&self.preview)) .with_only_authenticated(true); client = client.with(auth_middleware); diff --git a/crates/uv-configuration/Cargo.toml b/crates/uv-configuration/Cargo.toml index 3776ab8c1..5c8e69bf8 100644 --- a/crates/uv-configuration/Cargo.toml +++ b/crates/uv-configuration/Cargo.toml @@ -25,8 +25,10 @@ uv-normalize = { workspace = true } uv-pep440 = { workspace = true } uv-pep508 = { workspace = true, features = ["schemars"] } uv-platform-tags = { workspace = true } +uv-preview = { workspace = true } uv-redacted = { workspace = true } uv-static = { workspace = true } +uv-warnings = { workspace = true } clap = { workspace = true, features = ["derive"], optional = true } either = { workspace = true } fs-err = { workspace = true } diff --git a/crates/uv-configuration/src/authentication.rs b/crates/uv-configuration/src/authentication.rs index dee874dae..459c393d5 100644 --- a/crates/uv-configuration/src/authentication.rs +++ b/crates/uv-configuration/src/authentication.rs @@ -1,6 +1,8 @@ use std::str::FromStr; use uv_auth::{self, KeyringProvider}; +use uv_preview::{Preview, PreviewFeatures}; use uv_redacted::DisplaySafeUrl; +use uv_warnings::warn_user_once; /// Keyring provider type to use for credential lookup. #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] @@ -23,10 +25,18 @@ pub enum KeyringProviderType { // See for details. impl KeyringProviderType { - pub fn to_provider(&self) -> Option { + pub fn to_provider(&self, preview: &Preview) -> Option { match self { Self::Disabled => None, - Self::Native => Some(KeyringProvider::native()), + Self::Native => { + if !preview.is_enabled(PreviewFeatures::NATIVE_KEYRING) { + warn_user_once!( + "The native keyring provider is experimental and may change without warning. Pass `--preview-features {}` to disable this warning.", + PreviewFeatures::NATIVE_KEYRING + ); + } + Some(KeyringProvider::native()) + } Self::Subprocess => Some(KeyringProvider::subprocess()), } } diff --git a/crates/uv-publish/Cargo.toml b/crates/uv-publish/Cargo.toml index 17029763c..bee70b10c 100644 --- a/crates/uv-publish/Cargo.toml +++ b/crates/uv-publish/Cargo.toml @@ -22,6 +22,7 @@ uv-distribution-types = { workspace = true } uv-extract = { workspace = true } uv-fs = { workspace = true } uv-metadata = { workspace = true } +uv-preview = { workspace = true } uv-pypi-types = { workspace = true } uv-redacted = { workspace = true } uv-static = { workspace = true } diff --git a/crates/uv-requirements-txt/Cargo.toml b/crates/uv-requirements-txt/Cargo.toml index 478a90e43..66f425aea 100644 --- a/crates/uv-requirements-txt/Cargo.toml +++ b/crates/uv-requirements-txt/Cargo.toml @@ -22,6 +22,7 @@ uv-distribution-types = { workspace = true } uv-fs = { workspace = true } uv-normalize = { workspace = true } uv-pep508 = { workspace = true } +uv-preview = { workspace = true } uv-pypi-types = { workspace = true } uv-redacted = { workspace = true } uv-warnings = { workspace = true } diff --git a/crates/uv-requirements/Cargo.toml b/crates/uv-requirements/Cargo.toml index ef372032d..9494cb22b 100644 --- a/crates/uv-requirements/Cargo.toml +++ b/crates/uv-requirements/Cargo.toml @@ -27,6 +27,7 @@ uv-fs = { workspace = true } uv-git = { workspace = true } uv-normalize = { workspace = true } uv-pep508 = { workspace = true } +uv-preview = { workspace = true } uv-pypi-types = { workspace = true } uv-redacted = { workspace = true } uv-requirements-txt = { workspace = true, features = ["http"] } diff --git a/crates/uv/src/commands/auth/login.rs b/crates/uv/src/commands/auth/login.rs index fb67e3fbc..a0eff9b96 100644 --- a/crates/uv/src/commands/auth/login.rs +++ b/crates/uv/src/commands/auth/login.rs @@ -4,6 +4,7 @@ use std::fmt::Write; use console::Term; use uv_auth::Credentials; use uv_configuration::{KeyringProviderType, Service}; +use uv_preview::Preview; use crate::{commands::ExitStatus, printer::Printer}; @@ -15,6 +16,7 @@ pub(crate) async fn login( token: Option, keyring_provider: Option, printer: Printer, + preview: Preview, ) -> Result { let url = service.url(); let display_url = username @@ -44,7 +46,7 @@ pub(crate) async fn login( ); }; let provider = match keyring_provider { - KeyringProviderType::Native => keyring_provider.to_provider().unwrap(), + KeyringProviderType::Native => keyring_provider.to_provider(&preview).unwrap(), KeyringProviderType::Disabled | KeyringProviderType::Subprocess => { bail!( "Cannot login with `keyring-provider = {keyring_provider}`, use `keyring-provider = {}` instead", diff --git a/crates/uv/src/commands/auth/logout.rs b/crates/uv/src/commands/auth/logout.rs index 270dfdade..fe426f666 100644 --- a/crates/uv/src/commands/auth/logout.rs +++ b/crates/uv/src/commands/auth/logout.rs @@ -1,6 +1,7 @@ use anyhow::{Context, Result, bail}; use std::{borrow::Cow, fmt::Write}; use uv_configuration::{KeyringProviderType, Service}; +use uv_preview::Preview; use crate::{commands::ExitStatus, printer::Printer}; @@ -12,6 +13,7 @@ pub(crate) async fn logout( username: Option, keyring_provider: Option, printer: Printer, + preview: Preview, ) -> Result { let url = service.url(); let display_url = username @@ -28,7 +30,7 @@ pub(crate) async fn logout( // Be helpful about incompatible `keyring-provider` settings let provider = match keyring_provider { - KeyringProviderType::Native => keyring_provider.to_provider().unwrap(), + KeyringProviderType::Native => keyring_provider.to_provider(&preview).unwrap(), KeyringProviderType::Disabled | KeyringProviderType::Subprocess => { bail!( "Cannot logout with `keyring-provider = {keyring_provider}`, use `keyring-provider = {}` instead", diff --git a/crates/uv/src/commands/auth/token.rs b/crates/uv/src/commands/auth/token.rs index 9cd2db86e..e0bfb7f2d 100644 --- a/crates/uv/src/commands/auth/token.rs +++ b/crates/uv/src/commands/auth/token.rs @@ -3,6 +3,7 @@ use std::fmt::Write; use anyhow::{Context, Result, bail}; use uv_configuration::{KeyringProviderType, Service}; +use uv_preview::Preview; use crate::{Printer, commands::ExitStatus}; @@ -12,12 +13,13 @@ pub(crate) async fn token( username: Option, keyring_provider: Option, printer: Printer, + preview: Preview, ) -> Result { // Determine the keyring provider to use let Some(keyring_provider) = &keyring_provider else { bail!("Retrieving credentials requires setting a `keyring-provider`"); }; - let Some(provider) = keyring_provider.to_provider() else { + let Some(provider) = keyring_provider.to_provider(&preview) else { bail!("Cannot retrieve credentials with `keyring-provider = {keyring_provider}`"); }; diff --git a/crates/uv/src/commands/publish.rs b/crates/uv/src/commands/publish.rs index 879600946..db159db09 100644 --- a/crates/uv/src/commands/publish.rs +++ b/crates/uv/src/commands/publish.rs @@ -12,6 +12,7 @@ use uv_cache::Cache; use uv_client::{AuthIntegration, BaseClient, BaseClientBuilder, RegistryClientBuilder}; use uv_configuration::{KeyringProviderType, TrustedPublishing}; use uv_distribution_types::{IndexCapabilities, IndexLocations, IndexUrl}; +use uv_preview::Preview; use uv_publish::{ CheckUrlClient, TrustedPublishResult, check_trusted_publishing, files_for_publishing, upload, }; @@ -34,6 +35,7 @@ pub(crate) async fn publish( index_locations: IndexLocations, cache: &Cache, printer: Printer, + preview: Preview, ) -> Result { if client_builder.is_offline() { bail!("Unable to publish files in offline mode"); @@ -83,6 +85,7 @@ pub(crate) async fn publish( check_url.as_ref(), Prompt::Enabled, printer, + preview, ) .await?; @@ -197,6 +200,7 @@ async fn gather_credentials( check_url: Option<&IndexUrl>, prompt: Prompt, printer: Printer, + preview: Preview, ) -> Result<(DisplaySafeUrl, Credentials)> { // Support reading username and password from the URL, for symmetry with the index API. if let Some(url_password) = publish_url.password() { @@ -280,11 +284,11 @@ async fn gather_credentials( // If applicable, fetch the password from the keyring eagerly to avoid user confusion about // missing keyring entries later. - if let Some(keyring_provider) = keyring_provider.to_provider() { + if let Some(provider) = keyring_provider.to_provider(&preview) { if password.is_none() { if let Some(username) = &username { debug!("Fetching password from keyring"); - if let Some(keyring_password) = keyring_provider + if let Some(keyring_password) = provider .fetch(DisplaySafeUrl::ref_cast(&publish_url), Some(username)) .await .as_ref() @@ -350,6 +354,7 @@ mod tests { None, Prompt::Disabled, Printer::Quiet, + Preview::default(), ) .await } diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index ab866d57b..471f3dbfa 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -453,6 +453,7 @@ async fn run(mut cli: Cli) -> Result { args.token, args.keyring_provider, printer, + globals.preview, ) .await } @@ -463,7 +464,14 @@ async fn run(mut cli: Cli) -> Result { let args = settings::AuthLogoutSettings::resolve(args, filesystem); show_settings!(args); - commands::auth_logout(args.service, args.username, args.keyring_provider, printer).await + commands::auth_logout( + args.service, + args.username, + args.keyring_provider, + printer, + globals.preview, + ) + .await } Commands::Auth(AuthNamespace { command: AuthCommand::Token(args), @@ -472,7 +480,14 @@ async fn run(mut cli: Cli) -> Result { let args = settings::AuthTokenSettings::resolve(args, filesystem); show_settings!(args); - commands::auth_token(args.service, args.username, args.keyring_provider, printer).await + commands::auth_token( + args.service, + args.username, + args.keyring_provider, + printer, + globals.preview, + ) + .await } Commands::Help(args) => commands::help( args.command.unwrap_or_default().as_slice(), @@ -1668,6 +1683,7 @@ async fn run(mut cli: Cli) -> Result { index_locations, &cache, printer, + globals.preview, ) .await } diff --git a/crates/uv/tests/it/auth.rs b/crates/uv/tests/it/auth.rs index 0532c2dad..cbfc4c07f 100644 --- a/crates/uv/tests/it/auth.rs +++ b/crates/uv/tests/it/auth.rs @@ -38,6 +38,7 @@ fn add_package_native_keyring() -> Result<()> { ----- stdout ----- ----- stderr ----- + warning: The native keyring provider is experimental and may change without warning. Pass `--preview-features native-keyring` to disable this warning. × No solution found when resolving dependencies: ╰─▶ Because anyio was not found in the package registry and your project depends on anyio, we can conclude that your project's requirements are unsatisfiable. @@ -58,6 +59,7 @@ fn add_package_native_keyring() -> Result<()> { ----- stdout ----- ----- stderr ----- + warning: The native keyring provider is experimental and may change without warning. Pass `--preview-features native-keyring` to disable this warning. Logged in to public@https://pypi-proxy.fly.dev/basic-auth/simple " ); @@ -70,6 +72,7 @@ fn add_package_native_keyring() -> Result<()> { ----- stdout ----- ----- stderr ----- + warning: The native keyring provider is experimental and may change without warning. Pass `--preview-features native-keyring` to disable this warning. Resolved 4 packages in [TIME] Prepared 3 packages in [TIME] Installed 3 packages in [TIME] @@ -89,6 +92,7 @@ fn add_package_native_keyring() -> Result<()> { ----- stdout ----- ----- stderr ----- + warning: The native keyring provider is experimental and may change without warning. Pass `--preview-features native-keyring` to disable this warning. Logged out of public@https://pypi-proxy.fly.dev/basic-auth/simple " ); @@ -100,6 +104,7 @@ fn add_package_native_keyring() -> Result<()> { ----- stdout ----- ----- stderr ----- + warning: The native keyring provider is experimental and may change without warning. Pass `--preview-features native-keyring` to disable this warning. × No solution found when resolving dependencies: ╰─▶ Because iniconfig was not found in the package registry and your project depends on iniconfig, we can conclude that your project's requirements are unsatisfiable. @@ -159,6 +164,7 @@ fn token_native_keyring() -> Result<()> { ----- stdout ----- ----- stderr ----- + warning: The native keyring provider is experimental and may change without warning. Pass `--preview-features native-keyring` to disable this warning. error: Failed to fetch credentials for https://pypi-proxy.fly.dev/basic-auth/simple "); @@ -174,6 +180,7 @@ fn token_native_keyring() -> Result<()> { ----- stdout ----- ----- stderr ----- + warning: The native keyring provider is experimental and may change without warning. Pass `--preview-features native-keyring` to disable this warning. error: Failed to fetch credentials for public@https://pypi-proxy.fly.dev/basic-auth/simple "); @@ -191,6 +198,7 @@ fn token_native_keyring() -> Result<()> { ----- stdout ----- ----- stderr ----- + warning: The native keyring provider is experimental and may change without warning. Pass `--preview-features native-keyring` to disable this warning. Logged in to public@https://pypi-proxy.fly.dev/basic-auth/simple " ); @@ -208,6 +216,7 @@ fn token_native_keyring() -> Result<()> { heron ----- stderr ----- + warning: The native keyring provider is experimental and may change without warning. Pass `--preview-features native-keyring` to disable this warning. "); // Without the username @@ -221,6 +230,7 @@ fn token_native_keyring() -> Result<()> { ----- stdout ----- ----- stderr ----- + warning: The native keyring provider is experimental and may change without warning. Pass `--preview-features native-keyring` to disable this warning. error: Failed to fetch credentials for https://pypi-proxy.fly.dev/basic-auth/simple "); @@ -237,6 +247,7 @@ fn token_native_keyring() -> Result<()> { ----- stdout ----- ----- stderr ----- + warning: The native keyring provider is experimental and may change without warning. Pass `--preview-features native-keyring` to disable this warning. error: Failed to fetch credentials for private@https://pypi-proxy.fly.dev/basic-auth/simple "); @@ -252,6 +263,7 @@ fn token_native_keyring() -> Result<()> { ----- stdout ----- ----- stderr ----- + warning: The native keyring provider is experimental and may change without warning. Pass `--preview-features native-keyring` to disable this warning. Logged in to https://pypi-proxy.fly.dev/basic-auth/simple " ); @@ -267,6 +279,7 @@ fn token_native_keyring() -> Result<()> { heron ----- stderr ----- + warning: The native keyring provider is experimental and may change without warning. Pass `--preview-features native-keyring` to disable this warning. "); Ok(()) @@ -412,6 +425,7 @@ fn login_native_keyring() -> Result<()> { ----- stdout ----- ----- stderr ----- + warning: The native keyring provider is experimental and may change without warning. Pass `--preview-features native-keyring` to disable this warning. error: No password provided; did you mean to provide `--password` or `--token`? "); @@ -442,6 +456,7 @@ fn login_native_keyring() -> Result<()> { ----- stdout ----- ----- stderr ----- + warning: The native keyring provider is experimental and may change without warning. Pass `--preview-features native-keyring` to disable this warning. Logged in to public@https://pypi-proxy.fly.dev/basic-auth/simple " ); @@ -473,6 +488,7 @@ fn login_token_native_keyring() -> Result<()> { ----- stdout ----- ----- stderr ----- + warning: The native keyring provider is experimental and may change without warning. Pass `--preview-features native-keyring` to disable this warning. Logged in to https://pypi-proxy.fly.dev/basic-auth/simple " ); @@ -515,6 +531,7 @@ fn logout_native_keyring() -> Result<()> { ----- stdout ----- ----- stderr ----- + warning: The native keyring provider is experimental and may change without warning. Pass `--preview-features native-keyring` to disable this warning. Logged out of https://pypi-proxy.fly.dev/basic-auth/simple "); @@ -528,6 +545,7 @@ fn logout_native_keyring() -> Result<()> { ----- stdout ----- ----- stderr ----- + warning: The native keyring provider is experimental and may change without warning. Pass `--preview-features native-keyring` to disable this warning. error: Unable to remove credentials for https://pypi-proxy.fly.dev/basic-auth/simple Caused by: No matching entry found in secure storage "); @@ -544,6 +562,7 @@ fn logout_native_keyring() -> Result<()> { ----- stdout ----- ----- stderr ----- + warning: The native keyring provider is experimental and may change without warning. Pass `--preview-features native-keyring` to disable this warning. error: Unable to remove credentials for public@https://pypi-proxy.fly.dev/basic-auth/simple Caused by: No matching entry found in secure storage "); @@ -562,6 +581,7 @@ fn logout_native_keyring() -> Result<()> { ----- stdout ----- ----- stderr ----- + warning: The native keyring provider is experimental and may change without warning. Pass `--preview-features native-keyring` to disable this warning. Logged in to public@https://pypi-proxy.fly.dev/basic-auth/simple " ); @@ -577,6 +597,7 @@ fn logout_native_keyring() -> Result<()> { ----- stdout ----- ----- stderr ----- + warning: The native keyring provider is experimental and may change without warning. Pass `--preview-features native-keyring` to disable this warning. error: Unable to remove credentials for https://pypi-proxy.fly.dev/basic-auth/simple Caused by: No matching entry found in secure storage "); @@ -593,6 +614,7 @@ fn logout_native_keyring() -> Result<()> { ----- stdout ----- ----- stderr ----- + warning: The native keyring provider is experimental and may change without warning. Pass `--preview-features native-keyring` to disable this warning. Logged out of public@https://pypi-proxy.fly.dev/basic-auth/simple "); @@ -621,6 +643,7 @@ fn logout_token_native_keyring() -> Result<()> { ----- stdout ----- ----- stderr ----- + warning: The native keyring provider is experimental and may change without warning. Pass `--preview-features native-keyring` to disable this warning. Logged in to https://pypi-proxy.fly.dev/basic-auth/simple " ); @@ -635,6 +658,7 @@ fn logout_token_native_keyring() -> Result<()> { ----- stdout ----- ----- stderr ----- + warning: The native keyring provider is experimental and may change without warning. Pass `--preview-features native-keyring` to disable this warning. Logged out of https://pypi-proxy.fly.dev/basic-auth/simple "); @@ -659,6 +683,7 @@ fn login_url_parsing() { ----- stdout ----- ----- stderr ----- + warning: The native keyring provider is experimental and may change without warning. Pass `--preview-features native-keyring` to disable this warning. Logged in to test@https://example.com/ "); @@ -676,6 +701,7 @@ fn login_url_parsing() { ----- stdout ----- ----- stderr ----- + warning: The native keyring provider is experimental and may change without warning. Pass `--preview-features native-keyring` to disable this warning. Logged in to test@http://example.com/ "); @@ -692,6 +718,7 @@ fn login_url_parsing() { ----- stdout ----- ----- stderr ----- + warning: The native keyring provider is experimental and may change without warning. Pass `--preview-features native-keyring` to disable this warning. Logged in to test@https://example.com/ "); @@ -709,6 +736,7 @@ fn login_url_parsing() { ----- stdout ----- ----- stderr ----- + warning: The native keyring provider is experimental and may change without warning. Pass `--preview-features native-keyring` to disable this warning. Logged in to test@https://example.com/simple ");