This is an automated email from the ASF dual-hosted git repository.
xuanwo pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/opendal.git
The following commit(s) were added to refs/heads/main by this push:
new 8b45b121e feat(services/huggingface): Add repo_type=space support
(#6833)
8b45b121e is described below
commit 8b45b121e15d70cac77470f4a6b9a084a58577d2
Author: Aryan Bagade <[email protected]>
AuthorDate: Sat Nov 29 00:29:36 2025 -0800
feat(services/huggingface): Add repo_type=space support (#6833)
Enable HuggingFace Spaces support by extending the repo_type enum and URL
templates to allow reading/listing Space repositories like models and datasets.
Part of #6830
---
core/src/services/huggingface/backend.rs | 19 +++++---
core/src/services/huggingface/core.rs | 82 ++++++++++++++++++++++++++++++++
2 files changed, 95 insertions(+), 6 deletions(-)
diff --git a/core/src/services/huggingface/backend.rs
b/core/src/services/huggingface/backend.rs
index 49fa1618e..7bfe9e068 100644
--- a/core/src/services/huggingface/backend.rs
+++ b/core/src/services/huggingface/backend.rs
@@ -45,8 +45,8 @@ impl HuggingfaceBuilder {
/// - model
/// - dataset
/// - datasets (alias for dataset)
+ /// - space
///
- /// Currently, only models and datasets are supported.
/// [Reference](https://huggingface.co/docs/hub/repositories)
pub fn repo_type(mut self, repo_type: &str) -> Self {
if !repo_type.is_empty() {
@@ -130,10 +130,7 @@ impl Builder for HuggingfaceBuilder {
let repo_type = match self.config.repo_type.as_deref() {
Some("model") => Ok(RepoType::Model),
Some("dataset") | Some("datasets") => Ok(RepoType::Dataset),
- Some("space") => Err(Error::new(
- ErrorKind::ConfigInvalid,
- "repo type \"space\" is unsupported",
- )),
+ Some("space") => Ok(RepoType::Space),
Some(repo_type) => Err(Error::new(
ErrorKind::ConfigInvalid,
format!("unknown repo_type: {repo_type}").as_str(),
@@ -284,12 +281,13 @@ impl Access for HuggingfaceBackend {
}
}
-/// Repository type of Huggingface. Currently, we only support `model` and
`dataset`.
+/// Repository type of Huggingface. Supports `model`, `dataset`, and `space`.
/// [Reference](https://huggingface.co/docs/hub/repositories)
#[derive(Debug, Clone, Copy)]
pub enum RepoType {
Model,
Dataset,
+ Space,
}
#[cfg(test)]
@@ -304,4 +302,13 @@ mod tests {
.build()
.expect("builder should accept datasets alias");
}
+
+ #[test]
+ fn build_accepts_space_repo_type() {
+ HuggingfaceBuilder::default()
+ .repo_id("org/space")
+ .repo_type("space")
+ .build()
+ .expect("builder should accept space repo type");
+ }
}
diff --git a/core/src/services/huggingface/core.rs
b/core/src/services/huggingface/core.rs
index 12f036075..156e7b1d0 100644
--- a/core/src/services/huggingface/core.rs
+++ b/core/src/services/huggingface/core.rs
@@ -75,6 +75,12 @@ impl HuggingfaceCore {
&self.repo_id,
percent_encode_revision(&self.revision)
),
+ RepoType::Space => format!(
+ "{}/api/spaces/{}/paths-info/{}",
+ &self.endpoint,
+ &self.repo_id,
+ percent_encode_revision(&self.revision)
+ ),
};
let mut req = Request::post(&url);
@@ -121,6 +127,13 @@ impl HuggingfaceCore {
percent_encode_revision(&self.revision),
percent_encode_path(&p)
),
+ RepoType::Space => format!(
+ "{}/api/spaces/{}/tree/{}/{}?expand=True",
+ &self.endpoint,
+ &self.repo_id,
+ percent_encode_revision(&self.revision),
+ percent_encode_path(&p)
+ ),
};
if recursive {
@@ -183,6 +196,13 @@ impl HuggingfaceCore {
percent_encode_revision(&self.revision),
percent_encode_path(&p)
),
+ RepoType::Space => format!(
+ "{}/spaces/{}/resolve/{}/{}",
+ &self.endpoint,
+ &self.repo_id,
+ percent_encode_revision(&self.revision),
+ percent_encode_path(&p)
+ ),
};
let mut req = Request::get(&url);
@@ -509,6 +529,68 @@ mod tests {
Ok(())
}
+ #[tokio::test]
+ async fn test_hf_path_info_url_space() -> Result<()> {
+ let (core, mock_client) = create_test_core(
+ RepoType::Space,
+ "test-user/test-space",
+ "main",
+ "https://huggingface.co",
+ );
+
+ core.hf_path_info("app.py").await?;
+
+ let url = mock_client.get_captured_url();
+ assert_eq!(
+ url,
+
"https://huggingface.co/api/spaces/test-user/test-space/paths-info/main"
+ );
+
+ Ok(())
+ }
+
+ #[tokio::test]
+ async fn test_hf_list_url_space() -> Result<()> {
+ let (core, mock_client) = create_test_core(
+ RepoType::Space,
+ "org/space",
+ "main",
+ "https://huggingface.co",
+ );
+
+ core.hf_list("static", false, None).await?;
+
+ let url = mock_client.get_captured_url();
+ assert_eq!(
+ url,
+
"https://huggingface.co/api/spaces/org/space/tree/main/static?expand=True"
+ );
+
+ Ok(())
+ }
+
+ #[tokio::test]
+ async fn test_hf_resolve_url_space() -> Result<()> {
+ let (core, mock_client) = create_test_core(
+ RepoType::Space,
+ "user/space",
+ "main",
+ "https://huggingface.co",
+ );
+
+ let args = OpRead::default();
+ core.hf_resolve("README.md", BytesRange::default(), &args)
+ .await?;
+
+ let url = mock_client.get_captured_url();
+ assert_eq!(
+ url,
+ "https://huggingface.co/spaces/user/space/resolve/main/README.md"
+ );
+
+ Ok(())
+ }
+
#[tokio::test]
async fn test_hf_resolve_with_range() -> Result<()> {
let (core, mock_client) = create_test_core(