Skip to content

Commit e86af14

Browse files
committed
refactor: remove _dereference_path in favor of _join_paths
1 parent c4eb7d3 commit e86af14

File tree

6 files changed

+26
-100
lines changed

6 files changed

+26
-100
lines changed

src/zarr/storage/_common.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
from zarr.errors import ContainsArrayAndGroupError, ContainsArrayError, ContainsGroupError
2525
from zarr.storage._local import LocalStore
2626
from zarr.storage._memory import ManagedMemoryStore, MemoryStore
27-
from zarr.storage._utils import _dereference_path, normalize_path, parse_store_url
27+
from zarr.storage._utils import _join_paths, normalize_path, parse_store_url
2828

2929
_has_fsspec = importlib.util.find_spec("fsspec")
3030
if _has_fsspec:
@@ -255,10 +255,10 @@ def delete_sync(self) -> None:
255255

256256
def __truediv__(self, other: str) -> StorePath:
257257
"""Combine this store path with another path"""
258-
return self.__class__(self.store, _dereference_path(self.path, other))
258+
return self.__class__(self.store, _join_paths([self.path, other]))
259259

260260
def __str__(self) -> str:
261-
return _dereference_path(str(self.store), self.path)
261+
return _join_paths([str(self.store), self.path])
262262

263263
def __repr__(self) -> str:
264264
return f"StorePath({self.store.__class__.__name__}, '{self}')"

src/zarr/storage/_fsspec.py

Lines changed: 8 additions & 8 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 _dereference_path
19+
from zarr.storage._utils import _join_paths
2020

2121
if TYPE_CHECKING:
2222
from collections.abc import AsyncIterator, Iterable
@@ -282,7 +282,7 @@ async def get(
282282
# docstring inherited
283283
if not self._is_open:
284284
await self._open()
285-
path = _dereference_path(self.path, key)
285+
path = _join_paths([self.path, key])
286286

287287
try:
288288
if byte_range is None:
@@ -329,7 +329,7 @@ async def set(
329329
raise TypeError(
330330
f"FsspecStore.set(): `value` must be a Buffer instance. Got an instance of {type(value)} instead."
331331
)
332-
path = _dereference_path(self.path, key)
332+
path = _join_paths([self.path, key])
333333
# write data
334334
if byte_range:
335335
raise NotImplementedError
@@ -338,7 +338,7 @@ async def set(
338338
async def delete(self, key: str) -> None:
339339
# docstring inherited
340340
self._check_writable()
341-
path = _dereference_path(self.path, key)
341+
path = _join_paths([self.path, key])
342342
try:
343343
await self.fs._rm(path)
344344
except FileNotFoundError:
@@ -354,14 +354,14 @@ async def delete_dir(self, prefix: str) -> None:
354354
)
355355
self._check_writable()
356356

357-
path_to_delete = _dereference_path(self.path, prefix)
357+
path_to_delete = _join_paths([self.path, prefix])
358358

359359
with suppress(*self.allowed_exceptions):
360360
await self.fs._rm(path_to_delete, recursive=True)
361361

362362
async def exists(self, key: str) -> bool:
363363
# docstring inherited
364-
path = _dereference_path(self.path, key)
364+
path = _join_paths([self.path, key])
365365
exists: bool = await self.fs._exists(path)
366366
return exists
367367

@@ -378,7 +378,7 @@ async def get_partial_values(
378378
starts: list[int | None] = []
379379
stops: list[int | None] = []
380380
for key, byte_range in key_ranges:
381-
paths.append(_dereference_path(self.path, key))
381+
paths.append(_join_paths([self.path, key]))
382382
if byte_range is None:
383383
starts.append(None)
384384
stops.append(None)
@@ -429,7 +429,7 @@ async def list_prefix(self, prefix: str) -> AsyncIterator[str]:
429429
yield onefile.removeprefix(f"{self.path}/")
430430

431431
async def getsize(self, key: str) -> int:
432-
path = _dereference_path(self.path, key)
432+
path = _join_paths([self.path, key])
433433
info = await self.fs._info(path)
434434

435435
size = info.get("size")

src/zarr/storage/_memory.py

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from zarr.core.buffer.core import default_buffer_prototype
1212
from zarr.core.common import concurrent_map
1313
from zarr.storage._utils import (
14-
_dereference_path,
14+
_join_paths,
1515
_normalize_byte_range_index,
1616
normalize_path,
1717
parse_store_url,
@@ -700,7 +700,7 @@ def __init__(self, name: str | None = None, *, path: str = "", read_only: bool =
700700
self.path = normalize_path(path)
701701

702702
def __str__(self) -> str:
703-
return _dereference_path(f"memory://{self._name}", self.path)
703+
return _join_paths([f"memory://{self._name}", self.path])
704704

705705
def __repr__(self) -> str:
706706
return f"ManagedMemoryStore('{self}')"
@@ -792,7 +792,7 @@ async def get(
792792
) -> Buffer | None:
793793
# docstring inherited
794794
return await super().get(
795-
_dereference_path(self.path, key), prototype=prototype, byte_range=byte_range
795+
_join_paths([self.path, key]), prototype=prototype, byte_range=byte_range
796796
)
797797

798798
async def get_partial_values(
@@ -801,26 +801,24 @@ async def get_partial_values(
801801
key_ranges: Iterable[tuple[str, ByteRequest | None]],
802802
) -> list[Buffer | None]:
803803
# docstring inherited
804-
key_ranges = [
805-
(_dereference_path(self.path, key), byte_range) for key, byte_range in key_ranges
806-
]
804+
key_ranges = [(_join_paths([self.path, key]), byte_range) for key, byte_range in key_ranges]
807805
return await super().get_partial_values(prototype, key_ranges)
808806

809807
async def exists(self, key: str) -> bool:
810808
# docstring inherited
811-
return await super().exists(_dereference_path(self.path, key))
809+
return await super().exists(_join_paths([self.path, key]))
812810

813811
async def set(self, key: str, value: Buffer, byte_range: tuple[int, int] | None = None) -> None:
814812
# docstring inherited
815-
return await super().set(_dereference_path(self.path, key), value, byte_range=byte_range)
813+
return await super().set(_join_paths([self.path, key]), value, byte_range=byte_range)
816814

817815
async def set_if_not_exists(self, key: str, value: Buffer) -> None:
818816
# docstring inherited
819-
return await super().set_if_not_exists(_dereference_path(self.path, key), value)
817+
return await super().set_if_not_exists(_join_paths([self.path, key]), value)
820818

821819
async def delete(self, key: str) -> None:
822820
# docstring inherited
823-
return await super().delete(_dereference_path(self.path, key))
821+
return await super().delete(_join_paths([self.path, key]))
824822

825823
async def list(self) -> AsyncIterator[str]:
826824
# docstring inherited
@@ -831,16 +829,16 @@ async def list(self) -> AsyncIterator[str]:
831829

832830
async def list_prefix(self, prefix: str) -> AsyncIterator[str]:
833831
# docstring inherited
834-
# Don't use _dereference_path here because it strips trailing slashes,
835-
# which would break prefix matching (e.g., "fo/" vs "foo/")
832+
# Manual concatenation instead of _join_paths because we need "path/"
833+
# as the prefix when prefix is empty (to list all keys under self.path)
836834
full_prefix = f"{self.path}/{prefix}" if self.path else prefix
837835
path_prefix = self.path + "/" if self.path else ""
838836
async for key in super().list_prefix(full_prefix):
839837
yield key.removeprefix(path_prefix)
840838

841839
async def list_dir(self, prefix: str) -> AsyncIterator[str]:
842840
# docstring inherited
843-
full_prefix = _dereference_path(self.path, prefix)
841+
full_prefix = _join_paths([self.path, prefix])
844842
async for key in super().list_dir(full_prefix):
845843
yield key
846844

src/zarr/storage/_utils.py

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -160,33 +160,6 @@ def _normalize_byte_range_index(data: Buffer, byte_range: ByteRequest | None) ->
160160
return (start, stop)
161161

162162

163-
def _dereference_path(root: str, path: str) -> str:
164-
"""
165-
Combine a root path with a relative path.
166-
167-
Parameters
168-
----------
169-
root : str
170-
The root path.
171-
path : str
172-
The path relative to root.
173-
174-
Returns
175-
-------
176-
str
177-
The combined path with trailing slashes removed.
178-
"""
179-
if not isinstance(root, str):
180-
msg = f"{root=} is not a string ({type(root)=})" # type: ignore[unreachable]
181-
raise TypeError(msg)
182-
if not isinstance(path, str):
183-
msg = f"{path=} is not a string ({type(path)=})" # type: ignore[unreachable]
184-
raise TypeError(msg)
185-
root = root.rstrip("/")
186-
path = f"{root}/{path}" if root else path
187-
return path.rstrip("/")
188-
189-
190163
def _join_paths(paths: Iterable[str]) -> str:
191164
"""
192165
Filter out instances of '' and join the remaining strings with '/'.

src/zarr/testing/strategies.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
from zarr.core.metadata.v3 import RectilinearChunkGridMetadata, RegularChunkGridMetadata
2323
from zarr.core.sync import sync
2424
from zarr.storage import MemoryStore, StoreLike
25-
from zarr.storage._utils import _dereference_path, normalize_path
25+
from zarr.storage._utils import _join_paths, normalize_path
2626
from zarr.types import AnyArray
2727

2828
TrueOrFalse = Literal[True, False]
@@ -288,7 +288,7 @@ def arrays(
288288

289289
expected_attrs = {} if attributes is None else attributes
290290

291-
array_path = _dereference_path(path, name)
291+
array_path = _join_paths([path, name])
292292
root = zarr.open_group(store, mode=open_mode, zarr_format=zarr_format)
293293

294294
# Convert chunk grid metadata to a form create_array accepts:
@@ -621,7 +621,7 @@ def complex_rectilinear_arrays(
621621
store = draw(stores, label="store")
622622
path = draw(paths, label="array parent")
623623
name = draw(array_names, label="array name")
624-
array_path = _dereference_path(path, name)
624+
array_path = _join_paths([path, name])
625625

626626
root = zarr.open_group(store, mode="w", zarr_format=3)
627627
with zarr.config.set({"array.rectilinear_chunks": True}):

tests/test_store/test_utils.py

Lines changed: 1 addition & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@
55

66
import pytest
77

8-
from tests.conftest import Expect, ExpectFail
9-
from zarr.storage._utils import ParsedStoreUrl, _dereference_path, parse_store_url
8+
from zarr.storage._utils import ParsedStoreUrl, parse_store_url
109

1110

1211
class TestParseStoreUrl:
@@ -96,47 +95,3 @@ def test_drive_letter_not_special_on_non_windows(self, url: str) -> None:
9695
result = parse_store_url(url)
9796
# urlparse interprets the drive letter as a scheme
9897
assert result.scheme == "c"
99-
100-
101-
@pytest.mark.parametrize(
102-
"case",
103-
[
104-
Expect(input=("root", "path"), output="root/path", id="basic"),
105-
Expect(input=("root", ""), output="root", id="empty_path"),
106-
Expect(input=("", "path"), output="path", id="empty_root"),
107-
Expect(input=("", ""), output="", id="both_empty"),
108-
Expect(input=("root/", "path"), output="root/path", id="root_trailing_slash"),
109-
Expect(input=("root//", "path"), output="root/path", id="root_multiple_trailing_slashes"),
110-
Expect(input=("root", "path/"), output="root/path", id="path_trailing_slash"),
111-
Expect(input=("a/b", "c/d"), output="a/b/c/d", id="nested"),
112-
Expect(input=("memory://store", "key"), output="memory://store/key", id="url_root"),
113-
],
114-
ids=lambda case: case.id,
115-
)
116-
def test_dereference_path(case: Expect[tuple[str, str], str]) -> None:
117-
"""
118-
Test the normal behavior of _dereference_path
119-
"""
120-
root, path = case.input
121-
assert _dereference_path(root, path) == case.output
122-
123-
124-
@pytest.mark.parametrize(
125-
"case",
126-
[
127-
ExpectFail(
128-
input=(1, "path"), exception=TypeError, msg="root=.*not a string", id="root_not_str"
129-
),
130-
ExpectFail(
131-
input=("root", 1), exception=TypeError, msg="path=.*not a string", id="path_not_str"
132-
),
133-
],
134-
ids=lambda case: case.id,
135-
)
136-
def test_dereference_path_errors(case: ExpectFail[tuple[object, object]]) -> None:
137-
"""
138-
Test that _dereference_path raises TypeError for non-string inputs.
139-
"""
140-
root, path = case.input
141-
with pytest.raises(case.exception, match=case.msg):
142-
_dereference_path(root, path) # type: ignore[arg-type]

0 commit comments

Comments
 (0)