Skip to content
This repository was archived by the owner on Oct 24, 2024. It is now read-only.

Commit 07ac0ae

Browse files
DatasetView in map_over_subtree (#269)
* re-forbid initializing a DatasetView directly * test that map_over_subtree definitely doesn't modify data in-place * chnge behaviour to return an immmutable DatasetView within map_over_subtree * improve error messages * whatsew * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * remove unused return variable --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent ff86d3c commit 07ac0ae

File tree

4 files changed

+25
-16
lines changed

4 files changed

+25
-16
lines changed

datatree/datatree.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -108,13 +108,10 @@ class DatasetView(Dataset):
108108
An immutable Dataset-like view onto the data in a single DataTree node.
109109
110110
In-place operations modifying this object should raise an AttributeError.
111+
This requires overriding all inherited constructors.
111112
112113
Operations returning a new result will return a new xarray.Dataset object.
113114
This includes all API on Dataset, which will be inherited.
114-
115-
This requires overriding all inherited private constructors.
116-
117-
We leave the public init constructor because it is used by type() in some xarray code (see datatree GH issue #188)
118115
"""
119116

120117
# TODO what happens if user alters (in-place) a DataArray they extracted from this object?
@@ -130,6 +127,14 @@ class DatasetView(Dataset):
130127
"_variables",
131128
)
132129

130+
def __init__(
131+
self,
132+
data_vars: Optional[Mapping[Any, Any]] = None,
133+
coords: Optional[Mapping[Any, Any]] = None,
134+
attrs: Optional[Mapping[Any, Any]] = None,
135+
):
136+
raise AttributeError("DatasetView objects are not to be initialized directly")
137+
133138
@classmethod
134139
def _from_node(
135140
cls,
@@ -150,14 +155,16 @@ def _from_node(
150155

151156
def __setitem__(self, key, val) -> None:
152157
raise AttributeError(
153-
"Mutation of the DatasetView is not allowed, please use __setitem__ on the wrapping DataTree node, "
154-
"or use `DataTree.to_dataset()` if you want a mutable dataset"
158+
"Mutation of the DatasetView is not allowed, please use `.__setitem__` on the wrapping DataTree node, "
159+
"or use `dt.to_dataset()` if you want a mutable dataset. If calling this from within `map_over_subtree`,"
160+
"use `.copy()` first to get a mutable version of the input dataset."
155161
)
156162

157163
def update(self, other) -> None:
158164
raise AttributeError(
159-
"Mutation of the DatasetView is not allowed, please use .update on the wrapping DataTree node, "
160-
"or use `DataTree.to_dataset()` if you want a mutable dataset"
165+
"Mutation of the DatasetView is not allowed, please use `.update` on the wrapping DataTree node, "
166+
"or use `dt.to_dataset()` if you want a mutable dataset. If calling this from within `map_over_subtree`,"
167+
"use `.copy()` first to get a mutable version of the input dataset."
161168
)
162169

163170
# FIXME https://github.com/python/mypy/issues/7328

datatree/mapping.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -190,15 +190,14 @@ def _map_over_subtree(*args, **kwargs) -> DataTree | Tuple[DataTree, ...]:
190190
*args_as_tree_length_iterables,
191191
*list(kwargs_as_tree_length_iterables.values()),
192192
):
193-
node_args_as_datasets = [
194-
a.to_dataset() if isinstance(a, DataTree) else a
195-
for a in all_node_args[:n_args]
193+
node_args_as_datasetviews = [
194+
a.ds if isinstance(a, DataTree) else a for a in all_node_args[:n_args]
196195
]
197-
node_kwargs_as_datasets = dict(
196+
node_kwargs_as_datasetviews = dict(
198197
zip(
199198
[k for k in kwargs_as_tree_length_iterables.keys()],
200199
[
201-
v.to_dataset() if isinstance(v, DataTree) else v
200+
v.ds if isinstance(v, DataTree) else v
202201
for v in all_node_args[n_args:]
203202
],
204203
)
@@ -210,7 +209,7 @@ def _map_over_subtree(*args, **kwargs) -> DataTree | Tuple[DataTree, ...]:
210209
# Now we can call func on the data in this particular set of corresponding nodes
211210
results = (
212211
func_with_error_context(
213-
*node_args_as_datasets, **node_kwargs_as_datasets
212+
*node_args_as_datasetviews, **node_kwargs_as_datasetviews
214213
)
215214
if node_of_first_tree.has_data
216215
else None

datatree/tests/test_mapping.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ def weighted_mean(ds):
304304

305305
dt.map_over_subtree(weighted_mean)
306306

307-
def test_alter_inplace(self):
307+
def test_alter_inplace_forbidden(self):
308308
simpsons = DataTree.from_dict(
309309
d={
310310
"/": xr.Dataset({"age": 83}),
@@ -322,7 +322,8 @@ def fast_forward(ds: xr.Dataset, years: float) -> xr.Dataset:
322322
ds["age"] = ds["age"] + years
323323
return ds
324324

325-
simpsons.map_over_subtree(fast_forward, years=10)
325+
with pytest.raises(AttributeError):
326+
simpsons.map_over_subtree(fast_forward, years=10)
326327

327328

328329
@pytest.mark.xfail

docs/source/whats-new.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ Breaking changes
3232

3333
- Nodes containing only attributes but no data are now ignored by :py:func:`map_over_subtree` (:issue:`262`, :pull:`263`)
3434
By `Tom Nicholas <https://github.com/TomNicholas>`_.
35+
- Disallow altering of given dataset inside function called by :py:func:`map_over_subtree` (:pull:`269`, reverts part of :pull:`194`).
36+
By `Tom Nicholas <https://github.com/TomNicholas>`_.
3537

3638
Deprecations
3739
~~~~~~~~~~~~

0 commit comments

Comments
 (0)