@@ -675,13 +675,30 @@ async def _create(
675675
676676 if chunks is not None and chunk_shape is not None :
677677 raise ValueError ("Only one of chunk_shape or chunks can be provided." )
678- item_size = 1
679- if isinstance (dtype_parsed , HasItemSize ):
680- item_size = dtype_parsed .item_size
681- if chunks :
682- _chunks = normalize_chunks (chunks , shape , item_size )
678+
679+ # detect rectilinear (dask-style) chunks before normalize_chunks flattens them
680+ from zarr .core .chunk_grids import _is_rectilinear_chunks
681+
682+ _raw_chunks = chunks if chunks is not None else chunk_shape
683+ _rectilinear_chunk_grid : ChunkGridMetadata | None = None
684+ _chunks : tuple [int , ...] | None = None
685+ if _is_rectilinear_chunks (_raw_chunks ):
686+ from zarr .core .metadata .v3 import (
687+ RectilinearChunkGrid as RectilinearChunkGridMeta ,
688+ )
689+
690+ _rectilinear_chunk_grid = RectilinearChunkGridMeta (
691+ chunk_shapes = tuple (tuple (c ) for c in _raw_chunks )
692+ )
683693 else :
684- _chunks = normalize_chunks (chunk_shape , shape , item_size )
694+ item_size = 1
695+ if isinstance (dtype_parsed , HasItemSize ):
696+ item_size = dtype_parsed .item_size
697+ if chunks :
698+ _chunks = normalize_chunks (chunks , shape , item_size )
699+ else :
700+ _chunks = normalize_chunks (chunk_shape , shape , item_size )
701+
685702 config_parsed = parse_array_config (config )
686703
687704 result : AnyAsyncArray
@@ -714,6 +731,7 @@ async def _create(
714731 attributes = attributes ,
715732 overwrite = overwrite ,
716733 config = config_parsed ,
734+ chunk_grid = _rectilinear_chunk_grid ,
717735 )
718736 elif zarr_format == 2 :
719737 if codecs is not None :
@@ -726,6 +744,9 @@ async def _create(
726744 )
727745 if dimension_names is not None :
728746 raise ValueError ("dimension_names cannot be used for arrays with zarr_format 2." )
747+ if _rectilinear_chunk_grid is not None :
748+ raise ValueError ("Zarr format 2 does not support rectilinear chunk grids." )
749+ assert _chunks is not None
729750
730751 if order is None :
731752 order_parsed = config_parsed .order
@@ -760,7 +781,7 @@ async def _create(
760781 def _create_metadata_v3 (
761782 shape : ShapeLike ,
762783 dtype : ZDType [TBaseDType , TBaseScalar ],
763- chunk_shape : tuple [int , ...],
784+ chunk_shape : tuple [int , ...] | None = None ,
764785 fill_value : Any | None = DEFAULT_FILL_VALUE ,
765786 chunk_key_encoding : ChunkKeyEncodingLike | None = None ,
766787 codecs : Iterable [Codec | dict [str , JSON ]] | None = None ,
@@ -771,8 +792,12 @@ def _create_metadata_v3(
771792 """
772793 Create an instance of ArrayV3Metadata.
773794
774- If `chunk_grid` is provided, it takes precedence over `chunk_shape` .
795+ Exactly one of ``chunk_shape`` or ``chunk_grid`` must be provided .
775796 """
797+ if chunk_shape is not None and chunk_grid is not None :
798+ raise ValueError ("Only one of chunk_shape or chunk_grid can be provided." )
799+ if chunk_shape is None and chunk_grid is None :
800+ raise ValueError ("One of chunk_shape or chunk_grid must be provided." )
776801 filters : tuple [ArrayArrayCodec , ...]
777802 compressors : tuple [BytesBytesCodec , ...]
778803
@@ -799,11 +824,12 @@ def _create_metadata_v3(
799824 else :
800825 fill_value_parsed = fill_value
801826
802- chunk_grid_meta = (
803- chunk_grid
804- if chunk_grid is not None
805- else RegularChunkGrid (chunk_shape = parse_shapelike (chunk_shape ))
806- )
827+ chunk_grid_meta : ChunkGridMetadata
828+ if chunk_grid is not None :
829+ chunk_grid_meta = chunk_grid
830+ else :
831+ assert chunk_shape is not None # validated above
832+ chunk_grid_meta = RegularChunkGrid (chunk_shape = parse_shapelike (chunk_shape ))
807833 return ArrayV3Metadata (
808834 shape = shape ,
809835 data_type = dtype ,
@@ -822,7 +848,7 @@ async def _create_v3(
822848 * ,
823849 shape : ShapeLike ,
824850 dtype : ZDType [TBaseDType , TBaseScalar ],
825- chunk_shape : tuple [int , ...],
851+ chunk_shape : tuple [int , ...] | None = None ,
826852 config : ArrayConfig ,
827853 fill_value : Any | None = DEFAULT_FILL_VALUE ,
828854 chunk_key_encoding : (
@@ -835,7 +861,12 @@ async def _create_v3(
835861 dimension_names : DimensionNamesLike = None ,
836862 attributes : dict [str , JSON ] | None = None ,
837863 overwrite : bool = False ,
864+ chunk_grid : ChunkGridMetadata | None = None ,
838865 ) -> AsyncArrayV3 :
866+ if chunk_shape is not None and chunk_grid is not None :
867+ raise ValueError ("Only one of chunk_shape or chunk_grid can be provided." )
868+ if chunk_shape is None and chunk_grid is None :
869+ raise ValueError ("One of chunk_shape or chunk_grid must be provided." )
839870 if overwrite :
840871 if store_path .store .supports_deletes :
841872 await store_path .delete_dir ()
@@ -860,6 +891,7 @@ async def _create_v3(
860891 codecs = codecs ,
861892 dimension_names = dimension_names ,
862893 attributes = attributes ,
894+ chunk_grid = chunk_grid ,
863895 )
864896
865897 array = cls (metadata = metadata , store_path = store_path , config = config )
@@ -4902,7 +4934,7 @@ async def init_array(
49024934 shape = shape_parsed ,
49034935 dtype = zdtype ,
49044936 fill_value = fill_value ,
4905- chunk_shape = chunks_out ,
4937+ chunk_shape = chunks_out if rectilinear_meta is None else None ,
49064938 chunk_key_encoding = chunk_key_encoding_parsed ,
49074939 codecs = codecs_out ,
49084940 dimension_names = dimension_names ,
0 commit comments