Skip to content

Commit 7ac3555

Browse files
committed
fix: apply path normalization to fsspec store
1 parent 866aa8d commit 7ac3555

2 files changed

Lines changed: 24 additions & 2 deletions

File tree

src/zarr/storage/_fsspec.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
)
1717
from zarr.core.buffer import Buffer
1818
from zarr.errors import ZarrUserWarning
19-
from zarr.storage._utils import _join_paths
19+
from zarr.storage._utils import _join_paths, normalize_path
2020

2121
if TYPE_CHECKING:
2222
from collections.abc import AsyncIterator, Iterable
@@ -127,7 +127,7 @@ def __init__(
127127
) -> None:
128128
super().__init__(read_only=read_only)
129129
self.fs = fs
130-
self.path = path
130+
self.path = normalize_path(path)
131131
self.allowed_exceptions = allowed_exceptions
132132

133133
if not self.fs.async_impl:

tests/test_store/test_fsspec.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from zarr.errors import ZarrUserWarning
1818
from zarr.storage import FsspecStore
1919
from zarr.storage._fsspec import _make_async
20+
from zarr.storage._utils import normalize_path
2021
from zarr.testing.store import StoreTests
2122

2223
if TYPE_CHECKING:
@@ -286,6 +287,27 @@ def array_roundtrip(store: FsspecStore) -> None:
286287
np.testing.assert_array_equal(arr[:], data)
287288

288289

290+
@pytest.mark.skipif(
291+
parse_version(fsspec.__version__) < parse_version("2024.12.0"),
292+
reason="No AsyncFileSystemWrapper",
293+
)
294+
@pytest.mark.parametrize("path", ["", "/", "//", "foo", "foo/", "/foo", "/foo/", "//foo//"])
295+
def test_fsspec_store_path_normalization(path: str) -> None:
296+
"""`FsspecStore.path` is normalized to the canonical form, matching
297+
`normalize_path`, regardless of the surface representation the caller
298+
supplies.
299+
300+
Regression test for https://github.com/zarr-developers/zarr-python/issues/3922
301+
-- when a caller passed `path="/"` the leading slash flowed through
302+
unmodified to subsequent `_join_paths([self.path, key])` calls, producing
303+
`"//key"` and missing the underlying object.
304+
"""
305+
sync_fs = fsspec.filesystem("memory")
306+
fs = _make_async(sync_fs)
307+
store = FsspecStore(fs=fs, path=path)
308+
assert store.path == normalize_path(path)
309+
310+
289311
@pytest.mark.skipif(
290312
parse_version(fsspec.__version__) < parse_version("2024.12.0"),
291313
reason="No AsyncFileSystemWrapper",

0 commit comments

Comments
 (0)