Use "/" as the only glob separator, on every platform - #2112
Conversation
glob_translate took its separators from os.path.sep and os.path.altsep, so
on Windows a backslash became a path separator. fsspec paths always use "/",
as AbstractFileSystem.sep declares, and a backslash is an ordinary character
in an object store key, so the same call returned different results
depending on the host.
With a key named "/data/we\ird.txt" in a MemoryFileSystem:
glob("/data/*") posix ['/data/plain.txt', '/data/we\ird.txt']
windows ['/data/plain.txt']
glob("/data/we*") posix ['/data/we\ird.txt']
windows []
The key is listed by find() on both, only glob misses it. The pattern was
affected too: "we\ird.txt" compiled to we[\/]ird\.txt on Windows, so it
also matched "we/ird.txt".
Local paths are unaffected. glob() runs the pattern through
_strip_protocol first, and LocalFileSystem._strip_protocol calls
make_path_posix, which replaces every backslash with "/", so no local
pattern reaches glob_translate with one.
glob() also derives separators from os.path for its ends_with_sep check.
That runs on the raw argument before _strip_protocol, where a native path
is still possible, so it is left alone.
fallenmi
left a comment
There was a problem hiding this comment.
This fixes the cross-platform mismatch cleanly. fsspec normalizes abstract paths to /, but the glob translator previously inherited host separators, so a literal backslash in an object-store key became a separator only on Windows. Fixing seps="/" aligns the translator with AbstractFileSystem.sep; LocalFileSystem._strip_protocol() already normalizes native Windows paths before translation.
The new tests cover both the host-independent regex and the public MemoryFileSystem.glob() behavior. I checked exact head e6b2ef54d7b8768601bff77211216beae8888009 and current merge 82f5f301a52dff60fc56ecf7077ec73677606f23; their trees are identical, and all 11 check runs plus the pull-request workflow are successful, including pytest-win, downstream, gcsfs, s3fs, and lint. I do not see a blocker.
Disclosure: I used OpenAI Codex and Anthropic Claude to assist with the exact ref/merge/policy/CI verification and semantic red-team; I verified the source paths and conclusion before submission.
|
You mention that none of this affects local glob functionality - it would be good to have a test of this, something like the reverse of test_glob_translate_does_not_depend_on_the_host_os. You would need to explicitly monkeypatch os.sep or have separate win/posix tests that skip appropriately. |
glob_translatetakes its separators fromos.path.sepandos.path.altsep, so on Windows a backslash becomes a path separator. fsspec paths always use/, asAbstractFileSystem.sepdeclares, and a backslash is an ordinary character in an object store key. The result is thatglobreturns different things on different hosts.With a key named
/data/we\ird.txtin aMemoryFileSystem:/data/*['/data/plain.txt', '/data/we\\ird.txt']['/data/plain.txt']/data/we*['/data/we\\ird.txt'][]/data/*.txt['/data/plain.txt', '/data/we\\ird.txt']['/data/plain.txt']find()lists the key on both platforms. Onlyglobdrops it, and it does so silently.The pattern side is affected in the same way. On Windows
glob_translate("we\\ird.txt")compiles towe[\\/]ird\.txt, so a pattern meant to name one key also matcheswe/ird.txt.Fix
sepsis fixed to/. The rest of the function is unchanged, so the compiled pattern is now the same on every platform. CPython'sglob.translatetakessepsas a parameter for exactly this reason, andseps=None(use the host's separators) is the right default forglob.globover a local filesystem but not for fsspec's abstract paths.Why local paths are not affected
glob()puts the pattern through_strip_protocolbefore callingglob_translate, andLocalFileSystem._strip_protocolcallsmake_path_posix, which replaces\with/in every one of its NT branches. So no local pattern can reachglob_translatewith a backslash in it, and this change cannot alterLocalFileSystembehaviour.test_local.pypassing unchanged on Windows agrees.glob()also derives separators fromos.pathfor its ownends_with_sepandappend_slash_to_dirnamechecks. Those run on the raw argument, before_strip_protocol, where a native Windows path is still a legitimate input, so I have deliberately left them alone. Happy to revisit that separately if you would rather they were consistent.Tests
test_glob_translate_does_not_depend_on_the_host_osmonkeypatchesos.path.sepandos.path.altsepto both the posix and the Windows values and asserts the compiled pattern is identical. This one fails on Linux too without the fix, so the regression is caught wherever CI runs.test_glob_matches_a_name_containing_a_backslashcovers the behaviour throughfs.globon aMemoryFileSystem.Commands run, on Windows with Python 3.12:
fsspec/utils.py: 2 failed, 1 passed, so they do cover the reported behaviourpytest fsspec/tests/test_utils.py: 101 passedpytest fsspec/tests/test_spec.py: 127 passed, 123 skipped, 1 xfailed (the skips are the bash-based posix glob comparisons, which skip on Windows regardless of this change)pytest fsspec/implementations/tests/test_local.py: 152 passed, 5 skipped, 12 xfailedpytest fsspec/implementations/tests/test_memory.py: 37 passedruff checkandruff format --checkwith the pinned v0.14.3 from.pre-commit-config.yaml: cleanDisclosure: I used AI assistance while investigating this and preparing the patch. I have reviewed every changed line and ran all of the commands above myself.