Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/uv-auth/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ mod credentials;
mod index;
mod keyring;
mod middleware;
mod providers;
mod realm;

// TODO(zanieb): Consider passing a cache explicitly throughout
Expand Down
13 changes: 10 additions & 3 deletions crates/uv-auth/src/middleware.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use reqwest::{Request, Response};
use reqwest_middleware::{Error, Middleware, Next};
use tracing::{debug, trace, warn};

use crate::providers::HuggingFaceProvider;
use crate::{
CREDENTIALS_CACHE, CredentialsCache, KeyringProvider,
cache::FetchUrl,
Expand Down Expand Up @@ -457,9 +458,8 @@ impl AuthMiddleware {
Some(credentials)
};

return self
.complete_request(credentials, request, extensions, next, auth_policy)
.await;
self.complete_request(credentials, request, extensions, next, auth_policy)
.await
}

/// Fetch credentials for a URL.
Expand Down Expand Up @@ -503,6 +503,13 @@ impl AuthMiddleware {
return credentials;
}

// Support for known providers, like Hugging Face.
if let Some(credentials) = HuggingFaceProvider::credentials_for(url).map(Arc::new) {
debug!("Found Hugging Face credentials for {url}");
self.cache().fetches.done(key, Some(credentials.clone()));
return Some(credentials);
}

// Netrc support based on: <https://github.com/gribouille/netrc>.
let credentials = if let Some(credentials) = self.netrc.get().and_then(|netrc| {
debug!("Checking netrc for credentials for {url}");
Expand Down
49 changes: 49 additions & 0 deletions crates/uv-auth/src/providers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use std::sync::LazyLock;
use tracing::debug;
use url::Url;

use uv_static::EnvVars;

use crate::Credentials;
use crate::realm::Realm;

/// The [`Realm`] for the Hugging Face platform.
static HUGGING_FACE_REALM: LazyLock<Realm> = LazyLock::new(|| {
let url = Url::parse("https://huggingface.co").expect("Failed to parse Hugging Face URL");
Realm::from(&url)
});

/// The authentication token for the Hugging Face platform, if set.
static HUGGING_FACE_TOKEN: LazyLock<Option<Vec<u8>>> = LazyLock::new(|| {
// Extract the Hugging Face token from the environment variable, if it exists.
let hf_token = std::env::var(EnvVars::HF_TOKEN)
.ok()
.map(String::into_bytes)
.filter(|token| !token.is_empty())?;

if std::env::var_os(EnvVars::UV_NO_HF_TOKEN).is_some() {
debug!("Ignoring Hugging Face token from environment due to `UV_NO_HF_TOKEN`");
return None;
}

debug!("Found Hugging Face token in environment");
Some(hf_token)
});

/// A provider for authentication credentials for the Hugging Face platform.
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct HuggingFaceProvider;

impl HuggingFaceProvider {
/// Returns the credentials for the Hugging Face platform, if available.
pub(crate) fn credentials_for(url: &Url) -> Option<Credentials> {
if Realm::from(url) == *HUGGING_FACE_REALM {
if let Some(token) = HUGGING_FACE_TOKEN.as_ref() {
return Some(Credentials::Bearer {
token: token.clone(),
});
}
}
None
}
}
7 changes: 7 additions & 0 deletions crates/uv-static/src/env_vars.rs
Original file line number Diff line number Diff line change
Expand Up @@ -765,4 +765,11 @@ impl EnvVars {

/// Disable GitHub-specific requests that allow uv to skip `git fetch` in some circumstances.
pub const UV_NO_GITHUB_FAST_PATH: &'static str = "UV_NO_GITHUB_FAST_PATH";

/// Authentication token for Hugging Face requests. When set, uv will use this token
/// when making requests to `https://huggingface.co/` and any subdomains.
pub const HF_TOKEN: &'static str = "HF_TOKEN";

/// Disable Hugging Face authentication, even if `HF_TOKEN` is set.
pub const UV_NO_HF_TOKEN: &'static str = "UV_NO_HF_TOKEN";
}
15 changes: 15 additions & 0 deletions docs/concepts/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,3 +151,18 @@ insecure.

Use `allow-insecure-host` with caution and only in trusted environments, as it can expose you to
security risks due to the lack of certificate verification.

## Hugging Face support

uv supports automatic authentication for the Hugging Face Hub. Specifically, if the `HF_TOKEN`
environment variable is set, uv will propagate it to requests to `huggingface.co`.

This is particularly useful for accessing private scripts in Hugging Face Datasets. For example, you
can run the following command to execute the script `main.py` script from a private dataset:

```console
$ HF_TOKEN=hf_... uv run https://huggingface.co/datasets/<user>/<name>/resolve/<branch>/main.py
```

You can disable automatic Hugging Face authentication by setting the `UV_NO_HF_TOKEN=1` environment
variable.
9 changes: 9 additions & 0 deletions docs/reference/environment.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,10 @@ Ignore `.env` files when executing `uv run` commands.

Disable GitHub-specific requests that allow uv to skip `git fetch` in some circumstances.

### `UV_NO_HF_TOKEN`

Disable Hugging Face authentication, even if `HF_TOKEN` is set.

### `UV_NO_INSTALLER_METADATA`

Skip writing `uv` installer metadata files (e.g., `INSTALLER`, `REQUESTED`, and `direct_url.json`) to site-packages `.dist-info` directories.
Expand Down Expand Up @@ -528,6 +532,11 @@ See [force-color.org](https://force-color.org).

Used for trusted publishing via `uv publish`.

### `HF_TOKEN`

Authentication token for Hugging Face requests. When set, uv will use this token
when making requests to `https://huggingface.co/` and any subdomains.

### `HOME`

The standard `HOME` env var.
Expand Down
Loading