Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/3924.bugfix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Apply path normalization to the `path` attribute of `FsspecStore`.
4 changes: 2 additions & 2 deletions src/zarr/storage/_fsspec.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
)
from zarr.core.buffer import Buffer
from zarr.errors import ZarrUserWarning
from zarr.storage._utils import _join_paths
from zarr.storage._utils import _join_paths, normalize_path

if TYPE_CHECKING:
from collections.abc import AsyncIterator, Iterable
Expand Down Expand Up @@ -127,7 +127,7 @@ def __init__(
) -> None:
super().__init__(read_only=read_only)
self.fs = fs
self.path = path
self.path = normalize_path(path)
self.allowed_exceptions = allowed_exceptions

if not self.fs.async_impl:
Expand Down
22 changes: 22 additions & 0 deletions tests/test_store/test_fsspec.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from zarr.errors import ZarrUserWarning
from zarr.storage import FsspecStore
from zarr.storage._fsspec import _make_async
from zarr.storage._utils import normalize_path
from zarr.testing.store import StoreTests

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


@pytest.mark.skipif(
parse_version(fsspec.__version__) < parse_version("2024.12.0"),
reason="No AsyncFileSystemWrapper",
)
@pytest.mark.parametrize("path", ["", "/", "//", "foo", "foo/", "/foo", "/foo/", "//foo//"])
def test_fsspec_store_path_normalization(path: str) -> None:
"""`FsspecStore.path` is normalized to the canonical form, matching
`normalize_path`, regardless of the surface representation the caller
supplies.

Regression test for https://github.com/zarr-developers/zarr-python/issues/3922
-- when a caller passed `path="/"` the leading slash flowed through
unmodified to subsequent `_join_paths([self.path, key])` calls, producing
`"//key"` and missing the underlying object.
"""
sync_fs = fsspec.filesystem("memory")
fs = _make_async(sync_fs)
store = FsspecStore(fs=fs, path=path)
assert store.path == normalize_path(path)


@pytest.mark.skipif(
parse_version(fsspec.__version__) < parse_version("2024.12.0"),
reason="No AsyncFileSystemWrapper",
Expand Down
Loading