Skip to content

Commit d030502

Browse files
DataTree.lineage should be renamed to .parents xarray-contrib/datatree#286
* Replace 'lineage' occurences in code by 'parents' * Replace 'lineage' occurences in api.rst by 'parents' * MyPy ignore * whats-new * Re-introduce lineage and deprecate it * Added credit * Added back lineage in api.rst * Update datatree/tests/test_treenode.py Co-authored-by: Tom Nicholas <tom@cworthy.org> * Updated lineage and parents, broke tests * Replaced slash by point, tests pass * New PR * Fix tests * Remove iter_parents from api.rst * Avoid entering into the more complex else branch --------- Co-authored-by: Tom Nicholas <tom@cworthy.org>
1 parent a156da9 commit d030502

File tree

5 files changed

+69
-33
lines changed

5 files changed

+69
-33
lines changed

xarray/datatree_/.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,4 +132,5 @@ dmypy.json
132132
# version
133133
_version.py
134134

135-
.vscode
135+
# Ignore vscode specific settings
136+
.vscode/

xarray/datatree_/datatree/tests/test_treenode.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ def test_ancestors(self):
9595
michael = TreeNode(children={"Tony": tony})
9696
vito = TreeNode(children={"Michael": michael})
9797
assert tony.root is vito
98-
assert tony.lineage == (tony, michael, vito)
98+
assert tony.parents == (michael, vito)
9999
assert tony.ancestors == (vito, michael, tony)
100100

101101

@@ -279,12 +279,15 @@ def test_levelorderiter(self):
279279

280280

281281
class TestAncestry:
282+
def test_parents(self):
283+
_, leaf = create_test_tree()
284+
expected = ["e", "b", "a"]
285+
assert [node.name for node in leaf.parents] == expected
286+
282287
def test_lineage(self):
283288
_, leaf = create_test_tree()
284-
lineage = leaf.lineage
285289
expected = ["f", "e", "b", "a"]
286-
for node, expected_name in zip(lineage, expected):
287-
assert node.name == expected_name
290+
assert [node.name for node in leaf.lineage] == expected
288291

289292
def test_ancestors(self):
290293
_, leaf = create_test_tree()

xarray/datatree_/datatree/treenode.py

Lines changed: 53 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,7 @@ def _check_loop(self, new_parent: Tree | None) -> None:
121121
)
122122

123123
def _is_descendant_of(self, node: Tree) -> bool:
124-
_self, *lineage = list(node.lineage)
125-
return any(n is self for n in lineage)
124+
return any(n is self for n in node.parents)
126125

127126
def _detach(self, parent: Tree | None) -> None:
128127
if parent is not None:
@@ -236,26 +235,53 @@ def _post_attach_children(self: Tree, children: Mapping[str, Tree]) -> None:
236235
"""Method call after attaching `children`."""
237236
pass
238237

239-
def iter_lineage(self: Tree) -> Iterator[Tree]:
238+
def _iter_parents(self: Tree) -> Iterator[Tree]:
240239
"""Iterate up the tree, starting from the current node."""
241-
node: Tree | None = self
240+
node: Tree | None = self.parent
242241
while node is not None:
243242
yield node
244243
node = node.parent
245244

245+
def iter_lineage(self: Tree) -> Tuple[Tree, ...]:
246+
"""Iterate up the tree, starting from the current node."""
247+
from warnings import warn
248+
249+
warn(
250+
"`iter_lineage` has been deprecated, and in the future will raise an error."
251+
"Please use `parents` from now on.",
252+
DeprecationWarning,
253+
)
254+
return tuple((self, *self.parents))
255+
246256
@property
247257
def lineage(self: Tree) -> Tuple[Tree, ...]:
248258
"""All parent nodes and their parent nodes, starting with the closest."""
249-
return tuple(self.iter_lineage())
259+
from warnings import warn
260+
261+
warn(
262+
"`lineage` has been deprecated, and in the future will raise an error."
263+
"Please use `parents` from now on.",
264+
DeprecationWarning,
265+
)
266+
return self.iter_lineage()
267+
268+
@property
269+
def parents(self: Tree) -> Tuple[Tree, ...]:
270+
"""All parent nodes and their parent nodes, starting with the closest."""
271+
return tuple(self._iter_parents())
250272

251273
@property
252274
def ancestors(self: Tree) -> Tuple[Tree, ...]:
253275
"""All parent nodes and their parent nodes, starting with the most distant."""
254-
if self.parent is None:
255-
return (self,)
256-
else:
257-
ancestors = tuple(reversed(list(self.lineage)))
258-
return ancestors
276+
277+
from warnings import warn
278+
279+
warn(
280+
"`ancestors` has been deprecated, and in the future will raise an error."
281+
"Please use `parents`. Example: `tuple(reversed(node.parents))`",
282+
DeprecationWarning,
283+
)
284+
return tuple((*reversed(self.parents), self))
259285

260286
@property
261287
def root(self: Tree) -> Tree:
@@ -351,7 +377,7 @@ def level(self: Tree) -> int:
351377
depth
352378
width
353379
"""
354-
return len(self.ancestors) - 1
380+
return len(self.parents)
355381

356382
@property
357383
def depth(self: Tree) -> int:
@@ -591,9 +617,9 @@ def path(self) -> str:
591617
if self.is_root:
592618
return "/"
593619
else:
594-
root, *ancestors = self.ancestors
620+
root, *ancestors = tuple(reversed(self.parents))
595621
# don't include name of root because (a) root might not have a name & (b) we want path relative to root.
596-
names = [node.name for node in ancestors]
622+
names = [*(node.name for node in ancestors), self.name]
597623
return "/" + "/".join(names)
598624

599625
def relative_to(self: NamedNode, other: NamedNode) -> str:
@@ -608,7 +634,7 @@ def relative_to(self: NamedNode, other: NamedNode) -> str:
608634
)
609635

610636
this_path = NodePath(self.path)
611-
if other.path in list(ancestor.path for ancestor in self.lineage):
637+
if other.path in list(parent.path for parent in (self, *self.parents)):
612638
return str(this_path.relative_to(other.path))
613639
else:
614640
common_ancestor = self.find_common_ancestor(other)
@@ -623,18 +649,17 @@ def find_common_ancestor(self, other: NamedNode) -> NamedNode:
623649
624650
Raise ValueError if they are not in the same tree.
625651
"""
626-
common_ancestor = None
627-
for node in other.iter_lineage():
628-
if node.path in [ancestor.path for ancestor in self.ancestors]:
629-
common_ancestor = node
630-
break
652+
if self is other:
653+
return self
631654

632-
if not common_ancestor:
633-
raise NotFoundInTreeError(
634-
"Cannot find common ancestor because nodes do not lie within the same tree"
635-
)
655+
other_paths = [op.path for op in other.parents]
656+
for parent in (self, *self.parents):
657+
if parent.path in other_paths:
658+
return parent
636659

637-
return common_ancestor
660+
raise NotFoundInTreeError(
661+
"Cannot find common ancestor because nodes do not lie within the same tree"
662+
)
638663

639664
def _path_to_ancestor(self, ancestor: NamedNode) -> NodePath:
640665
"""Return the relative path from this node to the given ancestor node"""
@@ -643,12 +668,12 @@ def _path_to_ancestor(self, ancestor: NamedNode) -> NodePath:
643668
raise NotFoundInTreeError(
644669
"Cannot find relative path to ancestor because nodes do not lie within the same tree"
645670
)
646-
if ancestor.path not in list(a.path for a in self.ancestors):
671+
if ancestor.path not in list(a.path for a in (self, *self.parents)):
647672
raise NotFoundInTreeError(
648673
"Cannot find relative path to ancestor because given node is not an ancestor of this node"
649674
)
650675

651-
lineage_paths = list(ancestor.path for ancestor in self.lineage)
652-
generation_gap = list(lineage_paths).index(ancestor.path)
653-
path_upwards = "../" * generation_gap if generation_gap > 0 else "/"
676+
parents_paths = list(parent.path for parent in (self, *self.parents))
677+
generation_gap = list(parents_paths).index(ancestor.path)
678+
path_upwards = "../" * generation_gap if generation_gap > 0 else "."
654679
return NodePath(path_upwards)

xarray/datatree_/docs/source/api.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ Attributes relating to the recursive tree-like structure of a ``DataTree``.
3838
DataTree.descendants
3939
DataTree.siblings
4040
DataTree.lineage
41+
DataTree.parents
4142
DataTree.ancestors
4243
DataTree.groups
4344

xarray/datatree_/docs/source/whats-new.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ New Features
2626
Breaking changes
2727
~~~~~~~~~~~~~~~~
2828

29+
- Renamed `DataTree.lineage` to `DataTree.parents` to match `pathlib` vocabulary
30+
(:issue:`283`, :pull:`286`)
2931
- Minimum required version of xarray is now 2023.12.0, i.e. the latest version.
3032
This is required to prevent recent changes to xarray's internals from breaking datatree.
3133
(:issue:`293`, :pull:`294`)
@@ -37,6 +39,10 @@ Breaking changes
3739
Deprecations
3840
~~~~~~~~~~~~
3941

42+
- Renamed `DataTree.lineage` to `DataTree.parents` to match `pathlib` vocabulary
43+
(:issue:`283`, :pull:`286`). `lineage` is now deprecated and use of `parents` is encouraged.
44+
By `Etienne Schalk <https://github.com/etienneschalk>`_.
45+
4046
Bug fixes
4147
~~~~~~~~~
4248
- Keep attributes on nodes containing no data in :py:func:`map_over_subtree`. (:issue:`278`, :pull:`279`)

0 commit comments

Comments
 (0)