From c90c2e63a23acde540a466291dd646fcd6235b37 Mon Sep 17 00:00:00 2001 From: Andrew Chen <48723787+chuenchen309@users.noreply.github.com> Date: Sun, 19 Jul 2026 00:05:25 +0800 Subject: [PATCH 1/2] Expand ?/[] globs for a single-string path in read mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `get_fs_token_paths` expanded a single-string read-mode path only when it contained `*`, so `open_files("data?.txt")` and `open_files("data[0-9].txt")` returned the literal path instead of the matching files — while `data*.txt`, and every form given as a list, expanded correctly. The list branch already routes through `expand_paths_if_needed`, whose read-mode docstring promises expansion of "any of *?[]"; use `glob.has_magic` here too so the single-string branch agrees. The directory filtering is left unchanged. Co-Authored-By: Claude Opus 4.8 --- fsspec/core.py | 2 +- fsspec/tests/test_core.py | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/fsspec/core.py b/fsspec/core.py index 881b07db3..37fed2573 100644 --- a/fsspec/core.py +++ b/fsspec/core.py @@ -699,7 +699,7 @@ def get_fs_token_paths( else: if ("w" in mode or "x" in mode) and expand: paths = _expand_paths(paths, name_function, num) - elif "*" in paths: + elif has_magic(paths): paths = [f for f in sorted(fs.glob(paths)) if not fs.isdir(f)] else: paths = [paths] diff --git a/fsspec/tests/test_core.py b/fsspec/tests/test_core.py index 2424baba5..9338ea875 100644 --- a/fsspec/tests/test_core.py +++ b/fsspec/tests/test_core.py @@ -92,6 +92,17 @@ def test_expand_fs_token_paths(mode): assert len(get_fs_token_paths("path", mode, num=2, expand=True)[-1]) == 2 +@pytest.mark.parametrize("pattern", ["apath*", "apath?", "apath[12]"]) +def test_get_fs_token_paths_single_string_read_mode_globs(pattern): + d = str(tempfile.mkdtemp()) + for f in ["apath1", "apath2"]: + open(os.path.join(d, f), "w").write("test") + + _, _, paths = get_fs_token_paths(os.path.join(d, pattern), mode="rb") + + assert sorted(os.path.basename(p) for p in paths) == ["apath1", "apath2"] + + def test_openfile_api(m): m.open("somepath", "wb").write(b"data") of = OpenFile(m, "somepath") From bcebd8b31c3b71720afa424c489c297dc33da386 Mon Sep 17 00:00:00 2001 From: Andrew Chen <48723787+chuenchen309@users.noreply.github.com> Date: Sun, 19 Jul 2026 04:18:45 +0800 Subject: [PATCH 2/2] Exclude "?" from single-string read-mode glob detection "?" is a glob wildcard but also the query delimiter in URLs, and HTTPFileSystem's own magic_check ("[*[]") deliberately excludes it. Using glob.has_magic() here routed a normal URL like "https://host/f?x=1" through fs.glob(), which for HTTP means an extra _exists() round-trip and an empty result (so the open silently yields nothing) when existence checks are unreliable. Detect only "*" and "[" instead, matching the HTTP filesystem, and add a test that a "?" path is passed through literally. --- fsspec/core.py | 5 ++++- fsspec/tests/test_core.py | 10 +++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/fsspec/core.py b/fsspec/core.py index 37fed2573..a512c7080 100644 --- a/fsspec/core.py +++ b/fsspec/core.py @@ -699,7 +699,10 @@ def get_fs_token_paths( else: if ("w" in mode or "x" in mode) and expand: paths = _expand_paths(paths, name_function, num) - elif has_magic(paths): + elif "*" in paths or "[" in paths: + # Not has_magic(): "?" is also the URL query delimiter, so + # treating it as a glob wildcard breaks paths like + # "https://host/f?x=1" (HTTPFileSystem excludes it too). paths = [f for f in sorted(fs.glob(paths)) if not fs.isdir(f)] else: paths = [paths] diff --git a/fsspec/tests/test_core.py b/fsspec/tests/test_core.py index 9338ea875..fff9bc4c0 100644 --- a/fsspec/tests/test_core.py +++ b/fsspec/tests/test_core.py @@ -92,7 +92,7 @@ def test_expand_fs_token_paths(mode): assert len(get_fs_token_paths("path", mode, num=2, expand=True)[-1]) == 2 -@pytest.mark.parametrize("pattern", ["apath*", "apath?", "apath[12]"]) +@pytest.mark.parametrize("pattern", ["apath*", "apath[12]"]) def test_get_fs_token_paths_single_string_read_mode_globs(pattern): d = str(tempfile.mkdtemp()) for f in ["apath1", "apath2"]: @@ -103,6 +103,14 @@ def test_get_fs_token_paths_single_string_read_mode_globs(pattern): assert sorted(os.path.basename(p) for p in paths) == ["apath1", "apath2"] +def test_get_fs_token_paths_read_mode_keeps_question_mark_literal(): + # "?" is the URL query delimiter, so it must not be treated as a glob + # wildcard here; the path is passed through untouched rather than globbed. + url = "memory://host/file?download=1" + _, _, paths = get_fs_token_paths(url, mode="rb") + assert paths == ["/host/file?download=1"] + + def test_openfile_api(m): m.open("somepath", "wb").write(b"data") of = OpenFile(m, "somepath")