Skip to content

Commit 31f88cf

Browse files
authored
Merge pull request #138 from d-v-b/fix/deserialize-in-one-place
fix premature model construction
2 parents 157dacd + 8f7ac6d commit 31f88cf

File tree

6 files changed

+102
-15
lines changed

6 files changed

+102
-15
lines changed

docs/experimental/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ array = ArraySpec(
8484
chunk_grid={"name": "regular", "configuration": {"chunk_shape": (1,)}},
8585
chunk_key_encoding = {"name": "default"},
8686
fill_value = 0,
87+
attributes={}
8788
)
8889

8990
class MyMembers(TypedDict):

src/pydantic_zarr/experimental/v2.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -695,7 +695,7 @@ def from_zarr(cls, group: zarr.Group, *, depth: int = -1) -> Self:
695695

696696
result: GroupSpec
697697
attributes = group.attrs.asdict()
698-
members: dict[str, object] = {}
698+
members: dict[str, dict[str, object]] = {}
699699

700700
if depth < -1:
701701
msg = (

src/pydantic_zarr/experimental/v3.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
import numpy as np
2121
import numpy.typing as npt
2222
from packaging.version import Version
23-
from pydantic import BeforeValidator, Field, field_validator, model_validator
23+
from pydantic import BeforeValidator, field_validator, model_validator
2424
from typing_extensions import TypedDict
2525

2626
from pydantic_zarr.experimental.core import (
@@ -189,7 +189,7 @@ class ArraySpec(NodeSpec):
189189
"""
190190

191191
node_type: Literal["array"] = "array"
192-
attributes: BaseAttributes = Field(default_factory=dict) # type: ignore[arg-type]
192+
attributes: BaseAttributes
193193
shape: tuple[int, ...]
194194
data_type: DTypeLike
195195
chunk_grid: RegularChunking # todo: validate this against shape
@@ -799,7 +799,7 @@ def from_zarr(cls, group: zarr.Group, *, depth: int = -1) -> Self:
799799

800800
result: GroupSpec
801801
attributes = group.attrs.asdict()
802-
members: dict[str, ArraySpec | GroupSpec] = {}
802+
members: dict[str, dict[str, object]] = {}
803803

804804
if depth < -1:
805805
msg = (
@@ -812,9 +812,9 @@ def from_zarr(cls, group: zarr.Group, *, depth: int = -1) -> Self:
812812
new_depth = max(depth - 1, -1)
813813
for name, item in group.members():
814814
if isinstance(item, zarr.Array):
815-
members[name] = ArraySpec.from_zarr(item)
815+
members[name] = ArraySpec.from_zarr(item).model_dump()
816816
elif isinstance(item, zarr.Group):
817-
members[name] = GroupSpec.from_zarr(item, depth=new_depth)
817+
members[name] = GroupSpec.from_zarr(item, depth=new_depth).model_dump()
818818
else:
819819
msg = ( # type: ignore[unreachable]
820820
f"Unparsable object encountered: {type(item)}. Expected zarr.Array"

tests/test_pydantic_zarr/conftest.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@
22

33
import warnings
44
from dataclasses import dataclass
5-
from importlib.metadata import PackageNotFoundError, version
5+
from importlib.metadata import version
6+
from importlib.util import find_spec
67

78
from packaging.version import Version
89

9-
try:
10+
ZARR_AVAILABLE = find_spec("zarr") is not None
11+
12+
if ZARR_AVAILABLE:
1013
ZARR_PYTHON_VERSION = Version(version("zarr"))
11-
except PackageNotFoundError:
14+
else:
1215
ZARR_PYTHON_VERSION = Version("0.0.0")
1316

1417
DTYPE_EXAMPLES_V2: tuple[DTypeExample, ...]

tests/test_pydantic_zarr/test_experimental/test_v2.py

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from pydantic_zarr.core import tuplify_json
2020
from pydantic_zarr.experimental.core import json_eq
2121

22-
from ..conftest import DTYPE_EXAMPLES_V2, ZARR_PYTHON_VERSION, DTypeExample
22+
from ..conftest import DTYPE_EXAMPLES_V2, ZARR_AVAILABLE, ZARR_PYTHON_VERSION, DTypeExample
2323

2424
if TYPE_CHECKING:
2525
from numcodecs.abc import Codec
@@ -598,6 +598,48 @@ def test_mix_v3_v2_fails() -> None:
598598
GroupSpec.from_flat(members_flat) # type: ignore[arg-type]
599599

600600

601+
@pytest.mark.skipif(not ZARR_AVAILABLE, reason="zarr-python is not installed")
602+
def test_typed_members() -> None:
603+
"""
604+
Test GroupSpec creation with typed members
605+
"""
606+
array1d = ArraySpec(
607+
shape=(1,),
608+
dtype="uint8",
609+
chunks=(1,),
610+
fill_value=0,
611+
compressor=None,
612+
attributes={},
613+
)
614+
615+
class DatasetMembers(TypedDict):
616+
x: ArraySpec
617+
y: ArraySpec
618+
619+
class DatasetGroup(GroupSpec):
620+
members: DatasetMembers
621+
622+
class ExpectedMembers(TypedDict):
623+
r10m: DatasetGroup
624+
r20m: DatasetGroup
625+
626+
class ExpectedGroup(GroupSpec):
627+
members: ExpectedMembers
628+
629+
flat = {
630+
"": BaseGroupSpec(attributes={}),
631+
"/r10m": BaseGroupSpec(attributes={}),
632+
"/r20m": BaseGroupSpec(attributes={}),
633+
"/r10m/x": array1d,
634+
"/r10m/y": array1d,
635+
"/r20m/x": array1d,
636+
"/r20m/y": array1d,
637+
}
638+
639+
zg = GroupSpec.from_flat(flat).to_zarr({}, path="")
640+
ExpectedGroup.from_zarr(zg)
641+
642+
601643
def test_arrayspec_with_methods() -> None:
602644
"""
603645
Test that ArraySpec with_* methods create new validated copies

tests/test_pydantic_zarr/test_experimental/test_v3.py

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
from __future__ import annotations
22

3-
import importlib
4-
import importlib.util
53
import json
64
import re
75
from dataclasses import asdict
86

97
import numpy as np
108
import pytest
119
from pydantic import ValidationError
10+
from typing_extensions import TypedDict
1211

1312
from pydantic_zarr.experimental.core import json_eq
1413
from pydantic_zarr.experimental.v3 import (
@@ -23,9 +22,7 @@
2322
auto_codecs,
2423
)
2524

26-
from ..conftest import DTYPE_EXAMPLES_V3, DTypeExample
27-
28-
ZARR_AVAILABLE = importlib.util.find_spec("zarr") is not None
25+
from ..conftest import DTYPE_EXAMPLES_V3, ZARR_AVAILABLE, DTypeExample
2926

3027

3128
@pytest.mark.parametrize("invalid_dimension_names", [[], "hi", ["1", 2, None]], ids=str)
@@ -304,6 +301,50 @@ def test_dim_names_from_zarr_array(
304301
assert spec.dimension_names == expected_names
305302

306303

304+
@pytest.mark.skipif(not ZARR_AVAILABLE, reason="zarr-python is not installed")
305+
def test_typed_members() -> None:
306+
"""
307+
Test GroupSpec creation with typed members
308+
"""
309+
310+
array1d = ArraySpec(
311+
shape=(1,),
312+
data_type="uint8",
313+
chunk_grid={"name": "regular", "configuration": {"chunk_shape": (1,)}},
314+
chunk_key_encoding={"name": "default", "configuration": {"separator": "/"}},
315+
fill_value=0,
316+
codecs=({"name": "bytes"},),
317+
attributes={},
318+
)
319+
320+
class DatasetMembers(TypedDict):
321+
x: ArraySpec
322+
y: ArraySpec
323+
324+
class DatasetGroup(GroupSpec):
325+
members: DatasetMembers
326+
327+
class ExpectedMembers(TypedDict):
328+
r10m: DatasetGroup
329+
r20m: DatasetGroup
330+
331+
class ExpectedGroup(GroupSpec):
332+
members: ExpectedMembers
333+
334+
flat = {
335+
"": BaseGroupSpec(attributes={}),
336+
"/r10m": BaseGroupSpec(attributes={}),
337+
"/r20m": BaseGroupSpec(attributes={}),
338+
"/r10m/x": array1d,
339+
"/r10m/y": array1d,
340+
"/r20m/x": array1d,
341+
"/r20m/y": array1d,
342+
}
343+
344+
zg = GroupSpec.from_flat(flat).to_zarr({}, path="")
345+
ExpectedGroup.from_zarr(zg)
346+
347+
307348
def test_arrayspec_with_methods() -> None:
308349
"""
309350
Test that ArraySpec with_* methods create new validated copies

0 commit comments

Comments
 (0)