verysimpletree 0.1.0b0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
File without changes
@@ -0,0 +1,136 @@
1
+ from unittest import TestCase
2
+
3
+ from verysimpletree.tree import Tree, ChildNotFoundError
4
+
5
+
6
+ class A(Tree):
7
+ def __init__(self, name, parent=None, *args, **keyword):
8
+ super().__init__(*args, **keyword)
9
+ self._children = []
10
+ self._parent = parent
11
+ self.name = name
12
+
13
+ def _check_child_to_be_added(self, child):
14
+ if not isinstance(child, self.__class__):
15
+ raise TypeError
16
+
17
+ def add_child(self, name):
18
+ child = type(self)(parent=self, name=name)
19
+ return super().add_child(child)
20
+
21
+ def __str__(self):
22
+ return self.name
23
+
24
+ def __repr__(self):
25
+ return self.__str__()
26
+
27
+
28
+ class TestTree(TestCase):
29
+ def setUp(self) -> None:
30
+ self.root = A(name='root')
31
+ self.child1 = self.root.add_child('child1')
32
+ self.child2 = self.root.add_child('child2')
33
+ self.child3 = self.root.add_child('child3')
34
+ self.child4 = self.root.add_child('child4')
35
+ self.grandchild1 = self.child2.add_child('grandchild1')
36
+ self.grandchild2 = self.child2.add_child('grandchild2')
37
+ self.grandchild3 = self.child4.add_child('grandchild3')
38
+ self.greatgrandchild1 = self.grandchild2.add_child('greatgrandchild1')
39
+
40
+ def test_get_root(self):
41
+ assert self.greatgrandchild1.get_root() == self.root
42
+ assert self.child4.get_root() == self.root
43
+ assert self.root.get_root() == self.root
44
+
45
+ def test_is_leaf(self):
46
+ assert self.greatgrandchild1.is_leaf is True
47
+ assert self.child4.is_leaf is False
48
+ assert self.child3.is_leaf is True
49
+
50
+ def test_traverse(self):
51
+ assert list(self.root.traverse()) == [self.root, self.child1, self.child2, self.grandchild1, self.grandchild2,
52
+ self.greatgrandchild1, self.child3, self.child4, self.grandchild3]
53
+
54
+ def test_iterate_leaves(self):
55
+ assert list(self.root.iterate_leaves()) == [self.child1, self.grandchild1, self.greatgrandchild1,
56
+ self.child3, self.grandchild3]
57
+
58
+ def test_level(self):
59
+ assert [node.level for node in self.root.traverse()] == [0, 1, 1, 2, 2, 3, 1, 1, 2]
60
+ assert self.greatgrandchild1.level == 3
61
+ assert self.grandchild2.level == 2
62
+ assert self.child4.level == 1
63
+ assert self.root.level == 0
64
+
65
+ def test_tree_repr(self):
66
+ expected = """root
67
+ child1
68
+ child2
69
+ grandchild1
70
+ grandchild2
71
+ greatgrandchild1
72
+ child3
73
+ child4
74
+ grandchild3
75
+ """
76
+ assert self.root.tree_representation(lambda x: x.name) == expected
77
+
78
+ def test_reversed_path_to_root(self):
79
+ assert list(self.greatgrandchild1.reversed_path_to_root()) == [self.greatgrandchild1, self.grandchild2, self.child2, self.root]
80
+
81
+ def test_remove_child(self):
82
+ with self.assertRaises(ChildNotFoundError):
83
+ self.child2.remove(self.child1)
84
+
85
+ self.child2.remove(self.grandchild2)
86
+ assert self.child2.get_children() == [self.grandchild1]
87
+ assert self.grandchild2.get_parent() is None
88
+ assert self.greatgrandchild1.get_parent() == self.grandchild2
89
+
90
+ def test_get_coordinates(self):
91
+ assert self.greatgrandchild1.get_coordinates_in_tree() == '2.2.1'
92
+ assert self.child2.get_coordinates_in_tree() == '2'
93
+ assert self.root.get_coordinates_in_tree() == '0'
94
+
95
+ def test_replace_child(self):
96
+ self.child2.replace_child(self.grandchild1, A(name='new_grand_child'))
97
+ assert [ch.name for ch in self.child2.get_children()] == ['new_grand_child', 'grandchild2']
98
+ self.child1.add_child('grandchild')
99
+ self.child1.add_child('grandchild')
100
+ new = A(name='other_new_grand_child')
101
+ self.child1.replace_child(lambda x: x.name == 'grandchild', new, 1)
102
+ assert new.get_parent() == self.child1
103
+ assert [ch.name for ch in self.child1.get_children()] == ['grandchild', 'other_new_grand_child']
104
+ with self.assertRaises(ValueError):
105
+ self.child2.replace_child(None, None)
106
+ with self.assertRaises(TypeError):
107
+ self.root.replace_child(self.child1, 34)
108
+
109
+ def test_previous(self):
110
+ assert self.child4.previous == self.child3
111
+ assert self.child3.previous == self.child2
112
+ assert self.child2.previous == self.child1
113
+ assert self.child1.previous is None
114
+
115
+ def test_next(self):
116
+ assert self.child1.next == self.child2
117
+ assert self.child2.next == self.child3
118
+ assert self.child3.next == self.child4
119
+ assert self.child4.next is None
120
+
121
+ def test_get_leaves(self):
122
+ assert self.root.get_leaves(key=lambda x: x.name) == ['child1', ['grandchild1', ['greatgrandchild1']], 'child3', ['grandchild3']]
123
+
124
+ def test_get_layer(self):
125
+ assert self.root.get_layer(0) == [self.root]
126
+ assert self.root.get_layer(1) == [self.child1, self.child2, self.child3, self.child4]
127
+ assert self.root.get_layer(2) == [self.child1, self.grandchild1, self.grandchild2, self.child3, self.grandchild3]
128
+ assert self.root.get_layer(3) == [self.child1, self.grandchild1, self.greatgrandchild1, self.child3, self.grandchild3]
129
+ assert self.root.get_layer(4) == [self.child1, self.grandchild1, self.greatgrandchild1, self.child3, self.grandchild3]
130
+
131
+ def test_find_grandchild(self):
132
+ assert [n for n in self.root.traverse() if n.level == 2] == [self.grandchild1, self.grandchild2, self.grandchild3]
133
+ for n in self.root.traverse():
134
+ if n.level == 2:
135
+ print(n)
136
+ break
verysimpletree/tree.py ADDED
@@ -0,0 +1,396 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Optional, Callable, List, Iterator
3
+
4
+
5
+ class TreeException(Exception):
6
+ pass
7
+
8
+
9
+ class ChildNotFoundError(TreeException):
10
+ pass
11
+
12
+
13
+ class Tree(ABC):
14
+ """
15
+ An abstract lightweight tree class for managing tree structures in MusicXML and musicscore packages.
16
+ """
17
+ _TREE_ATTRIBUTES = {'compact_repr', 'is_leaf', 'level', '_parent', '_children', 'up'}
18
+
19
+ def __init__(self, *args, **kwargs):
20
+ super().__init__(*args, **kwargs)
21
+ self._parent = None
22
+ self._children = []
23
+ self._traversed = None
24
+ self._is_leaf = True
25
+ self._iterated_leaves = None
26
+ self._reversed_path_to_root = None
27
+
28
+ @abstractmethod
29
+ def _check_child_to_be_added(self, child):
30
+ pass
31
+
32
+ def _raw_traverse(self):
33
+ yield self
34
+ for child in self.get_children():
35
+ for node in child._raw_traverse():
36
+ yield node
37
+
38
+ def _raw_reversed_path_to_root(self):
39
+ yield self
40
+ if self.get_parent():
41
+ for node in self.get_parent().reversed_path_to_root():
42
+ yield node
43
+
44
+ def _reset_iterators(self):
45
+ """
46
+ This method is used to reset both parent's and this class's iterators for :obj:'~traverse', obj:'~iterate_leaves' and obj:'~reversed_path_to_root'
47
+ """
48
+ if self.up:
49
+ self.up._reset_iterators()
50
+ self._traversed = None
51
+ self._iterated_leaves = None
52
+ self._reversed_path_to_root = None
53
+
54
+ @property
55
+ def compact_repr(self) -> str:
56
+ """
57
+ :obj:`~tree.tree.Tree` property
58
+
59
+ :return: compact representation of a node. Default is the string representation. This property is used as default in the :obj:`~tree_representation` method and can be
60
+ customized in subclasses to get the most appropriate representation.
61
+ :rtype: str
62
+ """
63
+ return self.__str__()
64
+
65
+ @property
66
+ def is_leaf(self) -> bool:
67
+ """
68
+ :obj:`~tree.tree.Tree` property
69
+
70
+ :return: ``True`` if self has no children. ``False`` if self has one or more children.
71
+ :rtype: bool
72
+ """
73
+ return self._is_leaf
74
+
75
+ @property
76
+ def is_root(self) -> bool:
77
+ """
78
+ :obj:`~tree.tree.Tree` property
79
+
80
+ :return: ``True`` if self has no parent, else ``False``.
81
+ :rtype: bool
82
+ """
83
+ return True if self.get_parent() is None else False
84
+
85
+ @property
86
+ def level(self) -> int:
87
+ """
88
+ :obj:`~tree.tree.Tree` property
89
+
90
+ :return: ``0`` for ``root``, ``1, 2 etc.`` for each layer of children
91
+ :rtype: nonnegative int
92
+
93
+ >>> class TestTree(Tree):
94
+ ... def _check_child_to_be_added(self, child):
95
+ ... return True
96
+ >>> root = TestTree()
97
+ >>> root.level
98
+ 0
99
+ >>> ch = root.add_child(TestTree()).add_child(TestTree()).add_child(TestTree())
100
+ >>> ch.level
101
+ 3
102
+ """
103
+ if self.get_parent() is None:
104
+ return 0
105
+ else:
106
+ return self.get_parent().level + 1
107
+
108
+ @property
109
+ def next(self) -> Optional['Tree']:
110
+ """
111
+ :obj:`~tree.tree.Tree` property
112
+
113
+ :return: next sibling. ``None`` if this is the last current child of the parent.
114
+ :rtype: :obj:`~tree.tree.Tree`
115
+ """
116
+ if self.up and self != self.up.get_children()[-1]:
117
+ return self.up.get_children()[self.up.get_children().index(self) + 1]
118
+ else:
119
+ return None
120
+
121
+ @property
122
+ def previous(self) -> Optional['Tree']:
123
+ """
124
+ :obj:`~tree.tree.Tree` property
125
+
126
+ :return: previous sibling. ``None`` if this is the first child of the parent.
127
+ :rtype: :obj:`~tree.tree.Tree`
128
+ """
129
+ if self.up and self != self.up.get_children()[0]:
130
+ return self.up.get_children()[self.up.get_children().index(self) - 1]
131
+ else:
132
+ return None
133
+
134
+ @property
135
+ def up(self) -> 'Tree':
136
+ """
137
+ :obj:`~tree.tree.Tree` property
138
+
139
+ :return: :obj:`get_parent()`
140
+ :rtype: :obj:`~tree.tree.Tree`
141
+ """
142
+ return self.get_parent()
143
+
144
+ def add_child(self, child: 'Tree') -> 'Tree':
145
+ """
146
+ :obj:`~tree.tree.Tree` method
147
+
148
+ Check and add child to list of children. Child's parent is set to self.
149
+
150
+ :param child:
151
+ :return: child
152
+ :rtype: :obj:`~tree.tree.Tree`
153
+ """
154
+ self._check_child_to_be_added(child)
155
+ child._parent = self
156
+ self._children.append(child)
157
+ self._reset_iterators()
158
+ if self._is_leaf is True:
159
+ self._is_leaf = False
160
+ return child
161
+
162
+ def get_children(self) -> List['Tree']:
163
+ """
164
+ :obj:`~tree.tree.Tree` method
165
+
166
+ :return: list of added children.
167
+ :rtype: List[:obj:`~tree.tree.Tree`]
168
+ """
169
+ return self._children
170
+
171
+ def get_children_of_type(self, type) -> List['Tree']:
172
+ """
173
+ :obj:`~tree.tree.Tree` method
174
+
175
+ :return: list of added children of type.
176
+ :rtype: List[:obj:`~tree.tree.Tree`]
177
+ """
178
+ return [ch for ch in self.get_children() if isinstance(ch, type)]
179
+
180
+ def get_coordinates_in_tree(self) -> str:
181
+ """
182
+ :obj:`~tree.tree.Tree` method
183
+
184
+ :return: 0 for ``root``. 1, 2, ... for layer 1. Other layers: x.y.z.... Example: 3.2.2 => third child of secod child of second child
185
+ of the root.
186
+ :rtype: str
187
+
188
+ >>> class TestTree(Tree):
189
+ ... def _check_child_to_be_added(self, child):
190
+ ... return True
191
+ >>> root = TestTree()
192
+ >>> root.get_coordinates_in_tree()
193
+ '0'
194
+ >>> child1 = root.add_child(TestTree())
195
+ >>> child2 = root.add_child(TestTree())
196
+ >>> grandchild1 = child2.add_child(TestTree())
197
+ >>> grandchild2 = child2.add_child(TestTree())
198
+ >>> child1.get_coordinates_in_tree()
199
+ '1'
200
+ >>> child2.get_coordinates_in_tree()
201
+ '2'
202
+ >>> grandchild1.get_coordinates_in_tree()
203
+ '2.1'
204
+ >>> grandchild2.get_coordinates_in_tree()
205
+ '2.2'
206
+ """
207
+ if self.level == 0:
208
+ return '0'
209
+ elif self.level == 1:
210
+ return str(self.get_parent().get_children().index(self) + 1)
211
+ else:
212
+ return f"{self.get_parent().get_coordinates_in_tree()}.{self.get_parent().get_children().index(self) + 1}"
213
+
214
+ def get_indentation(self) -> str:
215
+ """
216
+ :obj:`~tree.tree.Tree` method
217
+
218
+ :return: indentation according to ``level`` (layer number). As default it is used for creating tabs in :obj:`tree_representation`
219
+ :rtype: str
220
+ """
221
+ indentation = ''
222
+ for i in range(self.level):
223
+ indentation += ' '
224
+ return indentation
225
+
226
+ def get_parent(self) -> 'Tree':
227
+ """
228
+ :obj:`~tree.tree.Tree` method
229
+
230
+ :return: parent. ``None`` for ``root``.
231
+ :rtype: :obj:`~tree.tree.Tree`
232
+ """
233
+ return self._parent
234
+
235
+ def get_leaves(self, key: Optional[Callable] = None) -> list:
236
+ """
237
+ :obj:`~tree.tree.Tree` method
238
+
239
+ :param key: An optional callable to be called on each leaf.
240
+ :return: nested list of leaves or values of key(leaf) for each leaf
241
+ :rtype: nested list of :obj:`~tree.tree.Tree`
242
+ """
243
+ output = []
244
+ for child in self.get_children():
245
+ if not child.is_leaf:
246
+ output.append(child.get_leaves(key=key))
247
+ else:
248
+ if key is not None:
249
+ output.append(key(child))
250
+ else:
251
+ output.append(child)
252
+
253
+ return output
254
+
255
+ def get_root(self) -> 'Tree':
256
+ """
257
+ :obj:`~tree.tree.Tree` method
258
+
259
+ :return: ``root`` (upmost node of a tree which has no parent)
260
+ :rtype: :obj:`~tree.tree.Tree`
261
+ """
262
+ node = self
263
+ parent = node.get_parent()
264
+ while parent is not None:
265
+ node = parent
266
+ parent = node.get_parent()
267
+ return node
268
+
269
+ def get_layer(self, level: int, key: Optional[Callable] = None) -> list:
270
+ """
271
+ :obj:`~tree.tree.Tree` method
272
+
273
+ :param level: layer number where 0 is the ``root``.
274
+ :param key: An optional callable for each node in the layer.
275
+ :return: All nodes on this level. The leaves of branches which are shorter than the given level will be repeated on this and all
276
+ following layers.
277
+ :rtype: list
278
+ """
279
+ if level == 0:
280
+ output = [self]
281
+ elif level == 1:
282
+ output = self.get_children()
283
+ else:
284
+ output = []
285
+ for child in self.get_layer(level - 1):
286
+ if child.is_leaf:
287
+ output.append(child)
288
+ else:
289
+ output.extend(child.get_children())
290
+ if key is None:
291
+ return output
292
+ else:
293
+ return [key(child) for child in output]
294
+
295
+ def iterate_leaves(self) -> Iterator['Tree']:
296
+ """
297
+ :obj:`~tree.tree.Tree` method
298
+
299
+ :return: A generator iterating over all leaves.
300
+ """
301
+ if self._iterated_leaves is None:
302
+ self._iterated_leaves = [n for n in self.traverse() if n.is_leaf]
303
+ return iter(self._iterated_leaves)
304
+
305
+ def remove(self, child: 'Tree') -> None:
306
+ """
307
+ :obj:`~tree.tree.Tree` method
308
+
309
+ Child's parent will be set to ``None`` and child will be removed from list of children.
310
+
311
+ :param child:
312
+ :return: None
313
+ """
314
+ if child not in self.get_children():
315
+ raise ChildNotFoundError
316
+ child._parent = None
317
+ self.get_children().remove(child)
318
+ self._reset_iterators()
319
+
320
+ def remove_children(self) -> None:
321
+ """
322
+ :obj:`~tree.tree.Tree` method
323
+
324
+ Calls :obj:`remove()` on all children.
325
+
326
+ :return: None
327
+ """
328
+ for child in self.get_children()[:]:
329
+ child.up.remove(child)
330
+
331
+ def replace_child(self, old, new, index: int = 0) -> None:
332
+ """
333
+ :obj:`~tree.tree.Tree` method
334
+
335
+ :param old: child or function
336
+ :param new: child
337
+ :param index: index of old child in the list of its appearances
338
+ :return: None
339
+ """
340
+ if hasattr(old, '__call__'):
341
+ list_of_olds = [ch for ch in self.get_children() if old(ch)]
342
+ else:
343
+ list_of_olds = [ch for ch in self.get_children() if ch == old]
344
+ if not list_of_olds:
345
+ raise ValueError(f"{old} not in list.")
346
+ self._check_child_to_be_added(new)
347
+ old_index = self.get_children().index(list_of_olds[index])
348
+ old_child = self.get_children()[old_index]
349
+ self.get_children().remove(old_child)
350
+ self.get_children().insert(old_index, new)
351
+ old_child._parent = None
352
+ self._reset_iterators()
353
+ new._parent = self
354
+
355
+ def reversed_path_to_root(self) -> Iterator['Tree']:
356
+ """
357
+ :obj:`~tree.tree.Tree` method
358
+
359
+ :return: path from self upwards through all ancestors up to the ``root``.
360
+ """
361
+ if self._reversed_path_to_root is None:
362
+ self._reversed_path_to_root = list(self._raw_reversed_path_to_root())
363
+ return self._reversed_path_to_root
364
+
365
+ def traverse(self) -> Iterator['Tree']:
366
+ """
367
+ :obj:`~tree.tree.Tree` method
368
+
369
+ Traverse all tree nodes.
370
+
371
+ :return: generator
372
+ """
373
+ if self._traversed is None:
374
+ self._traversed = list(self._raw_traverse())
375
+ return iter(self._traversed)
376
+
377
+ def tree_representation(self, key: Optional[Callable] = None, tab: Optional[Callable] = None) -> str:
378
+ """
379
+ :obj:`~tree.tree.Tree` method
380
+
381
+ :param key: An optional callable if ``None`` :obj:`~compact_repr` property of each node is called.
382
+ :param tab: An optional callable if ``None`` :obj:`get_indentation()` method of each node is called.
383
+ :return: a representation of all nodes as string in tree form.
384
+ :rtype: str
385
+ """
386
+ if not key:
387
+ key = lambda x: x.compact_repr
388
+
389
+ if not tab:
390
+ tab = lambda x: x.get_indentation()
391
+
392
+ output = ''
393
+ for node in self.traverse():
394
+ output += tab(node) + key(node)
395
+ output += '\n'
396
+ return output
@@ -0,0 +1,12 @@
1
+ Metadata-Version: 2.1
2
+ Name: verysimpletree
3
+ Version: 0.1.0b0
4
+ Summary: lightweight tree data structure for musicxml and musicscore
5
+ Home-page: https://github.com/alexgorji/verysimpletree.git
6
+ Author: Alex Gorji
7
+ Author-email: aligorji@hotmail.com
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Operating System :: OS Independent
10
+ Description-Content-Type: text/markdown
11
+
12
+ An abstract lightweight tree class for managing tree structures in musicxml and musicscore packages.
@@ -0,0 +1,7 @@
1
+ verysimpletree/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ verysimpletree/test_tree.py,sha256=RSp-Vxmfs0UoTO00a_l0f4u0YRqDcJlTbkbGOAfCht0,5587
3
+ verysimpletree/tree.py,sha256=Xpi-8fxuz3j4yW1dmvrP13BW_GYpYqYVtxthITkoS-8,12382
4
+ verysimpletree-0.1.0b0.dist-info/METADATA,sha256=aLkosPmNUPiEf7fZFrSUczzXGFYeAiQsp5jaePJmhmY,479
5
+ verysimpletree-0.1.0b0.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
6
+ verysimpletree-0.1.0b0.dist-info/top_level.txt,sha256=zEghJjeoIBa8XOrU-H1FPmlyq1LkKWoxmVBEdhrzGtk,15
7
+ verysimpletree-0.1.0b0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: bdist_wheel (0.41.2)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ verysimpletree