44import json
55import os
66import re
7+ import shutil
78import sys
89import tempfile
910import warnings
@@ -171,6 +172,50 @@ def get_jinja_version():
171172 return _jinja_version
172173
173174
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+
174219# Return value when trying to load a file from cache but the file does not exist in the distant repo.
175220_CACHED_NO_EXIST = object ()
176221REGEX_COMMIT_HASH = re .compile (r"^[0-9a-f]{40}$" )
@@ -848,7 +893,7 @@ def _normalize_etag(etag: Optional[str]) -> Optional[str]:
848893 return etag .strip ('"' )
849894
850895
851- def _create_relative_symlink (src : str , dst : str ) -> None :
896+ def _create_relative_symlink (src : str , dst : str , new_blob : bool = False ) -> None :
852897 """Create a symbolic link named dst pointing to src as a relative path to dst.
853898
854899 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:
858903 ├── [ 128] 2439f60ef33a0d46d85da5001d52aeda5b00ce9f
859904 │ ├── [ 52] README.md -> ../../blobs/d7edf6bd2a681fb0175f7735299831ee1b22b812
860905 │ └── [ 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.
861916 """
862917 relative_src = os .path .relpath (src , start = os .path .dirname (dst ))
863918 try :
864919 os .remove (dst )
865920 except OSError :
866921 pass
867- try :
922+
923+ if are_symlinks_supported ():
868924 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 )
880929
881930
882931def _cache_commit_hash_for_specific_revision (
@@ -1246,7 +1295,7 @@ def hf_hub_download(
12461295 if os .path .exists (blob_path ) and not force_download :
12471296 # we have the blob already, but not the pointer
12481297 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 )
12501299 return pointer_path
12511300
12521301 # Prevent parallel downloads of the same file with a lock.
@@ -1302,7 +1351,7 @@ def _resumable_file_manager() -> "io.BufferedWriter":
13021351 os .replace (temp_file .name , blob_path )
13031352
13041353 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 )
13061355
13071356 try :
13081357 os .remove (lock_path )
0 commit comments