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 6 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
144 changes: 144 additions & 0 deletions src/huggingface_hub/hf_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1197,12 +1197,58 @@ class User:
Name of the user on the Hub (unique).
fullname (`str`):
User's full name.
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
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 __init__(self, **kwargs) -> None:
self.avatar_url = kwargs.get("avatarUrl", "")
self.username = kwargs.get("user", "")
self.fullname = kwargs.get("fullname", "")
self.is_pro = kwargs.get("isPro")
self.num_models = kwargs.get("numModels")
self.num_datasets = kwargs.get("numDatasets")
self.num_spaces = kwargs.get("numSpaces")
self.num_discussions = kwargs.get("numDiscussions")
self.num_papers = kwargs.get("numPapers")
self.num_upvotes = kwargs.get("numUpvotes")
self.num_likes = kwargs.get("numLikes")
self.user_type = kwargs.get("type")
self.is_following = kwargs.get("isFollowing")
self.details = kwargs.get("details")

# forward compatibility
self.__dict__.update(**kwargs)


def future_compatible(fn: CallableT) -> CallableT:
Expand Down Expand Up @@ -8462,6 +8508,98 @@ 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)
return User(**r.json())

def list_organization_members(self, organization: str) -> Iterable[User]:
"""
List of members of an organization on the Hub.

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

Returns:
`Iterable[User]`: A list of [`User`] objects with the members of the organization.

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)

for member in r.json():
yield User(**member)

def list_user_followers(self, username: str) -> Iterable[User]:
"""
Get the list of followers of a user on the Hub.

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

Returns:
`Iterable[User]`: A list of [`User`] objects with the followers of the user.

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)

for follower in r.json():
yield User(**follower)

def list_user_following(self, username: str) -> Iterable[User]:
"""
Get the list of users followed by a user on the Hub.

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

Returns:
`Iterable[User]`: A list of [`User`] objects with the users followed by the user.

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)

for followed_user in r.json():
yield User(**followed_user)


def _prepare_upload_folder_additions(
folder_path: Union[str, Path],
Expand Down Expand Up @@ -8630,3 +8768,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
list_organization_members = api.list_organization_members
list_user_followers = api.list_user_followers
list_user_following = api.list_user_following
25 changes: 25 additions & 0 deletions tests/test_hf_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3791,3 +3791,28 @@ 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)


@with_production_testing
class UserApiTest(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
cls.api = HfApi() # no auth!

def test_user_overview(self) -> None:
overview = self.api.get_user_overview("julien-c")
self.assertEqual(overview.user_type, "user")
self.assertGreater(overview.num_likes, 10)
self.assertGreater(overview.num_upvotes, 10)

def test_organization_members(self) -> None:
members = self.api.list_organization_members("huggingface")
self.assertGreater(len(list(members)), 1)

def test_user_followers(self) -> None:
followers = self.api.list_user_followers("julien-c")
self.assertGreater(len(list(followers)), 10)

def test_user_following(self) -> None:
following = self.api.list_user_following("julien-c")
self.assertGreater(len(list(following)), 10)