|
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 |
@@ -276,7 +282,7 @@ async def _create_v3( |
276 | 282 | ) |
277 | 283 |
|
278 | 284 | array = cls(metadata=metadata, store_path=store_path) |
279 | | - await array._save_metadata(metadata) |
| 285 | + await array._save_metadata(metadata, ensure_parents=True) |
280 | 286 | return array |
281 | 287 |
|
282 | 288 | @classmethod |
@@ -315,7 +321,7 @@ async def _create_v2( |
315 | 321 | attributes=attributes, |
316 | 322 | ) |
317 | 323 | array = cls(metadata=metadata, store_path=store_path) |
318 | | - await array._save_metadata(metadata) |
| 324 | + await array._save_metadata(metadata, ensure_parents=True) |
319 | 325 | return array |
320 | 326 |
|
321 | 327 | @classmethod |
@@ -603,9 +609,24 @@ async def getitem( |
603 | 609 | ) |
604 | 610 | return await self._get_selection(indexer, prototype=prototype) |
605 | 611 |
|
606 | | - async def _save_metadata(self, metadata: ArrayMetadata) -> None: |
| 612 | + async def _save_metadata(self, metadata: ArrayMetadata, ensure_parents: bool = False) -> None: |
607 | 613 | to_save = metadata.to_buffer_dict(default_buffer_prototype()) |
608 | 614 | awaitables = [set_or_delete(self.store_path / key, value) for key, value in to_save.items()] |
| 615 | + |
| 616 | + if ensure_parents: |
| 617 | + # To enable zarr.create(store, path="a/b/c"), we need to create all the intermediates. |
| 618 | + parents = _build_parents(self) |
| 619 | + |
| 620 | + for parent in parents: |
| 621 | + awaitables.extend( |
| 622 | + [ |
| 623 | + (parent.store_path / key).setdefault(value) |
| 624 | + for key, value in parent.metadata.to_buffer_dict( |
| 625 | + default_buffer_prototype() |
| 626 | + ).items() |
| 627 | + ] |
| 628 | + ) |
| 629 | + |
609 | 630 | await gather(*awaitables) |
610 | 631 |
|
611 | 632 | async def _set_selection( |
@@ -2336,3 +2357,21 @@ def chunks_initialized(array: Array | AsyncArray) -> tuple[str, ...]: |
2336 | 2357 | out.append(chunk_key) |
2337 | 2358 |
|
2338 | 2359 | return tuple(out) |
| 2360 | + |
| 2361 | + |
| 2362 | +def _build_parents(node: AsyncArray | AsyncGroup) -> list[AsyncGroup]: |
| 2363 | + from zarr.core.group import AsyncGroup, GroupMetadata |
| 2364 | + |
| 2365 | + required_parts = node.store_path.path.split("/")[:-1] |
| 2366 | + parents = [] |
| 2367 | + |
| 2368 | + for i, part in enumerate(required_parts): |
| 2369 | + path = "/".join(required_parts[:i] + [part]) |
| 2370 | + parents.append( |
| 2371 | + AsyncGroup( |
| 2372 | + metadata=GroupMetadata(zarr_format=node.metadata.zarr_format), |
| 2373 | + store_path=StorePath(store=node.store_path.store, path=path), |
| 2374 | + ) |
| 2375 | + ) |
| 2376 | + |
| 2377 | + return parents |
0 commit comments