Skip to content

Commit 518984d

Browse files
authored
Set expand to True by default but make configurable (#1558)
1 parent ab1ccc2 commit 518984d

File tree

2 files changed

+52
-10
lines changed

2 files changed

+52
-10
lines changed

fsspec/core.py

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,18 @@
88
from pathlib import Path
99

1010
# for backwards compat, we export cache things from here too
11-
from .caching import ( # noqa: F401
11+
from fsspec.caching import ( # noqa: F401
1212
BaseCache,
1313
BlockCache,
1414
BytesCache,
1515
MMapCache,
1616
ReadAheadCache,
1717
caches,
1818
)
19-
from .compression import compr
20-
from .registry import filesystem, get_filesystem_class
21-
from .utils import (
19+
from fsspec.compression import compr
20+
from fsspec.config import conf
21+
from fsspec.registry import filesystem, get_filesystem_class
22+
from fsspec.utils import (
2223
_unstrip_protocol,
2324
build_name_function,
2425
infer_compression,
@@ -100,7 +101,18 @@ def __repr__(self):
100101
def __enter__(self):
101102
mode = self.mode.replace("t", "").replace("b", "") + "b"
102103

103-
f = self.fs.open(self.path, mode=mode)
104+
try:
105+
f = self.fs.open(self.path, mode=mode)
106+
except FileNotFoundError as e:
107+
if has_magic(self.path):
108+
raise FileNotFoundError(
109+
"%s not found. The URL contains glob characters: you maybe needed\n"
110+
"to pass expand=True in fsspec.open() or the storage_options of \n"
111+
"your library. You can also set the config value 'open_expand'\n"
112+
"before import, or fsspec.core.DEFAULT_EXPAND at runtime, to True.",
113+
self.path,
114+
) from e
115+
raise
104116

105117
self.fobjects = [f]
106118

@@ -396,6 +408,9 @@ def url_to_fs(url, **kwargs):
396408
return fs, urlpath
397409

398410

411+
DEFAULT_EXPAND = conf.get("open_expand", False)
412+
413+
399414
def open(
400415
urlpath,
401416
mode="rb",
@@ -404,6 +419,7 @@ def open(
404419
errors=None,
405420
protocol=None,
406421
newline=None,
422+
expand=None,
407423
**kwargs,
408424
):
409425
"""Given a path or paths, return one ``OpenFile`` object.
@@ -428,6 +444,13 @@ def open(
428444
newline: bytes or None
429445
Used for line terminator in text mode. If None, uses system default;
430446
if blank, uses no translation.
447+
expand: bool or Nonw
448+
Whether to regard file paths containing special glob characters as needing
449+
expansion (finding the first match) or absolute. Setting False allows using
450+
paths which do embed such characters. If None (default), this argument
451+
takes its value from the DEFAULT_EXPAND module variable, which takes
452+
its initial value from the "open_expand" config value at startup, which will
453+
be False if not set.
431454
**kwargs: dict
432455
Extra options that make sense to a particular storage connection, e.g.
433456
host, port, username, password, etc.
@@ -456,8 +479,7 @@ def open(
456479
- For implementations in separate packages see
457480
https://filesystem-spec.readthedocs.io/en/latest/api.html#other-known-implementations
458481
"""
459-
kw = {"expand": False}
460-
kw.update(kwargs)
482+
expand = DEFAULT_EXPAND if expand is None else expand
461483
out = open_files(
462484
urlpath=[urlpath],
463485
mode=mode,
@@ -466,7 +488,8 @@ def open(
466488
errors=errors,
467489
protocol=protocol,
468490
newline=newline,
469-
**kw,
491+
expand=expand,
492+
**kwargs,
470493
)
471494
if not out:
472495
raise FileNotFoundError(urlpath)

fsspec/tests/test_core.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,18 @@ def test_list():
168168
assert [f.path for f in of] == plist
169169

170170

171+
def test_open_expand(m, monkeypatch):
172+
m.pipe("/myfile", b"hello")
173+
with pytest.raises(FileNotFoundError, match="expand=True"):
174+
with fsspec.open("memory://my*", expand=False):
175+
pass
176+
with fsspec.open("memory://my*", expand=True) as f:
177+
assert f.path == "/myfile"
178+
monkeypatch.setattr(fsspec.core, "DEFAULT_EXPAND", True)
179+
with fsspec.open("memory://my*") as f:
180+
assert f.path == "/myfile"
181+
182+
171183
def test_pathobject(tmpdir):
172184
import pathlib
173185

@@ -282,18 +294,25 @@ def test_open_files_read_with_special_characters(tmp_path, char):
282294

283295

284296
@pytest.mark.parametrize("char", glob_magic_characters)
285-
def test_open_file_write_with_special_characters(tmp_path, char):
297+
def test_open_file_write_with_special_characters(tmp_path, char, monkeypatch):
286298
# Create a filename incorporating the special character
287299
file_name = f"test{char}.txt"
288300
file_path = tmp_path / file_name
289301
expected_content = "Hello, world!"
290302

291-
with fsspec.open(file_path, "w") as f:
303+
with fsspec.open(file_path, "w", expand=False) as f:
292304
f.write(expected_content)
293305

294306
with open(file_path, "r") as f:
295307
actual_content = f.read()
296308

309+
monkeypatch.setattr(fsspec.core, "DEFAULT_EXPAND", False)
310+
with fsspec.open(file_path, "w") as f:
311+
f.write(expected_content * 2)
312+
313+
with open(file_path, "r") as f:
314+
f.read() == actual_content * 2
315+
297316
assert actual_content == expected_content
298317

299318

0 commit comments

Comments
 (0)