Repository: pydata/xarray

Issue:
DataTree.__delitem__ should support deleting variables, not only child nodes
### What happened?

Currently this raises a very uninformative error message:
```
In [1]: from xarray.core.datatree import DataTree

In [2]: tree = DataTree()

In [3]: tree['x'] = 1

In [4]: tree
Out[4]:
<xarray.DataTree>
Group: /
    Dimensions:  ()
    Data variables:
        x        int64 8B 1

In [5]: del tree['x']
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
Cell In[5], line 1
----> 1 del tree['x']

File ~/dev/xarray/xarray/core/treenode.py:563, in TreeNode.__delitem__(self, key)
    561     child.orphan()
    562 else:
--> 563     raise KeyError("Cannot delete")

KeyError: 'Cannot delete'
```

### What did you expect to happen?

_No response_

### Minimal Complete Verifiable Example

_No response_

### MVCE confirmation

- [ ] Minimal example — the example is as focused as reasonably possible to demonstrate the underlying issue in xarray.
- [ ] Complete example — the example is self-contained, including all data and the text of any traceback.
- [ ] Verifiable example — the example copy & pastes into an IPython prompt or [Binder notebook](https://mybinder.org/v2/gh/pydata/xarray/main?urlpath=lab/tree/doc/examples/blank_template.ipynb), returning the result.
- [ ] New issue — a search of GitHub Issues suggests this is not a duplicate.
- [ ] Recent environment — the issue occurs with the latest version of xarray and its dependencies.

### Relevant log output

_No response_

### Anything else we need to know?

_No response_

### Environment

<details>



</details>


Discussion:

Patch:
diff --git a/xarray/core/datatree.py b/xarray/core/datatree.py
index 458d0efa..7eb491da 100644
--- a/xarray/core/datatree.py
+++ b/xarray/core/datatree.py
@@ -910,6 +910,24 @@ class DataTree(
         else:
             raise ValueError("Invalid format for key")
 
+    def __delitem__(self, key: str) -> None:
+        """Remove a variable or child node from this datatree node."""
+        if key in self.children:
+            super().__delitem__(key)
+
+        elif key in self._node_coord_variables:
+            if key in self._node_indexes:
+                del self._node_indexes[key]
+            del self._node_coord_variables[key]
+            self._node_dims = calculate_dimensions(self.variables)
+
+        elif key in self._data_variables:
+            del self._data_variables[key]
+            self._node_dims = calculate_dimensions(self.variables)
+
+        else:
+            raise KeyError(key)
+
     @overload
     def update(self, other: Dataset) -> None: ...
 
diff --git a/xarray/core/treenode.py b/xarray/core/treenode.py
index 9dfd3465..84ce392a 100644
--- a/xarray/core/treenode.py
+++ b/xarray/core/treenode.py
@@ -559,14 +559,14 @@ class TreeNode(Generic[Tree]):
         else:
             current_node._set(name, item)
 
-    def __delitem__(self: Tree, key: str):
+    def __delitem__(self: Tree, key: str) -> None:
         """Remove a child node from this tree object."""
         if key in self.children:
             child = self._children[key]
             del self._children[key]
             child.orphan()
         else:
-            raise KeyError("Cannot delete")
+            raise KeyError(key)
 
     def same_tree(self, other: Tree) -> bool:
         """True if other node is in the same tree as this node."""


Test patch:
diff --git a/xarray/tests/test_datatree.py b/xarray/tests/test_datatree.py
index 3368dd1f..20bea8fd 100644
--- a/xarray/tests/test_datatree.py
+++ b/xarray/tests/test_datatree.py
@@ -7,6 +7,7 @@ import numpy as np
 import pytest
 
 import xarray as xr
+from xarray import Dataset
 from xarray.core.datatree import DataTree
 from xarray.core.datatree_ops import _MAPPED_DOCSTRING_ADDENDUM, insert_doc_addendum
 from xarray.core.treenode import NotFoundInTreeError
@@ -525,7 +526,39 @@ class TestSetItem:
         assert_identical(results.to_dataset(), expected)
 
 
-class TestDictionaryInterface: ...
+def test_delitem():
+    ds = Dataset({"a": 0}, coords={"x": ("x", [1, 2]), "z": "a"})
+    dt = DataTree(ds, children={"c": DataTree()})
+
+    with pytest.raises(KeyError):
+        del dt["foo"]
+
+    # test delete children
+    del dt["c"]
+    assert dt.children == {}
+    assert set(dt.variables) == {"x", "z", "a"}
+    with pytest.raises(KeyError):
+        del dt["c"]
+
+    # test delete variables
+    del dt["a"]
+    assert set(dt.coords) == {"x", "z"}
+    with pytest.raises(KeyError):
+        del dt["a"]
+
+    # test delete coordinates
+    del dt["z"]
+    assert set(dt.coords) == {"x"}
+    with pytest.raises(KeyError):
+        del dt["z"]
+
+    # test delete indexed coordinates
+    del dt["x"]
+    assert dt.variables == {}
+    assert dt.coords == {}
+    assert dt.indexes == {}
+    with pytest.raises(KeyError):
+        del dt["x"]
 
 
 class TestTreeFromDict: