|
13 | 13 | from zarr.codecs import BytesCodec
|
14 | 14 | from zarr.codecs._v2 import V2Compressor, V2Filters
|
15 | 15 | from zarr.core.attributes import Attributes
|
16 |
| -from zarr.core.buffer import BufferPrototype, NDArrayLike, NDBuffer, default_buffer_prototype |
| 16 | +from zarr.core.buffer import ( |
| 17 | + BufferPrototype, |
| 18 | + NDArrayLike, |
| 19 | + NDBuffer, |
| 20 | + default_buffer_prototype, |
| 21 | +) |
17 | 22 | from zarr.core.chunk_grids import RegularChunkGrid, _guess_chunks
|
18 | 23 | from zarr.core.chunk_key_encodings import (
|
19 | 24 | ChunkKeyEncoding,
|
|
71 | 76 | from collections.abc import Iterable, Iterator, Sequence
|
72 | 77 |
|
73 | 78 | from zarr.abc.codec import Codec, CodecPipeline
|
| 79 | + from zarr.core.group import AsyncGroup |
74 | 80 | from zarr.core.metadata.common import ArrayMetadata
|
75 | 81 |
|
76 | 82 | # Array and AsyncArray are defined in the base ``zarr`` namespace
|
@@ -337,7 +343,7 @@ async def _create_v3(
|
337 | 343 | )
|
338 | 344 |
|
339 | 345 | array = cls(metadata=metadata, store_path=store_path)
|
340 |
| - await array._save_metadata(metadata) |
| 346 | + await array._save_metadata(metadata, ensure_parents=True) |
341 | 347 | return array
|
342 | 348 |
|
343 | 349 | @classmethod
|
@@ -376,7 +382,7 @@ async def _create_v2(
|
376 | 382 | attributes=attributes,
|
377 | 383 | )
|
378 | 384 | array = cls(metadata=metadata, store_path=store_path)
|
379 |
| - await array._save_metadata(metadata) |
| 385 | + await array._save_metadata(metadata, ensure_parents=True) |
380 | 386 | return array
|
381 | 387 |
|
382 | 388 | @classmethod
|
@@ -621,9 +627,24 @@ async def getitem(
|
621 | 627 | )
|
622 | 628 | return await self._get_selection(indexer, prototype=prototype)
|
623 | 629 |
|
624 |
| - async def _save_metadata(self, metadata: ArrayMetadata) -> None: |
| 630 | + async def _save_metadata(self, metadata: ArrayMetadata, ensure_parents: bool = False) -> None: |
625 | 631 | to_save = metadata.to_buffer_dict(default_buffer_prototype())
|
626 | 632 | awaitables = [set_or_delete(self.store_path / key, value) for key, value in to_save.items()]
|
| 633 | + |
| 634 | + if ensure_parents: |
| 635 | + # To enable zarr.create(store, path="a/b/c"), we need to create all the intermediate groups. |
| 636 | + parents = _build_parents(self) |
| 637 | + |
| 638 | + for parent in parents: |
| 639 | + awaitables.extend( |
| 640 | + [ |
| 641 | + (parent.store_path / key).set_if_not_exists(value) |
| 642 | + for key, value in parent.metadata.to_buffer_dict( |
| 643 | + default_buffer_prototype() |
| 644 | + ).items() |
| 645 | + ] |
| 646 | + ) |
| 647 | + |
627 | 648 | await gather(*awaitables)
|
628 | 649 |
|
629 | 650 | async def _set_selection(
|
@@ -2354,3 +2375,21 @@ def chunks_initialized(array: Array | AsyncArray) -> tuple[str, ...]:
|
2354 | 2375 | out.append(chunk_key)
|
2355 | 2376 |
|
2356 | 2377 | return tuple(out)
|
| 2378 | + |
| 2379 | + |
| 2380 | +def _build_parents(node: AsyncArray | AsyncGroup) -> list[AsyncGroup]: |
| 2381 | + from zarr.core.group import AsyncGroup, GroupMetadata |
| 2382 | + |
| 2383 | + required_parts = node.store_path.path.split("/")[:-1] |
| 2384 | + parents = [] |
| 2385 | + |
| 2386 | + for i, part in enumerate(required_parts): |
| 2387 | + path = "/".join(required_parts[:i] + [part]) |
| 2388 | + parents.append( |
| 2389 | + AsyncGroup( |
| 2390 | + metadata=GroupMetadata(zarr_format=node.metadata.zarr_format), |
| 2391 | + store_path=StorePath(store=node.store_path.store, path=path), |
| 2392 | + ) |
| 2393 | + ) |
| 2394 | + |
| 2395 | + return parents |
0 commit comments