From 9d253802016cba0a1115ef6bad2b5fc098237950 Mon Sep 17 00:00:00 2001 From: Issam Arabi Date: Thu, 5 Oct 2023 11:40:36 -0400 Subject: [PATCH 1/5] update list_repo_likers function Signed-off-by: Issam Arabi --- src/huggingface_hub/hf_api.py | 102 ++++++++++++++++++++++++++++++---- 1 file changed, 90 insertions(+), 12 deletions(-) diff --git a/src/huggingface_hub/hf_api.py b/src/huggingface_hub/hf_api.py index 4ff144b6b1..c22a23ed5f 100644 --- a/src/huggingface_hub/hf_api.py +++ b/src/huggingface_hub/hf_api.py @@ -919,6 +919,31 @@ class UserLikes: spaces: List[str] +@dataclass +class User: + """ + Contains information about a user on the Hub. + + Args: + name (`str`): + Name of the user. + avatarUrl (`str`): + URL of the user's avatar. + fullName (`str`): + User's full name. + """ + + # Metadata + name: str + avatarUrl: str + fullName: str + + def __init__(self, data: Dict) -> None: + self.name = data["name"] + self.avatarUrl = data["avatarUrl"] + self.fullName = data["fullName"] + + def future_compatible(fn: CallableT) -> CallableT: """Wrap a method of `HfApi` to handle `run_as_future=True`. @@ -1054,9 +1079,11 @@ def whoami(self, token: Optional[str] = None) -> Dict: hf_raise_for_status(r) except HTTPError as e: raise HTTPError( - "Invalid user token. If you didn't pass a user token, make sure you " - "are properly logged in by executing `huggingface-cli login`, and " - "if you did pass a user token, double-check it's correct.", + ( + "Invalid user token. If you didn't pass a user token, make sure you " + "are properly logged in by executing `huggingface-cli login`, and " + "if you did pass a user token, double-check it's correct." + ), request=e.request, response=e.response, ) from e @@ -1730,6 +1757,51 @@ def list_liked_repos( spaces=[like["repo"]["name"] for like in likes if like["repo"]["type"] == "space"], ) + @validate_hf_hub_args + def list_repo_likers( + self, + repo_id: str, + *, + repo_type: Optional[str] = None, + token: Optional[str] = None, + ) -> List[User]: + """ + List all users who liked a given repo on huggingface.co. + + See also [`like`] and [`list_liked_repos`]. + + Args: + repo_id (`str`): + The repository to retrieve . Example: `"user/my-cool-model"`. + + token (`str`, *optional*): + Authentication token. Will default to the stored token. + + repo_type (`str`, *optional*): + Set to `"dataset"` or `"space"` if uploading to a dataset or + space, `None` or `"model"` if uploading to a model. Default is + `None`. + + Returns: + `List[User]`: a list of [`User`] objects. + """ + + # Construct the API endpoint + path = f"{self.endpoint}/api/models/{repo_id}/likers" + if repo_type: + path = f"{self.endpoint}/api/{repo_type}/{repo_id}/likers" + headers = self._build_hf_headers(token=token) + + # Make the request + response = get_session().get(path, headers=headers) + hf_raise_for_status(response) + + # Parse the results into User objects + likers_data = response.json() + likers = [User(**user_data) for user_data in likers_data] + + return likers + @validate_hf_hub_args def model_info( self, @@ -5612,9 +5684,11 @@ def request_space_hardware( """ if sleep_time is not None and hardware == SpaceHardware.CPU_BASIC: warnings.warn( - "If your Space runs on the default 'cpu-basic' hardware, it will go to sleep if inactive for more" - " than 48 hours. This value is not configurable. If you don't want your Space to deactivate or if" - " you want to set a custom sleep time, you need to upgrade to a paid Hardware.", + ( + "If your Space runs on the default 'cpu-basic' hardware, it will go to sleep if inactive for more" + " than 48 hours. This value is not configurable. If you don't want your Space to deactivate or if" + " you want to set a custom sleep time, you need to upgrade to a paid Hardware." + ), UserWarning, ) payload: Dict[str, Any] = {"flavor": hardware} @@ -5667,9 +5741,11 @@ def set_space_sleep_time(self, repo_id: str, sleep_time: int, *, token: Optional hardware = runtime.requested_hardware or runtime.hardware if hardware == SpaceHardware.CPU_BASIC: warnings.warn( - "If your Space runs on the default 'cpu-basic' hardware, it will go to sleep if inactive for more" - " than 48 hours. This value is not configurable. If you don't want your Space to deactivate or if" - " you want to set a custom sleep time, you need to upgrade to a paid Hardware.", + ( + "If your Space runs on the default 'cpu-basic' hardware, it will go to sleep if inactive for more" + " than 48 hours. This value is not configurable. If you don't want your Space to deactivate or if" + " you want to set a custom sleep time, you need to upgrade to a paid Hardware." + ), UserWarning, ) return runtime @@ -5845,9 +5921,11 @@ def duplicate_space( if sleep_time is not None and hardware == SpaceHardware.CPU_BASIC: warnings.warn( - "If your Space runs on the default 'cpu-basic' hardware, it will go to sleep if inactive for more" - " than 48 hours. This value is not configurable. If you don't want your Space to deactivate or if" - " you want to set a custom sleep time, you need to upgrade to a paid Hardware.", + ( + "If your Space runs on the default 'cpu-basic' hardware, it will go to sleep if inactive for more" + " than 48 hours. This value is not configurable. If you don't want your Space to deactivate or if" + " you want to set a custom sleep time, you need to upgrade to a paid Hardware." + ), UserWarning, ) From 31303b511c62f6d8cc1efee68b7722a9e527334b Mon Sep 17 00:00:00 2001 From: Issam Arabi Date: Thu, 5 Oct 2023 12:07:25 -0400 Subject: [PATCH 2/5] add test for list_repo_likers --- tests/test_hf_api.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/tests/test_hf_api.py b/tests/test_hf_api.py index 10fa9fa5b9..1b85dc9c43 100644 --- a/tests/test_hf_api.py +++ b/tests/test_hf_api.py @@ -2467,6 +2467,22 @@ def test_list_likes_repos_auth_and_explicit_user(self) -> None: likes = self.api.list_liked_repos(user="__DUMMY_DATASETS_SERVER_USER__", token=TOKEN) self.assertEqual(likes.user, "__DUMMY_DATASETS_SERVER_USER__") + def test_list_repo_likers(self) -> None: + # Create a repo + like + repo_id = self.api.create_repo(repo_name(), token=TOKEN).repo_id + self.api.like(repo_id, token=TOKEN) + + # Use list_repo_likers to get the list of users who liked this repo + likers = self.api.list_repo_likers(repo_id, token=TOKEN) + + # Check if the test user is in the list of likers + liker_usernames = [user.name for user in likers] + self.assertGreater(len(likers), 0) + self.assertIn(USER, liker_usernames) + + # Cleanup + self.api.delete_repo(repo_id, token=TOKEN) + @with_production_testing def test_list_likes_on_production(self) -> None: # Test julien-c likes a lot of repos ! @@ -3134,8 +3150,10 @@ def test_repo_url_class(self): # __repr__ is modified for debugging purposes self.assertEqual( repr(url), - "RepoUrl('https://huggingface.co/gpt2'," - " endpoint='https://huggingface.co', repo_type='model', repo_id='gpt2')", + ( + "RepoUrl('https://huggingface.co/gpt2'," + " endpoint='https://huggingface.co', repo_type='model', repo_id='gpt2')" + ), ) def test_repo_url_endpoint(self): From 2dcc35f64461a32ba855cffe710e9eb748a7a3ea Mon Sep 17 00:00:00 2001 From: Issam Date: Thu, 5 Oct 2023 12:32:03 -0400 Subject: [PATCH 3/5] Update list_repo_likers description Co-authored-by: Lucain --- src/huggingface_hub/hf_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/huggingface_hub/hf_api.py b/src/huggingface_hub/hf_api.py index c22a23ed5f..a8da8d6de0 100644 --- a/src/huggingface_hub/hf_api.py +++ b/src/huggingface_hub/hf_api.py @@ -1766,7 +1766,7 @@ def list_repo_likers( token: Optional[str] = None, ) -> List[User]: """ - List all users who liked a given repo on huggingface.co. + List all users who liked a given repo on the hugging Face Hub. See also [`like`] and [`list_liked_repos`]. From 21177fb3cf10ede2093f53c0738101f9f43236ab Mon Sep 17 00:00:00 2001 From: Issam Date: Thu, 5 Oct 2023 12:32:42 -0400 Subject: [PATCH 4/5] optimize list_repo_likers implementation Co-authored-by: Lucain --- src/huggingface_hub/hf_api.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/huggingface_hub/hf_api.py b/src/huggingface_hub/hf_api.py index a8da8d6de0..ca9a003d21 100644 --- a/src/huggingface_hub/hf_api.py +++ b/src/huggingface_hub/hf_api.py @@ -1798,9 +1798,7 @@ def list_repo_likers( # Parse the results into User objects likers_data = response.json() - likers = [User(**user_data) for user_data in likers_data] - - return likers + return [User(**user_data) for user_data in likers_data] @validate_hf_hub_args def model_info( From 9246cf80d580eab4d228b4d23aae7a759bc6800b Mon Sep 17 00:00:00 2001 From: Lucain Pouget Date: Fri, 6 Oct 2023 10:02:32 +0200 Subject: [PATCH 5/5] make style + import at root --- docs/source/en/package_reference/hf_api.md | 4 ++ src/huggingface_hub/__init__.py | 4 ++ src/huggingface_hub/hf_api.py | 67 ++++++++++------------ tests/test_hf_api.py | 8 +-- 4 files changed, 42 insertions(+), 41 deletions(-) diff --git a/docs/source/en/package_reference/hf_api.md b/docs/source/en/package_reference/hf_api.md index 8dd7e19828..a28b4f22e9 100644 --- a/docs/source/en/package_reference/hf_api.md +++ b/docs/source/en/package_reference/hf_api.md @@ -71,6 +71,10 @@ models = hf_api.list_models() [[autodoc]] huggingface_hub.hf_api.SpaceInfo +### User + +[[autodoc]] huggingface_hub.hf_api.User + ### UserLikes [[autodoc]] huggingface_hub.hf_api.UserLikes diff --git a/src/huggingface_hub/__init__.py b/src/huggingface_hub/__init__.py index 7a476dad9b..43303e0d61 100644 --- a/src/huggingface_hub/__init__.py +++ b/src/huggingface_hub/__init__.py @@ -143,6 +143,7 @@ "HfApi", "ModelSearchArguments", "RepoUrl", + "User", "UserLikes", "add_collection_item", "add_space_secret", @@ -188,6 +189,7 @@ "list_models", "list_repo_commits", "list_repo_files", + "list_repo_likers", "list_repo_refs", "list_spaces", "merge_pull_request", @@ -462,6 +464,7 @@ def __dir__(): HfApi, # noqa: F401 ModelSearchArguments, # noqa: F401 RepoUrl, # noqa: F401 + User, # noqa: F401 UserLikes, # noqa: F401 add_collection_item, # noqa: F401 add_space_secret, # noqa: F401 @@ -507,6 +510,7 @@ def __dir__(): list_models, # noqa: F401 list_repo_commits, # noqa: F401 list_repo_files, # noqa: F401 + list_repo_likers, # noqa: F401 list_repo_refs, # noqa: F401 list_spaces, # noqa: F401 merge_pull_request, # noqa: F401 diff --git a/src/huggingface_hub/hf_api.py b/src/huggingface_hub/hf_api.py index ca9a003d21..33443e6f64 100644 --- a/src/huggingface_hub/hf_api.py +++ b/src/huggingface_hub/hf_api.py @@ -925,23 +925,18 @@ class User: Contains information about a user on the Hub. Args: - name (`str`): - Name of the user. - avatarUrl (`str`): + avatar_url (`str`): URL of the user's avatar. - fullName (`str`): + username (`str`): + Name of the user on the Hub (unique). + fullname (`str`): User's full name. """ # Metadata - name: str - avatarUrl: str - fullName: str - - def __init__(self, data: Dict) -> None: - self.name = data["name"] - self.avatarUrl = data["avatarUrl"] - self.fullName = data["fullName"] + avatar_url: str + username: str + fullname: str def future_compatible(fn: CallableT) -> CallableT: @@ -1079,11 +1074,9 @@ def whoami(self, token: Optional[str] = None) -> Dict: hf_raise_for_status(r) except HTTPError as e: raise HTTPError( - ( - "Invalid user token. If you didn't pass a user token, make sure you " - "are properly logged in by executing `huggingface-cli login`, and " - "if you did pass a user token, double-check it's correct." - ), + "Invalid user token. If you didn't pass a user token, make sure you " + "are properly logged in by executing `huggingface-cli login`, and " + "if you did pass a user token, double-check it's correct.", request=e.request, response=e.response, ) from e @@ -1787,9 +1780,9 @@ def list_repo_likers( """ # Construct the API endpoint - path = f"{self.endpoint}/api/models/{repo_id}/likers" - if repo_type: - path = f"{self.endpoint}/api/{repo_type}/{repo_id}/likers" + if repo_type is None: + repo_type = REPO_TYPE_MODEL + path = f"{self.endpoint}/api/{repo_type}s/{repo_id}/likers" headers = self._build_hf_headers(token=token) # Make the request @@ -1798,7 +1791,14 @@ def list_repo_likers( # Parse the results into User objects likers_data = response.json() - return [User(**user_data) for user_data in likers_data] + return [ + User( + username=user_data["user"], + fullname=user_data["fullname"], + avatar_url=user_data["avatarUrl"], + ) + for user_data in likers_data + ] @validate_hf_hub_args def model_info( @@ -5682,11 +5682,9 @@ def request_space_hardware( """ if sleep_time is not None and hardware == SpaceHardware.CPU_BASIC: warnings.warn( - ( - "If your Space runs on the default 'cpu-basic' hardware, it will go to sleep if inactive for more" - " than 48 hours. This value is not configurable. If you don't want your Space to deactivate or if" - " you want to set a custom sleep time, you need to upgrade to a paid Hardware." - ), + "If your Space runs on the default 'cpu-basic' hardware, it will go to sleep if inactive for more" + " than 48 hours. This value is not configurable. If you don't want your Space to deactivate or if" + " you want to set a custom sleep time, you need to upgrade to a paid Hardware.", UserWarning, ) payload: Dict[str, Any] = {"flavor": hardware} @@ -5739,11 +5737,9 @@ def set_space_sleep_time(self, repo_id: str, sleep_time: int, *, token: Optional hardware = runtime.requested_hardware or runtime.hardware if hardware == SpaceHardware.CPU_BASIC: warnings.warn( - ( - "If your Space runs on the default 'cpu-basic' hardware, it will go to sleep if inactive for more" - " than 48 hours. This value is not configurable. If you don't want your Space to deactivate or if" - " you want to set a custom sleep time, you need to upgrade to a paid Hardware." - ), + "If your Space runs on the default 'cpu-basic' hardware, it will go to sleep if inactive for more" + " than 48 hours. This value is not configurable. If you don't want your Space to deactivate or if" + " you want to set a custom sleep time, you need to upgrade to a paid Hardware.", UserWarning, ) return runtime @@ -5919,11 +5915,9 @@ def duplicate_space( if sleep_time is not None and hardware == SpaceHardware.CPU_BASIC: warnings.warn( - ( - "If your Space runs on the default 'cpu-basic' hardware, it will go to sleep if inactive for more" - " than 48 hours. This value is not configurable. If you don't want your Space to deactivate or if" - " you want to set a custom sleep time, you need to upgrade to a paid Hardware." - ), + "If your Space runs on the default 'cpu-basic' hardware, it will go to sleep if inactive for more" + " than 48 hours. This value is not configurable. If you don't want your Space to deactivate or if" + " you want to set a custom sleep time, you need to upgrade to a paid Hardware.", UserWarning, ) @@ -6551,6 +6545,7 @@ def _parse_revision_from_pr_url(pr_url: str) -> str: # Activity API list_liked_repos = api.list_liked_repos +list_repo_likers = api.list_repo_likers like = api.like unlike = api.unlike diff --git a/tests/test_hf_api.py b/tests/test_hf_api.py index 1b85dc9c43..be427c8cee 100644 --- a/tests/test_hf_api.py +++ b/tests/test_hf_api.py @@ -2476,7 +2476,7 @@ def test_list_repo_likers(self) -> None: likers = self.api.list_repo_likers(repo_id, token=TOKEN) # Check if the test user is in the list of likers - liker_usernames = [user.name for user in likers] + liker_usernames = [user.username for user in likers] self.assertGreater(len(likers), 0) self.assertIn(USER, liker_usernames) @@ -3150,10 +3150,8 @@ def test_repo_url_class(self): # __repr__ is modified for debugging purposes self.assertEqual( repr(url), - ( - "RepoUrl('https://huggingface.co/gpt2'," - " endpoint='https://huggingface.co', repo_type='model', repo_id='gpt2')" - ), + "RepoUrl('https://huggingface.co/gpt2'," + " endpoint='https://huggingface.co', repo_type='model', repo_id='gpt2')", ) def test_repo_url_endpoint(self):