Repository: huggingface/huggingface_hub

Issue:
Return the liked users for for a huggingface model
Currently, the is a function "list_liked_repos" to list user-liked models by inputting the user name. It would be better to have a function that outputs the list of liked users for a given model.


Discussion:
Hi @abstrcode, thanks for raising the question. It is currently not possible to get this information in `huggingface_hub` but an API endpoints exists for it. For example for [mistralai/Mistral-7B-v0.1](https://huggingface.co/mistralai/Mistral-7B-v0.1) model: https://huggingface.co/api/models/mistralai/Mistral-7B-v0.1/likers.

We can definitely add a method in `huggingface_hub` to wrap it:

```py
class HfApi:
    (...)

    def list_repo_likers(self, repo_id: str, *, repo_type: Optional[str] = None, token: Optional[str] = None) -> List[User]:
        (...)
```

Would you like to open a PR for that? I can give some guidance about what to update and where. First step would be to implement this method and create a `User` data type for the returned value (with `fullname`, `avatarUrl` and `name`). Then a test should be added in `tests/test_hf_api.py`.

WDYT?
yes, would be a https://github.com/huggingface/huggingface_hub/labels/good%20first%20issue
Hi @Wauplin, would you be able to provide more info about how to call the API?
Hi @issamarabi , the implementation would be very similar to `get_spaces_variables` for example (see [code here](https://github.com/huggingface/huggingface_hub/blob/5d2d297084b230d7725b9ccafe6eb9a7f7c9a40e/src/huggingface_hub/hf_api.py#L5486)). You have to build the URL given an endpoint+repo_id+repo_type, build the headers given the token and make the call (no parameters/no payload). Then the output must be parsed correctly as a `User` dataclass (that has to be defined as well). Would you be interested in opening a PR?

Resolving patch:
diff --git a/src/huggingface_hub/__init__.py b/src/huggingface_hub/__init__.py
--- 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
--- a/src/huggingface_hub/hf_api.py
+++ b/src/huggingface_hub/hf_api.py
@@ -919,6 +919,26 @@ class UserLikes:
     spaces: List[str]
 
 
+@dataclass
+class User:
+    """
+    Contains information about a user on the Hub.
+
+    Args:
+        avatar_url (`str`):
+            URL of the user's avatar.
+        username (`str`):
+            Name of the user on the Hub (unique).
+        fullname (`str`):
+            User's full name.
+    """
+
+    # Metadata
+    avatar_url: str
+    username: str
+    fullname: str
+
+
 def future_compatible(fn: CallableT) -> CallableT:
     """Wrap a method of `HfApi` to handle `run_as_future=True`.
 
@@ -1730,6 +1750,56 @@ 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 the hugging Face Hub.
+
+        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
+        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
+        response = get_session().get(path, headers=headers)
+        hf_raise_for_status(response)
+
+        # Parse the results into User objects
+        likers_data = response.json()
+        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(
         self,
@@ -6475,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
 


Test patch:
diff --git a/tests/test_hf_api.py b/tests/test_hf_api.py
--- 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.username 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 !