Skip to content

Commit dbc6413

Browse files
committed
Send token implicitly on LFS upload (#1084)
* Send token implicitely on LFS upload * add regression test
1 parent e01c2a9 commit dbc6413

File tree

6 files changed

+82
-22
lines changed

6 files changed

+82
-22
lines changed

src/huggingface_hub/_commit_api.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ def upload_lfs_files(
166166
additions: Iterable[CommitOperationAdd],
167167
repo_type: str,
168168
repo_id: str,
169-
token: str,
169+
token: Optional[str],
170170
endpoint: Optional[str] = None,
171171
num_threads: int = 5,
172172
):
@@ -184,7 +184,7 @@ def upload_lfs_files(
184184
repo_id (`str`):
185185
A namespace (user or an organization) and a repo name separated
186186
by a `/`.
187-
token (`str`):
187+
token (`str`, *optional*):
188188
An authentication token ( See https://huggingface.co/settings/tokens )
189189
num_threads (`int`, *optional*):
190190
The number of concurrent threads to use when uploading. Defaults to 5.
@@ -256,7 +256,7 @@ def upload_lfs_files(
256256

257257

258258
def _upload_lfs_object(
259-
operation: CommitOperationAdd, lfs_batch_action: dict, token: str
259+
operation: CommitOperationAdd, lfs_batch_action: dict, token: Optional[str]
260260
):
261261
"""
262262
Handles uploading a given object to the Hub with the LFS protocol.
@@ -272,7 +272,7 @@ def _upload_lfs_object(
272272
lfs_batch_action (`dict`):
273273
Upload instructions from the LFS batch endpoint for this object.
274274
See [`~utils.lfs.post_lfs_batch_info`] for more details.
275-
token (`str`):
275+
token (`str`, *optional*):
276276
A [user access token](https://hf.co/settings/tokens) to authenticate requests against the Hub
277277
278278
Raises: `ValueError` if `lfs_batch_action` is improperly formatted
@@ -321,7 +321,7 @@ def fetch_upload_modes(
321321
additions: Iterable[CommitOperationAdd],
322322
repo_type: str,
323323
repo_id: str,
324-
token: str,
324+
token: Optional[str],
325325
revision: str,
326326
endpoint: Optional[str] = None,
327327
create_pr: Optional[bool] = None,
@@ -339,7 +339,7 @@ def fetch_upload_modes(
339339
repo_id (`str`):
340340
A namespace (user or an organization) and a repo name separated
341341
by a `/`.
342-
token (`str`):
342+
token (`str`, *optional*):
343343
An authentication token ( See https://huggingface.co/settings/tokens )
344344
revision (`str`):
345345
The git revision to upload the files to. Can be any valid git revision.

src/huggingface_hub/hf_api.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2705,7 +2705,7 @@ def create_discussion(
27052705
repo_id: str,
27062706
title: str,
27072707
*,
2708-
token: str,
2708+
token: Optional[str],
27092709
description: Optional[str] = None,
27102710
repo_type: Optional[str] = None,
27112711
pull_request: bool = False,
@@ -2724,7 +2724,7 @@ def create_discussion(
27242724
The title of the discussion. It can be up to 200 characters long,
27252725
and must be at least 3 characters long. Leading and trailing whitespaces
27262726
will be stripped.
2727-
token (`str`):
2727+
token (`str`, *optional*):
27282728
An authentication token (See https://huggingface.co/settings/token)
27292729
description (`str`, *optional*):
27302730
An optional description for the Pull Request.
@@ -2794,7 +2794,7 @@ def create_pull_request(
27942794
repo_id: str,
27952795
title: str,
27962796
*,
2797-
token: str,
2797+
token: Optional[str],
27982798
description: Optional[str] = None,
27992799
repo_type: Optional[str] = None,
28002800
) -> DiscussionWithDetails:
@@ -2812,7 +2812,7 @@ def create_pull_request(
28122812
The title of the discussion. It can be up to 200 characters long,
28132813
and must be at least 3 characters long. Leading and trailing whitespaces
28142814
will be stripped.
2815-
token (`str`):
2815+
token (`str`, *optional*):
28162816
An authentication token (See https://huggingface.co/settings/token)
28172817
description (`str`, *optional*):
28182818
An optional description for the Pull Request.
@@ -3096,7 +3096,7 @@ def merge_pull_request(
30963096
repo_id: str,
30973097
discussion_num: int,
30983098
*,
3099-
token: str,
3099+
token: Optional[str],
31003100
comment: Optional[str] = None,
31013101
repo_type: Optional[str] = None,
31023102
):
@@ -3207,7 +3207,7 @@ def hide_discussion_comment(
32073207
discussion_num: int,
32083208
comment_id: str,
32093209
*,
3210-
token: str,
3210+
token: Optional[str],
32113211
repo_type: Optional[str] = None,
32123212
) -> DiscussionComment:
32133213
"""Hides a comment on a Discussion / Pull Request.

src/huggingface_hub/lfs.py

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,12 @@
2626
from huggingface_hub.constants import ENDPOINT, REPO_TYPES_URL_PREFIXES
2727
from requests.auth import HTTPBasicAuth
2828

29-
from .utils import hf_raise_for_status, http_backoff, validate_hf_hub_args
29+
from .utils import (
30+
get_token_to_send,
31+
hf_raise_for_status,
32+
http_backoff,
33+
validate_hf_hub_args,
34+
)
3035
from .utils.sha import sha256, sha_fileobj
3136

3237

@@ -132,7 +137,7 @@ def _validate_batch_error(lfs_batch_error: dict):
132137
@validate_hf_hub_args
133138
def post_lfs_batch_info(
134139
upload_infos: Iterable[UploadInfo],
135-
token: str,
140+
token: Optional[str],
136141
repo_type: str,
137142
repo_id: str,
138143
endpoint: Optional[str] = None,
@@ -151,7 +156,7 @@ def post_lfs_batch_info(
151156
repo_id (`str`):
152157
A namespace (user or an organization) and a repo name separated
153158
by a `/`.
154-
token (`str`):
159+
token (`str`, *optional*):
155160
An authentication token ( See https://huggingface.co/settings/tokens )
156161
157162
Returns:
@@ -187,7 +192,10 @@ def post_lfs_batch_info(
187192
],
188193
"hash_algo": "sha256",
189194
},
190-
auth=HTTPBasicAuth("access_token", token),
195+
auth=HTTPBasicAuth(
196+
"access_token",
197+
get_token_to_send(token or True), # Token must be provided or retrieved
198+
),
191199
)
192200
hf_raise_for_status(resp)
193201
batch_info = resp.json()
@@ -207,7 +215,7 @@ def lfs_upload(
207215
upload_info: UploadInfo,
208216
upload_action: dict,
209217
verify_action: Optional[dict],
210-
token: str,
218+
token: Optional[str],
211219
):
212220
"""
213221
Uploads a file using the git lfs protocol and determines automatically whether or not
@@ -225,7 +233,7 @@ def lfs_upload(
225233
The `verify` action from the LFS Batch endpoint. Must contain
226234
a `href` field, and optionally a `header` field. The `href` URL will
227235
be called after a successful upload.
228-
token (`str`):
236+
token (`str`, *optional*):
229237
A [user access token](https://hf.co/settings/tokens) to authenticate requests
230238
against the Hub.
231239
@@ -268,7 +276,11 @@ def lfs_upload(
268276
if verify_action is not None:
269277
verify_resp = requests.post(
270278
verify_action["href"],
271-
auth=HTTPBasicAuth(username="USER", password=token),
279+
auth=HTTPBasicAuth(
280+
username="USER",
281+
# Token must be provided or retrieved
282+
password=get_token_to_send(token or True),
283+
),
272284
json={"oid": upload_info.sha256.hex(), "size": upload_info.size},
273285
)
274286
hf_raise_for_status(verify_resp)

src/huggingface_hub/utils/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
hf_raise_for_status,
3737
)
3838
from ._fixes import yaml_dump
39-
from ._headers import build_hf_headers
39+
from ._headers import build_hf_headers, get_token_to_send
4040
from ._hf_folder import HfFolder
4141
from ._http import http_backoff
4242
from ._paths import filter_repo_objects

src/huggingface_hub/utils/_headers.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ def build_hf_headers(
112112
If `use_auth_token=True` but token is not saved locally.
113113
"""
114114
# Get auth token to send
115-
token_to_send = _get_token_to_send(use_auth_token)
115+
token_to_send = get_token_to_send(use_auth_token)
116116
_validate_token_to_send(token_to_send, is_write_action=is_write_action)
117117

118118
# Combine headers
@@ -128,7 +128,7 @@ def build_hf_headers(
128128
return headers
129129

130130

131-
def _get_token_to_send(use_auth_token: Optional[Union[bool, str]]) -> Optional[str]:
131+
def get_token_to_send(use_auth_token: Optional[Union[bool, str]]) -> Optional[str]:
132132
"""Select the token to send from either `use_auth_token` or the cache."""
133133
# Case token is explicitly provided
134134
if isinstance(use_auth_token, str):

tests/test_hf_api.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -951,6 +951,54 @@ def test_create_commit_repo_does_not_exist(self) -> None:
951951

952952
self.assertEqual(str(context.exception), expected_message)
953953

954+
@retry_endpoint
955+
def test_create_commit_lfs_file_implicit_token(self):
956+
"""Test that uploading a file as LFS works with implicit token (from cache).
957+
958+
Regression test for https://github.com/huggingface/huggingface_hub/pull/1084.
959+
"""
960+
REPO_NAME = repo_name("create_commit_with_lfs")
961+
repo_id = f"{USER}/{REPO_NAME}"
962+
963+
with patch("huggingface_hub.utils.HfFolder.get_token") as mock:
964+
mock.return_value = self._token # Set implicit token
965+
966+
# Create repo
967+
self._api.create_repo(repo_id=REPO_NAME, exist_ok=False)
968+
969+
# Set repo to track png files as LFS
970+
self._api.create_commit(
971+
operations=[
972+
CommitOperationAdd(
973+
path_in_repo=".gitattributes",
974+
path_or_fileobj=b"*.png filter=lfs diff=lfs merge=lfs -text",
975+
),
976+
],
977+
commit_message="Update .gitattributes",
978+
repo_id=repo_id,
979+
)
980+
981+
# Upload a PNG file
982+
self._api.create_commit(
983+
operations=[
984+
CommitOperationAdd(
985+
path_in_repo="image.png", path_or_fileobj=b"image data"
986+
),
987+
],
988+
commit_message="Test upload lfs file",
989+
repo_id=repo_id,
990+
)
991+
992+
# Check uploaded as LFS
993+
info = self._api.model_info(
994+
repo_id=repo_id, use_auth_token=self._token, files_metadata=True
995+
)
996+
siblings = {file.rfilename: file for file in info.siblings}
997+
self.assertIsInstance(siblings["image.png"].lfs, dict) # LFS file
998+
999+
# Delete repo
1000+
self._api.delete_repo(repo_id=REPO_NAME, token=self._token)
1001+
9541002

9551003
class HfApiPublicTest(unittest.TestCase):
9561004
def test_staging_list_models(self):

0 commit comments

Comments
 (0)