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,707 @@
1
+ import copy
2
+ import operator
3
+ import functools
4
+ import weakref
5
+ from collections import defaultdict, UserList
6
+ from contextlib import contextmanager
7
+ from typing import List, Dict, Tuple, Iterable, Union, Callable, TYPE_CHECKING
8
+
9
+ import numpy as np
10
+
11
+ from .constants import OPMAP, Instruction
12
+ from .typing import TD_Str_ListStr, TD_Str_Str, T_MaybeCondition
13
+ from .utils import AutoFillSeries
14
+ from deepfos.core.logictable.nodemixin import NodeMixin
15
+ from deepfos.core.dimension.sysdimension import unpack_expr
16
+ from deepfos.lib.decorator import cached_property
17
+ from deepfos.lib.utils import dict_to_expr
18
+ from loguru import logger
19
+
20
+
21
+ # -----------------------------------------------------------------------------
22
+ # constant
23
+
24
+ K_MEMBER = 'mbr'
25
+ K_EXPR = 'expr'
26
+
27
+
28
+ # -----------------------------------------------------------------------------
29
+ # Base Classes
30
+ class ValNode(NodeMixin):
31
+ """变量节点"""
32
+ def __init__(self, value):
33
+ self.value = value
34
+
35
+ def prepare(self):
36
+ pass
37
+
38
+ @property
39
+ def fix_mbrs(self) -> List[TD_Str_ListStr]:
40
+ """节点的锁定信息,以 维度名:维度成员列表 格式储存"""
41
+ return []
42
+
43
+ @property
44
+ def fix_exprs(self) -> List[TD_Str_Str]:
45
+ """节点的锁定信息,以 维度名:维度表达式 格式储存"""
46
+ return []
47
+
48
+ def __str__(self):
49
+ return str(self.value)
50
+
51
+
52
+ class OpNode(NodeMixin):
53
+ """运算符节点"""
54
+ def __init__(self, op):
55
+ self.op = op
56
+
57
+ def __str__(self):
58
+ return self.op
59
+
60
+ def solve(self):
61
+ args = []
62
+ for child in self.children:
63
+ if isinstance(child, ValNode):
64
+ args.append(child.value)
65
+ elif isinstance(child, self.__class__):
66
+ args.append(child.solve())
67
+ else:
68
+ raise TypeError(f"Expect type {self.__class__} or {ValNode}, got {type(child)}")
69
+ return self.calc(*args)
70
+
71
+ def calc(self, left, right):
72
+ op = getattr(operator, self.op, None)
73
+ if op is None:
74
+ raise ValueError(f"Unknown operator: {self.op}.")
75
+
76
+ return op(left, right)
77
+
78
+ def get_formula(self):
79
+ args = []
80
+
81
+ for child in self.children:
82
+ if isinstance(child, ValNode):
83
+ args.append(str(child))
84
+ elif isinstance(child, self.__class__):
85
+ args.append(child.get_formula())
86
+ else:
87
+ raise TypeError(f"Expect type {self.__class__} or {ValNode}, got {type(child)}")
88
+ op_repr = f" {OPMAP.get(self.op, self.op)} "
89
+ return "(" + op_repr.join(args) + ")"
90
+
91
+
92
+ class CalcUnit:
93
+ """计算单元"""
94
+ def __init__(self, op_cls=OpNode):
95
+ self._op_cls = op_cls
96
+ self.root = None
97
+
98
+ @cached_property
99
+ def as_node(self) -> ValNode:
100
+ return NotImplemented
101
+
102
+ def _validate(self, other):
103
+ pass
104
+
105
+ def _attach(self, other, new_root, reverse=False):
106
+ if isinstance(other, CalcUnit):
107
+ self._validate(other)
108
+ val_node = other.root or other.as_node
109
+ else:
110
+ val_node = ValNode(other)
111
+
112
+ if reverse:
113
+ val_node.set_parent(new_root)
114
+
115
+ if self.root is None:
116
+ self.as_node.set_parent(new_root)
117
+ else:
118
+ self.root.set_parent(new_root)
119
+
120
+ if not reverse:
121
+ val_node.set_parent(new_root)
122
+
123
+ self.root = new_root
124
+ return self
125
+
126
+ def __add__(self, other):
127
+ return self._attach(other, self._op_cls('add'))
128
+
129
+ def __sub__(self, other):
130
+ return self._attach(other, self._op_cls('sub'))
131
+
132
+ def __mul__(self, other):
133
+ return self._attach(other, self._op_cls('mul'))
134
+
135
+ def __truediv__(self, other):
136
+ return self._attach(other, self._op_cls('truediv'))
137
+
138
+ def __mod__(self, other):
139
+ return self._attach(other, self._op_cls('mod'))
140
+
141
+ def __pow__(self, other):
142
+ return self._attach(other, self._op_cls('pow'))
143
+
144
+ def __floordiv__(self, other):
145
+ return self._attach(other, self._op_cls('floordiv'))
146
+
147
+ def __radd__(self, other):
148
+ return self._attach(other, self._op_cls('add'), True)
149
+
150
+ def __rsub__(self, other):
151
+ return self._attach(other, self._op_cls('sub'), True)
152
+
153
+ def __rmul__(self, other):
154
+ return self._attach(other, self._op_cls('mul'), True)
155
+
156
+ def __rtruediv__(self, other):
157
+ return self._attach(other, self._op_cls('truediv'), True)
158
+
159
+ def __rmod__(self, other):
160
+ return self._attach(other, self._op_cls('mod'), True)
161
+
162
+ def __rpow__(self, other):
163
+ return self._attach(other, self._op_cls('pow'), True)
164
+
165
+ def __rfloordiv__(self, other):
166
+ return self._attach(other, self._op_cls('floordiv'), True)
167
+
168
+ def __str__(self):
169
+ if self.root is None:
170
+ return str(self.as_node)
171
+ return self.root.get_formula()
172
+
173
+ def iter_node(self):
174
+ if self.root is None:
175
+ node = self.as_node
176
+ if isinstance(node, ValNode):
177
+ yield node
178
+ else:
179
+ for node in self.root.iter_descendants():
180
+ if isinstance(node, ValNode):
181
+ yield node
182
+
183
+ def _node_init(self):
184
+ # 初始化所有计算节点,获取参与计算的数据集
185
+ for node in self.iter_node():
186
+ node.prepare()
187
+
188
+ def solve(self):
189
+ self._node_init()
190
+ if self.root is None:
191
+ # 只有一个赋值语句,没有计算
192
+ return self.as_node.value
193
+ return self.root.solve()
194
+
195
+
196
+ # -----------------------------------------------------------------------------
197
+ # Cube Nodes
198
+ class MemberNode(ValNode):
199
+ """
200
+ 根据第一个入参的不同,存在两种行为。
201
+ 1. 如果是一般实数,value即实数值,prepare将无行为
202
+ 2. 其他情况,prepare将根据fix条件对DataFrame作必要预处理。获取可以参与计算的数据列。
203
+ """
204
+
205
+ # noinspection PyMissingConstructor
206
+ def __init__(
207
+ self,
208
+ cube,
209
+ indicator: str = None,
210
+ hook: Callable = None,
211
+ fix_mbrs: TD_Str_ListStr = None,
212
+ fix_exprs: TD_Str_Str = None,
213
+ calc_dim: str = None,
214
+ calc_mbr: str = None,
215
+ ):
216
+ self.hook = hook
217
+ self.indicator = indicator
218
+ self._cube = cube
219
+ self._fix_mbrs = fix_mbrs or {}
220
+ self._fix_exprs = fix_exprs or {}
221
+ self._calc_dim = calc_dim
222
+ self._calc_mbr = calc_mbr
223
+
224
+ @property
225
+ def fix_mbrs(self) -> List[TD_Str_ListStr]:
226
+ """
227
+ 来自计算节点 `CubeCalcUnit` 的 :attr: `_extra_fix`
228
+ 包括计算维度(如Account['Price'])和on条件。
229
+ """
230
+ return [self._fix_mbrs]
231
+
232
+ @cached_property
233
+ def on(self) -> TD_Str_ListStr:
234
+ fix_mbrs = {**self._fix_mbrs}
235
+ fix_mbrs.pop(self._calc_dim, None)
236
+ return fix_mbrs
237
+
238
+ @property
239
+ def fix_exprs(self) -> List[TD_Str_Str]:
240
+ """
241
+ 类似于 :attr:`fix_mbrs`
242
+ """
243
+ return [self._fix_exprs]
244
+
245
+ def _pass(self):
246
+ pass
247
+
248
+ def prepare(self):
249
+ self._cube.calc_set.load_fixes(
250
+ self._calc_mbr, self._calc_dim,
251
+ self.on, str(self)
252
+ )
253
+
254
+ @property
255
+ def value(self):
256
+ column = str(self) if self.on else self._calc_mbr
257
+ data_src = self._cube.calc_set.data_proxy
258
+
259
+ if column not in data_src.columns:
260
+ value = AutoFillSeries(np.full(len(data_src), np.NAN))
261
+ else:
262
+ value = AutoFillSeries(data_src[column])
263
+
264
+ value.custom_options = self._cube.option_stack[0] # noqa
265
+
266
+ if self.hook is not None:
267
+ value = self.hook(value)
268
+
269
+ return value
270
+
271
+ def __str__(self):
272
+ return self.indicator
273
+
274
+
275
+ class FunctionNode(ValNode):
276
+ def __init__(self, func, *args, **kwargs):
277
+ super().__init__(None)
278
+ self.func = func
279
+ self._args = args
280
+ self._kwargs = kwargs
281
+
282
+ @property
283
+ def fix_mbrs(self) -> List[TD_Str_ListStr]:
284
+ return sum((node.fix_mbrs for node in self._iter_arg_node()), [])
285
+
286
+ @property
287
+ def fix_exprs(self) -> List[TD_Str_Str]:
288
+ return sum((node.fix_exprs for node in self._iter_arg_node()), [])
289
+
290
+ @staticmethod
291
+ def _resolve_node(arg):
292
+ if isinstance(arg, CalcUnit):
293
+ node = arg.as_node
294
+ if isinstance(node, ValNode):
295
+ node.prepare()
296
+ return node.value
297
+ else:
298
+ return arg
299
+
300
+ def prepare(self):
301
+ """
302
+ 1. 对参数中所有计算节点进行初始化;
303
+ 2. 获取所有参数并且进行对齐;
304
+ 3. 替换对齐后的参数,调用函数;
305
+ 4. 将返回结果包装为 CubeVariable,赋值至value
306
+ """
307
+
308
+ args = list(self._args)
309
+ kwargs = {**self._kwargs}
310
+
311
+ for idx, arg in enumerate(args):
312
+ args[idx] = self._resolve_node(arg)
313
+
314
+ for key, val in self._kwargs.items():
315
+ kwargs[key] = self._resolve_node(val)
316
+
317
+ self.value = self.func(*args, **kwargs)
318
+
319
+ def _iter_arg_node(self) -> Iterable[ValNode]:
320
+ """遍历包含在函数参数内的所有节点"""
321
+ for item in self._args:
322
+ if isinstance(item, CalcUnit):
323
+ yield from item.iter_node()
324
+
325
+ for item in self._kwargs.values():
326
+ if isinstance(item, CalcUnit):
327
+ yield from item.iter_node()
328
+
329
+ def __str__(self):
330
+ sig = list(map(str, self._args))
331
+ for k, v in self._kwargs.items():
332
+ sig.append(f"{k}={str(v)}")
333
+
334
+ return f"{self.func.__name__}({', '.join(sig)})"
335
+
336
+
337
+ def as_function_node(func):
338
+ """
339
+ 装饰器,将普通函数作为函数节点使用。
340
+ 用于cube多维计算。
341
+
342
+ Args:
343
+ func:
344
+
345
+ Returns:
346
+
347
+ """
348
+ @functools.wraps(func)
349
+ def wrapper(*args, **kwargs):
350
+ return FuncCalcUnit(func=func, args=args, kwargs=kwargs)
351
+ return wrapper
352
+
353
+
354
+ # -----------------------------------------------------------------------------
355
+ # Formula elements
356
+ def _get_calc_dim(expr: str, cube) -> Tuple[str, str]:
357
+ dim, member = unpack_expr(expr, silent=True)
358
+ if dim is not None:
359
+ return dim, member
360
+
361
+ if not cube.calc_dim:
362
+ raise ValueError(f"Cannot resolve dimension from expr: {expr}.")
363
+
364
+ dim = cube.calc_dim[-1]
365
+ return dim, member
366
+
367
+
368
+ class ValueCalcUnit(CalcUnit):
369
+ if TYPE_CHECKING:
370
+ from .cube import CubeBase
371
+ _cube: CubeBase
372
+
373
+ """
374
+ 记录计算逻辑,以及各个计算单元的限定条件。
375
+ 支持各种四则运算符,但是不立即触发计算。
376
+
377
+ 会将所有参与计算的成员包装成CubeValNode, CubeFunctionNode等对象的节点。
378
+
379
+ 当调用solve方法时,会调用所有节点的prepare方法,再求解所有算式。
380
+ """
381
+ def __init__(self, cube, calc_mbr: str):
382
+ super().__init__()
383
+ self.pre_hook = None
384
+ self.post_hook = None
385
+ self.calc_dim, self.calc_mbr = _get_calc_dim(calc_mbr, cube)
386
+ self._cube = cube
387
+ self._extra_fix = {self.calc_dim: [self.calc_mbr]}
388
+ self._fix_str_memo = {self.calc_dim: self.calc_mbr}
389
+
390
+ def on(self, **dim_mbr_map: Union[str, int, float]):
391
+ for dimension, member in self._validate_dims(dim_mbr_map).items():
392
+ member = str(member)
393
+ self._extra_fix[dimension] = [member]
394
+ self._fix_str_memo[dimension] = member
395
+ return self
396
+
397
+ def _to_expr(self):
398
+ return '->'.join(f"{k}{{{v}}}" for k, v in self._fix_str_memo.items())
399
+
400
+ def _validate_dims(self, kwargs):
401
+ rtn = {
402
+ k: v for k, v in kwargs.items()
403
+ if k in self._cube.dimensions}
404
+
405
+ filtered = kwargs.keys() - rtn.keys()
406
+ if filtered:
407
+ raise ValueError(f"Dimensions : {filtered} does not belong to cube.")
408
+ return rtn
409
+
410
+ def set_hook(self, func: Callable, at: str):
411
+ if not callable(func):
412
+ raise TypeError(f"Argument must be callable.")
413
+ at = at.lower()
414
+ if at == 'pre':
415
+ self.pre_hook = func
416
+ elif at == 'post':
417
+ self.post_hook = func
418
+ return self
419
+
420
+ @cached_property
421
+ def as_node(self):
422
+ return MemberNode(
423
+ self._cube,
424
+ indicator=self._to_expr(),
425
+ hook=self.pre_hook,
426
+ fix_mbrs=self._extra_fix,
427
+ fix_exprs=self._fix_str_memo,
428
+ calc_dim=self.calc_dim,
429
+ calc_mbr=self.calc_mbr
430
+ )
431
+
432
+
433
+ class FuncCalcUnit(CalcUnit):
434
+ def __init__(self, func, args, kwargs):
435
+ super().__init__()
436
+ self._args = args or ()
437
+ self._kwargs = kwargs or {}
438
+ self._func = func
439
+
440
+ @cached_property
441
+ def as_node(self):
442
+ return FunctionNode(
443
+ self._func,
444
+ *self._args,
445
+ **self._kwargs
446
+ )
447
+
448
+
449
+ class CubeFixer:
450
+ def __init__(self, cube_obj):
451
+ self._cube = cube_obj
452
+
453
+ def __getitem__(self, item: str) -> ValueCalcUnit:
454
+ """
455
+ cube.fix as 的返回对象在等式右边会调用此方法。
456
+ 方法返回计算单元,计算单元将记录维度锁定条件以及
457
+ 计算顺序。
458
+
459
+ Args:
460
+ item: 维度名或者 **单成员的维度表达式**
461
+
462
+ Returns:
463
+ 值类型计算单元
464
+
465
+ """
466
+ return ValueCalcUnit(cube=self._cube, calc_mbr=item)
467
+
468
+ def __setitem__(self, key, value):
469
+ dim, member = _get_calc_dim(key, self._cube)
470
+ self._cube.formulas.append((dim, member, value))
471
+
472
+
473
+ # -----------------------------------------------------------------------------
474
+ # Fomular Solver
475
+ class _Condition:
476
+ ENDIF = object()
477
+
478
+ def __init__(self, cond: T_MaybeCondition, flag):
479
+ self.cond = cond
480
+ self.flag = flag
481
+ self.filter = None
482
+ self._install_filter(cond)
483
+
484
+ def parse_str_cond(self, dataframe):
485
+ query_set = dataframe.query(self.cond)
486
+ if self.flag is True:
487
+ return query_set
488
+ else:
489
+ return dataframe.loc[dataframe.index.difference(query_set.index)]
490
+
491
+ def parse_callable_cond(self, dataframe):
492
+ query_set = self.cond(dataframe)
493
+ if self.flag is True:
494
+ return query_set
495
+ else:
496
+ return dataframe.loc[dataframe.index.difference(query_set.index)]
497
+
498
+ def _install_filter(self, cond):
499
+ if isinstance(cond, str):
500
+ self.filter = self.parse_str_cond
501
+ elif callable(cond):
502
+ self.filter = self.parse_callable_cond
503
+
504
+ def __str__(self):
505
+ return str(self.cond)
506
+
507
+
508
+ class FormulaContainer(UserList):
509
+ def __init__(self, cube, initlist=None):
510
+ super().__init__(initlist)
511
+ self.cube = weakref.ref(cube)
512
+ #: 公式左边出现的维度->维度成员集合
513
+ self.left = defaultdict(set)
514
+ self.fix_by_mbrs: List[TD_Str_ListStr] = []
515
+ self._fix_by_expr: List[TD_Str_ListStr] = []
516
+ self.__left_fix_memo: Dict[str, Dict[str, TD_Str_ListStr]] = {}
517
+
518
+ @property
519
+ def query_expr_list(self) -> List[str]:
520
+ """cube维度查询公式的列表"""
521
+ return [dict_to_expr(item) for item in self._fix_by_expr]
522
+
523
+ def _iter_with_filter(self):
524
+ calc_set = self.cube().calc_set
525
+
526
+ for item in self.data:
527
+ if isinstance(item, _Condition):
528
+ calc_set.add_filter(item)
529
+ elif item is _Condition.ENDIF:
530
+ calc_set.pop_filter()
531
+ elif isinstance(item, Instruction):
532
+ calc_set.add_instruction(item)
533
+ elif callable(item): # callbacks per line
534
+ item()
535
+ else:
536
+ yield item
537
+
538
+ @property
539
+ def _cube_dim_exprs(self) -> TD_Str_ListStr:
540
+ """返回cube.fix中general_fix的结果。"""
541
+ return {
542
+ dimname: [dim.expr_body]
543
+ for dimname, dim in self.cube().dimensions.items()
544
+ if dim.activated
545
+ }
546
+
547
+ def append(self, item) -> None:
548
+ if isinstance(item, Tuple):
549
+ calc_dim, calc_mbr, right = item
550
+ general_mbr_fix = self.cube().dim_state
551
+ general_expr_fix = self._cube_dim_exprs
552
+ if isinstance(right, CalcUnit):
553
+ self._handle_calc_unit(
554
+ calc_dim,
555
+ calc_mbr,
556
+ general_expr_fix,
557
+ general_mbr_fix,
558
+ right
559
+ )
560
+
561
+ self.left[calc_dim].add(calc_mbr)
562
+
563
+ return self.data.append(item)
564
+
565
+ def _handle_calc_unit(
566
+ self,
567
+ calc_dim: str,
568
+ calc_mbr: str,
569
+ general_expr_fix: TD_Str_ListStr,
570
+ general_mbr_fix: TD_Str_ListStr,
571
+ calc_unit: CalcUnit
572
+ ):
573
+ """
574
+ 根据当前提交的计算公式,整合成维度成员的查询表达式,
575
+ 原则上生成的维度表达式数量应该尽可能少。
576
+ 最终的维度表达式存放在 :attr:`query_expr_list` 中。
577
+ """
578
+
579
+ for node in calc_unit.iter_node():
580
+ if len(node.fix_exprs) != len(node.fix_exprs):
581
+ raise RuntimeError("Formula is corrupted.")
582
+
583
+ for fix_mbrs, fix_expr in zip(node.fix_mbrs, node.fix_exprs):
584
+ if not fix_mbrs:
585
+ continue
586
+ if calc_dim not in fix_mbrs:
587
+ raise ValueError(
588
+ "Dimension to calculate is not consistent. "
589
+ f"Expect '{calc_dim}' in right-side expression. "
590
+ f"Got '{list(fix_mbrs.keys())}'.")
591
+
592
+ node_mbr = fix_expr[calc_dim]
593
+
594
+ if node_mbr in self.left.get(calc_dim, []):
595
+ # 对于已经在等式左边出现的,即使在右边出现也不需要查询
596
+ continue
597
+
598
+ if len(fix_mbrs) == 1:
599
+ # 仅一个条件则表示没有使用过on, 可以并入到公式左边的计算成员
600
+ if calc_dim not in self.__left_fix_memo:
601
+ self.__left_fix_memo[calc_dim] = fix_memo = {}
602
+ fix_memo[K_MEMBER] = mbr_memo = copy.deepcopy(general_mbr_fix)
603
+ fix_memo[K_EXPR] = expr_memo = copy.deepcopy(general_expr_fix)
604
+ mbr_memo[calc_dim] = []
605
+ expr_memo[calc_dim] = []
606
+ self.fix_by_mbrs.append(mbr_memo)
607
+ self._fix_by_expr.append(expr_memo)
608
+ else:
609
+ mbr_memo = self.__left_fix_memo[calc_dim][K_MEMBER]
610
+ expr_memo = self.__left_fix_memo[calc_dim][K_EXPR]
611
+ mbr_memo[calc_dim].extend(fix_mbrs[calc_dim])
612
+ expr_memo[calc_dim].append(fix_expr[calc_dim])
613
+ else:
614
+ # 使用过on, 需要创建全新的fix条件
615
+ new_mbr_fix = copy.deepcopy(general_mbr_fix)
616
+ for k, v in fix_mbrs.items():
617
+ new_mbr_fix[k] = v
618
+ self.fix_by_mbrs.append(new_mbr_fix)
619
+
620
+ new_expr_fix = copy.deepcopy(general_expr_fix)
621
+ for k, v in fix_expr.items():
622
+ new_expr_fix[k] = [v]
623
+ self._fix_by_expr.append(new_expr_fix)
624
+
625
+ def _assign(self):
626
+ calc_set = self.cube().calc_set
627
+ last_calc_dim = None
628
+
629
+ for calc_dim, mbr, right in self._iter_with_filter():
630
+ logger.debug(f"Start calculation: {calc_dim}{{{mbr}}}={right}")
631
+
632
+ if last_calc_dim is None:
633
+ calc_set.pivot(calc_dim)
634
+ elif last_calc_dim != calc_dim:
635
+ calc_set.unpivot(last_calc_dim)
636
+ calc_set.pivot(calc_dim)
637
+
638
+ last_calc_dim = calc_dim
639
+
640
+ if isinstance(right, CalcUnit):
641
+ value = right.solve()
642
+ post_hook = getattr(right, 'post_hook', None)
643
+ else:
644
+ value = right
645
+ post_hook = None
646
+
647
+ if post_hook:
648
+ value = post_hook(value)
649
+
650
+ calc_set.set_value(mbr, value)
651
+
652
+ if last_calc_dim is not None:
653
+ calc_set.unpivot(last_calc_dim)
654
+
655
+ def solve(self):
656
+ """
657
+ 成员公式求解
658
+
659
+ 在进行计算前,会根据所有等式的fix条件查询出本次计算可能涉及的数据,
660
+ 以下称为“计算集”.计算集会绑定至Cube对象,并且作为所有计算节点初始化的数据源。
661
+
662
+ 对于每一个等式,计算分为两步:
663
+
664
+ 1. 调用所有计算节点的prepare方法,该方法会根据fix条件装载需要参与
665
+ 计算的 :class:`Series`,并且返回,
666
+ 2. :class:`Series` 进行运算,并且将运算结果赋值回计算集,
667
+ 以确保前面的计算结果可以正确影响后续计算。
668
+ """
669
+ # noinspection PyProtectedMember
670
+ self.cube()._load_calc_set(
671
+ self.query_expr_list,
672
+ self.fix_by_mbrs
673
+ )
674
+ self._assign()
675
+ self.clear()
676
+
677
+ def clear(self) -> None:
678
+ """删除公式列表中所有数据及状态"""
679
+ self.data.clear()
680
+ self.left.clear()
681
+ self.fix_by_mbrs.clear()
682
+ self._fix_by_expr.clear()
683
+ self.__left_fix_memo.clear()
684
+
685
+
686
+ class _ConditionWrapper:
687
+ def __init__(
688
+ self,
689
+ formula: FormulaContainer,
690
+ condition: T_MaybeCondition
691
+ ):
692
+ self.condition = condition
693
+ self.formulas = formula
694
+
695
+ @property
696
+ @contextmanager
697
+ def true(self):
698
+ self.formulas.append(_Condition(self.condition, True))
699
+ yield
700
+ self.formulas.append(_Condition.ENDIF)
701
+
702
+ @property
703
+ @contextmanager
704
+ def false(self):
705
+ self.formulas.append(_Condition(self.condition, False))
706
+ yield
707
+ self.formulas.append(_Condition.ENDIF)