4
4
import json
5
5
import os
6
6
import re
7
+ import shutil
7
8
import sys
8
9
import tempfile
9
10
import warnings
@@ -171,6 +172,50 @@ def get_jinja_version():
171
172
return _jinja_version
172
173
173
174
175
+ _are_symlinks_supported : Optional [bool ] = None
176
+
177
+
178
+ def are_symlinks_supported () -> bool :
179
+ # Check symlink compatibility only once at first time use
180
+ global _are_symlinks_supported
181
+
182
+ if _are_symlinks_supported is None :
183
+ _are_symlinks_supported = True
184
+
185
+ with tempfile .TemporaryDirectory () as tmpdir :
186
+ src_path = Path (tmpdir ) / "dummy_file_src"
187
+ src_path .touch ()
188
+ dst_path = Path (tmpdir ) / "dummy_file_dst"
189
+ try :
190
+ os .symlink (src_path , dst_path )
191
+ except OSError :
192
+ # Likely running on Windows
193
+ _are_symlinks_supported = False
194
+
195
+ if not os .environ .get ("DISABLE_SYMLINKS_WARNING" ):
196
+ message = (
197
+ "`huggingface_hub` cache-system uses symlinks by default to"
198
+ " efficiently store duplicated files but your machine doesn't"
199
+ " support them. Caching files will still work but in a degraded"
200
+ " version that might require more space on your disk. This"
201
+ " warning can be disabled by setting the"
202
+ " `DISABLE_SYMLINKS_WARNING` environment variable. For more"
203
+ " details, see"
204
+ " https://huggingface.co/docs/huggingface_hub/how-to-cache#limitations."
205
+ )
206
+ if os .name == "nt" :
207
+ message += (
208
+ "\n To support symlinks on Windows, you either need to"
209
+ " activate Developer Mode or to run Python as an"
210
+ " administrator. In order to see activate developer mode,"
211
+ " see this article:"
212
+ " https://docs.microsoft.com/en-us/windows/apps/get-started/enable-your-device-for-development"
213
+ )
214
+ warnings .warn (message )
215
+
216
+ return _are_symlinks_supported
217
+
218
+
174
219
# Return value when trying to load a file from cache but the file does not exist in the distant repo.
175
220
_CACHED_NO_EXIST = object ()
176
221
REGEX_COMMIT_HASH = re .compile (r"^[0-9a-f]{40}$" )
@@ -848,7 +893,7 @@ def _normalize_etag(etag: Optional[str]) -> Optional[str]:
848
893
return etag .strip ('"' )
849
894
850
895
851
- def _create_relative_symlink (src : str , dst : str ) -> None :
896
+ def _create_relative_symlink (src : str , dst : str , new_blob : bool = False ) -> None :
852
897
"""Create a symbolic link named dst pointing to src as a relative path to dst.
853
898
854
899
The relative part is mostly because it seems more elegant to the author.
@@ -858,25 +903,29 @@ def _create_relative_symlink(src: str, dst: str) -> None:
858
903
├── [ 128] 2439f60ef33a0d46d85da5001d52aeda5b00ce9f
859
904
│ ├── [ 52] README.md -> ../../blobs/d7edf6bd2a681fb0175f7735299831ee1b22b812
860
905
│ └── [ 76] pytorch_model.bin -> ../../blobs/403450e234d65943a7dcf7e05a771ce3c92faa84dd07db4ac20f592037a1e4bd
906
+
907
+ If symlinks cannot be created on this platform (most likely to be Windows), the
908
+ workaround is to avoid symlinks by having the actual file in `dst`. If it is a new
909
+ file (`new_blob=True`), we move it to `dst`. If it is not a new file
910
+ (`new_blob=False`), we don't know if the blob file is already referenced elsewhere.
911
+ To avoid breaking existing cache, the file is duplicated on the disk.
912
+
913
+ In case symlinks are not supported, a warning message is displayed to the user once
914
+ when loading `huggingface_hub`. The warning message can be disable with the
915
+ `DISABLE_SYMLINKS_WARNING` environment variable.
861
916
"""
862
917
relative_src = os .path .relpath (src , start = os .path .dirname (dst ))
863
918
try :
864
919
os .remove (dst )
865
920
except OSError :
866
921
pass
867
- try :
922
+
923
+ if are_symlinks_supported ():
868
924
os .symlink (relative_src , dst )
869
- except OSError :
870
- # Likely running on Windows
871
- if os .name == "nt" :
872
- raise OSError (
873
- "Windows requires Developer Mode to be activated, or to run Python as "
874
- "an administrator, in order to create symlinks.\n In order to "
875
- "activate Developer Mode, see this article: "
876
- "https://docs.microsoft.com/en-us/windows/apps/get-started/enable-your-device-for-development"
877
- )
878
- else :
879
- raise
925
+ elif new_blob :
926
+ os .replace (src , dst )
927
+ else :
928
+ shutil .copyfile (src , dst )
880
929
881
930
882
931
def _cache_commit_hash_for_specific_revision (
@@ -1246,7 +1295,7 @@ def hf_hub_download(
1246
1295
if os .path .exists (blob_path ) and not force_download :
1247
1296
# we have the blob already, but not the pointer
1248
1297
logger .info ("creating pointer to %s from %s" , blob_path , pointer_path )
1249
- _create_relative_symlink (blob_path , pointer_path )
1298
+ _create_relative_symlink (blob_path , pointer_path , new_blob = False )
1250
1299
return pointer_path
1251
1300
1252
1301
# Prevent parallel downloads of the same file with a lock.
@@ -1302,7 +1351,7 @@ def _resumable_file_manager() -> "io.BufferedWriter":
1302
1351
os .replace (temp_file .name , blob_path )
1303
1352
1304
1353
logger .info ("creating pointer to %s from %s" , blob_path , pointer_path )
1305
- _create_relative_symlink (blob_path , pointer_path )
1354
+ _create_relative_symlink (blob_path , pointer_path , new_blob = True )
1306
1355
1307
1356
try :
1308
1357
os .remove (lock_path )
0 commit comments