Skip to content

Commit 3c95192

Browse files
authored
Merge pull request #1206 from transformerlab/fix/async-context-fsspec
Fix fsspec from inferring async context
2 parents effc324 + 5a7e27b commit 3c95192

2 files changed

Lines changed: 55 additions & 22 deletions

File tree

lab-sdk/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "transformerlab"
7-
version = "0.0.64"
7+
version = "0.0.65"
88
description = "Python SDK for Transformer Lab"
99
readme = "README.md"
1010
requires-python = ">=3.10"

lab-sdk/src/lab/storage.py

Lines changed: 54 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
161191
async 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

434467
async def copy_file(src: str, dest: str) -> None:

0 commit comments

Comments
 (0)