hf-hub 1.0.0

Rust client for the Hugging Face Hub API
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
//! Users and organizations: identity, profile lookup, and social listings.
//!
//! This module exposes the [`User`], [`OrgMembership`], and [`Organization`]
//! types and the corresponding [`HFClient`] methods:
//!
//! - [`HFClient::whoami`] — identify the caller and verify that the current token is valid.
//! - [`HFClient::user_overview`] / [`HFClient::organization_overview`] — fetch a public profile by username or
//!   organization name.
//! - [`HFClient::list_user_followers`] / [`HFClient::list_user_following`] / [`HFClient::list_organization_members`] —
//!   paginated listings that yield [`User`] entries one page at a time.

use bon::bon;
use futures::Stream;
use serde::Deserialize;
use url::Url;

use crate::client::HFClient;
use crate::error::HFResult;
use crate::retry;

/// A Hugging Face Hub user account.
///
/// Returned by [`HFClient::whoami`] and the various user-lookup endpoints.
/// Only [`username`](Self::username) is guaranteed to be set; the remaining
/// fields are populated for the authenticated caller's own `whoami` response
/// or when the field is publicly visible on the target user's profile.
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct User {
    /// Hub handle (slug) of the user — the name used in URLs such as
    /// `https://huggingface.co/<username>`.
    #[serde(alias = "login", alias = "user", alias = "name")]
    pub username: String,
    /// Display name as shown on the user's profile, when set.
    pub fullname: Option<String>,
    /// URL to the user's avatar image.
    pub avatar_url: Option<String>,
    /// Account type, typically `"user"` or `"org"`.
    #[serde(rename = "type")]
    pub user_type: Option<String>,
    /// Free-text bio shown on the user's profile.
    pub details: Option<String>,
    /// Whether the authenticated caller follows this user.
    pub is_following: Option<bool>,
    /// Whether the user is on a Pro plan.
    pub is_pro: Option<bool>,
    /// Number of models created by the user.
    pub num_models: Option<u64>,
    /// Number of datasets created by the user.
    pub num_datasets: Option<u64>,
    /// Number of Spaces created by the user.
    pub num_spaces: Option<u64>,
    /// Number of discussions initiated by the user.
    pub num_discussions: Option<u64>,
    /// Number of papers authored by the user.
    pub num_papers: Option<u64>,
    /// Upvotes the user has received.
    pub num_upvotes: Option<u64>,
    /// Likes the user has given.
    pub num_likes: Option<u64>,
    /// Number of users this user is following.
    pub num_following: Option<u64>,
    /// Number of users following this user.
    pub num_followers: Option<u64>,
    /// Email address — only returned by `whoami` for the authenticated user.
    pub email: Option<String>,
    /// Whether the email has been verified — only returned by `whoami`.
    pub email_verified: Option<bool>,
    /// Billing plan identifier — only returned by `whoami`.
    pub plan: Option<String>,
    /// Whether the account has a valid payment method — only returned by `whoami`.
    pub can_pay: Option<bool>,
    /// Organizations the authenticated user belongs to. Only populated by
    /// `whoami` for the caller themselves.
    pub orgs: Option<Vec<OrgMembership>>,
}

/// Summary entry for an organization the authenticated user belongs to.
///
/// Returned inside [`User::orgs`]. This is a lighter-weight shape than
/// [`Organization`] — use [`HFClient::organization_overview`] to fetch the
/// full record by name.
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OrgMembership {
    /// Hub handle (slug) of the organization.
    pub name: Option<String>,
    /// Display name as shown on the organization's profile.
    pub fullname: Option<String>,
    /// URL to the organization's avatar image.
    pub avatar_url: Option<String>,
}

/// A Hugging Face Hub organization.
///
/// Returned by [`HFClient::organization_overview`].
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Organization {
    /// Hub handle (slug) of the organization — the name used in URLs such as
    /// `https://huggingface.co/<name>`.
    pub name: String,
    /// Display name as shown on the organization's profile, when set.
    pub fullname: Option<String>,
    /// URL to the organization's avatar image.
    pub avatar_url: Option<String>,
    /// Account type, typically `"org"`.
    #[serde(rename = "type")]
    pub org_type: Option<String>,
    /// Free-text description shown on the organization's profile.
    pub details: Option<String>,
    /// Whether the organization is verified.
    pub is_verified: Option<bool>,
    /// Whether the authenticated caller follows this organization.
    pub is_following: Option<bool>,
    /// Number of members in the organization.
    pub num_users: Option<u64>,
    /// Number of models owned by the organization.
    pub num_models: Option<u64>,
    /// Number of Spaces owned by the organization.
    pub num_spaces: Option<u64>,
    /// Number of datasets owned by the organization.
    pub num_datasets: Option<u64>,
    /// Number of followers of the organization.
    pub num_followers: Option<u64>,
    /// Number of papers authored by the organization.
    pub num_papers: Option<u64>,
    /// Plan identifier (e.g., `"enterprise"`, `"team"`).
    pub plan: Option<String>,
}

#[bon]
impl HFClient {
    /// Fetch the profile of the user that owns the current token.
    ///
    /// Returns the authenticated [`User`], including private fields like
    /// [`email`](User::email) and the caller's [`orgs`](User::orgs) list. Fails with
    /// [`HFError::AuthRequired`](crate::HFError::AuthRequired) if no valid token is configured.
    ///
    /// Endpoint: `GET /api/whoami-v2`.
    #[builder(finish_fn = send, derive(Debug, Clone))]
    pub async fn whoami(&self) -> HFResult<User> {
        let url = format!("{}/api/whoami-v2", self.endpoint());
        let headers = self.auth_headers();
        let response =
            retry::retry(self.retry_config(), || self.http_client().get(&url).headers(headers.clone()).send()).await?;
        let response = self
            .check_response(response, None, crate::error::NotFoundContext::Generic)
            .await?;
        Ok(response.json().await?)
    }

    /// Fetch the public profile of a user by Hub handle.
    ///
    /// Endpoint: `GET /api/users/{username}/overview`.
    ///
    /// # Parameters
    ///
    /// - `username` (required): Hub handle (slug) of the user.
    #[builder(finish_fn = send, derive(Debug, Clone))]
    pub async fn user_overview(
        &self,
        /// Hub handle (slug) of the user.
        username: &str,
    ) -> HFResult<User> {
        let url = format!("{}/api/users/{}/overview", self.endpoint(), username);
        let headers = self.auth_headers();
        let response =
            retry::retry(self.retry_config(), || self.http_client().get(&url).headers(headers.clone()).send()).await?;
        let response = self
            .check_response(response, None, crate::error::NotFoundContext::Generic)
            .await?;
        Ok(response.json().await?)
    }

    /// Fetch the public profile of an organization by Hub handle.
    ///
    /// Endpoint: `GET /api/organizations/{organization}/overview`.
    ///
    /// # Parameters
    ///
    /// - `organization` (required): Hub handle (slug) of the organization.
    #[builder(finish_fn = send, derive(Debug, Clone))]
    pub async fn organization_overview(
        &self,
        /// Hub handle (slug) of the organization.
        organization: &str,
    ) -> HFResult<Organization> {
        let url = format!("{}/api/organizations/{}/overview", self.endpoint(), organization);
        let headers = self.auth_headers();
        let response =
            retry::retry(self.retry_config(), || self.http_client().get(&url).headers(headers.clone()).send()).await?;
        let response = self
            .check_response(response, None, crate::error::NotFoundContext::Generic)
            .await?;
        Ok(response.json().await?)
    }

    /// Stream the followers of a user.
    ///
    /// Endpoint: `GET /api/users/{username}/followers`.
    ///
    /// # Parameters
    ///
    /// - `username` (required): Hub handle of the user.
    /// - `limit`: cap on the total number of items yielded.
    #[builder(finish_fn = send, derive(Debug, Clone))]
    pub fn list_user_followers(
        &self,
        /// Hub handle of the user.
        #[builder(into)]
        username: String,
        /// Cap on the total number of items yielded.
        limit: Option<usize>,
    ) -> HFResult<impl Stream<Item = HFResult<User>> + '_> {
        let url = Url::parse(&format!("{}/api/users/{}/followers", self.endpoint(), username))?;
        Ok(self.paginate(url, vec![], limit))
    }

    /// Stream the users that a user is following.
    ///
    /// Endpoint: `GET /api/users/{username}/following`.
    ///
    /// # Parameters
    ///
    /// - `username` (required): Hub handle of the user.
    /// - `limit`: cap on the total number of items yielded.
    #[builder(finish_fn = send, derive(Debug, Clone))]
    pub fn list_user_following(
        &self,
        /// Hub handle of the user.
        #[builder(into)]
        username: String,
        /// Cap on the total number of items yielded.
        limit: Option<usize>,
    ) -> HFResult<impl Stream<Item = HFResult<User>> + '_> {
        let url = Url::parse(&format!("{}/api/users/{}/following", self.endpoint(), username))?;
        Ok(self.paginate(url, vec![], limit))
    }

    /// Stream the members of an organization.
    ///
    /// Endpoint: `GET /api/organizations/{organization}/members`.
    ///
    /// # Parameters
    ///
    /// - `organization` (required): Hub handle of the organization.
    /// - `limit`: cap on the total number of items yielded.
    #[builder(finish_fn = send, derive(Debug, Clone))]
    pub fn list_organization_members(
        &self,
        /// Hub handle of the organization.
        #[builder(into)]
        organization: String,
        /// Cap on the total number of items yielded.
        limit: Option<usize>,
    ) -> HFResult<impl Stream<Item = HFResult<User>> + '_> {
        let url = Url::parse(&format!("{}/api/organizations/{}/members", self.endpoint(), organization))?;
        Ok(self.paginate(url, vec![], limit))
    }
}

#[cfg(feature = "blocking")]
#[bon]
impl crate::blocking::HFClientSync {
    /// Blocking counterpart of [`HFClient::whoami`].
    #[builder(finish_fn = send, derive(Debug, Clone))]
    pub fn whoami(&self) -> HFResult<User> {
        self.runtime.block_on(self.inner.whoami().send())
    }

    /// Blocking counterpart of [`HFClient::user_overview`]. See the async method for parameters
    /// and behavior.
    #[builder(finish_fn = send, derive(Debug, Clone))]
    pub fn user_overview(&self, username: &str) -> HFResult<User> {
        self.runtime.block_on(self.inner.user_overview().username(username).send())
    }

    /// Blocking counterpart of [`HFClient::organization_overview`]. See the async method for
    /// parameters and behavior.
    #[builder(finish_fn = send, derive(Debug, Clone))]
    pub fn organization_overview(&self, organization: &str) -> HFResult<Organization> {
        self.runtime
            .block_on(self.inner.organization_overview().organization(organization).send())
    }

    /// Blocking counterpart of [`HFClient::list_user_followers`]. Collects the stream into a
    /// `Vec<User>`. See the async method for parameters and behavior.
    #[builder(finish_fn = send, derive(Debug, Clone))]
    pub fn list_user_followers(&self, #[builder(into)] username: String, limit: Option<usize>) -> HFResult<Vec<User>> {
        use futures::StreamExt;
        self.runtime.block_on(async move {
            let stream = self.inner.list_user_followers().username(username).maybe_limit(limit).send()?;
            futures::pin_mut!(stream);
            let mut items = Vec::new();
            while let Some(item) = stream.next().await {
                items.push(item?);
            }
            Ok(items)
        })
    }

    /// Blocking counterpart of [`HFClient::list_user_following`]. Collects the stream into a
    /// `Vec<User>`. See the async method for parameters and behavior.
    #[builder(finish_fn = send, derive(Debug, Clone))]
    pub fn list_user_following(&self, #[builder(into)] username: String, limit: Option<usize>) -> HFResult<Vec<User>> {
        use futures::StreamExt;
        self.runtime.block_on(async move {
            let stream = self.inner.list_user_following().username(username).maybe_limit(limit).send()?;
            futures::pin_mut!(stream);
            let mut items = Vec::new();
            while let Some(item) = stream.next().await {
                items.push(item?);
            }
            Ok(items)
        })
    }

    /// Blocking counterpart of [`HFClient::list_organization_members`]. Collects the stream into a
    /// `Vec<User>`. See the async method for parameters and behavior.
    #[builder(finish_fn = send, derive(Debug, Clone))]
    pub fn list_organization_members(
        &self,
        #[builder(into)] organization: String,
        limit: Option<usize>,
    ) -> HFResult<Vec<User>> {
        use futures::StreamExt;
        self.runtime.block_on(async move {
            let stream = self
                .inner
                .list_organization_members()
                .organization(organization)
                .maybe_limit(limit)
                .send()?;
            futures::pin_mut!(stream);
            let mut items = Vec::new();
            while let Some(item) = stream.next().await {
                items.push(item?);
            }
            Ok(items)
        })
    }
}

#[cfg(test)]
mod tests {
    use super::{Organization, User};

    #[test]
    fn test_user_full_profile() {
        let json = r#"{
            "user":"alice",
            "fullname":"Alice Anderson",
            "avatarUrl":"https://example/a.png",
            "type":"user",
            "details":"researcher",
            "isFollowing":true,
            "isPro":false,
            "numModels":12,
            "numDatasets":3,
            "numSpaces":1,
            "numDiscussions":4,
            "numPapers":2,
            "numUpvotes":5,
            "numLikes":6,
            "numFollowing":7,
            "numFollowers":8
        }"#;
        let user: User = serde_json::from_str(json).unwrap();
        assert_eq!(user.username, "alice");
        assert_eq!(user.details.as_deref(), Some("researcher"));
        assert_eq!(user.is_following, Some(true));
        assert_eq!(user.num_models, Some(12));
        assert_eq!(user.num_datasets, Some(3));
        assert_eq!(user.num_spaces, Some(1));
        assert_eq!(user.num_discussions, Some(4));
        assert_eq!(user.num_papers, Some(2));
        assert_eq!(user.num_upvotes, Some(5));
        assert_eq!(user.num_likes, Some(6));
        assert_eq!(user.num_following, Some(7));
        assert_eq!(user.num_followers, Some(8));
    }

    #[test]
    fn test_organization_full_profile() {
        let json = r#"{
            "name":"acme",
            "fullname":"Acme Corp",
            "avatarUrl":"https://example/o.png",
            "type":"org",
            "details":"description",
            "isVerified":true,
            "isFollowing":false,
            "numUsers":42,
            "numModels":7,
            "numSpaces":2,
            "numDatasets":3,
            "numFollowers":100,
            "numPapers":5,
            "plan":"enterprise"
        }"#;
        let org: Organization = serde_json::from_str(json).unwrap();
        assert_eq!(org.name, "acme");
        assert_eq!(org.details.as_deref(), Some("description"));
        assert_eq!(org.is_verified, Some(true));
        assert_eq!(org.is_following, Some(false));
        assert_eq!(org.num_users, Some(42));
        assert_eq!(org.num_models, Some(7));
        assert_eq!(org.num_spaces, Some(2));
        assert_eq!(org.num_datasets, Some(3));
        assert_eq!(org.num_followers, Some(100));
        assert_eq!(org.num_papers, Some(5));
        assert_eq!(org.plan.as_deref(), Some("enterprise"));
    }
}