Skip to content

Use "/" as the only glob separator, on every platform - #2112

Open
Sreekant13 wants to merge 1 commit into
fsspec:masterfrom
Sreekant13:fix/glob-separator-is-always-forward-slash
Open

Use "/" as the only glob separator, on every platform#2112
Sreekant13 wants to merge 1 commit into
fsspec:masterfrom
Sreekant13:fix/glob-separator-is-always-forward-slash

Conversation

@Sreekant13

Copy link
Copy Markdown
Contributor

glob_translate takes its separators from os.path.sep and os.path.altsep, so on Windows a backslash becomes a path separator. fsspec paths always use /, as AbstractFileSystem.sep declares, and a backslash is an ordinary character in an object store key. The result is that glob returns different things on different hosts.

With a key named /data/we\ird.txt in a MemoryFileSystem:

pattern posix windows
/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. Only glob drops it, and it does so silently.

The pattern side is affected in the same way. On Windows glob_translate("we\\ird.txt") compiles to we[\\/]ird\.txt, so a pattern meant to name one key also matches we/ird.txt.

Fix

seps is fixed to /. The rest of the function is unchanged, so the compiled pattern is now the same on every platform. CPython's glob.translate takes seps as a parameter for exactly this reason, and seps=None (use the host's separators) is the right default for glob.glob over a local filesystem but not for fsspec's abstract paths.

Why local paths are not affected

glob() puts the pattern through _strip_protocol before calling glob_translate, and LocalFileSystem._strip_protocol calls make_path_posix, which replaces \ with / in every one of its NT branches. So no local pattern can reach glob_translate with a backslash in it, and this change cannot alter LocalFileSystem behaviour. test_local.py passing unchanged on Windows agrees.

glob() also derives separators from os.path for its own ends_with_sep and append_slash_to_dirname checks. 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_os monkeypatches os.path.sep and os.path.altsep to 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_backslash covers the behaviour through fs.glob on a MemoryFileSystem.

Commands run, on Windows with Python 3.12:

  • new tests against unpatched fsspec/utils.py: 2 failed, 1 passed, so they do cover the reported behaviour
  • pytest fsspec/tests/test_utils.py: 101 passed
  • pytest 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 xfailed
  • pytest fsspec/implementations/tests/test_memory.py: 37 passed
  • ruff check and ruff format --check with the pinned v0.14.3 from .pre-commit-config.yaml: clean

Disclosure: 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.

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 fallenmi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@martindurant

Copy link
Copy Markdown
Member

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants