Skip to content

Commit 920d5e1

Browse files
committed
Don't use a global static as credentials cache
1 parent 512c0ca commit 920d5e1

File tree

26 files changed

+302
-133
lines changed

26 files changed

+302
-133
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/uv-auth/src/cache.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ use url::Url;
1111
use uv_once_map::OnceMap;
1212
use uv_redacted::DisplaySafeUrl;
1313

14-
use crate::Realm;
1514
use crate::credentials::{Authentication, Username};
15+
use crate::{Credentials, Realm};
1616

1717
type FxOnceMap<K, V> = OnceMap<K, V, BuildHasherDefault<FxHasher>>;
1818

@@ -33,6 +33,7 @@ impl Display for FetchUrl {
3333
}
3434
}
3535

36+
#[derive(Debug)] // All internal types are redacted.
3637
pub struct CredentialsCache {
3738
/// A cache per realm and username
3839
realms: RwLock<FxHashMap<(Realm, Username), Arc<Authentication>>>,
@@ -58,6 +59,27 @@ impl CredentialsCache {
5859
}
5960
}
6061

62+
/// Populate the global authentication store with credentials on a URL, if there are any.
63+
///
64+
/// Returns `true` if the store was updated.
65+
pub fn store_credentials_from_url(&self, url: &DisplaySafeUrl) -> bool {
66+
if let Some(credentials) = Credentials::from_url(url) {
67+
trace!("Caching credentials for {url}");
68+
self.insert(url, Arc::new(Authentication::from(credentials)));
69+
true
70+
} else {
71+
false
72+
}
73+
}
74+
75+
/// Populate the global authentication store with credentials on a URL, if there are any.
76+
///
77+
/// Returns `true` if the store was updated.
78+
pub fn store_credentials(&self, url: &DisplaySafeUrl, credentials: Credentials) {
79+
trace!("Caching credentials for {url}");
80+
self.insert(url, Arc::new(Authentication::from(credentials)));
81+
}
82+
6183
/// Return the credentials that should be used for a realm and username, if any.
6284
pub(crate) fn get_realm(
6385
&self,

crates/uv-auth/src/lib.rs

Lines changed: 1 addition & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,5 @@
1-
use std::sync::{Arc, LazyLock};
2-
3-
use tracing::trace;
4-
5-
use uv_redacted::DisplaySafeUrl;
6-
7-
use crate::credentials::Authentication;
81
pub use access_token::AccessToken;
9-
use cache::CredentialsCache;
2+
pub use cache::CredentialsCache;
103
pub use credentials::{Credentials, Username};
114
pub use index::{AuthPolicy, Index, Indexes};
125
pub use keyring::KeyringProvider;
@@ -29,32 +22,3 @@ mod pyx;
2922
mod realm;
3023
mod service;
3124
mod store;
32-
33-
// TODO(zanieb): Consider passing a cache explicitly throughout
34-
35-
/// Global authentication cache for a uv invocation
36-
///
37-
/// This is used to share credentials across uv clients.
38-
pub(crate) static CREDENTIALS_CACHE: LazyLock<CredentialsCache> =
39-
LazyLock::new(CredentialsCache::default);
40-
41-
/// Populate the global authentication store with credentials on a URL, if there are any.
42-
///
43-
/// Returns `true` if the store was updated.
44-
pub fn store_credentials_from_url(url: &DisplaySafeUrl) -> bool {
45-
if let Some(credentials) = Credentials::from_url(url) {
46-
trace!("Caching credentials for {url}");
47-
CREDENTIALS_CACHE.insert(url, Arc::new(Authentication::from(credentials)));
48-
true
49-
} else {
50-
false
51-
}
52-
}
53-
54-
/// Populate the global authentication store with credentials on a URL, if there are any.
55-
///
56-
/// Returns `true` if the store was updated.
57-
pub fn store_credentials(url: &DisplaySafeUrl, credentials: Credentials) {
58-
trace!("Caching credentials for {url}");
59-
CREDENTIALS_CACHE.insert(url, Arc::new(Authentication::from(credentials)));
60-
}

crates/uv-auth/src/middleware.rs

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use crate::credentials::Authentication;
1616
use crate::providers::{HuggingFaceProvider, S3EndpointProvider};
1717
use crate::pyx::{DEFAULT_TOLERANCE_SECS, PyxTokenStore};
1818
use crate::{
19-
AccessToken, CREDENTIALS_CACHE, CredentialsCache, KeyringProvider,
19+
AccessToken, CredentialsCache, KeyringProvider,
2020
cache::FetchUrl,
2121
credentials::{Credentials, Username},
2222
index::{AuthPolicy, Indexes},
@@ -126,7 +126,8 @@ pub struct AuthMiddleware {
126126
netrc: NetrcMode,
127127
text_store: TextStoreMode,
128128
keyring: Option<KeyringProvider>,
129-
cache: Option<CredentialsCache>,
129+
/// Global authentication cache for a uv invocation to share credentials across uv clients.
130+
cache: Arc<CredentialsCache>,
130131
/// Auth policies for specific URLs.
131132
indexes: Indexes,
132133
/// Set all endpoints as needing authentication. We never try to send an
@@ -141,13 +142,20 @@ pub struct AuthMiddleware {
141142
preview: Preview,
142143
}
143144

145+
impl Default for AuthMiddleware {
146+
fn default() -> Self {
147+
Self::new()
148+
}
149+
}
150+
144151
impl AuthMiddleware {
145152
pub fn new() -> Self {
146153
Self {
147154
netrc: NetrcMode::default(),
148155
text_store: TextStoreMode::default(),
149156
keyring: None,
150-
cache: None,
157+
// TODO(konsti): There shouldn't be a credential cache without that in the initializer.
158+
cache: Arc::new(CredentialsCache::default()),
151159
indexes: Indexes::new(),
152160
only_authenticated: false,
153161
base_client: None,
@@ -200,7 +208,14 @@ impl AuthMiddleware {
200208
/// Configure the [`CredentialsCache`] to use.
201209
#[must_use]
202210
pub fn with_cache(mut self, cache: CredentialsCache) -> Self {
203-
self.cache = Some(cache);
211+
self.cache = Arc::new(cache);
212+
self
213+
}
214+
215+
/// Configure the [`CredentialsCache`] to use from an existing [`Arc`].
216+
#[must_use]
217+
pub fn with_cache_arc(mut self, cache: Arc<CredentialsCache>) -> Self {
218+
self.cache = cache;
204219
self
205220
}
206221

@@ -233,17 +248,9 @@ impl AuthMiddleware {
233248
self
234249
}
235250

236-
/// Get the configured authentication store.
237-
///
238-
/// If not set, the global store is used.
251+
/// Global authentication cache for a uv invocation to share credentials across uv clients.
239252
fn cache(&self) -> &CredentialsCache {
240-
self.cache.as_ref().unwrap_or(&CREDENTIALS_CACHE)
241-
}
242-
}
243-
244-
impl Default for AuthMiddleware {
245-
fn default() -> Self {
246-
Self::new()
253+
&self.cache
247254
}
248255
}
249256

crates/uv-build-frontend/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ doctest = false
1717
workspace = true
1818

1919
[dependencies]
20+
uv-auth = { workspace = true }
2021
uv-cache-key = { workspace = true }
2122
uv-configuration = { workspace = true }
2223
uv-distribution = { workspace = true }

crates/uv-build-frontend/src/lib.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ use tokio::io::AsyncBufReadExt;
2828
use tokio::process::Command;
2929
use tokio::sync::{Mutex, Semaphore};
3030
use tracing::{Instrument, debug, info_span, instrument, warn};
31-
31+
use uv_auth::CredentialsCache;
3232
use uv_cache_key::cache_digest;
3333
use uv_configuration::{BuildKind, BuildOutput, SourceStrategy};
3434
use uv_distribution::BuildRequires;
@@ -292,6 +292,7 @@ impl SourceBuild {
292292
mut environment_variables: FxHashMap<OsString, OsString>,
293293
level: BuildOutput,
294294
concurrent_builds: usize,
295+
credentials_cache: &CredentialsCache,
295296
preview: Preview,
296297
) -> Result<Self, Error> {
297298
let temp_dir = build_context.cache().venv_dir()?;
@@ -312,6 +313,7 @@ impl SourceBuild {
312313
source_strategy,
313314
workspace_cache,
314315
&default_backend,
316+
credentials_cache,
315317
)
316318
.await
317319
.map_err(|err| *err)?;
@@ -455,6 +457,7 @@ impl SourceBuild {
455457
&environment_variables,
456458
&modified_path,
457459
&temp_dir,
460+
credentials_cache,
458461
)
459462
.await?;
460463
}
@@ -561,6 +564,7 @@ impl SourceBuild {
561564
source_strategy: SourceStrategy,
562565
workspace_cache: &WorkspaceCache,
563566
default_backend: &Pep517Backend,
567+
credentials_cache: &CredentialsCache,
564568
) -> Result<(Pep517Backend, Option<Project>), Box<Error>> {
565569
match fs::read_to_string(source_tree.join("pyproject.toml")) {
566570
Ok(toml) => {
@@ -589,6 +593,7 @@ impl SourceBuild {
589593
locations,
590594
source_strategy,
591595
workspace_cache,
596+
credentials_cache,
592597
)
593598
.await
594599
.map_err(Error::Lowering)?;
@@ -961,6 +966,7 @@ async fn create_pep517_build_environment(
961966
environment_variables: &FxHashMap<OsString, OsString>,
962967
modified_path: &OsString,
963968
temp_dir: &TempDir,
969+
credentials_cache: &CredentialsCache,
964970
) -> Result<(), Error> {
965971
// Write the hook output to a file so that we can read it back reliably.
966972
let outfile = temp_dir
@@ -1055,6 +1061,7 @@ async fn create_pep517_build_environment(
10551061
locations,
10561062
source_strategy,
10571063
workspace_cache,
1064+
credentials_cache,
10581065
)
10591066
.await
10601067
.map_err(Error::Lowering)?;

crates/uv-client/src/base_client.rs

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ use tracing::{debug, trace};
2828
use url::ParseError;
2929
use url::Url;
3030

31-
use uv_auth::{AuthMiddleware, Credentials, Indexes, PyxTokenStore};
31+
use uv_auth::{AuthMiddleware, Credentials, CredentialsCache, Indexes, PyxTokenStore};
3232
use uv_configuration::{KeyringProviderType, TrustedHost};
3333
use uv_fs::Simplified;
3434
use uv_pep508::MarkerEnvironment;
@@ -78,6 +78,8 @@ pub struct BaseClientBuilder<'a> {
7878
markers: Option<&'a MarkerEnvironment>,
7979
platform: Option<&'a Platform>,
8080
auth_integration: AuthIntegration,
81+
/// Global authentication cache for a uv invocation to share credentials across uv clients.
82+
credentials_cache: Arc<CredentialsCache>,
8183
indexes: Indexes,
8284
timeout: Duration,
8385
extra_middleware: Option<ExtraMiddleware>,
@@ -136,6 +138,7 @@ impl Default for BaseClientBuilder<'_> {
136138
markers: None,
137139
platform: None,
138140
auth_integration: AuthIntegration::default(),
141+
credentials_cache: Arc::new(CredentialsCache::default()),
139142
indexes: Indexes::new(),
140143
timeout: Duration::from_secs(30),
141144
extra_middleware: None,
@@ -147,7 +150,7 @@ impl Default for BaseClientBuilder<'_> {
147150
}
148151
}
149152

150-
impl BaseClientBuilder<'_> {
153+
impl<'a> BaseClientBuilder<'a> {
151154
pub fn new(
152155
connectivity: Connectivity,
153156
native_tls: bool,
@@ -166,9 +169,7 @@ impl BaseClientBuilder<'_> {
166169
..Self::default()
167170
}
168171
}
169-
}
170172

171-
impl<'a> BaseClientBuilder<'a> {
172173
/// Use a custom reqwest client instead of creating a new one.
173174
///
174175
/// This allows you to provide your own reqwest client with custom configuration.
@@ -276,6 +277,20 @@ impl<'a> BaseClientBuilder<'a> {
276277
self
277278
}
278279

280+
pub fn credentials_cache(&self) -> &CredentialsCache {
281+
&self.credentials_cache
282+
}
283+
284+
/// See [`CredentialsCache::store_credentials_from_url`].
285+
pub fn store_credentials_from_url(&self, url: &DisplaySafeUrl) -> bool {
286+
self.credentials_cache.store_credentials_from_url(url)
287+
}
288+
289+
/// See [`CredentialsCache::store_credentials`].
290+
pub fn store_credentials(&self, url: &DisplaySafeUrl, credentials: Credentials) {
291+
self.credentials_cache.store_credentials(url, credentials);
292+
}
293+
279294
pub fn is_native_tls(&self) -> bool {
280295
self.native_tls
281296
}
@@ -324,6 +339,7 @@ impl<'a> BaseClientBuilder<'a> {
324339
dangerous_client,
325340
raw_dangerous_client,
326341
timeout,
342+
credentials_cache: self.credentials_cache.clone(),
327343
}
328344
}
329345

@@ -350,6 +366,7 @@ impl<'a> BaseClientBuilder<'a> {
350366
raw_client: existing.raw_client.clone(),
351367
raw_dangerous_client: existing.raw_dangerous_client.clone(),
352368
timeout: existing.timeout,
369+
credentials_cache: existing.credentials_cache.clone(),
353370
}
354371
}
355372

@@ -554,6 +571,7 @@ impl<'a> BaseClientBuilder<'a> {
554571
match self.auth_integration {
555572
AuthIntegration::Default => {
556573
let mut auth_middleware = AuthMiddleware::new()
574+
.with_cache_arc(self.credentials_cache.clone())
557575
.with_base_client(base_client)
558576
.with_indexes(self.indexes.clone())
559577
.with_keyring(self.keyring.to_provider())
@@ -565,6 +583,7 @@ impl<'a> BaseClientBuilder<'a> {
565583
}
566584
AuthIntegration::OnlyAuthenticated => {
567585
let mut auth_middleware = AuthMiddleware::new()
586+
.with_cache_arc(self.credentials_cache.clone())
568587
.with_base_client(base_client)
569588
.with_indexes(self.indexes.clone())
570589
.with_keyring(self.keyring.to_provider())
@@ -608,6 +627,8 @@ pub struct BaseClient {
608627
allow_insecure_host: Vec<TrustedHost>,
609628
/// The number of retries to attempt on transient errors.
610629
retries: u32,
630+
/// Global authentication cache for a uv invocation to share credentials across uv clients.
631+
credentials_cache: Arc<CredentialsCache>,
611632
}
612633

613634
#[derive(Debug, Clone, Copy)]
@@ -659,6 +680,10 @@ impl BaseClient {
659680
}
660681
builder.build_with_max_retries(self.retries)
661682
}
683+
684+
pub fn credentials_cache(&self) -> &CredentialsCache {
685+
&self.credentials_cache
686+
}
662687
}
663688

664689
/// Wrapper around [`ClientWithMiddleware`] that manages redirects.

0 commit comments

Comments
 (0)