diff --git a/CHANGES.md b/CHANGES.md
index 502910e..2dec497 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,5 +1,8 @@
## Version 0.9.0 (in development)
+* Added support for nested Zarr structures represented by `xarray.DataTree`,
+ including atomic rollback of complete DataTree slices and optional per-group
+ variable encoding configuration. (#113)
* Migrated to use `zarr >=3`. Supports writing Zarr v2 and v3 stores, with
Zarr v3 as the recommended format for new configurations. The `zarr_version`
setting is deprecated in favor of the now-required `zarr_format` and only
diff --git a/README.md b/README.md
index 6b4d53d..e3eee65 100644
--- a/README.md
+++ b/README.md
@@ -51,7 +51,8 @@ The `zappend` tool provides the following features:
created, effectively preventing concurrent dataset modifications.
* **Transaction-based dataset appends**: On failure during an append step,
the transaction is rolled back, so that the target dataset remains valid and
- preserves its integrity.
+ preserves its integrity. A nested `xarray.DataTree` slice is rolled back as
+ a whole if appending any of its groups fails.
* **Filesystem transparency**: The target dataset may be generated and updated
in any writable filesystems supported by the
[fsspec](https://filesystem-spec.readthedocs.io/) package.
@@ -64,7 +65,8 @@ The `zappend` tool provides the following features:
[`zappend`](cli.md) command or from Python. When used from Python using the
[`zappend()`](api.md) function, slice datasets can be passed as local file
paths, URIs, as datasets of type
- [xarray.Dataset](https://docs.xarray.dev/en/stable/generated/xarray.Dataset.html), or as custom
+ [xarray.Dataset](https://docs.xarray.dev/en/stable/generated/xarray.Dataset.html),
+ [xarray.DataTree](https://docs.xarray.dev/en/stable/generated/xarray.DataTree.html), or as custom
[slice sources](https://bcdev.github.io/zappend/guide/#slice-sources).
diff --git a/docs/cli.md b/docs/cli.md
index 243bc5b..fe8a70f 100644
--- a/docs/cli.md
+++ b/docs/cli.md
@@ -10,6 +10,8 @@ Usage: zappend [OPTIONS] [SLICES]...
The zappend command concatenates the dataset SLICES along a given append
dimension, e.g., "time" (the default) for geospatial satellite observations.
+ A nested Zarr slice is opened as an xarray.DataTree and its groups are
+ appended as one transaction.
Each append step is atomic, that is, the append operation is a transaction
that can be rolled back, in case the append operation fails. This ensures
integrity of the target data cube given by TARGET or in CONFIG.
diff --git a/docs/config.md b/docs/config.md
index 47af003..b6a5f8a 100644
--- a/docs/config.md
+++ b/docs/config.md
@@ -113,6 +113,30 @@ Variable metadata.
- `attrs`:
Arbitrary variable metadata attributes.
+### `group_variables`
+
+Type _object_.
+Optional variable-configuration overrides for individual `xarray.DataTree`
+groups. Keys are absolute Zarr group paths such as `"/group_0"` or `"/group_1"`; each
+value uses the same syntax as [`variables`](#variables).
+
+For a group, its configuration is merged over the global `variables`
+configuration. If a group has no entry here, normal zappend behavior applies:
+the global `variables` setting is used if present, otherwise encoding such as
+chunk sizes is taken from that group's first slice.
+
+```yaml
+group_variables:
+ "/group_0":
+ var_0:
+ encoding:
+ chunks: [31, 450, 450]
+ "/group_1":
+ var_0:
+ encoding:
+ chunks: [31, 450, 450]
+```
+
### `attrs`
Type _object_.
@@ -201,7 +225,7 @@ Must be one of the following:
### `slice_source`
Type _string_.
-The fully qualified name of a class or function that receives a slice item as argument(s) and provides the slice dataset. If a class is given, it must be derived from `zappend.api.SliceSource`. If the function is a context manager, it must yield an `xarray.Dataset`. If a plain function is given, it must return any valid slice item type. Refer to the user guide for more information.
+The fully qualified name of a class or function that receives a slice item as argument(s) and provides a slice object. If a class is given, it must be derived from `zappend.api.SliceSource`. If the function is a context manager, it must yield an `xarray.Dataset` or `xarray.DataTree`. If a plain function is given, it must return any valid slice item type. Refer to the user guide for more information.
### `slice_source_kwargs`
diff --git a/docs/guide.md b/docs/guide.md
index 104414a..6423b32 100644
--- a/docs/guide.md
+++ b/docs/guide.md
@@ -566,6 +566,10 @@ immediately after a target dataset modification failed. The rollback include res
all changed files and removing added files. After the rollback you can analyse what
went wrong and try to continue appending slices at the point it failed.
+For an `xarray.DataTree` slice, this transaction spans all data-bearing Zarr
+groups in the tree. If appending one group fails, changes already made to the
+other groups for that same slice are rolled back too.
+
To allow for rollbacks, a slice append operation is treated as a transaction,
hence temporary files must be written, e.g., to record required rollback actions and to
save backup files with the original data. The location of the temporary transaction
@@ -644,8 +648,26 @@ to `zappend`. Slice datasets can be provided in various ways.
of slice items or provide a Python generator that provides the slice items.
Each slice item can be a local file path or URI of type `str` or `FileObj`,
-a dataset of type `xarray.Dataset`, or a `SliceSource` object explained in more detail
-below.
+a dataset of type `xarray.Dataset`, a nested dataset of type `xarray.DataTree`,
+or a `SliceSource` object explained in more detail below.
+
+#### Nested Zarr Groups and DataTree
+
+Nested Zarr inputs are represented by
+[`xarray.DataTree`](https://docs.xarray.dev/en/stable/generated/xarray.DataTree.html).
+Pass a DataTree directly, return one from a `SliceSource`, or pass a path to a
+nested Zarr store: zappend opens the latter as a DataTree automatically.
+
+All DataTree slices in one run must have the same group paths. DataTree and
+Dataset slices cannot be mixed in one run. Each data-bearing group is appended
+along the configured append dimension, and the complete DataTree slice is
+rolled back if any group append fails.
+
+Use `group_variables` to override variable encoding for individual absolute
+group paths. Group settings override global `variables`; groups without an
+override use global `variables`, or the encoding of their first slice when no
+global setting exists. See the [configuration reference](config.md#group_variables)
+and `examples/datatree.ipynb` for a complete example.
#### Paths and URIs
@@ -695,7 +717,7 @@ An alternative to providing the slice dataset as path or URI is using the
`zappend.api.FileObj` class, which combines a URI with dedicated filesystem
storage options.
-#### Dataset Objects
+#### Dataset and DataTree Objects
You can also use dataset objects of type
[`xarray.Dataset`](https://docs.xarray.dev/en/stable/generated/xarray.Dataset.html)
@@ -703,6 +725,10 @@ as slice item. Such objects may originate from opening datasets from some storag
`xarray.open_dataset(slice_store, ...)` or by composing, aggregating, resampling
slice datasets from other datasets and dataset variables.
+Nested inputs can instead be passed as
+[`xarray.DataTree`](https://docs.xarray.dev/en/stable/generated/xarray.DataTree.html)
+objects. The same resource-management and persistence considerations apply.
+
!!! note "Datasets are not closed automatically"
If you pass [`xarray.Dataset`](https://docs.xarray.dev/en/stable/generated/xarray.Dataset.html) objects to `zappend` they will not be
automatically closed. This may become be issue, if you have many datasets
@@ -735,8 +761,8 @@ at the cost of additional i/o. It therefore defaults to `false`.
A slice source gives you full control about how a slice dataset is created, loaded,
or generated and how its bound resources, if any, are released. In its simplest form,
-a slice source is a plain Python function that can take any arguments and returns
-an `xarray.Dataset`:
+a slice source is a plain Python function that can take any arguments and returns
+an `xarray.Dataset` or `xarray.DataTree`:
```python
import xarray as xr
@@ -774,8 +800,8 @@ def get_dataset(path: str) -> xr.Dataset:
You can also implement your slice source as a class derived from the abstract
`zappend.api.SliceSource` class. Its interface methods are:
-* `get_dataset()`: a zero-argument method that returns the slice dataset of type
- `xarray.Dataset`. You must implement this abstract method.
+* `get_dataset()`: a zero-argument method that returns the slice object of type
+ `xarray.Dataset` or `xarray.DataTree`. You must implement this abstract method.
* `close()`: Optional method. Put your cleanup code here.
(in zappend < v0.7, the `close` method was called `dispose`).
diff --git a/docs/index.md b/docs/index.md
index 450fe8a..ed96536 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -42,7 +42,8 @@ The `zappend` tool provides the following features:
[`zappend`](cli.md) command or from Python. When used from Python using the
[`zappend()`](api.md) function, slice datasets can be passed as local file
paths, URIs, as datasets of type
- [xarray.Dataset](https://docs.xarray.dev/en/stable/generated/xarray.Dataset.html), or as custom
+ [xarray.Dataset](https://docs.xarray.dev/en/stable/generated/xarray.Dataset.html),
+ [xarray.DataTree](https://docs.xarray.dev/en/stable/generated/xarray.DataTree.html), or as custom
[slice sources](guide.md#slice-sources).
## How It Works
@@ -51,6 +52,9 @@ At its core, `zappend` calls the [to_zarr()](https://docs.xarray.dev/en/stable/g
for each dataset slice it receives and either creates the target dataset if it does
not exist yet or updates it with the current slice, if it already exists.
+For nested Zarr stores, it applies the same operation to each data-bearing
+`xarray.DataTree` group as one atomic slice transaction.
+
If there is no target dataset yet, `zappend` does the following:
* get target dataset outline and encoding from configuration and first slice dataset;
diff --git a/docs/requirements.md b/docs/requirements.md
index 88199c5..d458337 100644
--- a/docs/requirements.md
+++ b/docs/requirements.md
@@ -7,11 +7,15 @@ of the `zappend` tool.
* Create a target Zarr dataset by appending Zarr dataset slices along a
given *append dimension*, usually `time`.
+* Create a target nested Zarr store by appending compatible
+ `xarray.DataTree` slices along the append dimension of every data-bearing
+ group.
* The tool takes care of modifying the target dataset using the slices,
but doesn't care how the slice datasets are created.
* Slice datasets may be given as URIs with storage options or as
in-memory datasets of type
[xarray.Dataset](https://docs.xarray.dev/en/stable/generated/xarray.Dataset.html)
+ or [xarray.DataTree](https://docs.xarray.dev/en/stable/generated/xarray.DataTree.html)
or
[xcube.core.mldataset.MultiLevelDataset](https://xcube.readthedocs.io/en/latest/mldatasets.html).
* Target and slices are allowed to live in different filesystems.
diff --git a/examples/create_inputs.py b/examples/create_inputs.py
index 3809ed3..27b63a3 100644
--- a/examples/create_inputs.py
+++ b/examples/create_inputs.py
@@ -90,3 +90,45 @@ def make_netcdf_slices(n_slices: int) -> None:
engine="h5netcdf",
encoding=encoding,
)
+
+
+def make_datatree_slices(n_slices: int) -> None:
+ """Create *n_slices* daily nested Zarr v2 inputs with two resolutions."""
+ times = START_TIME + np.arange(n_slices) * np.timedelta64(1, "D")
+ directory = INPUT_DIR / "datatree"
+ directory.mkdir(parents=True, exist_ok=True)
+ for index, time in enumerate(times):
+ tree = xr.DataTree.from_dict(
+ {
+ "/": xr.Dataset(
+ attrs={
+ "title": "Zappend nested example",
+ "description": "Two spatial resolutions of daily data",
+ }
+ ),
+ "/0": _make_datatree_dataset(time, index, 1800, 3600),
+ "/1": _make_datatree_dataset(time, index, 900, 1800),
+ }
+ )
+ tree.to_zarr(
+ directory / f"slice-{time!s}.zarr",
+ mode="w",
+ zarr_format=2,
+ consolidated=True,
+ )
+
+
+def _make_datatree_dataset(
+ time: np.datetime64, index: int, lat_size: int, lon_size: int
+) -> xr.Dataset:
+ lat = np.linspace(-90, 90, lat_size, endpoint=False, dtype="float32")
+ lon = np.linspace(-180, 180, lon_size, endpoint=False, dtype="float32")
+ data = np.full((1, lat_size, lon_size), index, dtype="uint8")
+ return xr.Dataset(
+ data_vars={
+ "temperature": (("time", "lat", "lon"), data.astype("float32")),
+ "quality_flag": (("time", "lat", "lon"), data),
+ },
+ coords={"time": [time], "lat": lat, "lon": lon},
+ attrs={"spatial_resolution": f"{360 / lon_size:g} degrees"},
+ ).chunk({"time": 1, "lat": min(450, lat_size), "lon": min(450, lon_size)})
diff --git a/examples/datatree.ipynb b/examples/datatree.ipynb
new file mode 100644
index 0000000..750e636
--- /dev/null
+++ b/examples/datatree.ipynb
@@ -0,0 +1,1789 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "af9386c9aea0a587",
+ "metadata": {},
+ "source": [
+ "# Appending nested Zarr groups with DataTree\n",
+ "\n",
+ "This notebook generates daily nested Zarr v2 inputs with two spatial-resolution groups, `/0` and `/1`. `zappend` opens each input as an `xarray.DataTree` and appends all groups as one transactional slice: if one group fails, the complete daily tree is rolled back."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "9f9e2f0a0d05b730",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2026-10-30T11:23:55.980707308Z",
+ "start_time": "2026-10-30T11:23:55.001287664Z"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "import os\n",
+ "import shutil\n",
+ "from pathlib import Path\n",
+ "\n",
+ "import xarray as xr\n",
+ "from create_inputs import make_datatree_slices\n",
+ "\n",
+ "from zappend.api import zappend"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "e784207fd4840b95",
+ "metadata": {},
+ "source": [
+ "## Generate and collect nested daily inputs\n",
+ "\n",
+ "Each input has a metadata-only root group and two data-bearing child groups. Both carry the same daily `time` coordinate, but `/0` is 180 by 360 pixels and `/1` is 90 by 180 pixels."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "819ffbe9089aee7b",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2026-10-30T11:24:06.073416043Z",
+ "start_time": "2026-10-30T11:23:55.988596686Z"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "CPU times: user 10.8 s, sys: 2.52 s, total: 13.4 s\n",
+ "Wall time: 10.1 s\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ "['inputs/datatree/slice-2024-01-01.zarr',\n",
+ " 'inputs/datatree/slice-2024-01-02.zarr',\n",
+ " 'inputs/datatree/slice-2024-01-03.zarr']"
+ ]
+ },
+ "execution_count": 2,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%time\n",
+ "make_datatree_slices(31)\n",
+ "\n",
+ "input_dir = Path('inputs/datatree')\n",
+ "output_dir = Path('outputs')\n",
+ "output_dir.mkdir(exist_ok=True)\n",
+ "slices = sorted(str(path) for path in input_dir.glob('*.zarr'))\n",
+ "assert len(slices) == 31\n",
+ "slices[:3]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "3cc4d9f1cf156567",
+ "metadata": {},
+ "source": [
+ "## Default nested layout\n",
+ "\n",
+ "The first target keeps each group's encoding from its first slice. Because the inputs use one day per time chunk, this layout is suited to maps and single-day access."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "id": "cfad5c1fa9a2b5d6",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2026-10-30T11:24:20.959529751Z",
+ "start_time": "2026-10-30T11:24:06.089167470Z"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "2026-10-30 12:24:06,186 WARNING Setting 'force_new' is enabled. This will permanently delete existing targets (no rollback).\n",
+ "2026-10-30 12:24:06,188 WARNING Permanently deleting outputs/datatree_example_space_opt.zarr\n",
+ "2026-10-30 12:24:06,274 INFO Opening slice dataset from inputs/datatree/slice-2024-01-01.zarr\n",
+ "2026-10-30 12:24:06,319 INFO Creating target dataset outputs/datatree_example_space_opt.zarr/0\n",
+ "2026-10-30 12:24:06,547 INFO Creating target dataset outputs/datatree_example_space_opt.zarr/1\n",
+ "2026-10-30 12:24:06,688 INFO Transaction completed.\n",
+ "2026-10-30 12:24:06,689 INFO Slice dataset inputs/datatree/slice-2024-01-01.zarr closed\n",
+ "2026-10-30 12:24:06,697 INFO Opening slice dataset from inputs/datatree/slice-2024-01-02.zarr\n",
+ "2026-10-30 12:24:06,743 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/0\n",
+ "2026-10-30 12:24:07,031 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/1\n",
+ "2026-10-30 12:24:07,148 INFO Transaction completed.\n",
+ "2026-10-30 12:24:07,149 INFO Slice dataset inputs/datatree/slice-2024-01-02.zarr closed\n",
+ "2026-10-30 12:24:07,154 INFO Opening slice dataset from inputs/datatree/slice-2024-01-03.zarr\n",
+ "2026-10-30 12:24:07,191 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/0\n",
+ "2026-10-30 12:24:07,573 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/1\n",
+ "2026-10-30 12:24:07,740 INFO Transaction completed.\n",
+ "2026-10-30 12:24:07,741 INFO Slice dataset inputs/datatree/slice-2024-01-03.zarr closed\n",
+ "2026-10-30 12:24:07,748 INFO Opening slice dataset from inputs/datatree/slice-2024-01-04.zarr\n",
+ "2026-10-30 12:24:07,796 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/0\n",
+ "2026-10-30 12:24:08,046 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/1\n",
+ "2026-10-30 12:24:08,173 INFO Transaction completed.\n",
+ "2026-10-30 12:24:08,174 INFO Slice dataset inputs/datatree/slice-2024-01-04.zarr closed\n",
+ "2026-10-30 12:24:08,180 INFO Opening slice dataset from inputs/datatree/slice-2024-01-05.zarr\n",
+ "2026-10-30 12:24:08,224 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/0\n",
+ "2026-10-30 12:24:08,494 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/1\n",
+ "2026-10-30 12:24:08,618 INFO Transaction completed.\n",
+ "2026-10-30 12:24:08,619 INFO Slice dataset inputs/datatree/slice-2024-01-05.zarr closed\n",
+ "2026-10-30 12:24:08,624 INFO Opening slice dataset from inputs/datatree/slice-2024-01-06.zarr\n",
+ "2026-10-30 12:24:08,664 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/0\n",
+ "2026-10-30 12:24:08,986 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/1\n",
+ "2026-10-30 12:24:09,116 INFO Transaction completed.\n",
+ "2026-10-30 12:24:09,116 INFO Slice dataset inputs/datatree/slice-2024-01-06.zarr closed\n",
+ "2026-10-30 12:24:09,121 INFO Opening slice dataset from inputs/datatree/slice-2024-01-07.zarr\n",
+ "2026-10-30 12:24:09,167 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/0\n",
+ "2026-10-30 12:24:09,432 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/1\n",
+ "2026-10-30 12:24:09,552 INFO Transaction completed.\n",
+ "2026-10-30 12:24:09,553 INFO Slice dataset inputs/datatree/slice-2024-01-07.zarr closed\n",
+ "2026-10-30 12:24:09,558 INFO Opening slice dataset from inputs/datatree/slice-2024-01-08.zarr\n",
+ "2026-10-30 12:24:09,601 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/0\n",
+ "2026-10-30 12:24:09,955 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/1\n",
+ "2026-10-30 12:24:10,115 INFO Transaction completed.\n",
+ "2026-10-30 12:24:10,115 INFO Slice dataset inputs/datatree/slice-2024-01-08.zarr closed\n",
+ "2026-10-30 12:24:10,121 INFO Opening slice dataset from inputs/datatree/slice-2024-01-09.zarr\n",
+ "2026-10-30 12:24:10,160 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/0\n",
+ "2026-10-30 12:24:10,433 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/1\n",
+ "2026-10-30 12:24:10,561 INFO Transaction completed.\n",
+ "2026-10-30 12:24:10,561 INFO Slice dataset inputs/datatree/slice-2024-01-09.zarr closed\n",
+ "2026-10-30 12:24:10,567 INFO Opening slice dataset from inputs/datatree/slice-2024-01-10.zarr\n",
+ "2026-10-30 12:24:10,610 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/0\n",
+ "2026-10-30 12:24:10,886 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/1\n",
+ "2026-10-30 12:24:11,008 INFO Transaction completed.\n",
+ "2026-10-30 12:24:11,009 INFO Slice dataset inputs/datatree/slice-2024-01-10.zarr closed\n",
+ "2026-10-30 12:24:11,014 INFO Opening slice dataset from inputs/datatree/slice-2024-01-11.zarr\n",
+ "2026-10-30 12:24:11,060 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/0\n",
+ "2026-10-30 12:24:11,464 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/1\n",
+ "2026-10-30 12:24:11,581 INFO Transaction completed.\n",
+ "2026-10-30 12:24:11,582 INFO Slice dataset inputs/datatree/slice-2024-01-11.zarr closed\n",
+ "2026-10-30 12:24:11,587 INFO Opening slice dataset from inputs/datatree/slice-2024-01-12.zarr\n",
+ "2026-10-30 12:24:11,632 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/0\n",
+ "2026-10-30 12:24:11,908 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/1\n",
+ "2026-10-30 12:24:12,027 INFO Transaction completed.\n",
+ "2026-10-30 12:24:12,027 INFO Slice dataset inputs/datatree/slice-2024-01-12.zarr closed\n",
+ "2026-10-30 12:24:12,032 INFO Opening slice dataset from inputs/datatree/slice-2024-01-13.zarr\n",
+ "2026-10-30 12:24:12,077 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/0\n",
+ "2026-10-30 12:24:12,379 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/1\n",
+ "2026-10-30 12:24:12,502 INFO Transaction completed.\n",
+ "2026-10-30 12:24:12,503 INFO Slice dataset inputs/datatree/slice-2024-01-13.zarr closed\n",
+ "2026-10-30 12:24:12,508 INFO Opening slice dataset from inputs/datatree/slice-2024-01-14.zarr\n",
+ "2026-10-30 12:24:12,553 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/0\n",
+ "2026-10-30 12:24:12,816 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/1\n",
+ "2026-10-30 12:24:12,936 INFO Transaction completed.\n",
+ "2026-10-30 12:24:12,936 INFO Slice dataset inputs/datatree/slice-2024-01-14.zarr closed\n",
+ "2026-10-30 12:24:12,941 INFO Opening slice dataset from inputs/datatree/slice-2024-01-15.zarr\n",
+ "2026-10-30 12:24:12,984 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/0\n",
+ "2026-10-30 12:24:13,232 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/1\n",
+ "2026-10-30 12:24:13,383 INFO Transaction completed.\n",
+ "2026-10-30 12:24:13,383 INFO Slice dataset inputs/datatree/slice-2024-01-15.zarr closed\n",
+ "2026-10-30 12:24:13,389 INFO Opening slice dataset from inputs/datatree/slice-2024-01-16.zarr\n",
+ "2026-10-30 12:24:13,440 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/0\n",
+ "2026-10-30 12:24:13,684 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/1\n",
+ "2026-10-30 12:24:13,838 INFO Transaction completed.\n",
+ "2026-10-30 12:24:13,839 INFO Slice dataset inputs/datatree/slice-2024-01-16.zarr closed\n",
+ "2026-10-30 12:24:13,845 INFO Opening slice dataset from inputs/datatree/slice-2024-01-17.zarr\n",
+ "2026-10-30 12:24:13,898 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/0\n",
+ "2026-10-30 12:24:14,158 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/1\n",
+ "2026-10-30 12:24:14,311 INFO Transaction completed.\n",
+ "2026-10-30 12:24:14,312 INFO Slice dataset inputs/datatree/slice-2024-01-17.zarr closed\n",
+ "2026-10-30 12:24:14,321 INFO Opening slice dataset from inputs/datatree/slice-2024-01-18.zarr\n",
+ "2026-10-30 12:24:14,391 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/0\n",
+ "2026-10-30 12:24:14,646 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/1\n",
+ "2026-10-30 12:24:14,770 INFO Transaction completed.\n",
+ "2026-10-30 12:24:14,771 INFO Slice dataset inputs/datatree/slice-2024-01-18.zarr closed\n",
+ "2026-10-30 12:24:14,777 INFO Opening slice dataset from inputs/datatree/slice-2024-01-19.zarr\n",
+ "2026-10-30 12:24:14,841 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/0\n",
+ "2026-10-30 12:24:15,123 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/1\n",
+ "2026-10-30 12:24:15,237 INFO Transaction completed.\n",
+ "2026-10-30 12:24:15,237 INFO Slice dataset inputs/datatree/slice-2024-01-19.zarr closed\n",
+ "2026-10-30 12:24:15,242 INFO Opening slice dataset from inputs/datatree/slice-2024-01-20.zarr\n",
+ "2026-10-30 12:24:15,301 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/0\n",
+ "2026-10-30 12:24:15,567 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/1\n",
+ "2026-10-30 12:24:15,687 INFO Transaction completed.\n",
+ "2026-10-30 12:24:15,688 INFO Slice dataset inputs/datatree/slice-2024-01-20.zarr closed\n",
+ "2026-10-30 12:24:15,694 INFO Opening slice dataset from inputs/datatree/slice-2024-01-21.zarr\n",
+ "2026-10-30 12:24:15,745 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/0\n",
+ "2026-10-30 12:24:16,038 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/1\n",
+ "2026-10-30 12:24:16,157 INFO Transaction completed.\n",
+ "2026-10-30 12:24:16,158 INFO Slice dataset inputs/datatree/slice-2024-01-21.zarr closed\n",
+ "2026-10-30 12:24:16,163 INFO Opening slice dataset from inputs/datatree/slice-2024-01-22.zarr\n",
+ "2026-10-30 12:24:16,214 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/0\n",
+ "2026-10-30 12:24:16,494 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/1\n",
+ "2026-10-30 12:24:16,624 INFO Transaction completed.\n",
+ "2026-10-30 12:24:16,624 INFO Slice dataset inputs/datatree/slice-2024-01-22.zarr closed\n",
+ "2026-10-30 12:24:16,629 INFO Opening slice dataset from inputs/datatree/slice-2024-01-23.zarr\n",
+ "2026-10-30 12:24:16,682 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/0\n",
+ "2026-10-30 12:24:16,971 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/1\n",
+ "2026-10-30 12:24:17,092 INFO Transaction completed.\n",
+ "2026-10-30 12:24:17,093 INFO Slice dataset inputs/datatree/slice-2024-01-23.zarr closed\n",
+ "2026-10-30 12:24:17,098 INFO Opening slice dataset from inputs/datatree/slice-2024-01-24.zarr\n",
+ "2026-10-30 12:24:17,153 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/0\n",
+ "2026-10-30 12:24:17,443 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/1\n",
+ "2026-10-30 12:24:17,564 INFO Transaction completed.\n",
+ "2026-10-30 12:24:17,565 INFO Slice dataset inputs/datatree/slice-2024-01-24.zarr closed\n",
+ "2026-10-30 12:24:17,570 INFO Opening slice dataset from inputs/datatree/slice-2024-01-25.zarr\n",
+ "2026-10-30 12:24:17,623 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/0\n",
+ "2026-10-30 12:24:17,913 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/1\n",
+ "2026-10-30 12:24:18,036 INFO Transaction completed.\n",
+ "2026-10-30 12:24:18,036 INFO Slice dataset inputs/datatree/slice-2024-01-25.zarr closed\n",
+ "2026-10-30 12:24:18,041 INFO Opening slice dataset from inputs/datatree/slice-2024-01-26.zarr\n",
+ "2026-10-30 12:24:18,095 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/0\n",
+ "2026-10-30 12:24:18,430 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/1\n",
+ "2026-10-30 12:24:18,570 INFO Transaction completed.\n",
+ "2026-10-30 12:24:18,571 INFO Slice dataset inputs/datatree/slice-2024-01-26.zarr closed\n",
+ "2026-10-30 12:24:18,576 INFO Opening slice dataset from inputs/datatree/slice-2024-01-27.zarr\n",
+ "2026-10-30 12:24:18,623 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/0\n",
+ "2026-10-30 12:24:18,958 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/1\n",
+ "2026-10-30 12:24:19,092 INFO Transaction completed.\n",
+ "2026-10-30 12:24:19,093 INFO Slice dataset inputs/datatree/slice-2024-01-27.zarr closed\n",
+ "2026-10-30 12:24:19,099 INFO Opening slice dataset from inputs/datatree/slice-2024-01-28.zarr\n",
+ "2026-10-30 12:24:19,146 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/0\n",
+ "2026-10-30 12:24:19,419 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/1\n",
+ "2026-10-30 12:24:19,544 INFO Transaction completed.\n",
+ "2026-10-30 12:24:19,544 INFO Slice dataset inputs/datatree/slice-2024-01-28.zarr closed\n",
+ "2026-10-30 12:24:19,550 INFO Opening slice dataset from inputs/datatree/slice-2024-01-29.zarr\n",
+ "2026-10-30 12:24:19,601 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/0\n",
+ "2026-10-30 12:24:19,873 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/1\n",
+ "2026-10-30 12:24:20,003 INFO Transaction completed.\n",
+ "2026-10-30 12:24:20,004 INFO Slice dataset inputs/datatree/slice-2024-01-29.zarr closed\n",
+ "2026-10-30 12:24:20,010 INFO Opening slice dataset from inputs/datatree/slice-2024-01-30.zarr\n",
+ "2026-10-30 12:24:20,069 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/0\n",
+ "2026-10-30 12:24:20,369 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/1\n",
+ "2026-10-30 12:24:20,487 INFO Transaction completed.\n",
+ "2026-10-30 12:24:20,488 INFO Slice dataset inputs/datatree/slice-2024-01-30.zarr closed\n",
+ "2026-10-30 12:24:20,493 INFO Opening slice dataset from inputs/datatree/slice-2024-01-31.zarr\n",
+ "2026-10-30 12:24:20,552 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/0\n",
+ "2026-10-30 12:24:20,817 INFO Updating target dataset outputs/datatree_example_space_opt.zarr/1\n",
+ "2026-10-30 12:24:20,947 INFO Transaction completed.\n",
+ "2026-10-30 12:24:20,948 INFO Slice dataset inputs/datatree/slice-2024-01-31.zarr closed\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "CPU times: user 15.5 s, sys: 3.2 s, total: 18.7 s\n",
+ "Wall time: 14.9 s\n"
+ ]
+ }
+ ],
+ "source": [
+ "%%time\n",
+ "target = output_dir / 'datatree_example_space_opt.zarr'\n",
+ "count = zappend(\n",
+ " slices,\n",
+ " target_dir=str(target),\n",
+ " force_new=True,\n",
+ " logging='INFO',\n",
+ ")\n",
+ "assert count == len(slices)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "id": "a2e0e79dfe38f97a",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2026-10-30T11:24:21.085093795Z",
+ "start_time": "2026-10-30T11:24:20.960792517Z"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "