Skip to content

xarray & Zarr Interop#

Handing a mesh to xarray, and reading one back, in the UGRID-CF encoding.

xarray, zarr and dask are peer dependencies: they are imported inside these functions through the optional-extras helpers, never at this module's import, and a missing one raises the [lazy] install hint rather than a bare ImportError. There is no xugrid dependency — the UGRID-CF encoding is written and read directly.

One caveat, measured rather than assumed: importing the parent pyramids.netcdf package already pulls xarray and dask for reasons unrelated to this module, so import pyramids.netcdf.ugrid pays that cost today even though the ugrid package itself adds none of it.

The encoding trap this module exists to handle is the connectivity round trip: a file's start_index and _FillValue are restored on the way out and normalised back to the internal 0-indexed / -1-fill form on the way in. The dummy topology and grid-mapping variables carry a length-1 dimension rather than being true scalars, because GDAL's multidimensional reader does not expose a dimensionless variable — without it the exported dataset could be written to NetCDF but not read back.

pyramids.netcdf.ugrid.interop_xarray #

Standalone xarray / Zarr / dask interop for UGRID meshes.

Pure functions that translate between a :class:~pyramids.netcdf.ugrid.mesh.Mesh2d plus a {name: MeshVariable} mapping and the UGRID-CF encoding carried by an xarray.Dataset -- the in-memory mirror of the on-disk encoding written by :mod:pyramids.netcdf.ugrid.io. Keeping these functions mesh/variable-shaped (never a UgridDataset) keeps the module free of the dataset import cycle; the UgridDataset facade wraps the results.

xarray, zarr and dask are optional dependencies. None of them is imported at module scope: every guarded import happens inside the function that needs it, through :func:pyramids.base._utils.import_xarray / :func:~pyramids.base._utils.require_optional / :func:~pyramids.base._utils.import_dask, mirroring NetCDF.to_xarray. A missing package raises :class:pyramids.errors.OptionalPackageDoesNotExist.

The one encoding trap -- the connectivity start_index / _FillValue round-trip -- is handled by :func:_connectivity_to_disk / :func:_connectivity_from_disk, which mirror the write-side reverse mapping in :func:pyramids.netcdf.ugrid.io._write_connectivity_array and the read-side normalisation in :meth:pyramids.netcdf.ugrid.connectivity.Connectivity.from_gdal_array. The internal 0-indexed / -1-fill arrays are never emitted to xarray.

to_xarray(mesh, data_variables, mesh_name=DEFAULT_MESH_NAME, crs_wkt=None, volume_topology=None) #

Encode a mesh and its variables as a UGRID-CF xarray.Dataset.

Produces a plain xarray.Dataset carrying the mesh topology the UGRID convention defines -- a dummy mesh_topology variable, the node coordinates, the face_node_connectivity (with its start_index and file _FillValue restored to the on-disk convention), optional edge connectivity and face-centre coordinates, a CRS grid-mapping variable, and each data variable on its location dimension with mesh / location attributes. The result is readable by ecosystem tools such as xugrid.UgridDataset.from_dataset and round-trips losslessly through :func:from_xarray.

xarray is optional; it is imported through :func:pyramids.base._utils.import_xarray inside this function -- never at module scope.

Parameters:

Name Type Description Default
mesh Mesh2d

The mesh topology to export.

required
data_variables dict[str, MeshVariable]

Mapping of variable name to :class:MeshVariable.

required
mesh_name str

Name for the exported topology variable. Defaults to "mesh2d".

DEFAULT_MESH_NAME
crs_wkt str | None

WKT of the CRS to encode in the grid-mapping variable. None exports no grid mapping, so the result carries no georeferencing. Defaults to None.

None

Returns:

Type Description
Any

xarray.Dataset: The UGRID-CF encoded dataset.

Raises:

Type Description
OptionalPackageDoesNotExist

xarray is not installed.

Source code in src/pyramids/netcdf/ugrid/interop_xarray.py
def to_xarray(
    mesh: Mesh2d,
    data_variables: dict[str, MeshVariable],
    mesh_name: str = DEFAULT_MESH_NAME,
    crs_wkt: str | None = None,
    volume_topology: VolumeTopology | None = None,
) -> Any:
    """Encode a mesh and its variables as a UGRID-CF ``xarray.Dataset``.

    Produces a plain ``xarray.Dataset`` carrying the mesh topology the UGRID convention defines -- a
    dummy ``mesh_topology`` variable, the node coordinates, the ``face_node_connectivity`` (with its
    ``start_index`` and file ``_FillValue`` restored to the on-disk convention), optional
    edge connectivity and face-centre coordinates, a CRS grid-mapping variable, and each data variable
    on its ``location`` dimension with ``mesh`` / ``location`` attributes. The result is readable by
    ecosystem tools such as ``xugrid.UgridDataset.from_dataset`` and round-trips losslessly through
    :func:`from_xarray`.

    ``xarray`` is optional; it is imported through
    :func:`pyramids.base._utils.import_xarray` inside this function -- never at module scope.

    Args:
        mesh: The mesh topology to export.
        data_variables: Mapping of variable name to :class:`MeshVariable`.
        mesh_name: Name for the exported topology variable. Defaults to ``"mesh2d"``.
        crs_wkt: WKT of the CRS to encode in the grid-mapping variable. ``None`` exports no
            grid mapping, so the result carries no georeferencing. Defaults to ``None``.

    Returns:
        xarray.Dataset: The UGRID-CF encoded dataset.

    Raises:
        pyramids.errors.OptionalPackageDoesNotExist: ``xarray`` is not installed.
    """
    xr = import_xarray(_XARRAY_HINT)

    data_vars: dict[str, Any] = {}

    # The dummy topology and grid-mapping variables carry a length-1 dimension rather than
    # being true 0-d scalars. GDAL's multidimensional reader does not expose a dimensionless
    # variable as an array, so writing this dataset to NetCDF and reading it back -- with
    # `read_file`, or with any other GDAL-based UGRID reader -- found no topology variable at
    # all and reported the file as not being UGRID. pyramids' own writer uses the same
    # size-1 encoding.
    scalar_dim = f"{mesh_name}_scalar"
    data_vars[mesh_name] = xr.DataArray(
        np.array([0], dtype=np.int32),
        dims=(scalar_dim,),
        attrs=_topology_attrs(mesh, mesh_name),
    )

    data_vars[f"{mesh_name}_node_x"] = xr.DataArray(
        np.asarray(mesh.node_x, dtype=np.float64),
        dims=(f"{mesh_name}_nNodes",),
        attrs={
            "standard_name": "projection_x_coordinate",
            "long_name": "node x-coordinate",
        },
    )
    data_vars[f"{mesh_name}_node_y"] = xr.DataArray(
        np.asarray(mesh.node_y, dtype=np.float64),
        dims=(f"{mesh_name}_nNodes",),
        attrs={
            "standard_name": "projection_y_coordinate",
            "long_name": "node y-coordinate",
        },
    )

    fnc_data, fnc_attrs = _connectivity_to_disk(mesh.face_node_connectivity)
    data_vars[f"{mesh_name}_face_nodes"] = xr.DataArray(
        fnc_data,
        dims=(f"{mesh_name}_nFaces", f"{mesh_name}_nMaxFaceNodes"),
        attrs=fnc_attrs,
    )

    if mesh.edge_node_connectivity is not None:
        enc_data, enc_attrs = _connectivity_to_disk(mesh.edge_node_connectivity)
        data_vars[f"{mesh_name}_edge_nodes"] = xr.DataArray(
            enc_data,
            # "Two" is left un-namespaced on purpose: it is the conventional UGRID-CF name
            # for the edge-pair axis, and the ecosystem readers this export targets look for
            # it. Unlike the element dimensions it carries no mesh-specific length -- it is
            # always 2 -- so two meshes in one file share the name without conflicting.
            dims=(f"{mesh_name}_nEdges", "Two"),
            attrs=enc_attrs,
        )

    if mesh.has_face_coords:
        data_vars[f"{mesh_name}_face_x"] = xr.DataArray(
            np.asarray(mesh.face_x, dtype=np.float64), dims=(f"{mesh_name}_nFaces",)
        )
        data_vars[f"{mesh_name}_face_y"] = xr.DataArray(
            np.asarray(mesh.face_y, dtype=np.float64), dims=(f"{mesh_name}_nFaces",)
        )

    # The caller's WKT wins: a UgridDataset keeps its CRS on the dataset (from_arrays stores an
    # epsg there), not on the Mesh2d, so relying on the mesh alone silently dropped the CRS from
    # every export. Fall back to the mesh's own CRS for a bare-Mesh2d caller.
    exported_wkt = crs_wkt if crs_wkt is not None else _mesh_crs_wkt(mesh)
    if exported_wkt is not None:
        data_vars["crs"] = xr.DataArray(
            np.array([0], dtype=np.int32),
            dims=(scalar_dim,),
            attrs={"crs_wkt": exported_wkt, "spatial_ref": exported_wkt},
        )

    for name, var in data_variables.items():
        if var.data is None:
            continue
        data_vars[name] = _build_data_array(xr, var, mesh_name)
        if var.active is not None:
            # The active (wet/dry) mask as an `<var>_active` companion, the xarray counterpart
            # of the NetCDF companion (issue #1331). The data variable above points at it via
            # its `ancillary_variables` attribute (set in `_build_data_array`).
            data_vars[f"{name}{ACTIVE_MASK_SUFFIX}"] = _active_mask_data_array(
                xr, var, mesh_name
            )

    if volume_topology is not None:
        _add_volume_topology_vars(xr, data_vars, volume_topology, mesh_name)

    return xr.Dataset(data_vars=data_vars, attrs={"Conventions": _CONVENTIONS})

volume_topology_from_xarray(ds) #

Rebuild a ragged :class:VolumeTopology from an exported dataset, or None.

The inverse of :func:_add_volume_topology_vars: finds the layer_count / volume_offset arrays by their pyramids_role (the required pair) and the optional vertical coordinates — matched by role across the whole dataset, so no mesh name is needed — and reconstructs the object. None when the dataset carries no such arrays.

Source code in src/pyramids/netcdf/ugrid/interop_xarray.py
def volume_topology_from_xarray(ds: Any) -> VolumeTopology | None:
    """Rebuild a ragged :class:`VolumeTopology` from an exported dataset, or ``None``.

    The inverse of :func:`_add_volume_topology_vars`: finds the ``layer_count`` /
    ``volume_offset`` arrays by their ``pyramids_role`` (the required pair) and the optional
    vertical coordinates — matched by role across the whole dataset, so no mesh name is needed —
    and reconstructs the object. ``None`` when the dataset carries no such arrays.
    """
    by_role: dict[str, Any] = {}
    for var in ds.variables.values():
        role = var.attrs.get(PYRAMIDS_ROLE_ATTR)
        if role in (
            VOLUME_ROLE_LAYER_COUNT,
            VOLUME_ROLE_VOLUME_OFFSET,
            VOLUME_ROLE_LAYER_Z,
            VOLUME_ROLE_INTERFACE_Z,
        ):
            by_role[role] = np.asarray(var.values)
    if (
        VOLUME_ROLE_LAYER_COUNT not in by_role
        or VOLUME_ROLE_VOLUME_OFFSET not in by_role
    ):
        return None
    return VolumeTopology(
        layer_count=by_role[VOLUME_ROLE_LAYER_COUNT],
        volume_offset=by_role[VOLUME_ROLE_VOLUME_OFFSET],
        layer_z=by_role.get(VOLUME_ROLE_LAYER_Z),
        interface_z=by_role.get(VOLUME_ROLE_INTERFACE_Z),
    )

from_xarray(ds) #

Reconstruct a mesh and its variables from a UGRID-CF xarray.Dataset.

The inverse of :func:to_xarray: reads the mesh_topology variable, the node coordinates, the connectivity arrays (normalising start_index / _FillValue back to the internal 0-indexed / -1-fill form), and every data variable (a variable carrying a location attribute). Round-tripping to_xarray then from_xarray reproduces the same nodes, faces and values.

xarray is optional; it is imported inside this function, never at module scope.

Parameters:

Name Type Description Default
ds Any

A UGRID-CF xarray.Dataset, e.g. one produced by :func:to_xarray.

required

Returns:

Type Description
tuple[Mesh2d, dict[str, MeshVariable]]

A (mesh, data_variables) tuple -- a :class:Mesh2d and a {name: MeshVariable} mapping.

Raises:

Type Description
OptionalPackageDoesNotExist

xarray is not installed.

TypeError

ds is not an xarray.Dataset.

Source code in src/pyramids/netcdf/ugrid/interop_xarray.py
def from_xarray(ds: Any) -> tuple[Mesh2d, dict[str, MeshVariable]]:
    """Reconstruct a mesh and its variables from a UGRID-CF ``xarray.Dataset``.

    The inverse of :func:`to_xarray`: reads the ``mesh_topology`` variable, the node coordinates, the
    connectivity arrays (normalising ``start_index`` / ``_FillValue`` back to the internal
    0-indexed / ``-1``-fill form), and every data variable (a variable carrying a ``location``
    attribute). Round-tripping ``to_xarray`` then ``from_xarray`` reproduces the same nodes, faces and
    values.

    ``xarray`` is optional; it is imported inside this function, never at module scope.

    Args:
        ds: A UGRID-CF ``xarray.Dataset``, e.g. one produced by :func:`to_xarray`.

    Returns:
        A ``(mesh, data_variables)`` tuple -- a :class:`Mesh2d` and a ``{name: MeshVariable}`` mapping.

    Raises:
        pyramids.errors.OptionalPackageDoesNotExist: ``xarray`` is not installed.
        TypeError: ``ds`` is not an ``xarray.Dataset``.
    """
    xr = import_xarray(_XARRAY_HINT)
    if not isinstance(ds, xr.Dataset):
        raise TypeError(f"Expected xarray.Dataset, got {type(ds).__name__}")

    mesh_name = _topology_name(ds)
    mesh = _reconstruct_mesh(ds, mesh_name)

    data_variables: dict[str, MeshVariable] = {}
    for name in ds.data_vars:
        da = ds[name]
        attrs = dict(da.attrs)
        if "location" not in attrs:
            continue
        nodata = attrs.get("_FillValue")
        data_variables[str(name)] = MeshVariable(
            name=str(name),
            location=str(attrs["location"]),
            mesh_name=str(attrs.get("mesh", mesh_name)),
            shape=tuple(da.shape),
            attributes=attrs,
            nodata=None if nodata is None else float(nodata),
            units=attrs.get("units"),
            standard_name=attrs.get("standard_name"),
            dimensions=tuple(str(d) for d in da.dims),
            _data=np.asarray(da.values),
        )

    # Re-attach each `<var>_active` companion to the variable it masks (issue #1331). The
    # companions carry no `location`, so the loop above skipped them as data variables.
    for name in ds.variables:
        da = ds[name]
        if da.attrs.get(ACTIVE_MASK_ATTR) != ACTIVE_MASK_ROLE:
            continue
        parent = da.attrs.get(ACTIVE_MASK_PARENT_ATTR)
        if parent in data_variables:
            data_variables[parent].active = np.asarray(da.values).astype(bool)

    return mesh, data_variables

read_mesh_name(ds) #

The name of the topology variable a UGRID-CF dataset declares.

from_xarray answers the mesh and its variables, which leaves the topology's own name unreachable to a caller that wants to keep it. Exposed separately so from_xarray's two-value contract stays as it is.

Parameters:

Name Type Description Default
ds Any

The xarray.Dataset to read.

required

Returns:

Type Description
str

The topology variable's name, or the default when the dataset names none.

Source code in src/pyramids/netcdf/ugrid/interop_xarray.py
def read_mesh_name(ds: Any) -> str:
    """The name of the topology variable a UGRID-CF dataset declares.

    ``from_xarray`` answers the mesh and its variables, which leaves the topology's own name
    unreachable to a caller that wants to keep it. Exposed separately so ``from_xarray``'s
    two-value contract stays as it is.

    Args:
        ds: The ``xarray.Dataset`` to read.

    Returns:
        The topology variable's name, or the default when the dataset names none.
    """
    return _topology_name(ds)

read_crs_wkt(ds) #

The CRS WKT a UGRID-CF dataset carries, or None.

from_xarray answers the mesh and its variables; the CRS lives beside them on the grid-mapping variable, and the caller needs it to stamp the dataset it builds. Exposed separately so from_xarray's two-value contract stays as it is.

Parameters:

Name Type Description Default
ds Any

The xarray.Dataset to read.

required

Returns:

Type Description
str | None

The WKT string, or None when the dataset declares no CRS.

Source code in src/pyramids/netcdf/ugrid/interop_xarray.py
def read_crs_wkt(ds: Any) -> str | None:
    """The CRS WKT a UGRID-CF dataset carries, or ``None``.

    ``from_xarray`` answers the mesh and its variables; the CRS lives beside them on the grid-mapping
    variable, and the caller needs it to stamp the dataset it builds. Exposed separately so
    ``from_xarray``'s two-value contract stays as it is.

    Args:
        ds: The ``xarray.Dataset`` to read.

    Returns:
        The WKT string, or ``None`` when the dataset declares no CRS.
    """
    result: str | None = None
    if "crs" in ds.variables:
        wkt = ds["crs"].attrs.get("crs_wkt") or ds["crs"].attrs.get("spatial_ref")
        if isinstance(wkt, str):
            result = wkt
    return result

to_zarr(mesh, data_variables, store, mesh_name=DEFAULT_MESH_NAME, crs_wkt=None, **to_zarr_kwargs) #

Write a mesh and its variables to a UGRID-encoded Zarr store.

Routes through :func:to_xarray and xarray.Dataset.to_zarr, so the connectivity start_index / _FillValue encoding is handled once in :func:to_xarray and never re-encoded here. xarray and zarr are optional and imported inside this function.

Parameters:

Name Type Description Default
mesh Mesh2d

The mesh topology to write.

required
data_variables dict[str, MeshVariable]

Mapping of variable name to :class:MeshVariable.

required
store Any

A Zarr store -- a path, str, or any MutableMapping to_zarr accepts.

required
mesh_name str

Name for the exported topology variable. Defaults to "mesh2d".

DEFAULT_MESH_NAME
crs_wkt str | None

WKT of the CRS to encode, forwarded to :func:to_xarray. None writes no grid mapping. Defaults to None.

None
**to_zarr_kwargs Any

Extra keyword arguments forwarded to xarray.Dataset.to_zarr (mode defaults to "w").

{}

Returns:

Type Description
Any

Whatever xarray.Dataset.to_zarr returns (a ZarrStore / delayed object).

Raises:

Type Description
OptionalPackageDoesNotExist

xarray or zarr is not installed.

Source code in src/pyramids/netcdf/ugrid/interop_xarray.py
def to_zarr(
    mesh: Mesh2d,
    data_variables: dict[str, MeshVariable],
    store: Any,
    mesh_name: str = DEFAULT_MESH_NAME,
    crs_wkt: str | None = None,
    **to_zarr_kwargs: Any,
) -> Any:
    """Write a mesh and its variables to a UGRID-encoded Zarr store.

    Routes through :func:`to_xarray` and ``xarray.Dataset.to_zarr``, so the connectivity
    ``start_index`` / ``_FillValue`` encoding is handled once in :func:`to_xarray` and never
    re-encoded here. ``xarray`` and ``zarr`` are optional and imported inside this function.

    Args:
        mesh: The mesh topology to write.
        data_variables: Mapping of variable name to :class:`MeshVariable`.
        store: A Zarr store -- a path, ``str``, or any ``MutableMapping`` ``to_zarr`` accepts.
        mesh_name: Name for the exported topology variable. Defaults to ``"mesh2d"``.
        crs_wkt: WKT of the CRS to encode, forwarded to :func:`to_xarray`. ``None`` writes no
            grid mapping. Defaults to ``None``.
        **to_zarr_kwargs: Extra keyword arguments forwarded to ``xarray.Dataset.to_zarr``
            (``mode`` defaults to ``"w"``).

    Returns:
        Whatever ``xarray.Dataset.to_zarr`` returns (a ``ZarrStore`` / delayed object).

    Raises:
        pyramids.errors.OptionalPackageDoesNotExist: ``xarray`` or ``zarr`` is not installed.
    """
    require_optional("zarr", _ZARR_HINT)
    ds = to_xarray(mesh, data_variables, mesh_name=mesh_name, crs_wkt=crs_wkt)
    to_zarr_kwargs.setdefault("mode", "w")
    return ds.to_zarr(store, **to_zarr_kwargs)

from_zarr(store, **open_zarr_kwargs) #

Read a mesh and its variables back from a UGRID-encoded Zarr store.

The inverse of :func:to_zarr: opens the store with xarray.open_zarr and hands the dataset to :func:from_xarray. xarray and zarr are optional and imported inside this function.

Parameters:

Name Type Description Default
store Any

A Zarr store -- a path, str, or MutableMapping open_zarr accepts.

required
**open_zarr_kwargs Any

Extra keyword arguments forwarded to xarray.open_zarr.

{}

Returns:

Type Description
tuple[Mesh2d, dict[str, MeshVariable]]

A (mesh, data_variables) tuple, as :func:from_xarray returns.

Raises:

Type Description
OptionalPackageDoesNotExist

xarray or zarr is not installed.

Source code in src/pyramids/netcdf/ugrid/interop_xarray.py
def from_zarr(
    store: Any, **open_zarr_kwargs: Any
) -> tuple[Mesh2d, dict[str, MeshVariable]]:
    """Read a mesh and its variables back from a UGRID-encoded Zarr store.

    The inverse of :func:`to_zarr`: opens the store with ``xarray.open_zarr`` and hands the dataset to
    :func:`from_xarray`. ``xarray`` and ``zarr`` are optional and imported inside this function.

    Args:
        store: A Zarr store -- a path, ``str``, or ``MutableMapping`` ``open_zarr`` accepts.
        **open_zarr_kwargs: Extra keyword arguments forwarded to ``xarray.open_zarr``.

    Returns:
        A ``(mesh, data_variables)`` tuple, as :func:`from_xarray` returns.

    Raises:
        pyramids.errors.OptionalPackageDoesNotExist: ``xarray`` or ``zarr`` is not installed.
    """
    return from_xarray(open_zarr_dataset(store, **open_zarr_kwargs))

open_zarr_dataset(store, **open_zarr_kwargs) #

Open a Zarr store as an xarray.Dataset, without decoding the mesh.

:func:from_zarr answers the mesh and its variables, which leaves the CRS (a grid-mapping variable beside them) unreachable to a caller that needs to stamp it onto a dataset. Opening is therefore exposed on its own, so a caller can read both the mesh and :func:read_crs_wkt from one open.

Parameters:

Name Type Description Default
store Any

A Zarr store -- a path, str, or MutableMapping open_zarr accepts.

required
**open_zarr_kwargs Any

Extra keyword arguments forwarded to xarray.open_zarr.

{}

Returns:

Type Description
Any

The opened xarray.Dataset.

Raises:

Type Description
OptionalPackageDoesNotExist

xarray or zarr is not installed.

Source code in src/pyramids/netcdf/ugrid/interop_xarray.py
def open_zarr_dataset(store: Any, **open_zarr_kwargs: Any) -> Any:
    """Open a Zarr store as an ``xarray.Dataset``, without decoding the mesh.

    :func:`from_zarr` answers the mesh and its variables, which leaves the CRS (a grid-mapping
    variable beside them) unreachable to a caller that needs to stamp it onto a dataset. Opening is
    therefore exposed on its own, so a caller can read both the mesh and
    :func:`read_crs_wkt` from one open.

    Args:
        store: A Zarr store -- a path, ``str``, or ``MutableMapping`` ``open_zarr`` accepts.
        **open_zarr_kwargs: Extra keyword arguments forwarded to ``xarray.open_zarr``.

    Returns:
        The opened ``xarray.Dataset``.

    Raises:
        pyramids.errors.OptionalPackageDoesNotExist: ``xarray`` or ``zarr`` is not installed.
    """
    require_optional("zarr", _ZARR_HINT)
    xr = import_xarray(_XARRAY_HINT)
    return xr.open_zarr(store, **open_zarr_kwargs)

chunked_variables(data_variables, chunks='auto') #

Return dask-backed arrays for the given mesh variables, for out-of-core reads.

The lazy counterpart of reading MeshVariable.data eagerly: each variable becomes a dask.array so a large temporal variable can be reduced block by block without materialising the whole (n_time, n_elements) array. A file-backed variable (one carrying a source path) is wrapped with :func:pyramids.netcdf._lazy.build_lazy_array so its chunks are read straight from the store; an in-memory variable is wrapped with dask.array.from_array.

A variable with neither -- a metadata-only variable, carrying a declared shape and dtype but no array and no source -- has nothing to chunk and is left out of the result, so the mapping can be smaller than the input.

dask is optional and imported inside this function; a missing dask raises the [lazy] install hint rather than failing with a bare import error.

Parameters:

Name Type Description Default
data_variables dict[str, MeshVariable]

Mapping of variable name to :class:MeshVariable.

required
chunks Any

Chunk spec forwarded to the lazy reader -- "auto" (default), an int, a tuple, or a dict.

'auto'

Returns:

Type Description
dict[str, Any]

A {name: dask.array} mapping, one entry per variable that has readable data.

Raises:

Type Description
OptionalPackageDoesNotExist

dask is not installed.

Source code in src/pyramids/netcdf/ugrid/interop_xarray.py
def chunked_variables(
    data_variables: dict[str, MeshVariable], chunks: Any = "auto"
) -> dict[str, Any]:
    """Return dask-backed arrays for the given mesh variables, for out-of-core reads.

    The lazy counterpart of reading ``MeshVariable.data`` eagerly: each variable becomes a
    ``dask.array`` so a large temporal variable can be reduced block by block without materialising
    the whole ``(n_time, n_elements)`` array. A file-backed variable (one carrying a source path) is
    wrapped with :func:`pyramids.netcdf._lazy.build_lazy_array` so its chunks are read straight from
    the store; an in-memory variable is wrapped with ``dask.array.from_array``.

    A variable with neither -- a metadata-only variable, carrying a declared shape and dtype but no
    array and no source -- has nothing to chunk and is left out of the result, so the mapping can be
    smaller than the input.

    ``dask`` is optional and imported inside this function; a missing dask raises the ``[lazy]``
    install hint rather than failing with a bare import error.

    Args:
        data_variables: Mapping of variable name to :class:`MeshVariable`.
        chunks: Chunk spec forwarded to the lazy reader -- ``"auto"`` (default), an ``int``, a
            ``tuple``, or a ``dict``.

    Returns:
        A ``{name: dask.array}`` mapping, one entry per variable that has readable data.

    Raises:
        pyramids.errors.OptionalPackageDoesNotExist: ``dask`` is not installed.
    """
    dask = import_dask(_DASK_HINT)
    da_mod = dask.array

    result: dict[str, Any] = {}
    for name, var in data_variables.items():
        if not var.has_data_source:
            continue
        source_path = var._source_path
        if source_path is not None:
            result[name] = build_lazy_array(source_path, var.name, chunks, orient=False)
        else:
            data = var.data
            if data is None:
                continue
            result[name] = da_mod.from_array(np.asarray(data), chunks=chunks)
    return result