Use "/" as the only glob separator, on every platform - #2112
martindurant merged 5 commits into
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. |
…r-is-always-forward-slash
Review follow-up on fsspec#2112. The PR argued that local paths cannot be affected because glob() runs _strip_protocol first and LocalFileSystem._strip_protocol calls make_path_posix, which replaces every backslash. That was an argument in the description with nothing holding it. Two tests, split by platform as suggested rather than by monkeypatching os.sep, which would have to patch both os.sep and os.path.sep to be honest and would send make_path_posix down its NT branch on a posix host: - test_local_glob_is_unaffected_by_the_forward_slash_translator runs everywhere: a real LocalFileSystem glob over a tmp tree, with *, a bare * and **, and an assertion that nothing coming out carries a backslash. It is the counterpart named in its docstring to test_glob_translate_does_not_depend_on_the_host_os. - test_a_native_windows_pattern_is_normalised_before_the_translator_sees_it is Windows-only: a pattern full of native backslashes still globs, and make_path_posix leaves none of them, which is the mechanism the whole argument rests on. Neither is a stash-prove, deliberately. Local globbing was already correct before this change, so a test that failed without it would be testing the wrong thing; these are regression guards for the half the PR claims is untouched.
Formatting only; ruff format --check flagged the two new test bodies and I pushed before running it. No behaviour change.
|
Thanks @martindurant, and apologies for the slow reply. Added in c3771eb, plus a formatting-only follow-up. You were right that the claim had nothing holding it. The PR argued in prose that local paths cannot be affected, because I went with the second option you offered, separate win/posix tests, rather than monkeypatching. To simulate a Windows host honestly I would have had to patch both
One thing worth saying plainly: neither is a stash-prove, deliberately. Local globbing was already correct before this change, so a test that failed without it would be testing the wrong thing. These are regression guards for the half the PR claims is untouched, which I think is what you were after. Both confirmed as actually running rather than skipped, since I am on Windows here; the second one will skip on the Linux and macOS jobs by design. Also merged master, and thanks @fallenmi for the review. |
|
Hi @martindurant, a gentle note on this one. Since the last update it has picked up an approving review from @fallenmi, who independently checked that the head and the merge trees are identical and that all 11 check runs pass, including The change itself is a single word, with tests behind it. The glob translator inherited host separators, so a literal backslash in a key became a path separator on Windows and stayed literal on POSIX. Setting The tests you asked for are in |
Resolved one conflict in fsspec/tests/test_utils.py by keeping BOTH sides. Upstream added `test_check_contained` and this branch added the two glob tests, both immediately after `test_stringify_path` and both importing `os`, so git could not tell the additions were independent. Nothing was chosen between: upstream's cases and this branch's cases all survive, and the decorator the conflict split is re-opened for upstream's parametrize. `import os` is taken over `import os.path` because it covers what this branch uses. The fix itself is unchanged and still needed: upstream/master still derives the glob separators from `os.path.altsep`, so a backslash remains a separator on Windows only. 262 passed across test_utils.py and implementations/tests/test_local.py.
|
Rebased onto The conflict was test-only and I resolved it by keeping both sides: upstream's The fix itself is unchanged and still applies: No action needed from me that I can see — happy to rebase again if it drifts. |
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.