From 2d4b02c95eded980e7d382661b5d4b828df18aa8 Mon Sep 17 00:00:00 2001 From: Nick Hodgskin <36369090+VeckoTheGecko@users.noreply.github.com> Date: Wed, 25 Mar 2026 16:19:20 +0100 Subject: [PATCH 1/3] MAINT: Fix Variable.to_dict type hint (#11258) --- xarray/core/variable.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xarray/core/variable.py b/xarray/core/variable.py index 84b64f6ff9b..e4f6946f6d3 100644 --- a/xarray/core/variable.py +++ b/xarray/core/variable.py @@ -8,7 +8,7 @@ from collections.abc import Callable, Hashable, Mapping, Sequence from functools import partial from types import EllipsisType -from typing import TYPE_CHECKING, Any, NoReturn, cast +from typing import TYPE_CHECKING, Any, Literal, NoReturn, cast import numpy as np import pandas as pd @@ -582,7 +582,7 @@ def to_index(self) -> pd.Index: return self.to_index_variable().to_index() def to_dict( - self, data: bool | str = "list", encoding: bool = False + self, data: bool | Literal["list", "array"] = "list", encoding: bool = False ) -> dict[str, Any]: """Dictionary representation of variable.""" item: dict[str, Any] = { From 561e5e870580bb91b09ea1da7fa712ab489b7959 Mon Sep 17 00:00:00 2001 From: Alfonso Ladino Date: Wed, 25 Mar 2026 11:08:00 -0500 Subject: [PATCH 2/3] Add `inherit='all_coords'` option to `DataTree.to_dataset()` (#11230) * Add inherit='all' option to DataTree.to_dataset() * Add whats-new entry for inherit='all' (#11230) * Fix prune() signature accidentally modified by ruff-format * Fix mypy errors: remove unused type-ignore, add typing.cast in test * Rename inherit='all' to inherit='all_coords' per review feedback --- doc/whats-new.rst | 3 ++ xarray/core/datatree.py | 57 ++++++++++++++++++++++++++++------- xarray/tests/test_datatree.py | 22 ++++++++++++++ 3 files changed, 71 insertions(+), 11 deletions(-) diff --git a/doc/whats-new.rst b/doc/whats-new.rst index f714e3f7b07..ab96aff4d1a 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -14,6 +14,9 @@ v2026.03.0 (unreleased) New Features ~~~~~~~~~~~~ +- Added ``inherit='all_coords'`` option to :py:meth:`DataTree.to_dataset` to inherit + all parent coordinates, not just indexed ones (:issue:`10812`, :pull:`11230`). + By `Alfonso Ladino `_. Breaking Changes ~~~~~~~~~~~~~~~~ diff --git a/xarray/core/datatree.py b/xarray/core/datatree.py index 3f14f69052d..9c80322212d 100644 --- a/xarray/core/datatree.py +++ b/xarray/core/datatree.py @@ -588,6 +588,28 @@ def _coord_variables(self) -> ChainMap[Hashable, Variable]: *(p._node_coord_variables_with_index for p in self.parents), # type: ignore[arg-type] ) + @property + def _coord_variables_all(self) -> ChainMap[Hashable, Variable]: + return ChainMap( + self._node_coord_variables, + *(p._node_coord_variables for p in self.parents), + ) + + def _resolve_inherit( + self, inherit: bool | Literal["all_coords", "indexes"] + ) -> tuple[Mapping[Hashable, Variable], dict[Hashable, Index]]: + """Resolve the inherit parameter to (coord_vars, indexes).""" + if inherit is False: + return self._node_coord_variables, dict(self._node_indexes) + if inherit is True or inherit == "indexes": + return self._coord_variables, dict(self._indexes) + if inherit == "all_coords": + return self._coord_variables_all, dict(self._indexes) + raise ValueError( + f"Invalid value for inherit: {inherit!r}. " + "Expected True, False, 'indexes', or 'all'." + ) + @property def _dims(self) -> ChainMap[Hashable, int]: return ChainMap(self._node_dims, *(p._node_dims for p in self.parents)) @@ -596,8 +618,12 @@ def _dims(self) -> ChainMap[Hashable, int]: def _indexes(self) -> ChainMap[Hashable, Index]: return ChainMap(self._node_indexes, *(p._node_indexes for p in self.parents)) - def _to_dataset_view(self, rebuild_dims: bool, inherit: bool) -> DatasetView: - coord_vars = self._coord_variables if inherit else self._node_coord_variables + def _to_dataset_view( + self, + rebuild_dims: bool, + inherit: bool | Literal["all_coords", "indexes"] = True, + ) -> DatasetView: + coord_vars, indexes = self._resolve_inherit(inherit) variables = dict(self._data_variables) variables |= coord_vars if rebuild_dims: @@ -636,10 +662,10 @@ def _to_dataset_view(self, rebuild_dims: bool, inherit: bool) -> DatasetView: dims = dict(self._node_dims) return DatasetView._constructor( variables=variables, - coord_names=set(self._coord_variables), + coord_names=set(coord_vars), dims=dims, attrs=self._attrs, - indexes=dict(self._indexes if inherit else self._node_indexes), + indexes=indexes, encoding=self._encoding, close=None, ) @@ -669,30 +695,39 @@ def dataset(self, data: Dataset | None = None) -> None: # xarray-contrib/datatree ds = dataset - def to_dataset(self, inherit: bool = True) -> Dataset: + def to_dataset( + self, inherit: bool | Literal["all_coords", "indexes"] = True + ) -> Dataset: """ Return the data in this node as a new xarray.Dataset object. Parameters ---------- - inherit : bool, optional - If False, only include coordinates and indexes defined at the level - of this DataTree node, excluding any inherited coordinates and indexes. + inherit : bool or {"all_coords", "indexes"}, default True + Controls which coordinates are inherited from parent nodes. + + - True or "indexes": inherit only indexed coordinates (default). + - "all_coords": inherit all coordinates, including non-index coordinates. + - False: only include coordinates defined at this node. See Also -------- DataTree.dataset """ - coord_vars = self._coord_variables if inherit else self._node_coord_variables + coord_vars, indexes = self._resolve_inherit(inherit) variables = dict(self._data_variables) variables |= coord_vars - dims = calculate_dimensions(variables) if inherit else dict(self._node_dims) + dims = ( + dict(self._node_dims) + if inherit is False + else calculate_dimensions(variables) + ) return Dataset._construct_direct( variables, set(coord_vars), dims, None if self._attrs is None else dict(self._attrs), - dict(self._indexes if inherit else self._node_indexes), + indexes, None if self._encoding is None else dict(self._encoding), None, ) diff --git a/xarray/tests/test_datatree.py b/xarray/tests/test_datatree.py index 9384270a0a6..20c383d02cb 100644 --- a/xarray/tests/test_datatree.py +++ b/xarray/tests/test_datatree.py @@ -243,6 +243,28 @@ def test_to_dataset_inherited(self) -> None: assert_identical(tree.to_dataset(inherit=True), base) assert_identical(subtree.to_dataset(inherit=True), sub_and_base) + def test_to_dataset_inherit_all(self) -> None: + base = xr.Dataset(coords={"a": [1], "b": 2}) + sub = xr.Dataset(coords={"c": [3]}) + tree = DataTree.from_dict({"/": base, "/sub": sub}) + subtree = typing.cast(DataTree, tree["sub"]) + + expected = xr.Dataset(coords={"a": [1], "b": 2, "c": [3]}) + assert_identical(subtree.to_dataset(inherit="all_coords"), expected) + assert_identical(tree.to_dataset(inherit="all_coords"), base) + + mid = xr.Dataset(coords={"c": 3.0}) + leaf = xr.Dataset(coords={"d": [4]}) + deep = DataTree.from_dict({"/": base, "/mid": mid, "/mid/leaf": leaf}) + leaf_node = typing.cast(DataTree, deep["/mid/leaf"]) + result = leaf_node.to_dataset(inherit="all_coords") + assert set(result.coords) == {"a", "b", "c", "d"} + + def test_to_dataset_inherit_invalid(self) -> None: + tree = DataTree() + with pytest.raises(ValueError, match="Invalid value for inherit"): + tree.to_dataset(inherit="invalid") # type: ignore[arg-type] + class TestVariablesChildrenNameCollisions: def test_parent_already_has_variable_with_childs_name(self) -> None: From 8ac5ca03a24ce10070d22b1c246dff591b775d91 Mon Sep 17 00:00:00 2001 From: Nick Hodgskin <36369090+VeckoTheGecko@users.noreply.github.com> Date: Wed, 25 Mar 2026 17:53:04 +0100 Subject: [PATCH 3/3] MAINT: Remove setup.py (#11261) --- doc/whats-new.rst | 2 ++ setup.py | 4 ---- 2 files changed, 2 insertions(+), 4 deletions(-) delete mode 100755 setup.py diff --git a/doc/whats-new.rst b/doc/whats-new.rst index ab96aff4d1a..64fddac3460 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -132,6 +132,8 @@ Internal Changes runtime behavior. This enables CI integration for type stub validation and helps prevent type annotation regressions (:issue:`11086`). By `Kristian KollsgÄrd `_. +- Remove ``setup.py`` file (:pull:`11261`). + By `Nick Hodgskin `_. .. _whats-new.2026.02.0: diff --git a/setup.py b/setup.py deleted file mode 100755 index 69343515fd5..00000000000 --- a/setup.py +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env python -from setuptools import setup - -setup(use_scm_version={"fallback_version": "9999"})