pub mod cache;
pub mod cache_layout;
pub mod checksum;
mod chunked;
pub mod config;
pub mod discover;
pub mod download;
pub mod error;
pub mod inspect;
pub mod plan;
pub mod progress;
pub mod repo;
mod retry;
pub use chunked::build_client;
pub use config::{
compile_glob_patterns, file_matches, has_glob_chars, FetchConfig, FetchConfigBuilder, Filter,
};
pub use discover::{DiscoveredFamily, GateStatus, ModelCardMetadata, SearchResult};
pub use download::DownloadOutcome;
pub use error::{FetchError, FileFailure};
pub use inspect::AdapterConfig;
pub use plan::{download_plan, DownloadPlan, FilePlan};
pub use progress::{ProgressEvent, ProgressReceiver};
use std::collections::HashMap;
use std::path::PathBuf;
use hf_hub::{Repo, RepoType};
async fn preflight_gated_check(repo_id: &str, config: &FetchConfig) -> Result<(), FetchError> {
let Ok(metadata) = discover::fetch_model_card(repo_id).await else {
return Ok(());
};
if !metadata.gated.is_gated() {
return Ok(());
}
if config.token.is_none() {
return Err(FetchError::Auth {
reason: format!(
"{repo_id} is a gated model — accept the license at \
https://huggingface.co/{repo_id} and set HF_TOKEN or pass --token"
),
});
}
let probe_client = chunked::build_client(config.token.as_deref())?;
let probe = repo::list_repo_files_with_metadata(
repo_id,
config.token.as_deref(),
config.revision.as_deref(),
&probe_client,
)
.await;
if let Err(ref e) = probe {
let msg = e.to_string();
if msg.contains("401") || msg.contains("403") {
return Err(FetchError::Auth {
reason: format!(
"{repo_id} is a gated model and your token was rejected — \
accept the license at https://huggingface.co/{repo_id} \
and check that your token is valid"
),
});
}
}
Ok(())
}
pub async fn download(repo_id: String) -> Result<DownloadOutcome<PathBuf>, FetchError> {
let config = FetchConfig::builder().build()?;
download_with_config(repo_id, &config).await
}
pub async fn download_with_config(
repo_id: String,
config: &FetchConfig,
) -> Result<DownloadOutcome<PathBuf>, FetchError> {
preflight_gated_check(repo_id.as_str(), config).await?;
let mut builder = hf_hub::api::tokio::ApiBuilder::new().high();
if let Some(ref token) = config.token {
builder = builder.with_token(Some(token.clone()));
}
if let Some(ref dir) = config.output_dir {
builder = builder.with_cache_dir(dir.clone());
}
let api = builder.build().map_err(FetchError::Api)?;
let hf_repo = match config.revision {
Some(ref rev) => {
Repo::with_revision(repo_id.clone(), RepoType::Model, rev.clone())
}
None => Repo::new(repo_id.clone(), RepoType::Model),
};
let repo = api.repo(hf_repo);
download::download_all_files(repo, repo_id, Some(config)).await
}
pub fn download_blocking(repo_id: String) -> Result<DownloadOutcome<PathBuf>, FetchError> {
let rt = tokio::runtime::Runtime::new().map_err(|e| FetchError::Io {
path: PathBuf::from("<runtime>"),
source: e,
})?;
rt.block_on(download(repo_id))
}
pub fn download_with_config_blocking(
repo_id: String,
config: &FetchConfig,
) -> Result<DownloadOutcome<PathBuf>, FetchError> {
let rt = tokio::runtime::Runtime::new().map_err(|e| FetchError::Io {
path: PathBuf::from("<runtime>"),
source: e,
})?;
rt.block_on(download_with_config(repo_id, config))
}
pub async fn download_files(
repo_id: String,
) -> Result<DownloadOutcome<HashMap<String, PathBuf>>, FetchError> {
let config = FetchConfig::builder().build()?;
download_files_with_config(repo_id, &config).await
}
pub async fn download_files_with_config(
repo_id: String,
config: &FetchConfig,
) -> Result<DownloadOutcome<HashMap<String, PathBuf>>, FetchError> {
preflight_gated_check(repo_id.as_str(), config).await?;
let mut builder = hf_hub::api::tokio::ApiBuilder::new().high();
if let Some(ref token) = config.token {
builder = builder.with_token(Some(token.clone()));
}
if let Some(ref dir) = config.output_dir {
builder = builder.with_cache_dir(dir.clone());
}
let api = builder.build().map_err(FetchError::Api)?;
let hf_repo = match config.revision {
Some(ref rev) => {
Repo::with_revision(repo_id.clone(), RepoType::Model, rev.clone())
}
None => Repo::new(repo_id.clone(), RepoType::Model),
};
let repo = api.repo(hf_repo);
download::download_all_files_map(repo, repo_id, Some(config)).await
}
pub fn download_files_blocking(
repo_id: String,
) -> Result<DownloadOutcome<HashMap<String, PathBuf>>, FetchError> {
let rt = tokio::runtime::Runtime::new().map_err(|e| FetchError::Io {
path: PathBuf::from("<runtime>"),
source: e,
})?;
rt.block_on(download_files(repo_id))
}
pub async fn download_file(
repo_id: String,
filename: &str,
config: &FetchConfig,
) -> Result<DownloadOutcome<PathBuf>, FetchError> {
preflight_gated_check(repo_id.as_str(), config).await?;
let mut builder = hf_hub::api::tokio::ApiBuilder::new().high();
if let Some(ref token) = config.token {
builder = builder.with_token(Some(token.clone()));
}
if let Some(ref dir) = config.output_dir {
builder = builder.with_cache_dir(dir.clone());
}
let api = builder.build().map_err(FetchError::Api)?;
let hf_repo = match config.revision {
Some(ref rev) => {
Repo::with_revision(repo_id.clone(), RepoType::Model, rev.clone())
}
None => Repo::new(repo_id.clone(), RepoType::Model),
};
let repo = api.repo(hf_repo);
download::download_file_by_name(repo, repo_id, filename, config).await
}
pub fn download_file_blocking(
repo_id: String,
filename: &str,
config: &FetchConfig,
) -> Result<DownloadOutcome<PathBuf>, FetchError> {
let rt = tokio::runtime::Runtime::new().map_err(|e| FetchError::Io {
path: PathBuf::from("<runtime>"),
source: e,
})?;
rt.block_on(download_file(repo_id, filename, config))
}
pub fn download_files_with_config_blocking(
repo_id: String,
config: &FetchConfig,
) -> Result<DownloadOutcome<HashMap<String, PathBuf>>, FetchError> {
let rt = tokio::runtime::Runtime::new().map_err(|e| FetchError::Io {
path: PathBuf::from("<runtime>"),
source: e,
})?;
rt.block_on(download_files_with_config(repo_id, config))
}
pub async fn download_with_plan(
plan: &DownloadPlan,
config: &FetchConfig,
) -> Result<DownloadOutcome<PathBuf>, FetchError> {
if plan.fully_cached() {
let cache_dir = config
.output_dir
.clone()
.map_or_else(cache::hf_cache_dir, Ok)?;
let repo_dir = cache_layout::repo_dir(&cache_dir, plan.repo_id.as_str());
let snapshot_dir = cache_layout::snapshot_dir(&repo_dir, plan.revision.as_str());
return Ok(DownloadOutcome::Cached(snapshot_dir));
}
download_with_config(plan.repo_id.clone(), config).await
}
pub fn download_with_plan_blocking(
plan: &DownloadPlan,
config: &FetchConfig,
) -> Result<DownloadOutcome<PathBuf>, FetchError> {
let rt = tokio::runtime::Runtime::new().map_err(|e| FetchError::Io {
path: PathBuf::from("<runtime>"),
source: e,
})?;
rt.block_on(download_with_plan(plan, config))
}