170170 from zarr .abc .store import Store
171171 from zarr .codecs .sharding import IndexLocation , ShardingCodec
172172 from zarr .core .buffer import Buffer
173+ from zarr .core .context import Context
173174 from zarr .core .dtype .wrapper import TBaseDType , TBaseScalar
174175 from zarr .storage import StoreLike
175176 from zarr .types import AnyArray , AnyAsyncArray , ArrayV2 , ArrayV3 , AsyncArrayV2 , AsyncArrayV3
@@ -215,9 +216,13 @@ def _chunk_sizes_from_shape(
215216 return tuple (result )
216217
217218
218- def parse_array_metadata (data : Any , path : str | None = None ) -> ArrayMetadata :
219+ def parse_array_metadata (
220+ data : Any , path : str | None = None , * , context : Context | None = None
221+ ) -> ArrayMetadata :
219222 """Array metadata from a metadata object or a metadata document, naming the array at
220- `path` in warnings about how an invalid document was read.
223+ `path` in warnings about how an invalid document was read. With a `context`, a
224+ document's extension names resolve through it instead of the default registries;
225+ a metadata object is already resolved and passes through any context.
221226
222227 `ArrayV2Metadata` accepts a chunk size of 0, as it always has, though only an
223228 invalid document holds one: such metadata is read as the documents it would store
@@ -230,7 +235,7 @@ def parse_array_metadata(data: Any, path: str | None = None) -> ArrayMetadata:
230235 if isinstance (data , dict ):
231236 zarr_format = data .get ("zarr_format" )
232237 if zarr_format == 3 :
233- meta_out = ArrayV3Metadata .from_dict (data , path = path )
238+ meta_out = ArrayV3Metadata .from_dict (data , path = path , context = context )
234239 if len (meta_out .storage_transformers ) > 0 :
235240 msg = (
236241 f"Array metadata contains storage transformers: { meta_out .storage_transformers } ."
@@ -239,16 +244,19 @@ def parse_array_metadata(data: Any, path: str | None = None) -> ArrayMetadata:
239244 raise ValueError (msg )
240245 return meta_out
241246 elif zarr_format == 2 :
242- return ArrayV2Metadata .from_dict (data , path = path )
247+ return ArrayV2Metadata .from_dict (data , path = path , context = context )
243248 else :
244249 raise ValueError (f"Invalid zarr_format: { zarr_format } . Expected 2 or 3" )
245250 raise TypeError # pragma: no cover
246251
247252
248- def create_codec_pipeline (metadata : ArrayMetadata , * , store : Store | None = None ) -> CodecPipeline :
253+ def create_codec_pipeline (
254+ metadata : ArrayMetadata , * , store : Store | None = None , context : Context | None = None
255+ ) -> CodecPipeline :
256+ pipeline_class = get_pipeline_class () if context is None else context .pipeline_class ()
249257 if store is not None :
250258 try :
251- return get_pipeline_class () .from_array_metadata_and_store (
259+ return pipeline_class .from_array_metadata_and_store (
252260 array_metadata = metadata , store = store
253261 )
254262 except NotImplementedError :
@@ -261,7 +269,7 @@ def create_codec_pipeline(metadata: ArrayMetadata, *, store: Store | None = None
261269 # "disables partial reads" warning) fire here; `evolve_from_array_spec`
262270 # re-splits the same already-warned-about chain via
263271 # `codecs_from_list_unchecked`, so it does not re-emit them.
264- pipeline = get_pipeline_class () .from_codecs (metadata .codecs )
272+ pipeline = pipeline_class .from_codecs (metadata .codecs )
265273
266274 # Use the regular chunk shape if available, otherwise use a
267275 # placeholder. The ChunkTransform is shape-agnostic — the actual
@@ -280,7 +288,7 @@ def create_codec_pipeline(metadata: ArrayMetadata, *, store: Store | None = None
280288 return pipeline .evolve_from_array_spec (chunk_spec )
281289 elif isinstance (metadata , ArrayV2Metadata ):
282290 v2_codec = V2Codec (filters = metadata .filters , compressor = metadata .compressor )
283- pipeline = get_pipeline_class () .from_codecs ([v2_codec ])
291+ pipeline = pipeline_class .from_codecs ([v2_codec ])
284292 chunk_spec = ArraySpec (
285293 shape = metadata .chunks ,
286294 dtype = metadata .dtype ,
@@ -403,6 +411,7 @@ def __init__(
403411 metadata : ArrayV2Metadata | ArrayV2MetadataDict ,
404412 store_path : StorePath ,
405413 config : ArrayConfigLike | None = None ,
414+ context : Context | None = None ,
406415 ) -> None : ...
407416
408417 @overload
@@ -411,15 +420,17 @@ def __init__(
411420 metadata : ArrayV3Metadata | ArrayMetadataJSON_V3 ,
412421 store_path : StorePath ,
413422 config : ArrayConfigLike | None = None ,
423+ context : Context | None = None ,
414424 ) -> None : ...
415425
416426 def __init__ (
417427 self ,
418428 metadata : ArrayMetadata | ArrayMetadataDict ,
419429 store_path : StorePath ,
420430 config : ArrayConfigLike | None = None ,
431+ context : Context | None = None ,
421432 ) -> None :
422- object .__setattr__ (self , "_io" , AsyncArrayIO (metadata , store_path , config ))
433+ object .__setattr__ (self , "_io" , AsyncArrayIO (metadata , store_path , config , context ))
423434
424435 @classmethod
425436 def _from_io (cls , io : AsyncArrayIO ) -> AnyAsyncArray :
@@ -4599,21 +4610,25 @@ def build_array_metadata(
45994610 attributes : dict [str , JSON ] | None = None ,
46004611 chunk_key_encoding : ChunkKeyEncodingLike | None = None ,
46014612 dimension_names : DimensionNamesLike = None ,
4613+ context : Context | None = None ,
46024614) -> ArrayV2Metadata | ArrayV3Metadata :
46034615 """Build an array metadata document from creation-style keyword arguments.
46044616
46054617 This is the pure half of `init_array`: it normalizes the same keyword
46064618 arguments into an in-memory metadata object without touching any store.
46074619 The parameters have the same meaning as in
4608- [`zarr.api.asynchronous.create_array`][].
4620+ [`zarr.api.asynchronous.create_array`][]. With a `context`, the data type
4621+ and Zarr format 3 codec names resolve through it instead of the default
4622+ registries; Zarr format 2 filters and compressors are numcodecs objects,
4623+ which no context governs.
46094624 """
46104625
46114626 if zarr_format is None :
46124627 zarr_format = _default_zarr_format ()
46134628
46144629 from zarr .codecs .sharding import ShardingCodec
46154630
4616- zdtype = parse_dtype (dtype , zarr_format = zarr_format )
4631+ zdtype = parse_dtype (dtype , zarr_format = zarr_format , context = context )
46174632 shape_parsed = parse_shapelike (shape )
46184633 item_size = zdtype .item_size if isinstance (zdtype , HasItemSize ) else 1
46194634 chunk_key_encoding_parsed = _parse_chunk_key_encoding (
@@ -4689,6 +4704,7 @@ def build_array_metadata(
46894704 filters = filters ,
46904705 serializer = serializer ,
46914706 dtype = zdtype ,
4707+ context = context ,
46924708 )
46934709 sub_codecs = cast ("tuple[Codec, ...]" , (* array_array , array_bytes , * bytes_bytes ))
46944710 # A stored chunk grid passed as chunks= / shards= (e.g. by the "keep"
@@ -5312,6 +5328,7 @@ def _parse_chunk_encoding_v3(
53125328 filters : FiltersLike ,
53135329 serializer : SerializerLike ,
53145330 dtype : ZDType [TBaseDType , TBaseScalar ],
5331+ context : Context | None = None ,
53155332) -> tuple [tuple [ArrayArrayCodec , ...], ArrayBytesCodec , tuple [BytesBytesCodec , ...]]:
53165333 """
53175334 Generate chunk encoding classes for v3 arrays with optional defaults.
@@ -5328,15 +5345,17 @@ def _parse_chunk_encoding_v3(
53285345 maybe_array_array = (filters ,)
53295346 else :
53305347 maybe_array_array = cast ("Iterable[Codec | dict[str, JSON]]" , filters )
5331- out_array_array = tuple (_parse_array_array_codec (c ) for c in maybe_array_array )
5348+ out_array_array = tuple (
5349+ _parse_array_array_codec (c , context = context ) for c in maybe_array_array
5350+ )
53325351
53335352 if serializer == "auto" :
53345353 out_array_bytes = default_serializer_v3 (dtype )
53355354 else :
53365355 # TODO: ensure that the serializer is compatible with the ndarray produced by the
53375356 # array-array codecs. For example, if a sequence of array-array codecs produces an
53385357 # array with a single-byte data type, then the serializer should not specify endiannesss.
5339- out_array_bytes = _parse_array_bytes_codec (serializer )
5358+ out_array_bytes = _parse_array_bytes_codec (serializer , context = context )
53405359
53415360 if compressors is None :
53425361 out_bytes_bytes : tuple [BytesBytesCodec , ...] = ()
@@ -5349,7 +5368,9 @@ def _parse_chunk_encoding_v3(
53495368 else :
53505369 maybe_bytes_bytes = cast ("Iterable[Codec | dict[str, JSON]]" , compressors )
53515370
5352- out_bytes_bytes = tuple (_parse_bytes_bytes_codec (c ) for c in maybe_bytes_bytes )
5371+ out_bytes_bytes = tuple (
5372+ _parse_bytes_bytes_codec (c , context = context ) for c in maybe_bytes_bytes
5373+ )
53535374
53545375 # TODO: ensure that the serializer is compatible with the ndarray produced by the
53555376 # array-array codecs. For example, if a sequence of array-array codecs produces an
@@ -6207,11 +6228,18 @@ class AsyncArrayIO:
62076228 opened from a stored document that needed a repair stores that repair
62086229 before its first chunk write. Call `store_metadata` first to make that
62096230 write explicit.
6231+
6232+ A `context` (see [`zarr.core.context.Context`][]) defines the data types,
6233+ codecs and codec pipeline in scope for this array: extension names in a
6234+ stored document or in `build`'s keyword arguments resolve through it, and
6235+ a name outside it fails to resolve even when the default registries know
6236+ it. Without one, the default registries are used.
62106237 """
62116238
62126239 _metadata : ArrayMetadata
62136240 _store_path : StorePath
62146241 _config : ArrayConfig
6242+ _context : Context | None
62156243 _codec_pipeline : CodecPipeline
62166244 _chunk_grid : ChunkGrid
62176245
@@ -6220,14 +6248,16 @@ def __init__(
62206248 metadata : ArrayMetadata | ArrayMetadataDict ,
62216249 store_path : StorePath ,
62226250 config : ArrayConfigLike | None = None ,
6251+ context : Context | None = None ,
62236252 ) -> None :
6224- metadata_parsed = parse_array_metadata (metadata , str (store_path ))
6253+ metadata_parsed = parse_array_metadata (metadata , str (store_path ), context = context )
62256254 self ._metadata = metadata_parsed
62266255 self ._store_path = store_path
62276256 self ._config = parse_array_config (config )
6257+ self ._context = context
62286258 self ._chunk_grid = ChunkGrid .from_metadata (metadata_parsed )
62296259 self ._codec_pipeline = create_codec_pipeline (
6230- metadata = metadata_parsed , store = store_path .store
6260+ metadata = metadata_parsed , store = store_path .store , context = context
62316261 )
62326262
62336263 @classmethod
@@ -6250,12 +6280,15 @@ async def build(
62506280 chunk_key_encoding : ChunkKeyEncodingLike | None = None ,
62516281 dimension_names : DimensionNamesLike = None ,
62526282 config : ArrayConfigLike | None = None ,
6283+ context : Context | None = None ,
62536284 ) -> AsyncArrayIO :
62546285 """Declare an array bound to a location in a store, without storing anything.
62556286
62566287 The keyword arguments have the same meaning as in
62576288 [`zarr.api.asynchronous.create_array`][]. The returned instance holds
6258- the normalized metadata in memory; `store_metadata` stores it.
6289+ the normalized metadata in memory; `store_metadata` stores it. With a
6290+ `context`, the data type and codec names resolve through it instead of
6291+ the default registries.
62596292 """
62606293 metadata = build_array_metadata (
62616294 shape = shape ,
@@ -6271,9 +6304,10 @@ async def build(
62716304 attributes = attributes ,
62726305 chunk_key_encoding = chunk_key_encoding ,
62736306 dimension_names = dimension_names ,
6307+ context = context ,
62746308 )
62756309 store_path = await make_store_path (store , path = name )
6276- return cls (metadata , store_path , config )
6310+ return cls (metadata , store_path , config , context = context )
62776311
62786312 @classmethod
62796313 async def open (
@@ -6283,19 +6317,24 @@ async def open(
62836317 name : str | None = None ,
62846318 zarr_format : ZarrFormat | None = None ,
62856319 config : ArrayConfigLike | None = None ,
6320+ context : Context | None = None ,
62866321 ) -> AsyncArrayIO :
62876322 """Read the metadata of an existing array and bind to it.
62886323
62896324 This is the one classmethod constructor that performs IO: the array's
62906325 stored metadata document is read from the store. `zarr_format=None`
62916326 (the default) reads Zarr format 3 and falls back to format 2, as
6292- `open` does in `zarrs`; pass 2 or 3 to require one format.
6327+ `open` does in `zarrs`; pass 2 or 3 to require one format. With a
6328+ `context`, the document's data type and codec names resolve through it
6329+ instead of the default registries.
62936330 """
62946331 store_path = await make_store_path (store , path = name )
62956332 metadata = parse_array_metadata (
6296- await get_array_metadata (store_path , zarr_format = zarr_format ), str (store_path )
6333+ await get_array_metadata (store_path , zarr_format = zarr_format ),
6334+ str (store_path ),
6335+ context = context ,
62976336 )
6298- return cls (metadata , store_path , config )
6337+ return cls (metadata , store_path , config , context = context )
62996338
63006339 @property
63016340 def metadata (self ) -> ArrayMetadata :
@@ -6350,7 +6389,9 @@ async def verify(self) -> None:
63506389 """
63516390 bound = self ._metadata
63526391 stored = parse_array_metadata (
6353- await get_array_metadata (self ._store_path , zarr_format = None ), str (self ._store_path )
6392+ await get_array_metadata (self ._store_path , zarr_format = None ),
6393+ str (self ._store_path ),
6394+ context = self ._context ,
63546395 )
63556396 if stored == bound :
63566397 return
@@ -6515,8 +6556,9 @@ def __init__(
65156556 metadata : ArrayMetadata | ArrayMetadataDict ,
65166557 store_path : StorePath ,
65176558 config : ArrayConfigLike | None = None ,
6559+ context : Context | None = None ,
65186560 ) -> None :
6519- self ._async_io = AsyncArrayIO (metadata , store_path , config )
6561+ self ._async_io = AsyncArrayIO (metadata , store_path , config , context )
65206562
65216563 @classmethod
65226564 def _wrap (cls , async_io : AsyncArrayIO ) -> ArrayIO :
@@ -6544,6 +6586,7 @@ def build(
65446586 chunk_key_encoding : ChunkKeyEncodingLike | None = None ,
65456587 dimension_names : DimensionNamesLike = None ,
65466588 config : ArrayConfigLike | None = None ,
6589+ context : Context | None = None ,
65476590 ) -> ArrayIO :
65486591 """Declare an array bound to a location in a store, without storing anything.
65496592
@@ -6568,6 +6611,7 @@ def build(
65686611 chunk_key_encoding = chunk_key_encoding ,
65696612 dimension_names = dimension_names ,
65706613 config = config ,
6614+ context = context ,
65716615 )
65726616 )
65736617 )
@@ -6580,13 +6624,18 @@ def open(
65806624 name : str | None = None ,
65816625 zarr_format : ZarrFormat | None = None ,
65826626 config : ArrayConfigLike | None = None ,
6627+ context : Context | None = None ,
65836628 ) -> ArrayIO :
65846629 """Read the metadata of an existing array and bind to it.
65856630
65866631 See `AsyncArrayIO.open`.
65876632 """
65886633 return cls ._wrap (
6589- sync (AsyncArrayIO .open (store , name = name , zarr_format = zarr_format , config = config ))
6634+ sync (
6635+ AsyncArrayIO .open (
6636+ store , name = name , zarr_format = zarr_format , config = config , context = context
6637+ )
6638+ )
65906639 )
65916640
65926641 @property
0 commit comments