diff --git a/pyproject.toml b/pyproject.toml index b21262a..1b434f3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,7 @@ parquet = [ ] xarray = [ "xarray>=2026.04", - "zarr~=3.1.6", + "zarr>=3.1.6,<3.4", "zarrs>=0.2", "pint", "unum", @@ -41,7 +41,7 @@ dev = [ "fsspec", "tqdm", "xarray>=2026.04", - "zarr~=3.1.6", + "zarr>=3.1.6,<3.4", "zarrs>=0.2", "pint", "unum", diff --git a/uv.lock b/uv.lock index 0ec03b4..549b45f 100644 --- a/uv.lock +++ b/uv.lock @@ -1505,7 +1505,7 @@ wheels = [ [[package]] name = "viva-emitters" -version = "0.2.1" +version = "0.2.3" source = { editable = "." } dependencies = [ { name = "bigraph-schema" }, @@ -1564,8 +1564,8 @@ requires-dist = [ { name = "unum", marker = "extra == 'xarray'" }, { name = "xarray", marker = "extra == 'dev'", specifier = ">=2026.4" }, { name = "xarray", marker = "extra == 'xarray'", specifier = ">=2026.4" }, - { name = "zarr", marker = "extra == 'dev'", specifier = "~=3.1.6" }, - { name = "zarr", marker = "extra == 'xarray'", specifier = "~=3.1.6" }, + { name = "zarr", marker = "extra == 'dev'", specifier = ">=3.1.6,<3.4" }, + { name = "zarr", marker = "extra == 'xarray'", specifier = ">=3.1.6,<3.4" }, { name = "zarrs", marker = "extra == 'dev'", specifier = ">=0.2" }, { name = "zarrs", marker = "extra == 'xarray'", specifier = ">=0.2" }, ] diff --git a/viva_emitters/xarray_emitter/zarr_writer.py b/viva_emitters/xarray_emitter/zarr_writer.py index 77bdd2e..7218aed 100644 --- a/viva_emitters/xarray_emitter/zarr_writer.py +++ b/viva_emitters/xarray_emitter/zarr_writer.py @@ -156,8 +156,11 @@ async def consolidate_metadata( k: v.metadata async for (k, v) in group.members(max_depth=None, use_consolidated_for_children=False)} - # TODO: fix in `consolidate_metadata()` (zarr==3.1.6) - members_metadata |= {"": group.metadata} + # The root node is STRICTLY EXCLUDED from consolidated metadata: a "" entry + # is a self-reference that risks infinite loops in Zarr traversal functions + # (e.g. Group.tree()), not only ConsolidatedMetadata._flat_to_nested(). The + # root's own metadata lives on `group` itself. (Per @ntfrgl on PR #31; + # mirrors vEcoli be603b5d.) # combine and write consolidated metadata for k, v in members_metadata.items(): @@ -208,11 +211,14 @@ async def reconsolidate_metadata( # read metadata at updated paths group = _replace_consolidated_metadata(group, None) mod_members_metadata, add_members_metadata = [ - {k: n.metadata async for (k, n) in _iter_from_keys(group, keys)} + # `if k` strictly excludes the root node: a "" entry is a self-reference + # that risks infinite loops in Zarr traversal functions (see + # consolidate_metadata above). + {k: n.metadata async for (k, n) in _iter_from_keys(group, keys) if k} for keys in [modified_keys, added_keys]] # check assumptions about metadata updates - old_keys = set(members_metadata.keys()) | {""} + old_keys = set(members_metadata.keys()) assert set(mod_members_metadata.keys()).issubset(old_keys) assert set(add_members_metadata.keys()).isdisjoint(old_keys)