deepfos 1.1.60__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.
Files changed (175) hide show
  1. deepfos/__init__.py +6 -0
  2. deepfos/_version.py +21 -0
  3. deepfos/algo/__init__.py +0 -0
  4. deepfos/algo/graph.py +171 -0
  5. deepfos/algo/segtree.py +31 -0
  6. deepfos/api/V1_1/__init__.py +0 -0
  7. deepfos/api/V1_1/business_model.py +119 -0
  8. deepfos/api/V1_1/dimension.py +599 -0
  9. deepfos/api/V1_1/models/__init__.py +0 -0
  10. deepfos/api/V1_1/models/business_model.py +1033 -0
  11. deepfos/api/V1_1/models/dimension.py +2768 -0
  12. deepfos/api/V1_2/__init__.py +0 -0
  13. deepfos/api/V1_2/dimension.py +285 -0
  14. deepfos/api/V1_2/models/__init__.py +0 -0
  15. deepfos/api/V1_2/models/dimension.py +2923 -0
  16. deepfos/api/__init__.py +0 -0
  17. deepfos/api/account.py +167 -0
  18. deepfos/api/accounting_engines.py +147 -0
  19. deepfos/api/app.py +626 -0
  20. deepfos/api/approval_process.py +198 -0
  21. deepfos/api/base.py +983 -0
  22. deepfos/api/business_model.py +160 -0
  23. deepfos/api/consolidation.py +129 -0
  24. deepfos/api/consolidation_process.py +106 -0
  25. deepfos/api/datatable.py +341 -0
  26. deepfos/api/deep_pipeline.py +61 -0
  27. deepfos/api/deepconnector.py +36 -0
  28. deepfos/api/deepfos_task.py +92 -0
  29. deepfos/api/deepmodel.py +188 -0
  30. deepfos/api/dimension.py +486 -0
  31. deepfos/api/financial_model.py +319 -0
  32. deepfos/api/journal_model.py +119 -0
  33. deepfos/api/journal_template.py +132 -0
  34. deepfos/api/memory_financial_model.py +98 -0
  35. deepfos/api/models/__init__.py +3 -0
  36. deepfos/api/models/account.py +483 -0
  37. deepfos/api/models/accounting_engines.py +756 -0
  38. deepfos/api/models/app.py +1338 -0
  39. deepfos/api/models/approval_process.py +1043 -0
  40. deepfos/api/models/base.py +234 -0
  41. deepfos/api/models/business_model.py +805 -0
  42. deepfos/api/models/consolidation.py +711 -0
  43. deepfos/api/models/consolidation_process.py +248 -0
  44. deepfos/api/models/datatable_mysql.py +427 -0
  45. deepfos/api/models/deep_pipeline.py +55 -0
  46. deepfos/api/models/deepconnector.py +28 -0
  47. deepfos/api/models/deepfos_task.py +386 -0
  48. deepfos/api/models/deepmodel.py +308 -0
  49. deepfos/api/models/dimension.py +1576 -0
  50. deepfos/api/models/financial_model.py +1796 -0
  51. deepfos/api/models/journal_model.py +341 -0
  52. deepfos/api/models/journal_template.py +854 -0
  53. deepfos/api/models/memory_financial_model.py +478 -0
  54. deepfos/api/models/platform.py +178 -0
  55. deepfos/api/models/python.py +221 -0
  56. deepfos/api/models/reconciliation_engine.py +411 -0
  57. deepfos/api/models/reconciliation_report.py +161 -0
  58. deepfos/api/models/role_strategy.py +884 -0
  59. deepfos/api/models/smartlist.py +237 -0
  60. deepfos/api/models/space.py +1137 -0
  61. deepfos/api/models/system.py +1065 -0
  62. deepfos/api/models/variable.py +463 -0
  63. deepfos/api/models/workflow.py +946 -0
  64. deepfos/api/platform.py +199 -0
  65. deepfos/api/python.py +90 -0
  66. deepfos/api/reconciliation_engine.py +181 -0
  67. deepfos/api/reconciliation_report.py +64 -0
  68. deepfos/api/role_strategy.py +234 -0
  69. deepfos/api/smartlist.py +69 -0
  70. deepfos/api/space.py +582 -0
  71. deepfos/api/system.py +372 -0
  72. deepfos/api/variable.py +154 -0
  73. deepfos/api/workflow.py +264 -0
  74. deepfos/boost/__init__.py +6 -0
  75. deepfos/boost/py_jstream.py +89 -0
  76. deepfos/boost/py_pandas.py +20 -0
  77. deepfos/cache.py +121 -0
  78. deepfos/config.py +6 -0
  79. deepfos/core/__init__.py +27 -0
  80. deepfos/core/cube/__init__.py +10 -0
  81. deepfos/core/cube/_base.py +462 -0
  82. deepfos/core/cube/constants.py +21 -0
  83. deepfos/core/cube/cube.py +408 -0
  84. deepfos/core/cube/formula.py +707 -0
  85. deepfos/core/cube/syscube.py +532 -0
  86. deepfos/core/cube/typing.py +7 -0
  87. deepfos/core/cube/utils.py +238 -0
  88. deepfos/core/dimension/__init__.py +11 -0
  89. deepfos/core/dimension/_base.py +506 -0
  90. deepfos/core/dimension/dimcreator.py +184 -0
  91. deepfos/core/dimension/dimension.py +472 -0
  92. deepfos/core/dimension/dimexpr.py +271 -0
  93. deepfos/core/dimension/dimmember.py +155 -0
  94. deepfos/core/dimension/eledimension.py +22 -0
  95. deepfos/core/dimension/filters.py +99 -0
  96. deepfos/core/dimension/sysdimension.py +168 -0
  97. deepfos/core/logictable/__init__.py +5 -0
  98. deepfos/core/logictable/_cache.py +141 -0
  99. deepfos/core/logictable/_operator.py +663 -0
  100. deepfos/core/logictable/nodemixin.py +673 -0
  101. deepfos/core/logictable/sqlcondition.py +609 -0
  102. deepfos/core/logictable/tablemodel.py +497 -0
  103. deepfos/db/__init__.py +36 -0
  104. deepfos/db/cipher.py +660 -0
  105. deepfos/db/clickhouse.py +191 -0
  106. deepfos/db/connector.py +195 -0
  107. deepfos/db/daclickhouse.py +171 -0
  108. deepfos/db/dameng.py +101 -0
  109. deepfos/db/damysql.py +189 -0
  110. deepfos/db/dbkits.py +358 -0
  111. deepfos/db/deepengine.py +99 -0
  112. deepfos/db/deepmodel.py +82 -0
  113. deepfos/db/deepmodel_kingbase.py +83 -0
  114. deepfos/db/edb.py +214 -0
  115. deepfos/db/gauss.py +83 -0
  116. deepfos/db/kingbase.py +83 -0
  117. deepfos/db/mysql.py +184 -0
  118. deepfos/db/oracle.py +131 -0
  119. deepfos/db/postgresql.py +192 -0
  120. deepfos/db/sqlserver.py +99 -0
  121. deepfos/db/utils.py +135 -0
  122. deepfos/element/__init__.py +89 -0
  123. deepfos/element/accounting.py +348 -0
  124. deepfos/element/apvlprocess.py +215 -0
  125. deepfos/element/base.py +398 -0
  126. deepfos/element/bizmodel.py +1269 -0
  127. deepfos/element/datatable.py +2467 -0
  128. deepfos/element/deep_pipeline.py +186 -0
  129. deepfos/element/deepconnector.py +59 -0
  130. deepfos/element/deepmodel.py +1806 -0
  131. deepfos/element/dimension.py +1254 -0
  132. deepfos/element/fact_table.py +427 -0
  133. deepfos/element/finmodel.py +1485 -0
  134. deepfos/element/journal.py +840 -0
  135. deepfos/element/journal_template.py +943 -0
  136. deepfos/element/pyscript.py +412 -0
  137. deepfos/element/reconciliation.py +553 -0
  138. deepfos/element/rolestrategy.py +243 -0
  139. deepfos/element/smartlist.py +457 -0
  140. deepfos/element/variable.py +756 -0
  141. deepfos/element/workflow.py +560 -0
  142. deepfos/exceptions/__init__.py +239 -0
  143. deepfos/exceptions/hook.py +86 -0
  144. deepfos/lazy.py +104 -0
  145. deepfos/lazy_import.py +84 -0
  146. deepfos/lib/__init__.py +0 -0
  147. deepfos/lib/_javaobj.py +366 -0
  148. deepfos/lib/asynchronous.py +879 -0
  149. deepfos/lib/concurrency.py +107 -0
  150. deepfos/lib/constant.py +39 -0
  151. deepfos/lib/decorator.py +310 -0
  152. deepfos/lib/deepchart.py +778 -0
  153. deepfos/lib/deepux.py +477 -0
  154. deepfos/lib/discovery.py +273 -0
  155. deepfos/lib/edb_lexer.py +789 -0
  156. deepfos/lib/eureka.py +156 -0
  157. deepfos/lib/filterparser.py +751 -0
  158. deepfos/lib/httpcli.py +106 -0
  159. deepfos/lib/jsonstreamer.py +80 -0
  160. deepfos/lib/msg.py +394 -0
  161. deepfos/lib/nacos.py +225 -0
  162. deepfos/lib/patch.py +92 -0
  163. deepfos/lib/redis.py +241 -0
  164. deepfos/lib/serutils.py +181 -0
  165. deepfos/lib/stopwatch.py +99 -0
  166. deepfos/lib/subtask.py +572 -0
  167. deepfos/lib/sysutils.py +703 -0
  168. deepfos/lib/utils.py +1003 -0
  169. deepfos/local.py +160 -0
  170. deepfos/options.py +670 -0
  171. deepfos/translation.py +237 -0
  172. deepfos-1.1.60.dist-info/METADATA +33 -0
  173. deepfos-1.1.60.dist-info/RECORD +175 -0
  174. deepfos-1.1.60.dist-info/WHEEL +5 -0
  175. deepfos-1.1.60.dist-info/top_level.txt +1 -0
@@ -0,0 +1,673 @@
1
+ from operator import methodcaller
2
+ import weakref
3
+
4
+
5
+ class LoopError(Exception):
6
+ pass
7
+
8
+
9
+ class TreeError(Exception):
10
+ pass
11
+
12
+
13
+ class NamedTuple(tuple):
14
+ def __new__(cls, *args, **kwargs):
15
+ ins = super().__new__(cls, *args, **kwargs)
16
+ for ele in ins:
17
+ setattr(ins, ele.name, ele)
18
+ return ins
19
+
20
+
21
+ class MetaNodeMixin(type):
22
+ """
23
+ 给使用这个元类的类提供方便定义树结构的接口。
24
+
25
+ Example:
26
+ .. code-block:: python
27
+
28
+ class Root(metaclass=MetaNodeMixin):
29
+ pass
30
+
31
+ class ChildA(metaclass=MetaNodeMixin):
32
+ pass
33
+
34
+ class ChildB(metaclass=MetaNodeMixin):
35
+ pass
36
+
37
+ class GrandChildA(metaclass=MetaNodeMixin):
38
+ pass
39
+
40
+ ChildA.set_parent(Root)
41
+ ChildB.set_parent(Root)
42
+ GrandChildA.set_parent(ChildA)
43
+ # 经过上述定义后,将形成如下树结构
44
+ '''
45
+ Root--ChildA--GrandChildA
46
+ └─ChildB
47
+ '''
48
+ """
49
+
50
+ @property
51
+ def parent(cls):
52
+ """父节点"""
53
+ try:
54
+ return cls.__parent()
55
+ except AttributeError:
56
+ return None
57
+
58
+ @property
59
+ def children(cls):
60
+ """子节点"""
61
+ return [child() for child in cls.__children_ref]
62
+
63
+ @property
64
+ def __children_ref(cls):
65
+ """子节点的弱引用列表"""
66
+ try:
67
+ return cls.__children
68
+ except AttributeError:
69
+ cls.__children = []
70
+ return cls.__children
71
+
72
+ def set_parent(cls, node):
73
+ """设置父节点。
74
+
75
+ 将指定的节点设为父节点。如果当前节点已有父节点,
76
+ 将首先把当前节点(及其子树)从原树中移除,
77
+ 再把当前节点(及其子树)接入新的树中。
78
+
79
+ Args:
80
+ node: 待设定的父节点
81
+ """
82
+ if node is not None and not isinstance(node, MetaNodeMixin):
83
+ raise TreeError(f"父节点 {node!r} 不是 'NodeMixin'.")
84
+
85
+ if cls.parent is not node:
86
+ cls._check_loop(node)
87
+ cls._detach(cls.parent)
88
+ cls._attach(node)
89
+
90
+ def _check_loop(cls, new_parent):
91
+ if new_parent is None:
92
+ return
93
+ if new_parent is cls:
94
+ raise LoopError("父节点不能为自身.")
95
+ if any(ancestor is cls for ancestor in new_parent.iter_to_root()):
96
+ raise LoopError(f"无法设置父节点. {cls!r}已经是{new_parent!r}的祖先.")
97
+
98
+ def _detach(cls, parent):
99
+ if parent is None:
100
+ return
101
+
102
+ try:
103
+ parent.children.remove(cls)
104
+ except ValueError:
105
+ raise TreeError("Tree is corrupt.")
106
+
107
+ def _attach(cls, new_parent):
108
+ if new_parent is None:
109
+ return # 不用做任何操作,因为parent默认就是None
110
+
111
+ parentchildren = new_parent.__children_ref
112
+
113
+ if any(child is cls for child in parentchildren):
114
+ raise TreeError("Tree is corrupt.")
115
+ parentchildren.append(weakref.ref(cls))
116
+ cls.__parent = weakref.ref(new_parent)
117
+
118
+ def iter_to_root(cls):
119
+ """遍历至根节点
120
+
121
+ 从当前节点触发遍历至根节点,包括自身。
122
+ """
123
+ node = cls
124
+ while node is not None:
125
+ yield node
126
+ node = node.parent
127
+
128
+ def iter_descendants(cls):
129
+ """遍历所有后代节点
130
+
131
+ 先序遍历所有后代节点,不包括自身。
132
+ """
133
+ for child in cls.children:
134
+ if child is not None:
135
+ yield child
136
+ yield from child.iter_descendants()
137
+
138
+ def iter_from_root(cls):
139
+ """从根节点遍历至当前节点
140
+
141
+ 从根节点遍历至当前节点,包括自身。
142
+ """
143
+ for node in reversed(list(cls.iter_to_root())):
144
+ yield node
145
+
146
+ #: 祖先节点
147
+ ancestors = property(lambda self: tuple(self.iter_to_root())[1:])
148
+ #: 节点在树中的深度
149
+ depth = property(lambda self: len(self.ancestors))
150
+ #: tuple: 后代节点
151
+ descendants = property(lambda self: tuple(self.iter_descendants()))
152
+ #: 根节点
153
+ root = property(lambda self: list(self.iter_to_root())[-1])
154
+
155
+ @property
156
+ def siblings(cls):
157
+ """
158
+ 兄弟节点
159
+
160
+ Returns:
161
+ tuple: 如有,返回所有兄弟节点;否则返回空元组
162
+
163
+ """
164
+ parent = cls.parent
165
+ if parent is None:
166
+ return tuple()
167
+ else:
168
+ return tuple(node for node in parent.children if node is not cls)
169
+
170
+ #: 是否叶子节点
171
+ is_leaf = property(lambda self: not bool(self.children))
172
+ #: 是否根节点
173
+ is_root = property(lambda self: self.parent is None)
174
+ #: tuple: 同一颗树的所有节点
175
+ family = property(lambda self: tuple((self.root, *self.root.descendants)))
176
+
177
+ def common_ancestor(cls, *others):
178
+ """获取最小共同祖先
179
+
180
+ 获取当前节点与其他节点的最小共同祖先,包括其本身。
181
+
182
+ Args:
183
+ *others: 其他节点
184
+
185
+ Returns:
186
+ 最小共同祖先
187
+ """
188
+ common = None
189
+
190
+ for antr_me, *antr_others in zip(cls.iter_from_root(), *map(methodcaller('iter_from_root'), others)):
191
+ if all(antr is antr_me for antr in antr_others):
192
+ common = antr_me
193
+ else:
194
+ break
195
+
196
+ if common is None:
197
+ raise TreeError(f"{cls!r}和{others!r}不属于同一颗树")
198
+ return common
199
+
200
+ def iter_to_descendant(cls, descendant):
201
+ """
202
+ 遍历自身到后代节点所经过的所有节点,不包括自身。
203
+
204
+ Args:
205
+ descendant: 后代节点
206
+ Raises:
207
+ ValueError: descendant不是自己或自己的后代,
208
+ Note:
209
+ 传入 ``descendant==cls`` 并不会引起错误,但也不会返回任何节点
210
+ """
211
+ found = False
212
+
213
+ for antr in descendant.iter_from_root():
214
+ if found:
215
+ yield antr
216
+ else:
217
+ if antr is not cls:
218
+ continue
219
+ else:
220
+ found = True
221
+
222
+ if found is False:
223
+ raise ValueError(f"{descendant!r}不是{cls!r}的后代")
224
+
225
+
226
+ class NodeMixin:
227
+ """
228
+ 给使用这个元类的类提供方便定义树结构的接口。
229
+
230
+ Example:
231
+ .. code-block:: python
232
+
233
+ class Root(NodeMixin):
234
+ pass
235
+
236
+ class ChildA(NodeMixin):
237
+ pass
238
+
239
+ class ChildB(NodeMixin):
240
+ pass
241
+
242
+ class GrandChildA(NodeMixin):
243
+ pass
244
+
245
+ ChildA.set_parent(Root)
246
+ ChildB.set_parent(Root)
247
+ GrandChildA.set_parent(ChildA)
248
+ # 经过上述定义后,将形成如下树结构
249
+ '''
250
+ Root--ChildA--GrandChildA
251
+ └─ChildB
252
+ '''
253
+ """
254
+ @property
255
+ def parent(self):
256
+ """父节点"""
257
+ try:
258
+ return self.__parent()
259
+ except AttributeError:
260
+ return None
261
+
262
+ @property
263
+ def ichildren(self):
264
+ """返回节点的直接孩子节点,包含自身"""
265
+ return [self, *self.children]
266
+
267
+ @property
268
+ def children(self):
269
+ """返回当前节点的直接孩子节点,不包含自身,当前节点无孩子则返回空列表"""
270
+ try:
271
+ return self.__children
272
+ except AttributeError:
273
+ self.__children = []
274
+ return self.__children
275
+
276
+ def set_parent(self, node):
277
+ """
278
+ 将指定的节点设为父节点。
279
+ 如果当前节点已有父节点,将首先把当前节点(及其子树)从原树中移除,
280
+ 再把当前节点(及其子树)接入新的树中。
281
+
282
+ Args:
283
+ node: 待设定的父节点
284
+
285
+ """
286
+ if node is not None and not isinstance(node, NodeMixin):
287
+ raise TreeError(f"父节点 {node!r} 不是 '{self.__class__.__name__}'.")
288
+
289
+ if self.parent is not node:
290
+ self._check_loop(node)
291
+ self._detach(self.parent)
292
+ self._attach(node)
293
+
294
+ def add_child(self, node):
295
+ """
296
+ 将指定的节点设为孩子节点。
297
+ 如果指定节点已有父节点,将首先把指定节点(及其子树)从原树中移除,
298
+ 再把指定节点(及其子树)接入新的树中。
299
+
300
+ Args:
301
+ node: 待设定的孩子节点
302
+ """
303
+ if not isinstance(node, NodeMixin):
304
+ raise TreeError(f"子节点 {node!r} 不是 '{self.__class__.__name__}'.")
305
+
306
+ if node not in self.children:
307
+ node._check_loop(self)
308
+ node._detach(node.parent)
309
+ node._attach(self)
310
+
311
+ def _check_loop(self, new_parent):
312
+ if new_parent is None:
313
+ return
314
+ if new_parent is self:
315
+ raise LoopError("父节点不能为自身.")
316
+ if any(ancestor is self for ancestor in new_parent.iter_to_root()):
317
+ raise LoopError(f"无法设置父节点. {self!r}已经是{new_parent!r}的祖先.")
318
+
319
+ def _detach(self, parent):
320
+ if parent is None:
321
+ return
322
+
323
+ try:
324
+ parent.children.remove(self)
325
+ except ValueError:
326
+ raise TreeError("Tree is corrupt.")
327
+
328
+ def _attach(self, new_parent):
329
+ """将一棵树连接到父结点上"""
330
+ if new_parent is None:
331
+ return # 不用做任何操作,因为parent默认就是None
332
+
333
+ parentchildren = new_parent.children
334
+
335
+ if any(child is self for child in parentchildren):
336
+ raise TreeError("Tree is corrupt.")
337
+ parentchildren.append(self)
338
+ self.__parent = weakref.ref(new_parent)
339
+
340
+ def iter_to_root(self):
341
+ """从当前节点迭代至根节点,包括自身。返回生成器。"""
342
+ node = self
343
+ while node is not None:
344
+ yield node
345
+ node = node.parent
346
+
347
+ def iter_descendants(self, include=False):
348
+ """先序遍历所有后代节点,不包括自身。返回节点列表有顺序"""
349
+ if include:
350
+ yield self
351
+ for child in self.children:
352
+ if child is not None:
353
+ yield from child.iter_descendants(include=True)
354
+
355
+ def iter_from_root(self):
356
+ """从根节点迭代至当前节点,包括自身。"""
357
+ for node in reversed(list(self.iter_to_root())):
358
+ yield node
359
+
360
+ #: 祖先节点
361
+ ancestors = property(lambda self: tuple(self.iter_to_root())[1:])
362
+ #: 节点在树中的深度
363
+ depth = property(lambda self: len(self.ancestors))
364
+ #: tuple: 后代节点
365
+ descendant = property(lambda self: list(self.iter_descendants()))
366
+ #: tuple: 后代节点,包括自身
367
+ idescendant = property(lambda self: list(self.iter_descendants(include=True)))
368
+
369
+ #: 根节点
370
+ root = property(lambda self: list(self.iter_to_root())[-1])
371
+
372
+ def iter_base(self):
373
+ """遍历当前子树的所有叶子节点"""
374
+ for node in self.iter_descendants():
375
+ if node.is_leaf:
376
+ yield node
377
+
378
+ #: 子树的所有叶子节点
379
+ base = property(lambda self: list(self.iter_base()))
380
+
381
+ @property
382
+ def ibase(self):
383
+ """子树的所有叶子节点,包含节点自身。"""
384
+ return [self, *self.base]
385
+
386
+ @property
387
+ def siblings(self):
388
+ """
389
+ 兄弟节点,不包含自身。
390
+
391
+ Returns:
392
+ tuple: 如有,返回所有兄弟节点;否则返回空元组
393
+
394
+ """
395
+ parent = self.parent
396
+ if parent is None:
397
+ return tuple()
398
+ else:
399
+ return tuple(node for node in parent.children if node is not self)
400
+
401
+ #: 是否叶子节点
402
+ is_leaf = property(lambda self: not bool(self.children))
403
+ #: 是否根节点
404
+ is_root = property(lambda self: self.parent is None)
405
+ #: tuple: 同一颗树的所有节点
406
+ family = property(lambda self: self.root.idescendant)
407
+
408
+ def common_ancestor(self, *others):
409
+ """
410
+ 获取当前节点与其他节点的最小共同祖先,包括其本身。
411
+
412
+ Args:
413
+ *others: 其他节点
414
+
415
+ Returns:
416
+ 最小共同祖先
417
+
418
+ """
419
+ common = None
420
+
421
+ for antr_me, *antr_others in zip(self.iter_from_root(),
422
+ *map(methodcaller('iter_from_root'), others)):
423
+ if all(antr is antr_me for antr in antr_others):
424
+ common = antr_me
425
+ else:
426
+ break
427
+
428
+ if common is None:
429
+ raise TreeError(f"{self!r}和{others!r}不属于同一颗树")
430
+ return common
431
+
432
+ def iter_to_descendant(self, descendant):
433
+ """
434
+ 遍历自身到后代节点所经过的所有节点,不包括自身。
435
+
436
+ Args:
437
+ descendant: 后代节点
438
+
439
+ Raises:
440
+ descendant不是自己或自己的后代时,抛出 `ValueError` 异常
441
+
442
+ Note:
443
+ 传入 `descendant==self` 并不会引起错误,但也不会返回任何节点
444
+ """
445
+ found = False
446
+
447
+ for antr in descendant.iter_from_root():
448
+ if found:
449
+ yield antr
450
+ else:
451
+ if antr is not self:
452
+ continue
453
+ else:
454
+ found = True
455
+
456
+ if found is False:
457
+ raise ValueError(f"{descendant!r}不是{self!r}的后代")
458
+
459
+ def iter_level(self, from_offset, to_offset, include=True):
460
+ """
461
+ 返回与当前节点位置相对的节点,向上的节点只包括父节点,向下的节点以广度优先顺序遍历。
462
+
463
+ Args:
464
+ from_offset: 相对节点的开始位置
465
+ to_offset: 相对节点的结束位置,包括结束点
466
+ include: 是否包括自身节点
467
+
468
+ """
469
+
470
+ if to_offset < from_offset:
471
+ raise ValueError("Stop level should be greater than start.")
472
+
473
+ parent_list = []
474
+ parent = self.parent
475
+ search_up_cnt = from_offset
476
+
477
+ while search_up_cnt < 0 and parent:
478
+ parent_list.append(parent)
479
+ parent = parent.parent
480
+ search_up_cnt += 1
481
+
482
+ parent_list = parent_list[from_offset - to_offset - 1:]
483
+
484
+ if parent_list:
485
+ for node in reversed(parent_list):
486
+ yield node
487
+
488
+ if include:
489
+ yield self
490
+
491
+ if to_offset < 0:
492
+ return
493
+
494
+ yield from bfs(self, depth=to_offset, include=False)
495
+
496
+
497
+ class ShareableNodeMixin(NodeMixin):
498
+ _ref_shared_from = None
499
+
500
+ def add_child(self, node):
501
+ raise NotImplemented
502
+
503
+ @property
504
+ def children(self):
505
+ """返回当前节点的直接孩子节点,不包含自身,当前节点无孩子则返回空列表"""
506
+ if shared_from := self.shared_from:
507
+ return shared_from.children
508
+ else:
509
+ return super().children
510
+
511
+ @property
512
+ def shared_by(self):
513
+ """父节点"""
514
+ try:
515
+ return self.__shared_by
516
+ except AttributeError:
517
+ self.__shared_by = []
518
+ return self.__shared_by
519
+
520
+ @property
521
+ def is_shared(self):
522
+ return bool(self.shared_by)
523
+
524
+ @property
525
+ def shared_from(self):
526
+ if self._ref_shared_from:
527
+ return self._ref_shared_from()
528
+ else:
529
+ return None
530
+
531
+ def add_shared(self, node):
532
+ self.shared_by.append(node)
533
+ node._ref_shared_from = weakref.ref(self)
534
+
535
+ @property
536
+ def parent(self):
537
+ """父节点"""
538
+ try:
539
+ if parent := self.__parent:
540
+ return [p() for p in parent]
541
+ else:
542
+ return None
543
+ except AttributeError:
544
+ self.__parent = [] # noqa
545
+ return None
546
+
547
+ def set_parent(self, node, check_loop: bool = False):
548
+ if node is not None and not isinstance(node, NodeMixin):
549
+ raise TreeError(f"父节点 {node!r} 不是 '{self.__class__.__name__}'.")
550
+
551
+ parent = self.parent
552
+
553
+ if node is None:
554
+ if parent is None:
555
+ return
556
+
557
+ for p in self.parent:
558
+ p.children.remove(self)
559
+ self.parent.clear()
560
+
561
+ else:
562
+ if check_loop:
563
+ self._check_loop(node)
564
+ self._attach(node)
565
+
566
+ def _attach(self, new_parent):
567
+ """将一棵树连接到父结点上"""
568
+ parentchildren = new_parent.children
569
+
570
+ if any(child is self for child in parentchildren):
571
+ return
572
+ parentchildren.append(self)
573
+ self.__parent.append(weakref.ref(new_parent))
574
+
575
+ def iter_to_root(self, exclude=None):
576
+ """从当前节点迭代至根节点,包括自身。返回生成器。"""
577
+ exclude = exclude or set()
578
+ yield self
579
+ exclude.add(self)
580
+ for p in self.parent or []:
581
+ yield from p.iter_to_root(exclude)
582
+
583
+ @property
584
+ def siblings(self):
585
+ sib = set()
586
+ for parent in self.parent or []:
587
+ if parent is not None:
588
+ sib.update(node for node in parent.children if node is not self)
589
+ return tuple(sib)
590
+
591
+
592
+ def bfs(node, depth=-1, include=True):
593
+ """
594
+ 广度优先遍历树
595
+
596
+ Args:
597
+ node(NodeMixin): 遍历以 ``node`` 为根节点的维度树
598
+ depth(int): 遍历的深度
599
+ include(bool): 是或否包含自身
600
+
601
+ Returns:
602
+ 返回生成器,包含遍历到的所有节点
603
+ """
604
+ if depth == 0:
605
+ return
606
+ elif depth > 0:
607
+ depth += node.depth + 1
608
+
609
+ if include:
610
+ yield node
611
+
612
+ node_to_visit = node.children[:]
613
+
614
+ while node_to_visit:
615
+ child = node_to_visit.pop(0)
616
+ if child.depth == depth:
617
+ break
618
+
619
+ yield child
620
+
621
+ for grandchild in child.children:
622
+ node_to_visit.append(grandchild)
623
+
624
+
625
+ class TreeRenderer:
626
+ """渲染树形结构"""
627
+ def __init__(self):
628
+ self.blank = ' '
629
+ self.low = '└── '
630
+ self.mid = '├── '
631
+ self.gap = '| '
632
+ self.len = len(self.blank)
633
+
634
+ def iter_line(self, root, sty='', fill=''):
635
+ yield fill[:-self.len] + sty * (len(fill) > 0) + str(root)
636
+
637
+ child_num = len(root.children)
638
+ for idx, child in enumerate(root.children, 1):
639
+ if idx == child_num:
640
+ yield from self.iter_line(child, sty=self.low, fill=fill + self.blank)
641
+ else:
642
+ yield from self.iter_line(child, sty=self.mid, fill=fill + self.gap)
643
+
644
+ def render(self, root) -> str:
645
+ """
646
+ 渲染树结构
647
+
648
+ Args:
649
+ root(NodeMixin): 树的根节点
650
+
651
+ Examples:
652
+ .. code-block:: python
653
+
654
+ # dim.root 为需要打印的维度树的根节点
655
+ print(TreeRenderer().render(dim.root))
656
+ # 经过上述定义后,将形成如下树结构
657
+ '''
658
+ #root
659
+ └── TotalPeriod
660
+ └── Q1
661
+ ├── 1
662
+ ├── 2
663
+ └── 3
664
+ '''
665
+ """
666
+ if isinstance(root, (NodeMixin, MetaNodeMixin)):
667
+ return '\n'.join(self.iter_line(root))
668
+ else:
669
+ raise TypeError(f'{root.__class__.__name__}不是NodeMixin或MetaNodeMixin')
670
+
671
+ def show(self, root):
672
+ """打印树结构"""
673
+ print(self.render(root))