Skip to content

TreeSource

Usage

Data sources are abstractions that allow you to define the data being managed by your application independent of the GUI representation of that data. For details on the use of data sources, see the topic guide.

TreeSource is an implementation of an ordered hierarchical tree of values. When a TreeSource is created, it can be given a list of accessors - these are the attributes that all items managed by the TreeSource will have. The API provided by TreeSource is [list][]-like; the operations you'd expect on a normal Python list, such as insert, remove, index, and indexing with [], are also possible on a TreeSource. These methods are available on the TreeSource itself to manipulate root nodes, and also on each node within the tree.

from toga.sources import TreeSource

source = TreeSource(
    accessors=["name", "height"],
    data={
        "Animals": [
            ({"name": "Numbat", "height": 0.15}, None),
            ({"name": "Thylacine", "height": 0.6}, None),
        ],
        "Plants": [
            ({"name": "Woollybush", "height": 2.4}, None),
            ({"name": "Boronia", "height": 0.9}, None),
        ],
    }
)

# Get the Animal group in the source.
# The Animal group won't have a "height" attribute.
group = source[0]
print(f"Group's name is {group.name}")

# Get the second item in the animal group
animal = group[1]
print(f"Animals's name is {animal.name}; it is {animal.height}m tall.")

# Find an animal with a name of "Thylacine"
row = source.find(parent=source[0], {"name": "Thylacine"})

# Remove that row from the data. Even though "Thylacine" isn't a root node,
# remove will find it and remove it from the list of animals.
source.remove(row)

# Insert a new item at the start of the list of animals.
group.insert(0, {"name": "Bettong", "height": 0.35})

# Insert a new root item in the middle of the list of root nodes
source.insert(1, {"name": "Minerals"})

The TreeSource manages a tree of Node objects. Each Node has all the attributes described by the source's accessors. A Node object will be constructed for each item that is added to the TreeSource.

Each Node object in the TreeSource can have children; those children can in turn have their own children. A child that cannot have children is called a leaf Node. Whether a child can have children is independent of whether it does have children - it is possible for a Node to have no children and not be a leaf node. This is analogous to files and directories on a file system: a file is a leaf Node, as it cannot have children; a directory can contain files and other directories in it, but it can also be empty. An empty directory would not be a leaf Node.

When creating a single Node for a TreeSource (e.g., when inserting a new item), the data for the Node can be specified as:

  • A dictionary; or
  • Any iterable object (except for a string), with the accessors being mapped onto the items in the iterable in order of definition; or
  • Any other object, which will be mapped onto the first accessor.

If the TreeSource was constructed without specifying accessors, node data must be in dictionary form.

When constructing an entire TreeSource, the data can be specified as:

  • A dictionary. The keys of the dictionary will be converted into Nodes, and used as parents; the values of the dictionary will become the children of their corresponding parent.
  • Any other iterable object (except a string). Each value in the iterable will be treated as a 2-item tuple, with the first item being data for the parent Node, and the second item being the child data.
  • Any other object will be converted into a single node with no children.

If the TreeSource was constructed without specifying accessors, value data for each node must be in dictionary form.

When specifying children, a value of [None][] for the children will result in the creation of a leaf node. Any other value will be processed recursively - so, a child specifier can itself be a dictionary, an iterable of 2-tuples, or data for a single child, and so on.

Although Toga provides TreeSource, you are not required to create one directly. A TreeSource will be transparently constructed for you if you provide one of the items listed above (e.g. [list][], [dict][], etc) to a GUI widget that displays tree-like data (i.e., toga.Tree).

Custom TreeSources

For more complex applications, you can replace TreeSource with a custom data source class. Such a class must:

  • Inherit from Source
  • Provide the same methods as TreeSource
  • Return items whose attributes match the accessors expected by the widget
  • Generate a change notification when any of those attributes change
  • Generate insert, remove and clear notifications when nodes are added or removed

Reference

Bases: Row[T]

Source code in core/src/toga/sources/tree_source.py
 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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
class Node(Row[T]):
    _source: TreeSource

    def __init__(self, **data: T):
        """Create a new Node object.

        The keyword arguments specified in the constructor will be converted into
        attributes on the new object.

        When initially constructed, the Node will be a leaf node (i.e., no children,
        and marked unable to have children).

        When any public attributes of the node are modified (i.e., any attribute whose
        name doesn't start with `_`), the source to which the node belongs will be
        notified.
        """
        super().__init__(**data)
        self._children: list[Node[T]] | None = None
        self._parent: Node[T] | None = None

    def __repr__(self) -> str:
        descriptor = " ".join(
            f"{attr}={getattr(self, attr)!r}"
            for attr in sorted(self.__dict__)
            if not attr.startswith("_")
        )
        if not descriptor:
            descriptor = "(no attributes)"
        if self._children is not None:
            descriptor += f"; {len(self)} children"

        return (
            f"<{'Leaf ' if self._children is None else ''}Node "
            f"{id(self):x} {descriptor}>"
        )

    ######################################################################
    # Methods required by the TreeSource interface
    ######################################################################

    def __getitem__(self, index: int) -> Node[T]:
        if self._children is None:
            raise ValueError(f"{self} is a leaf node")

        return self._children[index]

    def __delitem__(self, index: int) -> None:
        if self._children is None:
            raise ValueError(f"{self} is a leaf node")

        child = self._children[index]
        del self._children[index]

        # Child isn't part of this source, or a child of this node anymore.
        child._parent = None
        child._source = None

        self._source.notify("remove", parent=self, index=index, item=child)

    def __len__(self) -> int:
        if self.can_have_children():
            return len(self._children)
        else:
            return 0

    def can_have_children(self) -> bool:
        """Can the node have children?

        A value of [`True`][] does not necessarily mean the node *has* any children,
        only that the node is *allowed* to have children. The value of `len()` for
        the node indicates the number of actual children.
        """
        return self._children is not None

    ######################################################################
    # Utility methods to make TreeSource more list-like
    ######################################################################

    def __iter__(self) -> Iterator[Node[T]]:
        return iter(self._children or [])

    def __setitem__(self, index: int, data: object) -> None:
        """Set the value of a specific child in the Node.

        :param index: The index of the child to change
        :param data: The data for the updated child. This data will be converted
            into a Node object.
        """
        if self._children is None:
            raise ValueError(f"{self} is a leaf node")

        old_node = self._children[index]
        old_node._parent = None
        old_node._source = None

        node = self._source._create_node(parent=self, data=data)
        self._children[index] = node
        self._source.notify("change", item=node)

    def insert(self, index: int, data: object, children: object = None) -> Node[T]:
        """Insert a node as a child of this node a specific index.

        :param index: The index at which to insert the new child.
        :param data: The data to insert into the Node as a child. This data will be
            converted into a Node object.
        :param children: The data for the children of the new child node.
        :returns: The new added child Node object.
        """
        if self._children is None:
            self._children = []

        if index < 0:
            index = max(len(self) + index, 0)
        else:
            index = min(len(self), index)

        node = self._source._create_node(parent=self, data=data, children=children)
        self._children.insert(index, node)
        self._source.notify("insert", parent=self, index=index, item=node)
        return node

    def append(self, data: object, children: object = None) -> Node[T]:
        """Append a node to the end of the list of children of this node.

        :param data: The data to append as a child of this node. This data will be
            converted into a Node object.
        :param children: The data for the children of the new child node.
        :returns: The new added child Node object.
        """
        return self.insert(len(self), data=data, children=children)

    def remove(self, child: Node[T]) -> None:
        """Remove a child node from this node.

        :param child: The child node to remove from this node.
        """
        # Index will raise ValueError if the node is a leaf
        del self[self.index(child)]

    def index(self, child: Node[T]) -> int:
        """The index of a specific node in children of this node.

        This search uses Node instances, and searches for an *instance* match.
        If two Node instances have the same values, only the Node that is the
        same Python instance will match. To search for values based on equality,
        use [`Node.find()`][toga.sources.Node.find].

        :param child: The node to find in the children of this node.
        :returns: The index of the node in the children of this node.
        :raises ValueError: If the node cannot be found in children of this node.
        """
        if self._children is None:
            raise ValueError(f"{self} is a leaf node")

        return self._children.index(child)

    def find(self, data: object, start: Node[T] | None = None) -> Node[T]:
        """Find the first item in the child nodes of this node that matches all the
        provided attributes.

        This is a value based search, rather than an instance search. If two Node
        instances have the same values, the first instance that matches will be
        returned. To search for a second instance, provide the first found instance as
        the `start` argument. To search for a specific Node instance, use the
        [`Node.index()`][toga.sources.Node.index].

        :param data: The data to search for. Only the values specified in data will be
            used as matching criteria; if the node contains additional data attributes,
            they won't be considered as part of the match.
        :param start: The instance from which to start the search. Defaults to `None`,
            indicating that the first match should be returned.
        :return: The matching Node object.
        :raises ValueError: If no match is found.
        :raises ValueError: If the node is a leaf node.
        """
        if self._children is None:
            raise ValueError(f"{self} is a leaf node")

        return _find_item(
            candidates=self._children,
            data=data,
            accessors=self._source._accessors,
            start=start,
            error=f"No child matching {data!r} in {self}",
            value_type="node",
        )

__init__(**data)

Create a new Node object.

The keyword arguments specified in the constructor will be converted into attributes on the new object.

When initially constructed, the Node will be a leaf node (i.e., no children, and marked unable to have children).

When any public attributes of the node are modified (i.e., any attribute whose name doesn't start with _), the source to which the node belongs will be notified.

Source code in core/src/toga/sources/tree_source.py
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
def __init__(self, **data: T):
    """Create a new Node object.

    The keyword arguments specified in the constructor will be converted into
    attributes on the new object.

    When initially constructed, the Node will be a leaf node (i.e., no children,
    and marked unable to have children).

    When any public attributes of the node are modified (i.e., any attribute whose
    name doesn't start with `_`), the source to which the node belongs will be
    notified.
    """
    super().__init__(**data)
    self._children: list[Node[T]] | None = None
    self._parent: Node[T] | None = None

__setitem__(index, data)

Set the value of a specific child in the Node.

:param index: The index of the child to change :param data: The data for the updated child. This data will be converted into a Node object.

Source code in core/src/toga/sources/tree_source.py
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
def __setitem__(self, index: int, data: object) -> None:
    """Set the value of a specific child in the Node.

    :param index: The index of the child to change
    :param data: The data for the updated child. This data will be converted
        into a Node object.
    """
    if self._children is None:
        raise ValueError(f"{self} is a leaf node")

    old_node = self._children[index]
    old_node._parent = None
    old_node._source = None

    node = self._source._create_node(parent=self, data=data)
    self._children[index] = node
    self._source.notify("change", item=node)

append(data, children=None)

Append a node to the end of the list of children of this node.

:param data: The data to append as a child of this node. This data will be converted into a Node object. :param children: The data for the children of the new child node. :returns: The new added child Node object.

Source code in core/src/toga/sources/tree_source.py
139
140
141
142
143
144
145
146
147
def append(self, data: object, children: object = None) -> Node[T]:
    """Append a node to the end of the list of children of this node.

    :param data: The data to append as a child of this node. This data will be
        converted into a Node object.
    :param children: The data for the children of the new child node.
    :returns: The new added child Node object.
    """
    return self.insert(len(self), data=data, children=children)

can_have_children()

Can the node have children?

A value of [True][] does not necessarily mean the node has any children, only that the node is allowed to have children. The value of len() for the node indicates the number of actual children.

Source code in core/src/toga/sources/tree_source.py
83
84
85
86
87
88
89
90
def can_have_children(self) -> bool:
    """Can the node have children?

    A value of [`True`][] does not necessarily mean the node *has* any children,
    only that the node is *allowed* to have children. The value of `len()` for
    the node indicates the number of actual children.
    """
    return self._children is not None

find(data, start=None)

Find the first item in the child nodes of this node that matches all the provided attributes.

This is a value based search, rather than an instance search. If two Node instances have the same values, the first instance that matches will be returned. To search for a second instance, provide the first found instance as the start argument. To search for a specific Node instance, use the Node.index().

:param data: The data to search for. Only the values specified in data will be used as matching criteria; if the node contains additional data attributes, they won't be considered as part of the match. :param start: The instance from which to start the search. Defaults to None, indicating that the first match should be returned. :return: The matching Node object. :raises ValueError: If no match is found. :raises ValueError: If the node is a leaf node.

Source code in core/src/toga/sources/tree_source.py
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
def find(self, data: object, start: Node[T] | None = None) -> Node[T]:
    """Find the first item in the child nodes of this node that matches all the
    provided attributes.

    This is a value based search, rather than an instance search. If two Node
    instances have the same values, the first instance that matches will be
    returned. To search for a second instance, provide the first found instance as
    the `start` argument. To search for a specific Node instance, use the
    [`Node.index()`][toga.sources.Node.index].

    :param data: The data to search for. Only the values specified in data will be
        used as matching criteria; if the node contains additional data attributes,
        they won't be considered as part of the match.
    :param start: The instance from which to start the search. Defaults to `None`,
        indicating that the first match should be returned.
    :return: The matching Node object.
    :raises ValueError: If no match is found.
    :raises ValueError: If the node is a leaf node.
    """
    if self._children is None:
        raise ValueError(f"{self} is a leaf node")

    return _find_item(
        candidates=self._children,
        data=data,
        accessors=self._source._accessors,
        start=start,
        error=f"No child matching {data!r} in {self}",
        value_type="node",
    )

index(child)

The index of a specific node in children of this node.

This search uses Node instances, and searches for an instance match. If two Node instances have the same values, only the Node that is the same Python instance will match. To search for values based on equality, use Node.find().

:param child: The node to find in the children of this node. :returns: The index of the node in the children of this node. :raises ValueError: If the node cannot be found in children of this node.

Source code in core/src/toga/sources/tree_source.py
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
def index(self, child: Node[T]) -> int:
    """The index of a specific node in children of this node.

    This search uses Node instances, and searches for an *instance* match.
    If two Node instances have the same values, only the Node that is the
    same Python instance will match. To search for values based on equality,
    use [`Node.find()`][toga.sources.Node.find].

    :param child: The node to find in the children of this node.
    :returns: The index of the node in the children of this node.
    :raises ValueError: If the node cannot be found in children of this node.
    """
    if self._children is None:
        raise ValueError(f"{self} is a leaf node")

    return self._children.index(child)

insert(index, data, children=None)

Insert a node as a child of this node a specific index.

:param index: The index at which to insert the new child. :param data: The data to insert into the Node as a child. This data will be converted into a Node object. :param children: The data for the children of the new child node. :returns: The new added child Node object.

Source code in core/src/toga/sources/tree_source.py
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
def insert(self, index: int, data: object, children: object = None) -> Node[T]:
    """Insert a node as a child of this node a specific index.

    :param index: The index at which to insert the new child.
    :param data: The data to insert into the Node as a child. This data will be
        converted into a Node object.
    :param children: The data for the children of the new child node.
    :returns: The new added child Node object.
    """
    if self._children is None:
        self._children = []

    if index < 0:
        index = max(len(self) + index, 0)
    else:
        index = min(len(self), index)

    node = self._source._create_node(parent=self, data=data, children=children)
    self._children.insert(index, node)
    self._source.notify("insert", parent=self, index=index, item=node)
    return node

remove(child)

Remove a child node from this node.

:param child: The child node to remove from this node.

Source code in core/src/toga/sources/tree_source.py
149
150
151
152
153
154
155
def remove(self, child: Node[T]) -> None:
    """Remove a child node from this node.

    :param child: The child node to remove from this node.
    """
    # Index will raise ValueError if the node is a leaf
    del self[self.index(child)]

A type describing any object adhering to the same interface as TreeSource.

Bases: Source

Source code in core/src/toga/sources/tree_source.py
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
class TreeSource(Source):
    _roots: list[Node]
    _accessors: list[str] | None

    def __init__(
        self,
        accessors: Iterable[str] | None = None,
        data: object | None = None,
    ):
        super().__init__()
        if accessors is None:
            self._accessors = None
        elif isinstance(accessors, str) or not hasattr(accessors, "__iter__"):
            raise ValueError("accessors should be a list of attribute names")
        else:
            # Copy the list of accessors.
            self._accessors = list(accessors)

        if data is not None:
            self._roots = self._create_nodes(parent=None, value=data)
        else:
            self._roots = []

    @property
    def accessors(self) -> list[str] | None:
        """The attribute names for accessing the value in each column of a row."""
        if self._accessors is None:
            return None
        return self._accessors.copy()

    ######################################################################
    # Methods required by the TreeSource interface
    ######################################################################

    def __len__(self) -> int:
        return len(self._roots)

    def __getitem__(self, index: int) -> Node:
        return self._roots[index]

    def __delitem__(self, index: int) -> None:
        node = self._roots[index]
        del self._roots[index]
        node._source = None
        self.notify("remove", parent=None, index=index, item=node)

    ######################################################################
    # Factory methods for new nodes
    ######################################################################

    def _create_node(
        self,
        parent: Node | None,
        data: object,
        children: object | None = None,
    ) -> Node:
        if isinstance(data, Mapping):
            node = Node(**data)
        elif self._accessors is not None:
            if hasattr(data, "__iter__") and not isinstance(data, str):
                node = Node(**dict(zip(self._accessors, data, strict=False)))
            else:
                node = Node(**{self._accessors[0]: data})
        else:
            raise ValueError("TreeSource requires accessors for non-mapping node data")

        node._parent = parent
        node._source = self

        if children is not None:
            node._children = self._create_nodes(parent=node, value=children)

        return node

    def _create_nodes(self, parent: Node | None, value: object) -> list[Node]:
        if isinstance(value, Mapping):
            return [
                self._create_node(parent=parent, data=data, children=children)
                for data, children in value.items()
            ]
        elif hasattr(value, "__iter__") and not isinstance(value, str):
            return [
                self._create_node(parent=parent, data=item[0], children=item[1])
                for item in value
            ]
        else:
            return [self._create_node(parent=parent, data=value)]

    ######################################################################
    # Utility methods to make TreeSources more list-like
    ######################################################################

    def __setitem__(self, index: int, data: object) -> None:
        """Set the value of a specific root item in the data source.

        :param index: The root item to change
        :param data: The data for the updated item. This data will be converted
            into a Node object.
        """
        old_root = self._roots[index]
        old_root._parent = None
        old_root._source = None

        root = self._create_node(parent=None, data=data)
        self._roots[index] = root
        self.notify("change", item=root)

    def clear(self) -> None:
        """Clear all data from the data source."""
        self._roots = []
        self.notify("clear")

    def insert(self, index: int, data: object, children: object = None) -> Node:
        """Insert a root node into the data source at a specific index.

        If the node is a leaf node, it will be converted into a non-leaf node.

        :param index: The index into the list of children at which to insert the item.
        :param data: The data to insert into the TreeSource. This data will be converted
            into a Node object.
        :param children: The data for the children to insert into the TreeSource.
        :returns: The newly constructed Node object.
        :raises ValueError: If the provided parent is not part of this TreeSource.
        """
        if index < 0:
            index = max(len(self) + index, 0)
        else:
            index = min(len(self), index)

        node = self._create_node(parent=None, data=data, children=children)
        self._roots.insert(index, node)
        node._parent = None
        self.notify("insert", parent=None, index=index, item=node)

        return node

    def append(self, data: object, children: object | None = None) -> Node:
        """Append a root node at the end of the list of children of this source.

        If the node is a leaf node, it will be converted into a non-leaf node.

        :param data: The data to append onto the list of children of the given parent.
            This data will be converted into a Node object.
        :param children: The data for the children to insert into the TreeSource.
        :returns: The newly constructed Node object.
        :raises ValueError: If the provided parent is not part of this TreeSource.
        """
        return self.insert(len(self), data=data, children=children)

    def remove(self, node: Node) -> None:
        """Remove a node from the data source.

        This will also remove the node if it is a descendant of a root node.

        :param node: The node to remove from the data source.
        """
        if node._source != self:
            raise ValueError(f"{node} is not managed by this data source")

        if node._parent is None:
            del self[self.index(node)]
        else:
            node._parent.remove(node)

    def index(self, node: Node) -> int:
        """The index of a specific root node in the data source.

        This search uses Node instances, and searches for an *instance* match.
        If two Node instances have the same values, only the Node that is the
        same Python instance will match. To search for values based on equality,
        use [`TreeSource.find()`][toga.sources.TreeSource.find].

        :param node: The node to find in the data source.
        :returns: The index of the node in the child list it is a part of.
        :raises ValueError: If the node cannot be found in the data source.
        """
        return self._roots.index(node)

    def find(self, data: object, start: Node | None = None) -> Node:
        """Find the first item in the child nodes of the given node that matches all the
        provided attributes.

        This is a value based search, rather than an instance search. If two Node
        instances have the same values, the first instance that matches will be
        returned. To search for a second instance, provide the first found instance as
        the `start` argument. To search for a specific Node instance, use the
        [`TreeSource.index()`][toga.sources.TreeSource.index].

        :param data: The data to search for. Only the values specified in data will be
            used as matching criteria; if the node contains additional data attributes,
            they won't be considered as part of the match.
        :param start: The instance from which to start the search. Defaults to `None`,
            indicating that the first match should be returned.
        :return: The matching Node object.
        :raises ValueError: If no match is found.
        :raises ValueError: If the provided parent is not part of this TreeSource.
        """
        return _find_item(
            candidates=self._roots,
            data=data,
            accessors=self._accessors,
            start=start,
            error=f"No root node matching {data!r} in {self}",
            value_type="node",
        )

accessors property

The attribute names for accessing the value in each column of a row.

__setitem__(index, data)

Set the value of a specific root item in the data source.

:param index: The root item to change :param data: The data for the updated item. This data will be converted into a Node object.

Source code in core/src/toga/sources/tree_source.py
298
299
300
301
302
303
304
305
306
307
308
309
310
311
def __setitem__(self, index: int, data: object) -> None:
    """Set the value of a specific root item in the data source.

    :param index: The root item to change
    :param data: The data for the updated item. This data will be converted
        into a Node object.
    """
    old_root = self._roots[index]
    old_root._parent = None
    old_root._source = None

    root = self._create_node(parent=None, data=data)
    self._roots[index] = root
    self.notify("change", item=root)

append(data, children=None)

Append a root node at the end of the list of children of this source.

If the node is a leaf node, it will be converted into a non-leaf node.

:param data: The data to append onto the list of children of the given parent. This data will be converted into a Node object. :param children: The data for the children to insert into the TreeSource. :returns: The newly constructed Node object. :raises ValueError: If the provided parent is not part of this TreeSource.

Source code in core/src/toga/sources/tree_source.py
342
343
344
345
346
347
348
349
350
351
352
353
def append(self, data: object, children: object | None = None) -> Node:
    """Append a root node at the end of the list of children of this source.

    If the node is a leaf node, it will be converted into a non-leaf node.

    :param data: The data to append onto the list of children of the given parent.
        This data will be converted into a Node object.
    :param children: The data for the children to insert into the TreeSource.
    :returns: The newly constructed Node object.
    :raises ValueError: If the provided parent is not part of this TreeSource.
    """
    return self.insert(len(self), data=data, children=children)

clear()

Clear all data from the data source.

Source code in core/src/toga/sources/tree_source.py
313
314
315
316
def clear(self) -> None:
    """Clear all data from the data source."""
    self._roots = []
    self.notify("clear")

find(data, start=None)

Find the first item in the child nodes of the given node that matches all the provided attributes.

This is a value based search, rather than an instance search. If two Node instances have the same values, the first instance that matches will be returned. To search for a second instance, provide the first found instance as the start argument. To search for a specific Node instance, use the TreeSource.index().

:param data: The data to search for. Only the values specified in data will be used as matching criteria; if the node contains additional data attributes, they won't be considered as part of the match. :param start: The instance from which to start the search. Defaults to None, indicating that the first match should be returned. :return: The matching Node object. :raises ValueError: If no match is found. :raises ValueError: If the provided parent is not part of this TreeSource.

Source code in core/src/toga/sources/tree_source.py
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
def find(self, data: object, start: Node | None = None) -> Node:
    """Find the first item in the child nodes of the given node that matches all the
    provided attributes.

    This is a value based search, rather than an instance search. If two Node
    instances have the same values, the first instance that matches will be
    returned. To search for a second instance, provide the first found instance as
    the `start` argument. To search for a specific Node instance, use the
    [`TreeSource.index()`][toga.sources.TreeSource.index].

    :param data: The data to search for. Only the values specified in data will be
        used as matching criteria; if the node contains additional data attributes,
        they won't be considered as part of the match.
    :param start: The instance from which to start the search. Defaults to `None`,
        indicating that the first match should be returned.
    :return: The matching Node object.
    :raises ValueError: If no match is found.
    :raises ValueError: If the provided parent is not part of this TreeSource.
    """
    return _find_item(
        candidates=self._roots,
        data=data,
        accessors=self._accessors,
        start=start,
        error=f"No root node matching {data!r} in {self}",
        value_type="node",
    )

index(node)

The index of a specific root node in the data source.

This search uses Node instances, and searches for an instance match. If two Node instances have the same values, only the Node that is the same Python instance will match. To search for values based on equality, use TreeSource.find().

:param node: The node to find in the data source. :returns: The index of the node in the child list it is a part of. :raises ValueError: If the node cannot be found in the data source.

Source code in core/src/toga/sources/tree_source.py
370
371
372
373
374
375
376
377
378
379
380
381
382
def index(self, node: Node) -> int:
    """The index of a specific root node in the data source.

    This search uses Node instances, and searches for an *instance* match.
    If two Node instances have the same values, only the Node that is the
    same Python instance will match. To search for values based on equality,
    use [`TreeSource.find()`][toga.sources.TreeSource.find].

    :param node: The node to find in the data source.
    :returns: The index of the node in the child list it is a part of.
    :raises ValueError: If the node cannot be found in the data source.
    """
    return self._roots.index(node)

insert(index, data, children=None)

Insert a root node into the data source at a specific index.

If the node is a leaf node, it will be converted into a non-leaf node.

:param index: The index into the list of children at which to insert the item. :param data: The data to insert into the TreeSource. This data will be converted into a Node object. :param children: The data for the children to insert into the TreeSource. :returns: The newly constructed Node object. :raises ValueError: If the provided parent is not part of this TreeSource.

Source code in core/src/toga/sources/tree_source.py
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
def insert(self, index: int, data: object, children: object = None) -> Node:
    """Insert a root node into the data source at a specific index.

    If the node is a leaf node, it will be converted into a non-leaf node.

    :param index: The index into the list of children at which to insert the item.
    :param data: The data to insert into the TreeSource. This data will be converted
        into a Node object.
    :param children: The data for the children to insert into the TreeSource.
    :returns: The newly constructed Node object.
    :raises ValueError: If the provided parent is not part of this TreeSource.
    """
    if index < 0:
        index = max(len(self) + index, 0)
    else:
        index = min(len(self), index)

    node = self._create_node(parent=None, data=data, children=children)
    self._roots.insert(index, node)
    node._parent = None
    self.notify("insert", parent=None, index=index, item=node)

    return node

remove(node)

Remove a node from the data source.

This will also remove the node if it is a descendant of a root node.

:param node: The node to remove from the data source.

Source code in core/src/toga/sources/tree_source.py
355
356
357
358
359
360
361
362
363
364
365
366
367
368
def remove(self, node: Node) -> None:
    """Remove a node from the data source.

    This will also remove the node if it is a descendant of a root node.

    :param node: The node to remove from the data source.
    """
    if node._source != self:
        raise ValueError(f"{node} is not managed by this data source")

    if node._parent is None:
        del self[self.index(node)]
    else:
        node._parent.remove(node)

Bases: ListListener[ItemT], Protocol, Generic[ItemT]

The protocol that must be implemented by objects that will act as a listener on a tree data source.

Source code in core/src/toga/sources/base.py
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
@runtime_checkable
class TreeListener(ListListener[ItemT], Protocol, Generic[ItemT]):
    """The protocol that must be implemented by objects that will act as a listener on
    a tree data source.
    """

    def source_insert(
        self,
        *,
        index: int,
        item: object,
        parent: ItemT | None = None,
    ) -> None:
        """An item has been added to the data source.

        :param index: The 0-index position in the data.
        :param item: The data object that was added.
        :param parent: The parent of the data object that was added, or `None`
            if it is a root item.
        """

    def source_remove(
        self,
        *,
        index: int,
        item: object,
        parent: ItemT | None = None,
    ) -> None:
        """An item has been removed from the data source.

        :param index: The 0-index position in the data.
        :param item: The data object that was added.
        :param parent: The parent of the data object that was removed, or `None`
            if it is a root item.
        """

source_insert(*, index, item, parent=None)

An item has been added to the data source.

:param index: The 0-index position in the data. :param item: The data object that was added. :param parent: The parent of the data object that was added, or None if it is a root item.

Source code in core/src/toga/sources/base.py
53
54
55
56
57
58
59
60
61
62
63
64
65
66
def source_insert(
    self,
    *,
    index: int,
    item: object,
    parent: ItemT | None = None,
) -> None:
    """An item has been added to the data source.

    :param index: The 0-index position in the data.
    :param item: The data object that was added.
    :param parent: The parent of the data object that was added, or `None`
        if it is a root item.
    """

source_remove(*, index, item, parent=None)

An item has been removed from the data source.

:param index: The 0-index position in the data. :param item: The data object that was added. :param parent: The parent of the data object that was removed, or None if it is a root item.

Source code in core/src/toga/sources/base.py
68
69
70
71
72
73
74
75
76
77
78
79
80
81
def source_remove(
    self,
    *,
    index: int,
    item: object,
    parent: ItemT | None = None,
) -> None:
    """An item has been removed from the data source.

    :param index: The 0-index position in the data.
    :param item: The data object that was added.
    :param parent: The parent of the data object that was removed, or `None`
        if it is a root item.
    """