Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

User API endpoints #2147

Merged
merged 8 commits into from
Mar 29, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
171 changes: 171 additions & 0 deletions src/huggingface_hub/hf_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1197,12 +1197,45 @@ class User:
Name of the user on the Hub (unique).
fullname (`str`):
User's full name.
is_pro (`bool`):
Whether the user is a pro user.
user_type (`str`):
Type of user.
Wauplin marked this conversation as resolved.
Show resolved Hide resolved
num_models (`int`, *optional*):
Number of models created by the user.
num_datasets (`int`, *optional*):
Number of datasets created by the user.
num_spaces (`int`, *optional*):
Number of spaces created by the user.
num_discussions (`int`, *optional*):
Number of discussions initiated by the user.
num_papers (`int`, *optional*):
Number of papers authored by the user.
num_upvotes (`int`, *optional*):
Number of upvotes received by the user.
num_likes (`int`, *optional*):
Number of likes given by the user.
is_following (`bool`, *optional*):
Whether the authenticated user is following this user.
details (`str`, *optional*):
User's details.
"""

# Metadata
avatar_url: str
username: str
fullname: str
is_pro: bool
user_type: str
Wauplin marked this conversation as resolved.
Show resolved Hide resolved
num_models: Optional[int] = None
Wauplin marked this conversation as resolved.
Show resolved Hide resolved
num_datasets: Optional[int] = None
num_spaces: Optional[int] = None
num_discussions: Optional[int] = None
num_papers: Optional[int] = None
num_upvotes: Optional[int] = None
num_likes: Optional[int] = None
is_following: Optional[bool] = None
details: Optional[str] = None
Wauplin marked this conversation as resolved.
Show resolved Hide resolved


def future_compatible(fn: CallableT) -> CallableT:
Expand Down Expand Up @@ -8462,6 +8495,138 @@ def _prepare_upload_folder_deletions(
if relpath_to_abspath[relpath] != ".gitattributes"
]

def get_user_overview(self, username: str) -> User:
"""
Get an overview of a user on the Hub.

Args:
username (`str`):
Username of the user to get an overview of.

Returns:
`User`: A [`User`] object with the user's overview.

Raises:
`HTTPError`:
HTTP 404 If the user does not exist on the Hub.
"""
r = get_session().get(f"{ENDPOINT}/api/users/{username}/overview")

hf_raise_for_status(r)
user_data = r.json()
return User(
avatar_url=user_data.get("avatarUrl"),
username=user_data["user"],
fullname=user_data.get("fullname"),
is_pro=user_data.get("isPro"),
num_models=user_data.get("numModels"),
num_datasets=user_data.get("numDatasets"),
num_spaces=user_data.get("numSpaces"),
num_discussions=user_data.get("numDiscussions"),
num_papers=user_data.get("numPapers"),
num_upvotes=user_data.get("numUpvotes"),
num_likes=user_data.get("numLikes"),
user_type=user_data.get("type"),
is_following=user_data.get("isFollowing"),
details=user_data.get("details"),
)
Wauplin marked this conversation as resolved.
Show resolved Hide resolved

def get_organization_members(self, organization: str) -> List[User]:
"""
Get the list of members of an organization on the Hub.
Wauplin marked this conversation as resolved.
Show resolved Hide resolved

Args:
organization (`str`):
Name of the organization to get the members of.

Returns:
`List[User]`: A list of [`User`] objects with the members of the organization.
Wauplin marked this conversation as resolved.
Show resolved Hide resolved

Raises:
`HTTPError`:
HTTP 404 If the organization does not exist on the Hub.

"""

r = get_session().get(f"{ENDPOINT}/api/organizations/{organization}/members")

hf_raise_for_status(r)

return [
User(
avatar_url=member.get("avatarUrl"),
fullname=member.get("fullname"),
is_pro=member.get("isPro"),
username=member.get("user"),
user_type=member.get("type"),
)
for member in r.json()
]
Wauplin marked this conversation as resolved.
Show resolved Hide resolved

def get_user_followers(self, username: str) -> List[User]:
"""
Get the list of followers of a user on the Hub.
Wauplin marked this conversation as resolved.
Show resolved Hide resolved

Args:
username (`str`):
Username of the user to get the followers of.

Returns:
`List[User]`: A list of [`User`] objects with the followers of the user.
Wauplin marked this conversation as resolved.
Show resolved Hide resolved

Raises:
`HTTPError`:
HTTP 404 If the user does not exist on the Hub.

"""

r = get_session().get(f"{ENDPOINT}/api/users/{username}/followers")

hf_raise_for_status(r)

return [
User(
avatar_url=follower.get("avatarUrl"),
fullname=follower.get("fullname"),
is_pro=follower.get("isPro"),
username=follower.get("user"),
user_type=follower.get("type"),
)
for follower in r.json()
]
Wauplin marked this conversation as resolved.
Show resolved Hide resolved

def get_user_following(self, username: str) -> List[User]:
"""
Get the list of users followed by a user on the Hub.
Wauplin marked this conversation as resolved.
Show resolved Hide resolved

Args:
username (`str`):
Username of the user to get the users followed by.

Returns:
`List[User]`: A list of [`User`] objects with the users followed by the user.
Wauplin marked this conversation as resolved.
Show resolved Hide resolved

Raises:
`HTTPError`:
HTTP 404 If the user does not exist on the Hub.

"""

r = get_session().get(f"{ENDPOINT}/api/users/{username}/following")

hf_raise_for_status(r)

return [
User(
avatar_url=following.get("avatarUrl"),
fullname=following.get("fullname"),
is_pro=following.get("isPro"),
username=following.get("user"),
user_type=following.get("type"),
)
for following in r.json()
]
Wauplin marked this conversation as resolved.
Show resolved Hide resolved


def _prepare_upload_folder_additions(
folder_path: Union[str, Path],
Expand Down Expand Up @@ -8630,3 +8795,9 @@ def _parse_revision_from_pr_url(pr_url: str) -> str:
accept_access_request = api.accept_access_request
reject_access_request = api.reject_access_request
grant_access = api.grant_access

# User API
get_user_overview = api.get_user_overview
get_organization_members = api.get_organization_members
get_user_followers = api.get_user_followers
get_user_following = api.get_user_following
Wauplin marked this conversation as resolved.
Show resolved Hide resolved
27 changes: 27 additions & 0 deletions tests/test_hf_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3791,3 +3791,30 @@ def test_access_request_error(self):
self._api.cancel_access_request(self.repo_id, OTHER_USER)
with self.assertRaises(HTTPError):
self._api.cancel_access_request(self.repo_id, OTHER_USER)


class UserApiTest(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
cls.api = HfApi() # no auth!
Wauplin marked this conversation as resolved.
Show resolved Hide resolved

def test_user_overview(self) -> None:
overview = self.api.get_user_overview(USER)
self.assertEqual(overview.user_type, "user")
self.assertEqual(overview.num_likes, 0)
self.assertEqual(overview.num_upvotes, 0)
self.assertEqual(overview.details, None)
self.assertEqual(overview.fullname, FULL_NAME)

@with_production_testing
def test_organization_members(self) -> None:
members = self.api.get_organization_members("huggingface")
self.assertGreater(len(members), 1)

def test_user_followers(self) -> None:
followers = self.api.get_user_followers(USER)
self.assertEqual(len(followers), 0)

def test_user_following(self) -> None:
following = self.api.get_user_following(USER)
self.assertEqual(len(following), 0)
Loading