@@ -129,35 +129,65 @@ def _get_storage_options() -> dict:
129129 return {}
130130
131131
132- def _get_fs_and_root ( ):
132+ def _get_fs_for_uri ( uri : str ):
133133 """
134- Initialize filesystem and root path from context variable or TFL_STORAGE_URI.
135- Falls back to local ~/.transformerlab or TFL_HOME_DIR when not set.
136- """
137- # Check context variable first, then fall back to environment variable
138- tfl_uri = _current_tfl_storage_uri .get () or os .getenv ("TFL_STORAGE_URI" )
134+ Create a sync filesystem for a given URI.
135+ Returns (filesystem, root_path) tuple.
139136
140- if not tfl_uri or tfl_uri .strip () == "" :
137+ This explicitly creates a sync filesystem to avoid issues with async s3fs
138+ passing storage_options to AioSession incorrectly when an event loop is running.
139+ """
140+ if not uri or uri .strip () == "" :
141141 root = os .getenv (
142142 "TFL_HOME_DIR" ,
143143 os .path .join (os .path .expanduser ("~" ), ".transformerlab" ),
144144 )
145- fs = fsspec .filesystem ("file" )
145+ fs = fsspec .filesystem ("file" , asynchronous = False )
146146 return fs , root
147147
148- # Get storage options based on cloud provider
149- storage_options = _get_storage_options ()
150-
151- # Let fsspec parse the URI
152- fs , _token , paths = fsspec .get_fs_token_paths (tfl_uri , storage_options = storage_options )
153- # For S3 and other remote filesystems, we need to maintain the full URI format
154- if tfl_uri .startswith (("s3://" , "gs://" , "abfs://" , "gcs://" )):
155- root = tfl_uri .rstrip ("/" )
148+ # Extract protocol
149+ if uri .startswith ("s3://" ):
150+ protocol = "s3"
151+ elif uri .startswith ("gs://" ) or uri .startswith ("gcs://" ):
152+ protocol = "gcs"
153+ elif uri .startswith ("abfs://" ):
154+ protocol = "abfs"
155+ else :
156+ # Local filesystem or unknown protocol - use fsspec's default handling
157+ fs = fsspec .filesystem ("file" , asynchronous = False )
158+ return fs , uri
159+
160+ # Build storage options as kwargs (not as nested dict)
161+ # This ensures they're passed correctly to the filesystem, not to AioSession
162+ fs_kwargs = {"asynchronous" : False } # Explicitly force sync mode
163+ if protocol == "s3" and _AWS_PROFILE :
164+ fs_kwargs ["profile" ] = _AWS_PROFILE
165+ elif protocol in ("gcs" , "gs" ) and _GCP_PROJECT :
166+ fs_kwargs ["project" ] = _GCP_PROJECT
167+
168+ # Explicitly create sync filesystem to avoid async s3fs issues
169+ # Use filesystem() directly with asynchronous=False to force sync version
170+ # This prevents fsspec from auto-detecting async mode and creating async filesystem
171+ fs = fsspec .filesystem (protocol , ** fs_kwargs )
172+
173+ # For remote filesystems, maintain the full URI format as root
174+ if uri .startswith (("s3://" , "gs://" , "abfs://" , "gcs://" )):
175+ root = uri .rstrip ("/" )
156176 else :
157- root = paths [ 0 ] if paths else ""
177+ root = uri
158178 return fs , root
159179
160180
181+ def _get_fs_and_root ():
182+ """
183+ Initialize filesystem and root path from context variable or TFL_STORAGE_URI.
184+ Falls back to local ~/.transformerlab or TFL_HOME_DIR when not set.
185+ """
186+ # Check context variable first, then fall back to environment variable
187+ tfl_uri = _current_tfl_storage_uri .get () or os .getenv ("TFL_STORAGE_URI" )
188+ return _get_fs_for_uri (tfl_uri )
189+
190+
161191async def root_uri () -> str :
162192 _ , root = _get_fs_and_root ()
163193 return root
@@ -372,6 +402,7 @@ async def _get_uncached_filesystem(path: str, fs=None):
372402 if protocol :
373403 # Create a new uncached filesystem with the same protocol and options
374404 fs_kwargs = {
405+ "asynchronous" : False , # Explicitly force sync mode
375406 "skip_instance_cache" : True ,
376407 "default_fill_cache" : False ,
377408 "use_listings_cache" : False ,
@@ -394,6 +425,7 @@ async def _get_uncached_filesystem(path: str, fs=None):
394425 # Create a new filesystem instance with caching disabled
395426 fs_uncached = fsspec .filesystem (
396427 protocol ,
428+ asynchronous = False , # Explicitly force sync mode
397429 skip_instance_cache = True ,
398430 default_fill_cache = False ,
399431 use_listings_cache = False ,
@@ -409,6 +441,7 @@ async def _get_uncached_filesystem(path: str, fs=None):
409441 storage_options = _get_storage_options ()
410442 fs_uncached = fsspec .filesystem (
411443 protocol ,
444+ asynchronous = False , # Explicitly force sync mode
412445 skip_instance_cache = True ,
413446 default_fill_cache = False ,
414447 use_listings_cache = False ,
@@ -424,11 +457,11 @@ def _get_fs_for_path(path: str):
424457 """
425458 Get filesystem for a given path, handling S3 storage_options correctly.
426459 Returns (filesystem, parsed_path) tuple.
460+
461+ Reuses _get_fs_for_uri() to avoid code duplication.
427462 """
428- storage_options = {}
429- if path .startswith ("s3://" ) and _AWS_PROFILE :
430- storage_options ["profile" ] = _AWS_PROFILE
431- return fsspec .core .url_to_fs (path , storage_options = storage_options if storage_options else None )
463+ fs , _ = _get_fs_for_uri (path )
464+ return fs , path
432465
433466
434467async def copy_file (src : str , dest : str ) -> None :
0 commit comments