Skip to content

Commit 46070f4

Browse files
committed
Use dataclasses
1 parent 650fb38 commit 46070f4

File tree

14 files changed

+104
-102
lines changed

14 files changed

+104
-102
lines changed

src/zarr/abc/store.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22

33
from abc import ABC, abstractmethod
44
from asyncio import gather
5+
from dataclasses import dataclass
56
from itertools import starmap
6-
from typing import TYPE_CHECKING, Protocol, TypedDict, runtime_checkable
7+
from typing import TYPE_CHECKING, Protocol, runtime_checkable
78

89
from zarr.core.buffer.core import default_buffer_prototype
910
from zarr.core.common import concurrent_map
@@ -20,7 +21,8 @@
2021
__all__ = ["ByteGetter", "ByteSetter", "Store", "set_or_delete"]
2122

2223

23-
class ExplicitRange(TypedDict):
24+
@dataclass
25+
class ExplicitRange:
2426
"""Request a specific byte range"""
2527

2628
start: int
@@ -29,21 +31,23 @@ class ExplicitRange(TypedDict):
2931
"""The end of the byte range request (exclusive)."""
3032

3133

32-
class OffsetRange(TypedDict):
34+
@dataclass
35+
class OffsetRange:
3336
"""Request all bytes starting from a given byte offset"""
3437

3538
offset: int
3639
"""The byte offset for the offset range request."""
3740

3841

39-
class SuffixRange(TypedDict):
42+
@dataclass
43+
class SuffixRange:
4044
"""Request up to the last `n` bytes"""
4145

4246
suffix: int
4347
"""The number of bytes from the suffix to request."""
4448

4549

46-
ByteRangeRequest: TypeAlias = None | ExplicitRange | OffsetRange | SuffixRange
50+
ByteRangeRequest: TypeAlias = ExplicitRange | OffsetRange | SuffixRange
4751

4852

4953
class Store(ABC):
@@ -190,7 +194,7 @@ async def get(
190194
async def get_partial_values(
191195
self,
192196
prototype: BufferPrototype,
193-
key_ranges: Iterable[tuple[str, ByteRangeRequest]],
197+
key_ranges: Iterable[tuple[str, ByteRangeRequest | None]],
194198
) -> list[Buffer | None]:
195199
"""Retrieve possibly partial values from given key_ranges.
196200

src/zarr/codecs/sharding.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,13 @@
1717
Codec,
1818
CodecPipeline,
1919
)
20-
from zarr.abc.store import ByteGetter, ByteRangeRequest, ByteSetter
20+
from zarr.abc.store import (
21+
ByteGetter,
22+
ByteRangeRequest,
23+
ByteSetter,
24+
ExplicitRange,
25+
SuffixRange,
26+
)
2127
from zarr.codecs.bytes import BytesCodec
2228
from zarr.codecs.crc32c_ import Crc32cCodec
2329
from zarr.core.array_spec import ArrayConfig, ArraySpec
@@ -505,7 +511,7 @@ async def _decode_partial_single(
505511
if chunk_byte_slice:
506512
chunk_bytes = await byte_getter.get(
507513
prototype=chunk_spec.prototype,
508-
byte_range={"start": chunk_byte_slice[0], "end": chunk_byte_slice[1]},
514+
byte_range=ExplicitRange(chunk_byte_slice[0], chunk_byte_slice[1]),
509515
)
510516
if chunk_bytes:
511517
shard_dict[chunk_coords] = chunk_bytes
@@ -697,11 +703,11 @@ async def _load_shard_index_maybe(
697703
shard_index_size = self._shard_index_size(chunks_per_shard)
698704
if self.index_location == ShardingCodecIndexLocation.start:
699705
index_bytes = await byte_getter.get(
700-
prototype=numpy_buffer_prototype(), byte_range={"start": 0, "end": shard_index_size}
706+
prototype=numpy_buffer_prototype(), byte_range=ExplicitRange(0, shard_index_size)
701707
)
702708
else:
703709
index_bytes = await byte_getter.get(
704-
prototype=numpy_buffer_prototype(), byte_range={"suffix": shard_index_size}
710+
prototype=numpy_buffer_prototype(), byte_range=SuffixRange(shard_index_size)
705711
)
706712
if index_bytes is not None:
707713
return await self._decode_shard_index(index_bytes, chunks_per_shard)

src/zarr/storage/_common.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ async def open(
102102
async def get(
103103
self,
104104
prototype: BufferPrototype | None = None,
105-
byte_range: ByteRangeRequest = None,
105+
byte_range: ByteRangeRequest | None = None,
106106
) -> Buffer | None:
107107
"""
108108
Read bytes from the store.

src/zarr/storage/_fsspec.py

Lines changed: 27 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import warnings
44
from typing import TYPE_CHECKING, Any
55

6-
from zarr.abc.store import ByteRangeRequest, Store
6+
from zarr.abc.store import ByteRangeRequest, ExplicitRange, OffsetRange, Store, SuffixRange
77
from zarr.storage._common import _dereference_path
88

99
if TYPE_CHECKING:
@@ -199,7 +199,7 @@ async def get(
199199
self,
200200
key: str,
201201
prototype: BufferPrototype,
202-
byte_range: ByteRangeRequest = None,
202+
byte_range: ByteRangeRequest | None = None,
203203
) -> Buffer | None:
204204
# docstring inherited
205205
if not self._is_open:
@@ -209,25 +209,22 @@ async def get(
209209
try:
210210
if byte_range is None:
211211
value = prototype.buffer.from_bytes(await self.fs._cat_file(path))
212-
elif isinstance(byte_range, dict):
213-
if "start" in byte_range:
214-
value = prototype.buffer.from_bytes(
215-
await self.fs._cat_file(
216-
path,
217-
start=byte_range["start"], # type: ignore[typeddict-item]
218-
end=byte_range["end"], # type: ignore[typeddict-item]
219-
)
212+
elif isinstance(byte_range, ExplicitRange):
213+
value = prototype.buffer.from_bytes(
214+
await self.fs._cat_file(
215+
path,
216+
start=byte_range.start,
217+
end=byte_range.end,
220218
)
221-
elif "offset" in byte_range:
222-
value = prototype.buffer.from_bytes(
223-
await self.fs._cat_file(path, start=byte_range["offset"], end=None) # type: ignore[typeddict-item]
224-
)
225-
elif "suffix" in byte_range:
226-
value = prototype.buffer.from_bytes(
227-
await self.fs._cat_file(path, start=-byte_range["suffix"], end=None)
228-
)
229-
else:
230-
raise ValueError("Invalid format for ByteRangeRequest")
219+
)
220+
elif isinstance(byte_range, OffsetRange):
221+
value = prototype.buffer.from_bytes(
222+
await self.fs._cat_file(path, start=byte_range.offset, end=None)
223+
)
224+
elif isinstance(byte_range, SuffixRange):
225+
value = prototype.buffer.from_bytes(
226+
await self.fs._cat_file(path, start=-byte_range.suffix, end=None)
227+
)
231228
else:
232229
raise ValueError("Invalid format for ByteRangeRequest")
233230
except self.allowed_exceptions:
@@ -276,7 +273,7 @@ async def exists(self, key: str) -> bool:
276273
async def get_partial_values(
277274
self,
278275
prototype: BufferPrototype,
279-
key_ranges: Iterable[tuple[str, ByteRangeRequest]],
276+
key_ranges: Iterable[tuple[str, ByteRangeRequest | None]],
280277
) -> list[Buffer | None]:
281278
# docstring inherited
282279
if key_ranges:
@@ -290,18 +287,15 @@ async def get_partial_values(
290287
if byte_range is None:
291288
starts.append(None)
292289
stops.append(None)
293-
elif isinstance(byte_range, dict):
294-
if "start" in byte_range:
295-
starts.append(byte_range["start"]) # type: ignore[typeddict-item]
296-
stops.append(byte_range["end"]) # type: ignore[typeddict-item]
297-
elif "offset" in byte_range:
298-
starts.append(byte_range["offset"]) # type: ignore[typeddict-item]
299-
stops.append(None)
300-
elif "suffix" in byte_range:
301-
starts.append(-byte_range["suffix"])
302-
stops.append(None)
303-
else:
304-
raise ValueError("Invalid format for ByteRangeRequest")
290+
elif isinstance(byte_range, ExplicitRange):
291+
starts.append(byte_range.start)
292+
stops.append(byte_range.end)
293+
elif isinstance(byte_range, OffsetRange):
294+
starts.append(byte_range.offset)
295+
stops.append(None)
296+
elif isinstance(byte_range, SuffixRange):
297+
starts.append(-byte_range.suffix)
298+
stops.append(None)
305299
else:
306300
raise ValueError("Invalid format for ByteRangeRequest")
307301
else:

src/zarr/storage/_local.py

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from pathlib import Path
88
from typing import TYPE_CHECKING
99

10-
from zarr.abc.store import ByteRangeRequest, Store
10+
from zarr.abc.store import ByteRangeRequest, ExplicitRange, OffsetRange, Store, SuffixRange
1111
from zarr.core.buffer import Buffer
1212
from zarr.core.buffer.core import default_buffer_prototype
1313
from zarr.core.common import concurrent_map
@@ -18,24 +18,21 @@
1818
from zarr.core.buffer import BufferPrototype
1919

2020

21-
def _get(path: Path, prototype: BufferPrototype, byte_range: ByteRangeRequest) -> Buffer:
21+
def _get(path: Path, prototype: BufferPrototype, byte_range: ByteRangeRequest | None) -> Buffer:
2222
if byte_range is None:
2323
return prototype.buffer.from_bytes(path.read_bytes())
2424
with path.open("rb") as f:
2525
size = f.seek(0, io.SEEK_END)
26-
if isinstance(byte_range, dict):
27-
if "start" in byte_range:
28-
f.seek(byte_range["start"]) # type: ignore[typeddict-item]
29-
return prototype.buffer.from_bytes(f.read(byte_range["end"] - f.tell())) # type: ignore[typeddict-item]
30-
elif "offset" in byte_range:
31-
f.seek(byte_range["offset"]) # type: ignore[typeddict-item]
32-
elif "suffix" in byte_range:
33-
f.seek(max(0, size - byte_range["suffix"]))
34-
else:
35-
raise TypeError("Invalid format for ByteRangeRequest")
36-
return prototype.buffer.from_bytes(f.read())
26+
if isinstance(byte_range, ExplicitRange):
27+
f.seek(byte_range.start)
28+
return prototype.buffer.from_bytes(f.read(byte_range.end - f.tell()))
29+
elif isinstance(byte_range, OffsetRange):
30+
f.seek(byte_range.offset)
31+
elif isinstance(byte_range, SuffixRange):
32+
f.seek(max(0, size - byte_range.suffix))
3733
else:
3834
raise TypeError("Invalid format for ByteRangeRequest")
35+
return prototype.buffer.from_bytes(f.read())
3936

4037

4138
def _put(
@@ -121,7 +118,7 @@ async def get(
121118
self,
122119
key: str,
123120
prototype: BufferPrototype | None = None,
124-
byte_range: ByteRangeRequest = None,
121+
byte_range: ByteRangeRequest | None = None,
125122
) -> Buffer | None:
126123
# docstring inherited
127124
if prototype is None:
@@ -139,7 +136,7 @@ async def get(
139136
async def get_partial_values(
140137
self,
141138
prototype: BufferPrototype,
142-
key_ranges: Iterable[tuple[str, ByteRangeRequest]],
139+
key_ranges: Iterable[tuple[str, ByteRangeRequest | None]],
143140
) -> list[Buffer | None]:
144141
# docstring inherited
145142
args = []

src/zarr/storage/_logging.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ async def get(
161161
self,
162162
key: str,
163163
prototype: BufferPrototype,
164-
byte_range: ByteRangeRequest = None,
164+
byte_range: ByteRangeRequest | None = None,
165165
) -> Buffer | None:
166166
# docstring inherited
167167
with self.log(key):
@@ -170,7 +170,7 @@ async def get(
170170
async def get_partial_values(
171171
self,
172172
prototype: BufferPrototype,
173-
key_ranges: Iterable[tuple[str, ByteRangeRequest]],
173+
key_ranges: Iterable[tuple[str, ByteRangeRequest | None]],
174174
) -> list[Buffer | None]:
175175
# docstring inherited
176176
keys = ",".join([k[0] for k in key_ranges])

src/zarr/storage/_memory.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ async def get(
7575
self,
7676
key: str,
7777
prototype: BufferPrototype,
78-
byte_range: ByteRangeRequest = None,
78+
byte_range: ByteRangeRequest | None = None,
7979
) -> Buffer | None:
8080
# docstring inherited
8181
if not self._is_open:
@@ -91,12 +91,12 @@ async def get(
9191
async def get_partial_values(
9292
self,
9393
prototype: BufferPrototype,
94-
key_ranges: Iterable[tuple[str, ByteRangeRequest]],
94+
key_ranges: Iterable[tuple[str, ByteRangeRequest | None]],
9595
) -> list[Buffer | None]:
9696
# docstring inherited
9797

9898
# All the key-ranges arguments goes with the same prototype
99-
async def _get(key: str, byte_range: ByteRangeRequest) -> Buffer | None:
99+
async def _get(key: str, byte_range: ByteRangeRequest | None) -> Buffer | None:
100100
return await self.get(key, prototype=prototype, byte_range=byte_range)
101101

102102
return await concurrent_map(key_ranges, _get, limit=None)

src/zarr/storage/_utils.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
from pathlib import Path
55
from typing import TYPE_CHECKING
66

7+
from zarr.abc.store import ExplicitRange, OffsetRange, SuffixRange
8+
79
if TYPE_CHECKING:
810
from zarr.abc.store import ByteRangeRequest
911
from zarr.core.buffer import Buffer
@@ -45,21 +47,22 @@ def normalize_path(path: str | bytes | Path | None) -> str:
4547
return result
4648

4749

48-
def _normalize_byte_range_index(data: Buffer, byte_range: ByteRangeRequest) -> tuple[int, int]:
50+
def _normalize_byte_range_index(
51+
data: Buffer, byte_range: ByteRangeRequest | None
52+
) -> tuple[int, int]:
4953
"""
5054
Convert an ByteRangeRequest into an explicit start and stop
5155
"""
5256
if byte_range is None:
5357
start = 0
5458
stop = len(data) + 1
55-
elif "start" in byte_range:
56-
# See https://github.com/python/mypy/issues/17087 for typeddict-item ignore explanation
57-
start = byte_range["start"] # type: ignore[typeddict-item]
58-
stop = byte_range["end"] # type: ignore[typeddict-item]
59-
elif "offset" in byte_range:
60-
start = byte_range["offset"] # type: ignore[typeddict-item]
59+
elif isinstance(byte_range, ExplicitRange):
60+
start = byte_range.start
61+
stop = byte_range.end
62+
elif isinstance(byte_range, OffsetRange):
63+
start = byte_range.offset
6164
stop = len(data) + 1
62-
elif "suffix" in byte_range:
63-
start = len(data) - byte_range["suffix"]
65+
elif isinstance(byte_range, SuffixRange):
66+
start = len(data) - byte_range.suffix
6467
stop = len(data) + 1
6568
return (start, stop)

src/zarr/storage/_wrapper.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ async def get(
7777
async def get_partial_values(
7878
self,
7979
prototype: BufferPrototype,
80-
key_ranges: Iterable[tuple[str, ByteRangeRequest]],
80+
key_ranges: Iterable[tuple[str, ByteRangeRequest | None]],
8181
) -> list[Buffer | None]:
8282
return await self._store.get_partial_values(prototype, key_ranges)
8383

0 commit comments

Comments
 (0)