Skip to content

Conversation

@Wauplin
Copy link
Contributor

@Wauplin Wauplin commented Nov 24, 2025

Suggested by @abidlabs on private slack after gradio-app/trackio#344 being opened to fix rate limits errors while dealing with whoami.

The root problem is that /whoami-v2 is heavily IP-rate-limited to avoid abuse. This is usually not a problem since user needs to do it only once to check if they are logged in. But in some subsequent libraries, the whoami endpoint is used to determine what will be the default namespace for a newly create repo (default to user's username). If the library calls that endpoint too often, it will lead to a HTTP 429 rate limit that could have been easily prevented. PR gradio-app/trackio#344 introduced a cache in trackio but let's make it available at huggingface_hub level directly. Note that we do not want to cache the result by default as this would not be consistent with any other methods in HfApi. HfApi remains a simple HTTP client and by default, someone calling a method expects an HTTP endpoint to be actually called.

With this PR:

  • whoami(..., cache=True) to cache results from /whoami-v2 endpoint
  • cache is isolated per HfApi instance
  • cache is in-memory and lives for the duration of the Python process
  • no way to reset the cache (let's reassess in the future if requested, but I doubt it)
  • I did not set a max size limit, in any case one would be rate limited before getting out of memory 😄
  • if user gets rate-limited 429, an explicit message nudge them to use caching

Also added some tests + docs.

@HuggingFaceDocBuilderDev

The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update.

Comment on lines +1762 to +1763
if token is False:
raise ValueError("Cannot use `token=False` with `whoami` method as it requires authentication.")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a change in behavior, right? Previously, if token=False, we could still get the token from get_token(), but now we raise an exception?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, since token=False explicitly means "do not retrieve the token locally". The breaking change is that previously it would raise an HTTP 401 unauthorized issue, and now a ValueError. I don't believe though that it's better like this to fail early.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I had to step away earlier but to clarify, I do agree that this change is the way to go and is inline with the docstring. But what I was saying was that previously, you would actually use the locally-saved token, even if a user passed token=False, because token or self.token or get_token() would keep going until a truthy value was found, so the logic would still fall back to a local token via get_token().

Unless I'm misunderstanding something, this would be a breaking change -- although I do agree that this is the right way to go. In fact, maybe a nit but I'd say we go even farther, and replace token = self.token with token = self.token if token is None else token, because again, this would mean that if a user does whoami(token=False), but has a token saved to the class would end up using that token, which is contrary to the docstring.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

^agree that we should do token = self.token if token is None else token since token can be False (as we do in _build_hf_headers).

Wauplin and others added 2 commits November 24, 2025 18:08
Co-authored-by: Abubakar Abid <abubakar@huggingface.co>
Co-authored-by: Abubakar Abid <abubakar@huggingface.co>
Copy link
Member

@julien-c julien-c left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice

Copy link
Member

@abidlabs abidlabs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small comment but otherwise LGTM, thanks @Wauplin!

Copy link
Contributor

@hanouticelina hanouticelina left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks! this is a nice addition 👍

Comment on lines +1764 to +1771
if token is True or token is None:
effective_token = get_token()
if effective_token is None:
raise LocalTokenNotFoundError(
"Token is required to call the /whoami-v2 endpoint, but no token found. You must provide a token or be logged in to "
"Hugging Face with `hf auth login` or `huggingface_hub.login`. See https://huggingface.co/settings/tokens."
)
token = effective_token
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(nit) not sure if we need an intermediate variable here

Suggested change
if token is True or token is None:
effective_token = get_token()
if effective_token is None:
raise LocalTokenNotFoundError(
"Token is required to call the /whoami-v2 endpoint, but no token found. You must provide a token or be logged in to "
"Hugging Face with `hf auth login` or `huggingface_hub.login`. See https://huggingface.co/settings/tokens."
)
token = effective_token
if token is True or token is None:
token = get_token()
if token is None:
raise LocalTokenNotFoundError(
"Token is required to call the /whoami-v2 endpoint, but no token found. You must provide a token or be logged in to "
"Hugging Face with `hf auth login` or `huggingface_hub.login`. See https://huggingface.co/settings/tokens."
)

Comment on lines +1773 to +1775
if cache:
if token in self._whoami_cache:
return self._whoami_cache[token]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just an excuse to use more Walrus operators in the codebase :)

Suggested change
if cache:
if token in self._whoami_cache:
return self._whoami_cache[token]
if cache and (cached_token := self._whoami_cache.get(token)):
return cached_token

Comment on lines +1762 to +1763
if token is False:
raise ValueError("Cannot use `token=False` with `whoami` method as it requires authentication.")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

^agree that we should do token = self.token if token is None else token since token can be False (as we do in _build_hf_headers).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants