Skip to content

Commit bd41d10

Browse files
committed
Merge branch 'v3' of https://github.com/zarr-developers/zarr-python into feat/metadata-support-storage-transformers
2 parents 9981eea + 692593b commit bd41d10

File tree

615 files changed

+870
-29507
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

615 files changed

+870
-29507
lines changed

.github/workflows/releases.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ jobs:
5555
with:
5656
name: releases
5757
path: dist
58-
- uses: pypa/[email protected].1
58+
- uses: pypa/[email protected].2
5959
with:
6060
user: __token__
6161
password: ${{ secrets.pypi_password }}

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,5 @@ fixture/
8484
.DS_Store
8585
tests/.hypothesis
8686
.hypothesis/
87+
88+
zarr/version.py

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ default_language_version:
77
python: python3
88
repos:
99
- repo: https://github.com/astral-sh/ruff-pre-commit
10-
rev: v0.6.5
10+
rev: v0.6.7
1111
hooks:
1212
- id: ruff
1313
args: ["--fix", "--show-fixes"]

pyproject.toml

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ test = [
6666
"flask",
6767
"requests",
6868
"mypy",
69-
"hypothesis"
69+
"hypothesis",
70+
"universal-pathlib",
7071
]
7172

7273
jupyter = [
@@ -206,18 +207,36 @@ extend-exclude = [
206207

207208
[tool.ruff.lint]
208209
extend-select = [
209-
"B", # flake8-bugbear
210-
"I", # isort
211-
"ISC",
212-
"UP", # pyupgrade
213-
"RSE",
210+
"B", # flake8-bugbear
211+
"I", # isort
212+
"ISC", # flake8-implicit-str-concat
213+
"PGH", # pygrep-hooks
214+
"PYI", # flake8-pyi
215+
"RSE", # flake8-raise
214216
"RUF",
215217
"TCH", # flake8-type-checking
216218
"TRY", # tryceratops
219+
"UP", # pyupgrade
217220
]
218221
ignore = [
222+
"PYI013",
219223
"RUF005",
220224
"TRY003",
225+
# https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules
226+
"W191",
227+
"E111",
228+
"E114",
229+
"E117",
230+
"D206",
231+
"D300",
232+
"Q000",
233+
"Q001",
234+
"Q002",
235+
"Q003",
236+
"COM812",
237+
"COM819",
238+
"ISC001",
239+
"ISC002",
221240
]
222241

223242
[tool.mypy]
@@ -273,6 +292,7 @@ filterwarnings = [
273292
"ignore:PY_SSIZE_T_CLEAN will be required.*:DeprecationWarning",
274293
"ignore:The loop argument is deprecated since Python 3.8.*:DeprecationWarning",
275294
"ignore:Creating a zarr.buffer.gpu.*:UserWarning",
295+
"ignore:Duplicate name:UserWarning", # from ZipFile
276296
]
277297
markers = [
278298
"gpu: mark a test as requiring CuPy and GPU"

src/zarr/_compat.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import warnings
2+
from collections.abc import Callable
3+
from functools import wraps
4+
from inspect import Parameter, signature
5+
from typing import Any, TypeVar
6+
7+
T = TypeVar("T")
8+
9+
# Based off https://github.com/scikit-learn/scikit-learn/blob/e87b32a81c70abed8f2e97483758eb64df8255e9/sklearn/utils/validation.py#L63
10+
11+
12+
def _deprecate_positional_args(
13+
func: Callable[..., T] | None = None, *, version: str = "3.1.0"
14+
) -> Callable[..., T]:
15+
"""Decorator for methods that issues warnings for positional arguments.
16+
17+
Using the keyword-only argument syntax in pep 3102, arguments after the
18+
* will issue a warning when passed as a positional argument.
19+
20+
Parameters
21+
----------
22+
func : callable, default=None
23+
Function to check arguments on.
24+
version : callable, default="3.1.0"
25+
The version when positional arguments will result in error.
26+
"""
27+
28+
def _inner_deprecate_positional_args(f: Callable[..., T]) -> Callable[..., T]:
29+
sig = signature(f)
30+
kwonly_args = []
31+
all_args = []
32+
33+
for name, param in sig.parameters.items():
34+
if param.kind == Parameter.POSITIONAL_OR_KEYWORD:
35+
all_args.append(name)
36+
elif param.kind == Parameter.KEYWORD_ONLY:
37+
kwonly_args.append(name)
38+
39+
@wraps(f)
40+
def inner_f(*args: Any, **kwargs: Any) -> T:
41+
extra_args = len(args) - len(all_args)
42+
if extra_args <= 0:
43+
return f(*args, **kwargs)
44+
45+
# extra_args > 0
46+
args_msg = [
47+
f"{name}={arg}"
48+
for name, arg in zip(kwonly_args[:extra_args], args[-extra_args:], strict=False)
49+
]
50+
formatted_args_msg = ", ".join(args_msg)
51+
warnings.warn(
52+
(
53+
f"Pass {formatted_args_msg} as keyword args. From version "
54+
f"{version} passing these as positional arguments "
55+
"will result in an error"
56+
),
57+
FutureWarning,
58+
stacklevel=2,
59+
)
60+
kwargs.update(zip(sig.parameters, args, strict=False))
61+
return f(**kwargs)
62+
63+
return inner_f
64+
65+
if func is not None:
66+
return _inner_deprecate_positional_args(func)
67+
68+
return _inner_deprecate_positional_args # type: ignore[return-value]

src/zarr/abc/store.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from abc import ABC, abstractmethod
22
from asyncio import gather
33
from collections.abc import AsyncGenerator, Iterable
4+
from types import TracebackType
45
from typing import Any, NamedTuple, Protocol, runtime_checkable
56

67
from typing_extensions import Self
@@ -35,7 +36,7 @@ class Store(ABC):
3536
_mode: AccessMode
3637
_is_open: bool
3738

38-
def __init__(self, mode: AccessModeLiteral = "r", *args: Any, **kwargs: Any):
39+
def __init__(self, mode: AccessModeLiteral = "r", *args: Any, **kwargs: Any) -> None:
3940
self._is_open = False
4041
self._mode = AccessMode.from_literal(mode)
4142

@@ -49,7 +50,12 @@ def __enter__(self) -> Self:
4950
"""Enter a context manager that will close the store upon exiting."""
5051
return self
5152

52-
def __exit__(self, *args: Any) -> None:
53+
def __exit__(
54+
self,
55+
exc_type: type[BaseException] | None,
56+
exc_value: BaseException | None,
57+
traceback: TracebackType | None,
58+
) -> None:
5359
"""Close the store."""
5460
self.close()
5561

src/zarr/api/asynchronous.py

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

33
import asyncio
44
import warnings
5-
from typing import TYPE_CHECKING, Any, Literal, Union, cast
5+
from typing import TYPE_CHECKING, Any, Literal, cast
66

77
import numpy as np
88
import numpy.typing as npt
@@ -25,6 +25,10 @@
2525
from zarr.core.buffer import NDArrayLike
2626
from zarr.core.chunk_key_encodings import ChunkKeyEncoding
2727

28+
# TODO: this type could use some more thought
29+
ArrayLike = AsyncArray | Array | npt.NDArray[Any]
30+
PathLike = str
31+
2832
__all__ = [
2933
"consolidate_metadata",
3034
"copy",
@@ -53,10 +57,6 @@
5357
"zeros_like",
5458
]
5559

56-
# TODO: this type could use some more thought, noqa to avoid "Variable "asynchronous.ArrayLike" is not valid as a type"
57-
ArrayLike = Union[AsyncArray | Array | npt.NDArray[Any]] # noqa
58-
PathLike = str
59-
6060

6161
def _get_shape_chunks(a: ArrayLike | Any) -> tuple[ChunkCoords | None, ChunkCoords | None]:
6262
"""helper function to get the shape and chunks from an array-like object"""
@@ -503,7 +503,7 @@ async def group(
503503
try:
504504
return await AsyncGroup.open(store=store_path, zarr_format=zarr_format)
505505
except (KeyError, FileNotFoundError):
506-
return await AsyncGroup.create(
506+
return await AsyncGroup.from_store(
507507
store=store_path,
508508
zarr_format=zarr_format or _default_zarr_version(),
509509
exists_ok=overwrite,
@@ -512,8 +512,8 @@ async def group(
512512

513513

514514
async def open_group(
515-
*, # Note: this is a change from v2
516515
store: StoreLike | None = None,
516+
*, # Note: this is a change from v2
517517
mode: AccessModeLiteral | None = None,
518518
cache_attrs: bool | None = None, # not used, default changed
519519
synchronizer: Any = None, # not used
@@ -590,7 +590,7 @@ async def open_group(
590590
try:
591591
return await AsyncGroup.open(store_path, zarr_format=zarr_format)
592592
except (KeyError, FileNotFoundError):
593-
return await AsyncGroup.create(
593+
return await AsyncGroup.from_store(
594594
store_path,
595595
zarr_format=zarr_format or _default_zarr_version(),
596596
exists_ok=True,

src/zarr/api/synchronous.py

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

55
import zarr.api.asynchronous as async_api
6+
from zarr._compat import _deprecate_positional_args
67
from zarr.core.array import Array, AsyncArray
78
from zarr.core.group import Group
89
from zarr.core.sync import sync
@@ -63,9 +64,10 @@ def load(
6364
return sync(async_api.load(store=store, zarr_version=zarr_version, path=path))
6465

6566

67+
@_deprecate_positional_args
6668
def open(
67-
*,
6869
store: StoreLike | None = None,
70+
*,
6971
mode: AccessModeLiteral | None = None, # type and value changed
7072
zarr_version: ZarrFormat | None = None, # deprecated
7173
zarr_format: ZarrFormat | None = None,
@@ -107,6 +109,7 @@ def save(
107109
)
108110

109111

112+
@_deprecate_positional_args
110113
def save_array(
111114
store: StoreLike,
112115
arr: NDArrayLike,
@@ -159,9 +162,10 @@ def array(data: NDArrayLike, **kwargs: Any) -> Array:
159162
return Array(sync(async_api.array(data=data, **kwargs)))
160163

161164

165+
@_deprecate_positional_args
162166
def group(
163-
*, # Note: this is a change from v2
164167
store: StoreLike | None = None,
168+
*, # Note: this is a change from v2
165169
overwrite: bool = False,
166170
chunk_store: StoreLike | None = None, # not used in async_api
167171
cache_attrs: bool | None = None, # default changed, not used in async_api
@@ -190,9 +194,10 @@ def group(
190194
)
191195

192196

197+
@_deprecate_positional_args
193198
def open_group(
194-
*, # Note: this is a change from v2
195199
store: StoreLike | None = None,
200+
*, # Note: this is a change from v2
196201
mode: AccessModeLiteral | None = None, # not used in async api
197202
cache_attrs: bool | None = None, # default changed, not used in async api
198203
synchronizer: Any = None, # not used in async api

src/zarr/codecs/_v2.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,18 @@
88

99
from zarr.abc.codec import ArrayArrayCodec, ArrayBytesCodec
1010
from zarr.core.buffer import Buffer, NDBuffer, default_buffer_prototype
11-
from zarr.core.common import JSON, to_thread
11+
from zarr.core.common import to_thread
1212
from zarr.registry import get_ndbuffer_class
1313

1414
if TYPE_CHECKING:
15+
import numcodecs.abc
16+
1517
from zarr.core.array_spec import ArraySpec
1618

1719

1820
@dataclass(frozen=True)
1921
class V2Compressor(ArrayBytesCodec):
20-
compressor: dict[str, JSON] | None
22+
compressor: numcodecs.abc.Codec | None
2123

2224
is_fixed_size = False
2325

@@ -27,9 +29,8 @@ async def _decode_single(
2729
chunk_spec: ArraySpec,
2830
) -> NDBuffer:
2931
if self.compressor is not None:
30-
compressor = numcodecs.get_codec(self.compressor)
3132
chunk_numpy_array = ensure_ndarray(
32-
await to_thread(compressor.decode, chunk_bytes.as_array_like())
33+
await to_thread(self.compressor.decode, chunk_bytes.as_array_like())
3334
)
3435
else:
3536
chunk_numpy_array = ensure_ndarray(chunk_bytes.as_array_like())
@@ -47,14 +48,13 @@ async def _encode_single(
4748
) -> Buffer | None:
4849
chunk_numpy_array = chunk_array.as_numpy_array()
4950
if self.compressor is not None:
50-
compressor = numcodecs.get_codec(self.compressor)
5151
if (
5252
not chunk_numpy_array.flags.c_contiguous
5353
and not chunk_numpy_array.flags.f_contiguous
5454
):
5555
chunk_numpy_array = chunk_numpy_array.copy(order="A")
5656
encoded_chunk_bytes = ensure_bytes(
57-
await to_thread(compressor.encode, chunk_numpy_array)
57+
await to_thread(self.compressor.encode, chunk_numpy_array)
5858
)
5959
else:
6060
encoded_chunk_bytes = ensure_bytes(chunk_numpy_array)

src/zarr/codecs/transpose.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,16 +96,14 @@ async def _decode_single(
9696
chunk_spec: ArraySpec,
9797
) -> NDBuffer:
9898
inverse_order = np.argsort(self.order)
99-
chunk_array = chunk_array.transpose(inverse_order)
100-
return chunk_array
99+
return chunk_array.transpose(inverse_order)
101100

102101
async def _encode_single(
103102
self,
104103
chunk_array: NDBuffer,
105104
_chunk_spec: ArraySpec,
106105
) -> NDBuffer | None:
107-
chunk_array = chunk_array.transpose(self.order)
108-
return chunk_array
106+
return chunk_array.transpose(self.order)
109107

110108
def compute_encoded_size(self, input_byte_length: int, _chunk_spec: ArraySpec) -> int:
111109
return input_byte_length

0 commit comments

Comments
 (0)