@@ -69,49 +69,55 @@ def fill_value_or_default(chunk_spec: ArraySpec) -> Any:
6969 return fill_value
7070
7171
72- @dataclass (frozen = True , slots = True )
73- class CodecChain :
74- """Codec chain with pre-resolved metadata specs .
72+ @dataclass (slots = True , kw_only = True )
73+ class ChunkTransform :
74+ """A stored chunk, modeled as a layered array .
7575
76- Constructed from an iterable of codecs and a chunk ArraySpec.
77- Resolves each codec against the spec so that encode/decode can
78- run without re-resolving.
76+ Each layer corresponds to one ArrayArrayCodec and the ArraySpec
77+ at its input boundary. ``layers[0]`` is the outermost (user-visible)
78+ transform; after the last layer comes the ArrayBytesCodec.
79+
80+ The chunk's ``shape`` and ``dtype`` reflect the representation
81+ **after** all ArrayArrayCodec layers have been applied — i.e. the
82+ spec that feeds the ArrayBytesCodec.
7983 """
8084
8185 codecs : tuple [Codec , ...]
82- chunk_spec : ArraySpec
86+ array_spec : ArraySpec
8387
84- _aa_codecs : tuple [ArrayArrayCodec , ...] = field (init = False , repr = False , compare = False )
85- _aa_specs : tuple [ArraySpec , ...] = field (init = False , repr = False , compare = False )
88+ # Each element is (ArrayArrayCodec, input_spec_for_that_codec).
89+ layers : tuple [tuple [ArrayArrayCodec , ArraySpec ], ...] = field (
90+ init = False , repr = False , compare = False
91+ )
8692 _ab_codec : ArrayBytesCodec = field (init = False , repr = False , compare = False )
8793 _ab_spec : ArraySpec = field (init = False , repr = False , compare = False )
8894 _bb_codecs : tuple [BytesBytesCodec , ...] = field (init = False , repr = False , compare = False )
89- _bb_spec : ArraySpec = field (init = False , repr = False , compare = False )
9095 _all_sync : bool = field (init = False , repr = False , compare = False )
9196
9297 def __post_init__ (self ) -> None :
9398 aa , ab , bb = codecs_from_list (list (self .codecs ))
9499
95- aa_specs : list [ ArraySpec ] = []
96- spec = self .chunk_spec
100+ layers : tuple [ tuple [ ArrayArrayCodec , ArraySpec ], ...] = ()
101+ spec = self .array_spec
97102 for aa_codec in aa :
98- aa_specs . append ( spec )
103+ layers = ( * layers , ( aa_codec , spec ) )
99104 spec = aa_codec .resolve_metadata (spec )
100105
101- object .__setattr__ (self , "_aa_codecs" , aa )
102- object .__setattr__ (self , "_aa_specs" , tuple (aa_specs ))
103- object .__setattr__ (self , "_ab_codec" , ab )
104- object .__setattr__ (self , "_ab_spec" , spec )
106+ self .layers = layers
107+ self ._ab_codec = ab
108+ self ._ab_spec = spec
109+ self ._bb_codecs = bb
110+ self ._all_sync = all (isinstance (c , SupportsSyncCodec ) for c in self .codecs )
105111
106- spec = ab .resolve_metadata (spec )
107- object .__setattr__ (self , "_bb_codecs" , bb )
108- object .__setattr__ (self , "_bb_spec" , spec )
112+ @property
113+ def shape (self ) -> tuple [int , ...]:
114+ """Shape after all ArrayArrayCodec layers (input to the ArrayBytesCodec)."""
115+ return self ._ab_spec .shape
109116
110- object .__setattr__ (
111- self ,
112- "_all_sync" ,
113- all (isinstance (c , SupportsSyncCodec ) for c in self .codecs ),
114- )
117+ @property
118+ def dtype (self ) -> ZDType [TBaseDType , TBaseScalar ]:
119+ """Dtype after all ArrayArrayCodec layers (input to the ArrayBytesCodec)."""
120+ return self ._ab_spec .dtype
115121
116122 @property
117123 def all_sync (self ) -> bool :
@@ -127,11 +133,11 @@ def decode_chunk(
127133 """
128134 bb_out : Any = chunk_bytes
129135 for bb_codec in reversed (self ._bb_codecs ):
130- bb_out = cast ("SupportsSyncCodec" , bb_codec )._decode_sync (bb_out , self ._bb_spec )
136+ bb_out = cast ("SupportsSyncCodec" , bb_codec )._decode_sync (bb_out , self ._ab_spec )
131137
132138 ab_out : Any = cast ("SupportsSyncCodec" , self ._ab_codec )._decode_sync (bb_out , self ._ab_spec )
133139
134- for aa_codec , spec in zip ( reversed (self ._aa_codecs ), reversed ( self . _aa_specs ), strict = True ):
140+ for aa_codec , spec in reversed (self .layers ):
135141 ab_out = cast ("SupportsSyncCodec" , aa_codec )._decode_sync (ab_out , spec )
136142
137143 return ab_out # type: ignore[no-any-return]
@@ -146,7 +152,7 @@ def encode_chunk(
146152 """
147153 aa_out : Any = chunk_array
148154
149- for aa_codec , spec in zip ( self ._aa_codecs , self . _aa_specs , strict = True ) :
155+ for aa_codec , spec in self .layers :
150156 if aa_out is None :
151157 return None
152158 aa_out = cast ("SupportsSyncCodec" , aa_codec )._encode_sync (aa_out , spec )
@@ -158,7 +164,7 @@ def encode_chunk(
158164 for bb_codec in self ._bb_codecs :
159165 if bb_out is None :
160166 return None
161- bb_out = cast ("SupportsSyncCodec" , bb_codec )._encode_sync (bb_out , self ._bb_spec )
167+ bb_out = cast ("SupportsSyncCodec" , bb_codec )._encode_sync (bb_out , self ._ab_spec )
162168
163169 return bb_out # type: ignore[no-any-return]
164170
@@ -369,7 +375,7 @@ async def read_batch(
369375 out [out_selection ] = fill_value_or_default (chunk_spec )
370376 else :
371377 chunk_bytes_batch = await concurrent_map (
372- [(byte_getter , array_spec .prototype ) for byte_getter , array_spec , * _ in batch_info ],
378+ [(byte_getter , chunk_spec .prototype ) for byte_getter , chunk_spec , * _ in batch_info ],
373379 lambda byte_getter , prototype : byte_getter .get (prototype ),
374380 config .get ("async.concurrency" ),
375381 )
0 commit comments