hf_hub/repository/
listing.rs1use bon::bon;
12use futures::stream::Stream;
13use reqwest::Url;
14
15use super::files::{extract_commit_hash, extract_etag, extract_file_size, extract_xet_hash};
16use super::{FileMetadataInfo, HFRepository, RepoTreeEntry, RepoType};
17use crate::client::encode_ref;
18use crate::error::{HFError, HFResult};
19use crate::{constants, retry};
20
21#[bon]
22impl<T: RepoType> HFRepository<T> {
23 #[builder(finish_fn = send, derive(Debug, Clone))]
39 pub fn list_tree(
40 &self,
41 #[builder(into)]
43 revision: Option<String>,
44 #[builder(into)]
48 path_in_repo: Option<String>,
49 #[builder(default)]
51 recursive: bool,
52 #[builder(default)]
54 expand: bool,
55 limit: Option<usize>,
57 ) -> HFResult<impl Stream<Item = HFResult<RepoTreeEntry>> + '_> {
58 let revision = revision.as_deref().unwrap_or(constants::DEFAULT_REVISION);
59 let url_str = format!(
60 "{}/tree/{}",
61 self.hf_client.api_url(self.repo_type.plural(), &self.repo_path()),
62 encode_ref(revision)
63 );
64 let mut url = Url::parse(&url_str)?;
65 if let Some(path) = path_in_repo.as_deref() {
66 crate::client::append_path_segments(&mut url, path)?;
67 }
68
69 let mut query: Vec<(String, String)> = Vec::new();
70 if recursive {
71 query.push(("recursive".into(), "true".into()));
72 }
73 if expand {
74 query.push(("expand".into(), "true".into()));
75 }
76
77 Ok(self.hf_client.paginate(url, query, limit))
78 }
79
80 #[builder(finish_fn = send, derive(Debug, Clone))]
92 pub async fn get_paths_info(
93 &self,
94 paths: Vec<String>,
96 #[builder(into)]
98 revision: Option<String>,
99 ) -> HFResult<Vec<RepoTreeEntry>> {
100 let revision = revision.as_deref().unwrap_or(constants::DEFAULT_REVISION);
101 let url = format!(
102 "{}/paths-info/{}",
103 self.hf_client.api_url(self.repo_type.plural(), &self.repo_path()),
104 encode_ref(revision)
105 );
106
107 let body = serde_json::json!({ "paths": paths });
108
109 let headers = self.hf_client.auth_headers();
110 let response = retry::retry(self.hf_client.retry_config(), || {
111 self.hf_client
112 .http_client()
113 .post(&url)
114 .headers(headers.clone())
115 .json(&body)
116 .send()
117 })
118 .await?;
119
120 let repo_path = self.repo_path();
121 let response = self
122 .hf_client
123 .check_response(response, Some(&repo_path), crate::error::NotFoundContext::Entry { path: paths.join(", ") })
124 .await?;
125 Ok(response.json().await?)
126 }
127
128 #[builder(finish_fn = send, derive(Debug, Clone))]
140 pub async fn get_file_metadata(
141 &self,
142 #[builder(into)]
144 filepath: String,
145 revision: Option<&str>,
147 ) -> HFResult<FileMetadataInfo> {
148 let filename = filepath;
149 let revision = revision.unwrap_or(constants::DEFAULT_REVISION);
150 let repo_path = self.repo_path();
151 let url = self
152 .hf_client
153 .download_url(self.repo_type.url_prefix(), &repo_path, revision, &filename)?;
154
155 let headers = self.hf_client.auth_headers();
156 let response = retry::retry(self.hf_client.retry_config(), || {
157 self.hf_client.http_client().head(&url).headers(headers.clone()).send()
158 })
159 .await?;
160 let response = self
161 .hf_client
162 .check_response(response, Some(&repo_path), crate::error::NotFoundContext::Entry { path: filename.clone() })
163 .await?;
164
165 let etag = extract_etag(&response).ok_or_else(|| {
166 HFError::malformed_response_at(format!("missing ETag header for {filename}"), url.to_string())
167 })?;
168 let commit_hash = extract_commit_hash(&response).ok_or_else(|| {
169 HFError::malformed_response_at(format!("missing X-Repo-Commit header for {filename}"), url.to_string())
170 })?;
171 let xet_hash = extract_xet_hash(&response);
172 let file_size = extract_file_size(&response).unwrap_or_else(|| {
173 tracing::warn!(
174 file = %filename,
175 "missing or invalid Content-Length/X-Linked-Size header, defaulting file size to 0"
176 );
177 0
178 });
179 let location = Some(response.url().to_string());
180
181 Ok(FileMetadataInfo {
182 filename,
183 etag,
184 commit_hash,
185 xet_hash,
186 file_size,
187 location,
188 })
189 }
190}
191
192#[cfg(feature = "blocking")]
193use futures::stream::StreamExt as _;
194
195#[cfg(feature = "blocking")]
196#[bon]
197impl<T: RepoType> crate::blocking::HFRepositorySync<T> {
198 #[builder(finish_fn = send, derive(Debug, Clone))]
201 pub fn list_tree(
202 &self,
203 #[builder(into)] revision: Option<String>,
204 #[builder(into)] path_in_repo: Option<String>,
205 #[builder(default)] recursive: bool,
206 #[builder(default)] expand: bool,
207 limit: Option<usize>,
208 ) -> HFResult<Vec<RepoTreeEntry>> {
209 self.runtime.block_on(async move {
210 let stream = self
211 .inner
212 .list_tree()
213 .maybe_revision(revision)
214 .maybe_path_in_repo(path_in_repo)
215 .recursive(recursive)
216 .expand(expand)
217 .maybe_limit(limit)
218 .send()?;
219 futures::pin_mut!(stream);
220 let mut items = Vec::new();
221 while let Some(item) = stream.next().await {
222 items.push(item?);
223 }
224 Ok(items)
225 })
226 }
227
228 #[builder(finish_fn = send, derive(Debug, Clone))]
231 pub fn get_paths_info(
232 &self,
233 paths: Vec<String>,
234 #[builder(into)] revision: Option<String>,
235 ) -> HFResult<Vec<RepoTreeEntry>> {
236 self.runtime
237 .block_on(self.inner.get_paths_info().paths(paths).maybe_revision(revision).send())
238 }
239
240 #[builder(finish_fn = send, derive(Debug, Clone))]
243 pub fn get_file_metadata(
244 &self,
245 #[builder(into)] filepath: String,
246 revision: Option<&str>,
247 ) -> HFResult<FileMetadataInfo> {
248 self.runtime.block_on(
249 self.inner
250 .get_file_metadata()
251 .filepath(filepath)
252 .maybe_revision(revision)
253 .send(),
254 )
255 }
256}