@@ -82,6 +82,50 @@ def _check_for_name_collisions(
82
82
)
83
83
84
84
85
+ class DatasetView (Dataset ):
86
+ _wrapping_node : DataTree
87
+
88
+ __slots__ = ["_wrapping_node" ]
89
+
90
+ @classmethod
91
+ def _from_node (
92
+ cls ,
93
+ wrapping_node ,
94
+ ) -> DatasetView :
95
+ """Constructor, using dataset attributes from wrapping node"""
96
+
97
+ obj : DatasetView = object .__new__ (cls )
98
+ obj ._wrapping_node = wrapping_node
99
+ obj ._variables = wrapping_node ._variables
100
+ obj ._coord_names = wrapping_node ._coord_names
101
+ obj ._dims = wrapping_node ._dims
102
+ obj ._indexes = wrapping_node ._indexes
103
+ obj ._attrs = wrapping_node ._attrs
104
+ obj ._close = wrapping_node ._close
105
+ obj ._encoding = wrapping_node ._encoding
106
+
107
+ return obj
108
+
109
+ def __setitem__ (self , key , val ) -> None :
110
+ raise AttributeError (
111
+ "Mutation of the DatasetView is not allowed, please use __setitem__ on the wrapping DataTree node, "
112
+ "or use `DataTree.to_dataset()` if you want a mutable dataset"
113
+ )
114
+
115
+ def __getitem__ (self , key ) -> DataArray :
116
+ # calling the `_get_item` method of DataTree allows path-like access to contents of other nodes
117
+ obj = self ._wrapping_node [key ]
118
+ if isinstance (obj , DataArray ):
119
+ return obj
120
+ else :
121
+ raise KeyError (
122
+ "DatasetView is only allowed to return variables, not entire DataTree nodes"
123
+ )
124
+
125
+ # all API that doesn't modify state in-place can just be inherited from Dataset
126
+ ...
127
+
128
+
85
129
class DataTree (
86
130
TreeNode ,
87
131
MappedDatasetMethodsMixin ,
@@ -201,15 +245,19 @@ def parent(self: DataTree) -> DataTree | None:
201
245
202
246
@parent .setter
203
247
def parent (self : DataTree , new_parent : DataTree ) -> None :
204
- if new_parent and self .name is None :
248
+ if new_parent is not None and self .name is None :
205
249
raise ValueError ("Cannot set an unnamed node as a child of another node" )
206
250
self ._set_parent (new_parent , self .name )
207
251
208
252
@property
209
- def ds (self ) -> Dataset :
210
- """The data in this node, returned as a Dataset."""
211
- # TODO change this to return only an immutable view onto this node's data (see GH #80)
212
- return self .to_dataset ()
253
+ def ds (self ) -> DatasetView :
254
+ """
255
+ An immutable Dataset-like view onto the data in this node.
256
+
257
+ If you want a mutable Dataset containing the same data as in this node,
258
+ use `.to_dataset()` instead.
259
+ """
260
+ return DatasetView ._from_node (self )
213
261
214
262
@ds .setter
215
263
def ds (self , data : Union [Dataset , DataArray ] = None ) -> None :
0 commit comments