diff --git a/src/zarr/core/metadata/v2.py b/src/zarr/core/metadata/v2.py index d5128bab1a..17b589afe5 100644 --- a/src/zarr/core/metadata/v2.py +++ b/src/zarr/core/metadata/v2.py @@ -19,8 +19,8 @@ from zarr.core.common import ChunkCoords import json -from dataclasses import dataclass, field, fields, replace import numbers +from dataclasses import dataclass, field, fields, replace import numcodecs import numpy as np @@ -150,14 +150,15 @@ def _json_convert( json_indent = config.get("json_indent") return { ZARRAY_JSON: prototype.buffer.from_bytes( - json.dumps(zarray_dict, default=_json_convert, indent=json_indent, allow_nan=False).encode() + json.dumps( + zarray_dict, default=_json_convert, indent=json_indent, allow_nan=False + ).encode() ), ZATTRS_JSON: prototype.buffer.from_bytes( json.dumps(zattrs_dict, indent=json_indent, allow_nan=False).encode() ), } - @classmethod def from_dict(cls, data: dict[str, Any]) -> ArrayV2Metadata: # Make a copy to protect the original from modification. @@ -194,8 +195,6 @@ def from_dict(cls, data: dict[str, Any]) -> ArrayV2Metadata: return cls(**_data) - - def to_dict(self) -> dict[str, JSON]: def _sanitize_fill_value(fv: Any): if fv is None: diff --git a/src/zarr/testing/stateful.py b/src/zarr/testing/stateful.py index 1b8d9f4232..1a8122d9a5 100644 --- a/src/zarr/testing/stateful.py +++ b/src/zarr/testing/stateful.py @@ -20,7 +20,8 @@ from zarr.core.buffer import Buffer, BufferPrototype, cpu, default_buffer_prototype from zarr.core.sync import SyncMixin from zarr.storage import LocalStore, MemoryStore -from zarr.testing.strategies import key_ranges, node_names, np_array_and_chunks, numpy_arrays, keys as zarr_keys +from zarr.testing.strategies import key_ranges, node_names, np_array_and_chunks, numpy_arrays +from zarr.testing.strategies import keys as zarr_keys MAX_BINARY_SIZE = 100 diff --git a/src/zarr/testing/strategies/__init__.py b/src/zarr/testing/strategies/__init__.py index 09431d68b6..c538ee20b7 100644 --- a/src/zarr/testing/strategies/__init__.py +++ b/src/zarr/testing/strategies/__init__.py @@ -1,18 +1,25 @@ -from .arrays import keys, key_ranges, node_names, np_array_and_chunks, basic_indices, numpy_arrays, zarr_arrays, keys, zarr_formats from .array_metadata_generators import array_metadata_v2, array_metadata_v3 - - +from .arrays import ( + basic_indices, + key_ranges, + keys, + node_names, + np_array_and_chunks, + numpy_arrays, + zarr_arrays, + zarr_formats, +) __all__ = [ "array_metadata_v2", "array_metadata_v3", + "basic_indices", + "key_ranges", "keys", "node_names", "np_array_and_chunks", - "key_ranges", - "zarr_arrays", - "basic_indices", "numpy_arrays", "zarr_arrays", - "zarr_formats" + "zarr_arrays", + "zarr_formats", ] diff --git a/src/zarr/testing/strategies/array_metadata_generators.py b/src/zarr/testing/strategies/array_metadata_generators.py index 98fa6c2923..4c4e2721e6 100644 --- a/src/zarr/testing/strategies/array_metadata_generators.py +++ b/src/zarr/testing/strategies/array_metadata_generators.py @@ -1,22 +1,16 @@ -from typing import Any, Iterable, Literal, Tuple, Dict -import numpy as np -import numpy.typing as npt -import numcodecs -from hypothesis import strategies as st import hypothesis.extra.numpy as npst +import numcodecs from hypothesis import assume -from dataclasses import dataclass, field +from hypothesis import strategies as st -from zarr.codecs.bytes import BytesCodec -from zarr.core.chunk_grids import RegularChunkGrid, ChunkGrid -from zarr.core.chunk_key_encodings import DefaultChunkKeyEncoding, ChunkKeyEncoding +from zarr.core.chunk_grids import RegularChunkGrid +from zarr.core.chunk_key_encodings import DefaultChunkKeyEncoding from zarr.core.metadata.v2 import ArrayV2Metadata from zarr.core.metadata.v3 import ArrayV3Metadata -from zarr.core.chunk_key_encodings import ChunkKeyEncoding, ChunkKeyEncodingLike - from .dtypes import v2_dtypes, v3_dtypes + def simple_text(): """A strategy for generating simple text strings.""" return st.text(st.characters(min_codepoint=32, max_codepoint=126), min_size=1, max_size=10) @@ -26,15 +20,20 @@ def simple_attrs(): """A strategy for generating simple attribute dictionaries.""" return st.dictionaries( simple_text(), - st.one_of(st.integers(), - st.floats(allow_nan=False, allow_infinity=False), - st.booleans(), - simple_text())) + st.one_of( + st.integers(), + st.floats(allow_nan=False, allow_infinity=False), + st.booleans(), + simple_text(), + ), + ) def array_shapes(min_dims=1, max_dims=3, max_len=100): """A strategy for generating array shapes.""" - return st.lists(st.integers(min_value=1, max_value=max_len), min_size=min_dims, max_size=max_dims) + return st.lists( + st.integers(min_value=1, max_value=max_len), min_size=min_dims, max_size=max_dims + ) # def zarr_compressors(): @@ -49,12 +48,20 @@ def array_shapes(min_dims=1, max_dims=3, max_len=100): def zarr_filters(): """A strategy for generating Zarr filters.""" - return st.lists(st.just(numcodecs.Delta(dtype='i4')), min_size=0, max_size=2) # Example filter, expand as needed + return st.lists( + st.just(numcodecs.Delta(dtype="i4")), min_size=0, max_size=2 + ) # Example filter, expand as needed def zarr_storage_transformers(): """A strategy for generating Zarr storage transformers.""" - return st.lists(st.dictionaries(simple_text(), st.one_of(st.integers(), st.floats(), st.booleans(), simple_text())), min_size=0, max_size=2) + return st.lists( + st.dictionaries( + simple_text(), st.one_of(st.integers(), st.floats(), st.booleans(), simple_text()) + ), + min_size=0, + max_size=2, + ) @st.composite @@ -63,16 +70,22 @@ def array_metadata_v2(draw: st.DrawFn) -> ArrayV2Metadata: dims = draw(st.integers(min_value=1, max_value=3)) # Limit dimensions for complexity shape = tuple(draw(array_shapes(min_dims=dims, max_dims=dims, max_len=100))) max_chunk_len = max(shape) if shape else 100 - chunks = tuple(draw(st.lists(st.integers(min_value=1, max_value=max_chunk_len), min_size=dims, max_size=dims))) + chunks = tuple( + draw( + st.lists( + st.integers(min_value=1, max_value=max_chunk_len), min_size=dims, max_size=dims + ) + ) + ) # Validate shape and chunks relationship - assume(all(c <= s for s, c in zip(shape, chunks))) # Chunk size must be <= shape + assume(all(c <= s for s, c in zip(shape, chunks, strict=False))) # Chunk size must be <= shape dtype = draw(v2_dtypes()) fill_value = draw(st.one_of([st.none(), npst.from_dtype(dtype)])) order = draw(st.sampled_from(["C", "F"])) dimension_separator = draw(st.sampled_from([".", "/"])) - #compressor = draw(zarr_compressors()) + # compressor = draw(zarr_compressors()) filters = tuple(draw(zarr_filters())) if draw(st.booleans()) else None attributes = draw(simple_attrs()) @@ -84,7 +97,7 @@ def array_metadata_v2(draw: st.DrawFn) -> ArrayV2Metadata: fill_value=fill_value, order=order, dimension_separator=dimension_separator, - # compressor=compressor, + # compressor=compressor, filters=filters, attributes=attributes, ) @@ -96,16 +109,26 @@ def array_metadata_v3(draw: st.DrawFn) -> ArrayV3Metadata: dims = draw(st.integers(min_value=1, max_value=3)) shape = tuple(draw(array_shapes(min_dims=dims, max_dims=dims, max_len=100))) max_chunk_len = max(shape) if shape else 100 - chunks = tuple(draw(st.lists(st.integers(min_value=1, max_value=max_chunk_len), min_size=dims, max_size=dims))) - assume(all(c <= s for s, c in zip(shape, chunks))) + chunks = tuple( + draw( + st.lists( + st.integers(min_value=1, max_value=max_chunk_len), min_size=dims, max_size=dims + ) + ) + ) + assume(all(c <= s for s, c in zip(shape, chunks, strict=False))) dtype = draw(v3_dtypes()) fill_value = draw(npst.from_dtype(dtype)) - chunk_grid = RegularChunkGrid(chunks) # Ensure chunks is passed as tuple. + chunk_grid = RegularChunkGrid(chunks) # Ensure chunks is passed as tuple. chunk_key_encoding = DefaultChunkKeyEncoding(separator="/") # Or st.sampled_from(["/", "."]) - #codecs = tuple(draw(st.lists(zarr_codecs(), min_size=0, max_size=3))) + # codecs = tuple(draw(st.lists(zarr_codecs(), min_size=0, max_size=3))) attributes = draw(simple_attrs()) - dimension_names = tuple(draw(st.lists(st.one_of(st.none(), simple_text()), min_size=dims, max_size=dims))) if draw(st.booleans()) else None + dimension_names = ( + tuple(draw(st.lists(st.one_of(st.none(), simple_text()), min_size=dims, max_size=dims))) + if draw(st.booleans()) + else None + ) storage_transformers = tuple(draw(zarr_storage_transformers())) return ArrayV3Metadata( @@ -114,7 +137,7 @@ def array_metadata_v3(draw: st.DrawFn) -> ArrayV3Metadata: chunk_grid=chunk_grid, chunk_key_encoding=chunk_key_encoding, fill_value=fill_value, - # codecs=codecs, + # codecs=codecs, attributes=attributes, dimension_names=dimension_names, storage_transformers=storage_transformers, diff --git a/src/zarr/testing/strategies/arrays.py b/src/zarr/testing/strategies/arrays.py index e0dcb9dd8a..ce5a83b923 100644 --- a/src/zarr/testing/strategies/arrays.py +++ b/src/zarr/testing/strategies/arrays.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, List, Optional +from typing import Any import hypothesis.extra.numpy as npst import hypothesis.strategies as st @@ -10,11 +10,10 @@ from zarr.abc.store import RangeByteRequest from zarr.core.array import Array from zarr.core.common import ZarrFormat -from zarr.core.sync import sync -from zarr.storage import MemoryStore, StoreLike +from zarr.storage import MemoryStore from zarr.storage._common import _dereference_path -from .dtypes import safe_unicode_for_dtype, dtypes +from .dtypes import dtypes, safe_unicode_for_dtype # Copied from Xarray _attr_keys = st.text(st.characters(), min_size=1) @@ -47,10 +46,12 @@ zarr_formats: st.SearchStrategy[ZarrFormat] = st.sampled_from([2, 3]) array_shapes = npst.array_shapes(max_dims=4, min_side=0) + def memory_stores() -> st.SearchStrategy[MemoryStore]: """Creates a Hypothesis strategy for generating MemoryStore.""" return st.builds(MemoryStore, st.just({})) + @st.composite # type: ignore[misc] def numpy_arrays( draw: st.DrawFn, @@ -72,9 +73,7 @@ def numpy_arrays( @st.composite # type: ignore[misc] def np_array_and_chunks( - draw: st.DrawFn, - *, - nparrays: st.SearchStrategy[np.ndarray] = numpy_arrays + draw: st.DrawFn, *, nparrays: st.SearchStrategy[np.ndarray] = numpy_arrays ) -> tuple[np.ndarray, tuple[int, ...]]: # type: ignore[type-arg] """A hypothesis strategy to generate small sized random arrays. @@ -85,7 +84,10 @@ def np_array_and_chunks( # 1. st.integers() shrinks towards smaller values. So we use that to generate number of chunks numchunks = draw( st.tuples( - *[st.integers(min_value=0 if size == 0 else 1, max_value=size) for size in nparray.shape] + *[ + st.integers(min_value=0 if size == 0 else 1, max_value=size) + for size in nparray.shape + ] ) ) # 2. and now generate the chunks tuple diff --git a/src/zarr/testing/strategies/dtypes.py b/src/zarr/testing/strategies/dtypes.py index ccc6da24a2..e3f1e4b05c 100644 --- a/src/zarr/testing/strategies/dtypes.py +++ b/src/zarr/testing/strategies/dtypes.py @@ -1,18 +1,9 @@ -from typing import Any - import hypothesis.extra.numpy as npst import hypothesis.strategies as st import numpy as np from hypothesis import given, settings # noqa: F401 -from hypothesis.strategies import SearchStrategy -import zarr -from zarr.abc.store import RangeByteRequest -from zarr.core.array import Array from zarr.core.common import ZarrFormat -from zarr.core.sync import sync -from zarr.storage import MemoryStore, StoreLike -from zarr.storage._common import _dereference_path def v3_dtypes() -> st.SearchStrategy[np.dtype]: @@ -60,4 +51,4 @@ def safe_unicode_for_dtype(dtype: np.dtype[np.str_]) -> st.SearchStrategy[str]: ), min_size=1, max_size=max_len, - ) \ No newline at end of file + ) diff --git a/src/zarr/testing/strategies/strategies.py b/src/zarr/testing/strategies/strategies.py deleted file mode 100644 index be66ae764c..0000000000 --- a/src/zarr/testing/strategies/strategies.py +++ /dev/null @@ -1,227 +0,0 @@ -# from typing import Any - -# import hypothesis.extra.numpy as npst -# import hypothesis.strategies as st -# import numpy as np -# from hypothesis import given, settings # noqa: F401 -# from hypothesis.strategies import SearchStrategy - -# import zarr -# from zarr.abc.store import RangeByteRequest -# from zarr.core.array import Array -# from zarr.core.common import ZarrFormat -# from zarr.core.sync import sync -# from zarr.storage import MemoryStore, StoreLike -# from zarr.storage._common import _dereference_path - -# # Copied from Xarray -# _attr_keys = st.text(st.characters(), min_size=1) -# _attr_values = st.recursive( -# st.none() | st.booleans() | st.text(st.characters(), max_size=5), -# lambda children: st.lists(children) | st.dictionaries(_attr_keys, children), -# max_leaves=3, -# ) - - -# def v3_dtypes() -> st.SearchStrategy[np.dtype]: -# return ( -# npst.boolean_dtypes() -# | npst.integer_dtypes(endianness="=") -# | npst.unsigned_integer_dtypes(endianness="=") -# | npst.floating_dtypes(endianness="=") -# | npst.complex_number_dtypes(endianness="=") -# # | npst.byte_string_dtypes(endianness="=") -# # | npst.unicode_string_dtypes() -# # | npst.datetime64_dtypes() -# # | npst.timedelta64_dtypes() -# ) - - -# def v2_dtypes() -> st.SearchStrategy[np.dtype]: -# return ( -# npst.boolean_dtypes() -# | npst.integer_dtypes(endianness="=") -# | npst.unsigned_integer_dtypes(endianness="=") -# | npst.floating_dtypes(endianness="=") -# | npst.complex_number_dtypes(endianness="=") -# | npst.byte_string_dtypes(endianness="=") -# | npst.unicode_string_dtypes(endianness="=") -# | npst.datetime64_dtypes(endianness="=") -# # | npst.timedelta64_dtypes() -# ) - - -# def safe_unicode_for_dtype(dtype: np.dtype[np.str_]) -> st.SearchStrategy[str]: -# """Generate UTF-8-safe text constrained to max_len of dtype.""" -# # account for utf-32 encoding (i.e. 4 bytes/character) -# max_len = max(1, dtype.itemsize // 4) - -# return st.text( -# alphabet=st.characters( -# blacklist_categories=["Cs"], # Avoid *technically allowed* surrogates -# min_codepoint=32, -# ), -# min_size=1, -# max_size=max_len, -# ) - - -# # From https://zarr-specs.readthedocs.io/en/latest/v3/core/v3.0.html#node-names -# # 1. must not be the empty string ("") -# # 2. must not include the character "/" -# # 3. must not be a string composed only of period characters, e.g. "." or ".." -# # 4. must not start with the reserved prefix "__" -# zarr_key_chars = st.sampled_from( -# ".-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz" -# ) -# node_names = st.text(zarr_key_chars, min_size=1).filter( -# lambda t: t not in (".", "..") and not t.startswith("__") -# ) -# array_names = node_names -# attrs = st.none() | st.dictionaries(_attr_keys, _attr_values) -# keys = st.lists(node_names, min_size=1).map("/".join) -# paths = st.just("/") | keys -# # st.builds will only call a new store constructor for different keyword arguments -# # i.e. stores.examples() will always return the same object per Store class. -# # So we map a clear to reset the store. -# stores = st.builds(MemoryStore, st.just({})).map(lambda x: sync(x.clear())) -# compressors = st.sampled_from([None, "default"]) -# zarr_formats: st.SearchStrategy[ZarrFormat] = st.sampled_from([2, 3]) -# array_shapes = npst.array_shapes(max_dims=4, min_side=0) - - -# @st.composite # type: ignore[misc] -# def numpy_arrays( -# draw: st.DrawFn, -# *, -# shapes: st.SearchStrategy[tuple[int, ...]] = array_shapes, -# zarr_formats: st.SearchStrategy[ZarrFormat] = zarr_formats, -# ) -> Any: -# """ -# Generate numpy arrays that can be saved in the provided Zarr format. -# """ -# zarr_format = draw(zarr_formats) -# dtype = draw(v3_dtypes() if zarr_format == 3 else v2_dtypes()) -# if np.issubdtype(dtype, np.str_): -# safe_unicode_strings = safe_unicode_for_dtype(dtype) -# return draw(npst.arrays(dtype=dtype, shape=shapes, elements=safe_unicode_strings)) - -# return draw(npst.arrays(dtype=dtype, shape=shapes)) - - -# @st.composite # type: ignore[misc] -# def np_array_and_chunks( -# draw: st.DrawFn, *, arrays: st.SearchStrategy[np.ndarray] = numpy_arrays -# ) -> tuple[np.ndarray, tuple[int, ...]]: # type: ignore[type-arg] -# """A hypothesis strategy to generate small sized random arrays. - -# Returns: a tuple of the array and a suitable random chunking for it. -# """ -# array = draw(arrays) -# # We want this strategy to shrink towards arrays with smaller number of chunks -# # 1. st.integers() shrinks towards smaller values. So we use that to generate number of chunks -# numchunks = draw( -# st.tuples( -# *[st.integers(min_value=0 if size == 0 else 1, max_value=size) for size in array.shape] -# ) -# ) -# # 2. and now generate the chunks tuple -# chunks = tuple( -# size // nchunks if nchunks > 0 else 0 -# for size, nchunks in zip(array.shape, numchunks, strict=True) -# ) -# return (array, chunks) - - -# @st.composite # type: ignore[misc] -# def arrays( -# draw: st.DrawFn, -# *, -# shapes: st.SearchStrategy[tuple[int, ...]] = array_shapes, -# compressors: st.SearchStrategy = compressors, -# stores: st.SearchStrategy[StoreLike] = stores, -# paths: st.SearchStrategy[str | None] = paths, -# array_names: st.SearchStrategy = array_names, -# arrays: st.SearchStrategy | None = None, -# attrs: st.SearchStrategy = attrs, -# zarr_formats: st.SearchStrategy = zarr_formats, -# ) -> Array: -# store = draw(stores) -# path = draw(paths) -# name = draw(array_names) -# attributes = draw(attrs) -# zarr_format = draw(zarr_formats) -# if arrays is None: -# arrays = numpy_arrays(shapes=shapes, zarr_formats=st.just(zarr_format)) -# nparray, chunks = draw(np_array_and_chunks(arrays=arrays)) -# # test that None works too. -# fill_value = draw(st.one_of([st.none(), npst.from_dtype(nparray.dtype)])) -# # compressor = draw(compressors) - -# expected_attrs = {} if attributes is None else attributes - -# array_path = _dereference_path(path, name) -# root = zarr.open_group(store, mode="w", zarr_format=zarr_format) - -# a = root.create_array( -# array_path, -# shape=nparray.shape, -# chunks=chunks, -# dtype=nparray.dtype, -# attributes=attributes, -# # compressor=compressor, # FIXME -# fill_value=fill_value, -# ) - -# assert isinstance(a, Array) -# if a.metadata.zarr_format == 3: -# assert a.fill_value is not None -# assert a.name is not None -# assert isinstance(root[array_path], Array) -# assert nparray.shape == a.shape -# assert chunks == a.chunks -# assert array_path == a.path, (path, name, array_path, a.name, a.path) -# assert a.basename == name, (a.basename, name) -# assert dict(a.attrs) == expected_attrs - -# a[:] = nparray - -# return a - - -# def is_negative_slice(idx: Any) -> bool: -# return isinstance(idx, slice) and idx.step is not None and idx.step < 0 - - -# @st.composite # type: ignore[misc] -# def basic_indices(draw: st.DrawFn, *, shape: tuple[int], **kwargs: Any) -> Any: -# """Basic indices without unsupported negative slices.""" -# return draw( -# npst.basic_indices(shape=shape, **kwargs).filter( -# lambda idxr: ( -# not ( -# is_negative_slice(idxr) -# or (isinstance(idxr, tuple) and any(is_negative_slice(idx) for idx in idxr)) -# ) -# ) -# ) -# ) - - -# def key_ranges( -# keys: SearchStrategy = node_names, max_size: int | None = None -# ) -> SearchStrategy[list[int]]: -# """ -# Function to generate key_ranges strategy for get_partial_values() -# returns list strategy w/ form:: - -# [(key, (range_start, range_end)), -# (key, (range_start, range_end)),...] -# """ -# byte_ranges = st.builds( -# RangeByteRequest, -# start=st.integers(min_value=0, max_value=max_size), -# end=st.integers(min_value=0, max_value=max_size), -# ) -# key_tuple = st.tuples(keys, byte_ranges) -# return st.lists(key_tuple, min_size=1, max_size=10) diff --git a/tests/test_properties.py b/tests/test_properties.py index 16c4a90916..ffc43ab8ce 100644 --- a/tests/test_properties.py +++ b/tests/test_properties.py @@ -1,8 +1,5 @@ -import base64 import dataclasses import json -import numbers -from typing import cast import numpy as np import pytest @@ -15,14 +12,22 @@ from hypothesis import given from zarr.core.buffer import default_buffer_prototype -from zarr.core.common import ZARRAY_JSON, ZATTRS_JSON, parse_shapelike -from zarr.core.metadata.v2 import ArrayV2Metadata, parse_fill_value, parse_dtype, parse_shapelike -from zarr.testing.strategies import zarr_arrays, basic_indices, numpy_arrays, zarr_formats, array_metadata_v2 +from zarr.core.common import ZARRAY_JSON, ZATTRS_JSON +from zarr.core.metadata.v2 import ArrayV2Metadata +from zarr.testing.strategies import ( + array_metadata_v2, + basic_indices, + numpy_arrays, + zarr_arrays, + zarr_formats, +) def deep_equal(a, b): """Deep equality check w/ NaN e to handle array metadata serialization and deserialization behaviors""" - if isinstance(a, (complex, np.complexfloating)) and isinstance(b, (complex, np.complexfloating)): + if isinstance(a, (complex, np.complexfloating)) and isinstance( + b, (complex, np.complexfloating) + ): # Convert to Python float to force standard NaN handling. a_real, a_imag = float(a.real), float(a.imag) b_real, b_imag = float(b.real), float(b.imag) @@ -36,7 +41,7 @@ def deep_equal(a, b): else: imag_eq = a_imag == b_imag return real_eq and imag_eq - + # Handle floats (including numpy floating types) and treat NaNs as equal. if isinstance(a, (float, np.floating)) and isinstance(b, (float, np.floating)): if np.isnan(a) and np.isnan(b): @@ -54,7 +59,7 @@ def deep_equal(a, b): if a.shape != b.shape: return False # Compare elementwise. - return all(deep_equal(x, y) for x, y in zip(a.flat, b.flat)) + return all(deep_equal(x, y) for x, y in zip(a.flat, b.flat, strict=False)) # Handle dictionaries. if isinstance(a, dict) and isinstance(b, dict): @@ -66,7 +71,7 @@ def deep_equal(a, b): if isinstance(a, (list, tuple)) and isinstance(b, (list, tuple)): if len(a) != len(b): return False - return all(deep_equal(x, y) for x, y in zip(a, b)) + return all(deep_equal(x, y) for x, y in zip(a, b, strict=False)) # Fallback to default equality. return a == b @@ -142,7 +147,7 @@ def test_v2meta_roundtrip(metadata): zarray_dict["attributes"] = zattrs_dict metadata_roundtripped = ArrayV2Metadata.from_dict(zarray_dict) - + # Convert both metadata instances to dictionaries. orig = dataclasses.asdict(metadata) rt = dataclasses.asdict(metadata_roundtripped) @@ -170,4 +175,4 @@ def test_v2meta_nan_and_infinity(fill_value): elif np.isinf(fill_value): assert zarray_dict["fill_value"] == "-Infinity" else: - assert zarray_dict["fill_value"] == fill_value \ No newline at end of file + assert zarray_dict["fill_value"] == fill_value