Skip to content

BinaryNodeObject

src.drawpyo.diagram_types.binary_tree.BinaryNodeObject

Bases: NodeObject

NodeObject variant for binary trees exposing left and right properties and enforcing at most two children.

Source code in src/drawpyo/diagram_types/binary_tree.py
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
class BinaryNodeObject(NodeObject):
    """
    NodeObject variant for binary trees exposing `left` and `right` properties
    and enforcing at most two children.
    """

    def __init__(self, tree=None, **kwargs) -> None:
        """
        Initialize a binary node with exactly 2 child slots [left, right].

        If `tree_children` is provided, it is normalized to two elements.
        """
        children: List[Optional[BinaryNodeObject]] = kwargs.get("tree_children", [])

        # Normalize provided list to exactly 2 elements
        if len(children) == 0:
            normalized = [None, None]
        elif len(children) == 1:
            normalized = [children[0], None]
        elif len(children) == 2:
            normalized = children[:]
        else:
            raise ValueError("BinaryNodeObject cannot have more than two children")

        kwargs["tree_children"] = normalized
        super().__init__(tree=tree, **kwargs)

    # ---------------------------------------------------------
    # Private methods
    # ---------------------------------------------------------

    def _ensure_two_slots(self) -> None:
        """Ensure tree_children always has exactly 2 slots."""
        if len(self.tree_children) < 2:
            missing = 2 - len(self.tree_children)
            self.tree_children.extend([None] * missing)
        elif len(self.tree_children) > 2:
            self.tree_children[:] = self.tree_children[:2]

    def _detach_from_old_parent(self, node: NodeObject) -> None:
        """Remove `node` from its old parent's children (if any)."""
        parent = getattr(node, "tree_parent", None)
        if parent is None or parent is self:
            return

        if hasattr(parent, "tree_children"):
            for i, existing in enumerate(parent.tree_children):
                if existing is node:
                    parent.tree_children[i] = None
                    break

        node._tree_parent = None

    def _clear_existing_slot(self, node: NodeObject, target_index: int) -> None:
        """
        If `node` already belongs to this parent in the *other* slot,
        clear the old slot.
        """
        for i, existing in enumerate(self.tree_children):
            if existing is node and i != target_index:
                self.tree_children[i] = None

    def _assign_child(self, index: int, node: Optional[NodeObject]) -> None:
        """
        Handles:
        - Ensuring slot count
        - Clearing old child if assigning None
        - Preventing more than 2 distinct children
        - Correctly detaching and reattaching node
        """
        self._ensure_two_slots()
        existing = self.tree_children[index]

        if node is None:
            if existing is not None:
                self.tree_children[index] = None
                existing._tree_parent = None
            return

        if not node in self.tree_children:
            other = 1 - index
            if (
                self.tree_children[index] is not None
                and self.tree_children[other] is not None
            ):
                raise ValueError("BinaryNodeObject cannot have more than two children")

        self._detach_from_old_parent(node)

        self._clear_existing_slot(node, index)

        self.tree_children[index] = node
        node._tree_parent = self

    # ---------------------------------------------------------
    # Properties and setters
    # ---------------------------------------------------------

    @property
    def left(self) -> Optional[NodeObject]:
        self._ensure_two_slots()
        return self.tree_children[0]

    @left.setter
    def left(self, node: Optional[NodeObject]) -> None:
        self._assign_child(0, node)

    @property
    def right(self) -> Optional[NodeObject]:
        self._ensure_two_slots()
        return self.tree_children[1]

    @right.setter
    def right(self, node: Optional[NodeObject]) -> None:
        self._assign_child(1, node)

__init__(tree=None, **kwargs)

Initialize a binary node with exactly 2 child slots [left, right].

If tree_children is provided, it is normalized to two elements.

Source code in src/drawpyo/diagram_types/binary_tree.py
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
def __init__(self, tree=None, **kwargs) -> None:
    """
    Initialize a binary node with exactly 2 child slots [left, right].

    If `tree_children` is provided, it is normalized to two elements.
    """
    children: List[Optional[BinaryNodeObject]] = kwargs.get("tree_children", [])

    # Normalize provided list to exactly 2 elements
    if len(children) == 0:
        normalized = [None, None]
    elif len(children) == 1:
        normalized = [children[0], None]
    elif len(children) == 2:
        normalized = children[:]
    else:
        raise ValueError("BinaryNodeObject cannot have more than two children")

    kwargs["tree_children"] = normalized
    super().__init__(tree=tree, **kwargs)