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,609 @@
1
+ from collections.abc import Iterable
2
+ from functools import reduce, wraps
3
+
4
+ from deepfos.lib.decorator import cached_property
5
+ from ._operator import *
6
+
7
+
8
+ class CachedProperty(cached_property):
9
+ cached_names = set()
10
+
11
+ def __init__(self, func):
12
+ self.__class__.cached_names.add(func.__name__)
13
+ super().__init__(func)
14
+
15
+
16
+ class SqlCondError(Exception):
17
+ pass
18
+
19
+
20
+ def _df2cond(df, quote_char):
21
+ return ' AND '.join(df.apply(
22
+ lambda s: s.op.to_sql_template(quote_char=quote_char).format(s.field),
23
+ axis=1
24
+ ))
25
+
26
+
27
+ def _df2pandascond(df):
28
+ return ' and '.join(
29
+ df.apply(lambda s: s.op.to_pandasql().format(s.field), axis=1))
30
+
31
+
32
+ def _df2str(df, strfunc=repr):
33
+ col_str = '#'.join(map(repr, df.columns))
34
+ if df.empty:
35
+ return col_str
36
+ return col_str + '#' + df.applymap(lambda x: strfunc(x) + '#').sum().sum()
37
+
38
+
39
+ def _op_and(s):
40
+ return reduce(lambda x, y: x & y, s, BaseOperator(1))
41
+
42
+
43
+ def _op_agg(df_grp):
44
+ op_list = df_grp.apply(lambda x: OpFactory(x.op, x.value), axis=1).values
45
+ return pd.DataFrame(
46
+ [[df_grp['field'].iat[0], _op_and(op_list)]],
47
+ columns=['field', 'op'])
48
+
49
+
50
+ def merge_conditions(val_list: pd.DataFrame, sta_cond: pd.DataFrame):
51
+ """
52
+ 将 |联合查询条件| 与 |固定查询条件| 合并,即进行and运算。
53
+
54
+ Args:
55
+ val_list: |SQLCND| 中的联合查询条件
56
+ sta_cond: |SQLCND| 中的固定查询条件
57
+
58
+ Returns:
59
+ 合并后的 |联合查询条件| 及 |固定查询条件|
60
+
61
+ >>> vl = pd.DataFrame({'f1': range(5), 'f2': range(5)})
62
+ >>> sc = pd.DataFrame([['f1', OpEqual(1)], ['f3', OpEqual(2)]], columns=['field', 'op'])
63
+ >>> r1, r2 = merge_conditions(vl, sc)
64
+ >>> r1 # 由于固定条件sc中f1==1,联合条件中所有f1!=1的行被删除
65
+ f1 f2
66
+ 0 1 1
67
+ >>> r2 # 联合条件中已体现f1==1,固定条件中f1==1被删除,避免重复
68
+ field op
69
+ 0 f3 =2
70
+
71
+ """
72
+ if val_list.empty:
73
+ return val_list, sta_cond
74
+ common_fields = set(val_list.columns).intersection(set(sta_cond['field']))
75
+ if not common_fields:
76
+ return val_list, sta_cond
77
+
78
+ for index, field, op in sta_cond[sta_cond['field'].isin(common_fields)].itertuples(name=None):
79
+ if isinstance(op, (OpIn, OpEqual, OpMultiInterval)):
80
+ op_in = OpIn
81
+ op_eq = OpEqual
82
+ else:
83
+ op_in = OpStrIn
84
+ op_eq = OpStrEqual
85
+
86
+ remain = op & op_in(val_list[field].values)
87
+ if isinstance(remain, op_eq):
88
+ val_list = val_list[val_list[field] == remain.value]
89
+ else:
90
+ val_list = val_list[val_list[field].isin(remain.value)]
91
+ sta_cond.loc[index, 'op'] = None
92
+
93
+ return val_list.reset_index(drop=True), sta_cond.dropna().reset_index(drop=True)
94
+
95
+
96
+ class SQLCondition:
97
+ """SQL查询条件
98
+
99
+ .. |联合查询条件| replace:: :ref:`联合查询条件<comb-query>`
100
+ .. |固定查询条件| replace:: :ref:`固定查询条件<fix-query>`
101
+ .. |SQLCND| replace:: :class:`SQLCondition`
102
+
103
+ 记录SQL的查询条件,主要功能:
104
+
105
+ * 输出对应的SQL查询语句
106
+ * 进行不同条件的and运算
107
+ * 取出部分字段条件
108
+
109
+ 在这个类中,查询条件被分为两种:
110
+
111
+ .. _comb-query:
112
+
113
+ 1.联合查询条件
114
+ | 指多个字段与一个值列表的值同时对应相等。
115
+ | 例如有字段: ``f1, f2`` ,值列表:
116
+
117
+ == ==
118
+ f1 f2
119
+ == ==
120
+ 1 4
121
+ 2 5
122
+ 3 6
123
+ == ==
124
+
125
+ | 联合查询条件即:
126
+ | ``(f1=1 and f2=4) or (f1=2 and f2=5) or (f1=3 and f2=6)``
127
+
128
+ .. _fix-query:
129
+
130
+ 2.固定查询条件:
131
+ | 除联合查询条件外,其余查询条件均归类为固定查询条件。例如:
132
+ | ``f1>=1, f2 in (1, 2, 3), f1!=2`` 等等。
133
+
134
+ Args:
135
+ fields: 联合查询字段
136
+ value_list: 联合查询的值列表
137
+ quote_char: 转义字符
138
+ **kwargs: 其余关键字参数,一般用于指定固定查询条件
139
+
140
+ Example:
141
+ >>> SQLCondition(fields=['a', 'b'], value_list=[[1, 4], [2, 5], [3, 6]])
142
+ (a=1 AND b=4) OR (a=2 AND b=5) OR (a=3 AND b=6)
143
+ >>> # kwargs 部分也可以提供联合查询字段。
144
+ >>> SQLCondition(a=range(1, 4), b=range(4, 7))
145
+ (a=1 AND b=4) OR (a=2 AND b=5) OR (a=3 AND b=6)
146
+ >>> # 同时,kwargs部分可以提供一些固定条件
147
+ >>> SQLCondition(a__eq=1, b__in=(1, 2), c__lt=2)
148
+ `a`=1 AND `b` IN (1, 2) AND `c`<2
149
+
150
+ Warnings:
151
+ ``kwargs`` 部分虽然也可以提供联合查询字段,
152
+ 但是仅当 ``fields, value_list`` 未提供时有效。
153
+ 两者同时存在时,``kwargs`` 的联合查询字段会被无视。
154
+
155
+ Note:
156
+ 固定查询条件的入参规则为 ``{字段名}__{条件字符}={值}``
157
+ 所有支持的条件字符为:
158
+
159
+ +----------+------------------+---------------------+
160
+ | | | 是否支持 |
161
+ | 条件字符 | 全称 +--------+------------+
162
+ | | | 字符串 | 数字,日期 |
163
+ +----------+------------------+--------+------------+
164
+ | eq | equal | ✓ | ✓ |
165
+ +----------+------------------+--------+------------+
166
+ | ne | not equal | ✓ | ✓ |
167
+ +----------+------------------+--------+------------+
168
+ | in | in | ✓ | ✓ |
169
+ +----------+------------------+--------+------------+
170
+ | ni | not in | ✓ | ✓ |
171
+ +----------+------------------+--------+------------+
172
+ | lt | less than | × | ✓ |
173
+ +----------+------------------+--------+------------+
174
+ | le | less equal | × | ✓ |
175
+ +----------+------------------+--------+------------+
176
+ | gt | greater than | × | ✓ |
177
+ +----------+------------------+--------+------------+
178
+ | ge | greater or equal | × | ✓ |
179
+ +----------+------------------+--------+------------+
180
+
181
+ """
182
+ def __init__(self, fields=None, value_list=None, quote_char='`', **kwargs):
183
+ self.fields = []
184
+ self.quote_char = quote_char
185
+ self.__static_cond = pd.DataFrame()
186
+
187
+ if fields is not None and value_list is not None:
188
+ self.fields = list(fields)
189
+ if isinstance(value_list, pd.DataFrame):
190
+ self.val_list = value_list.rename(
191
+ dict(zip(value_list.columns, fields)), axis='columns')
192
+ else:
193
+ self.val_list = pd.DataFrame(value_list, columns=fields)
194
+ self._parse_static_cond(kwargs)
195
+ elif not kwargs:
196
+ raise ValueError("Init failed. Either provide `fields` and `value_list`, or provide kwargs.")
197
+ else:
198
+ self._parse_kwargs(**kwargs)
199
+
200
+ # 合并val_list和static_cond
201
+ self.val_list, self.__static_cond = merge_conditions(self.val_list, self.__static_cond)
202
+ if not self.val_list.empty:
203
+ self.val_list.drop_duplicates(inplace=True)
204
+ else:
205
+ self.fields = []
206
+
207
+ @cached_property
208
+ def all_fields(self):
209
+ """set: 查询条件涉及的所有字段"""
210
+ return set(self.__static_cond['field'].tolist()) | self.field_set
211
+
212
+ @CachedProperty
213
+ def cond_template(self):
214
+ """当前的 |联合查询条件| 模板"""
215
+ return "(" + " AND ".join(f"{k}={{!r}}" for k in self.fields) + ")"
216
+
217
+ @CachedProperty
218
+ def field_set(self):
219
+ """set: |联合查询条件| 的字段"""
220
+ return set(self.fields)
221
+
222
+ @CachedProperty
223
+ def static_cond(self):
224
+ """ |固定查询条件| 字符串"""
225
+ if self.__static_cond.empty:
226
+ return ''
227
+ return _df2cond(self.__static_cond, self.quote_char)
228
+
229
+ def rename_field(self, field_map: dict):
230
+ """重命名字段,将引起类中所有字符串缓存失效"""
231
+ self.fields = [field_map.get(f, f) for f in self.fields]
232
+ self.val_list.rename(field_map, axis=1, inplace=True)
233
+ self.__static_cond['field'].replace(field_map, inplace=True)
234
+ self._clear_cached_property()
235
+
236
+ def _clear_cached_property(self):
237
+ for key in CachedProperty.cached_names & self.__dict__.keys():
238
+ del self.__dict__[key]
239
+
240
+ def _parse_kwargs(self, **kwargs):
241
+ self._parse_static_cond(kwargs)
242
+
243
+ for field in kwargs.keys():
244
+ self.fields.append(field)
245
+ self.val_list = pd.DataFrame(
246
+ list(zip(*kwargs.values())), columns=self.fields)
247
+
248
+ def _parse_static_cond(self, kwargs):
249
+ data = []
250
+
251
+ for key in list(kwargs.keys()):
252
+ if key.rfind('__') != -1:
253
+ field, op = key.rsplit('__', maxsplit=1)
254
+ data.append([field, op, kwargs[key]])
255
+ kwargs.pop(key)
256
+
257
+ self.__check_static_cond_valid(
258
+ pd.DataFrame(data, columns=['field', 'op', 'value']))
259
+
260
+ @staticmethod
261
+ def _iter_single_valset(value_list):
262
+ if isinstance(value_list, pd.DataFrame):
263
+ for val in value_list.itertuples(index=False, name=None):
264
+ yield val
265
+ elif isinstance(value_list, Iterable): # pragma: no cover
266
+ for val in value_list:
267
+ yield val
268
+ else: # pragma: no cover
269
+ raise TypeError(f"Expect Iterable or DataFrame, got: {type(value_list)}")
270
+
271
+ @staticmethod
272
+ def _cal_len(x):
273
+ if isinstance(x, str):
274
+ return len(x) + 3
275
+ else:
276
+ return len(str(x)) + 1
277
+
278
+ def to_sql(self, max_len=10E6):
279
+ """
280
+ 将所有查询条件转化为符合sql语法的查询条件字符串。
281
+
282
+ Args:
283
+ max_len: 单次输出的条件字符串的最大长度
284
+
285
+ Returns:
286
+ 查询条件字符串的生成器
287
+
288
+ Raises:
289
+ ValueError: 给定的max_len太小以至于无法产生sql语句
290
+
291
+ Example:
292
+ >>> sc = SQLCondition(f1=range(1, 4), f2=range(4, 7), a__eq=1, b__in=(1, 2), c__lt=2)
293
+ >>> next(sc.to_sql())
294
+ '`a`=1 AND `b` IN (1, 2) AND `c`<2 AND ((f1=1 AND f2=4) OR (f1=2 AND f2=5) OR (f1=3 AND f2=6))'
295
+ >>> for sql in sc.to_sql(max_len=80):
296
+ ... print(sql)
297
+ `a`=1 AND `b` IN (1, 2) AND `c`<2 AND ((f1=1 AND f2=4) OR (f1=2 AND f2=5))
298
+ `a`=1 AND `b` IN (1, 2) AND `c`<2 AND ((f1=3 AND f2=6))
299
+ """
300
+ fix_cond = self.static_cond # 本地变量减少引用消耗
301
+ val_list = self.val_list
302
+ qc = self.quote_char
303
+
304
+ # 如果只有一列,可以用IN代替
305
+ if len(self.fields) == 1:
306
+ field = self.fields[0]
307
+ cond_tmpl = f"{qc}{field}{qc} IN ({{}})"
308
+
309
+ if fix_cond == '':
310
+ fix_part = ''
311
+ else:
312
+ fix_part = fix_cond + ' AND '
313
+
314
+ max_len -= len(fix_part) + 8 # 8 is the length of '`` IN ()'
315
+
316
+ len_cum = val_list.iloc[:, 0].apply(self._cal_len).cumsum()
317
+ start = end = 0
318
+ vl_len = len(val_list)
319
+ while end < vl_len:
320
+ ses_to_use = len_cum.loc[len_cum <= max_len]
321
+ if ses_to_use.empty:
322
+ raise ValueError(f"Given max length: [{max_len+len(fix_part)+8}] is too small.")
323
+
324
+ used_len = ses_to_use.iat[-1]
325
+ end = ses_to_use.index[-1] + 1
326
+ del ses_to_use
327
+ len_cum = len_cum.loc[len_cum > max_len]
328
+ len_cum -= used_len
329
+ sql_cond = cond_tmpl.format(','.join(map(repr, val_list.loc[start: end-1, field])))
330
+ yield fix_part + sql_cond
331
+ start = end
332
+ else:
333
+ cond_tmpl = self.cond_template
334
+ tmp = [] # 临时保存不超过max_len的值对
335
+ tmp_len = 0 # 记录当前sql语句的长度
336
+ if fix_cond == '':
337
+ fix_form = '{}'
338
+ else:
339
+ fix_form = fix_cond + ' AND ({})'
340
+
341
+ max_len -= len(fix_form) - 2 # 2 is the length of '{}'
342
+
343
+ for val in self._iter_single_valset(val_list):
344
+ str_to_add = cond_tmpl.format(*val)
345
+ len_to_add = len(str_to_add)
346
+ if len_to_add > max_len:
347
+ raise ValueError(f"Given max length: [{max_len+len(fix_form)-2}] is too small.")
348
+
349
+ tmp_len += 4 + len_to_add # 4 is the length of ' OR '
350
+
351
+ if tmp_len - 4 > max_len:
352
+ yield fix_form.format(' OR '.join(tmp))
353
+ tmp = [str_to_add]
354
+ tmp_len = 4 + len_to_add
355
+ else:
356
+ tmp.append(str_to_add)
357
+ if not tmp:
358
+ yield fix_cond
359
+ else:
360
+ yield fix_form.format(' OR '.join(tmp))
361
+
362
+ def to_pandasql(self):
363
+ """
364
+ 将 |固定查询条件| 转换成符合 :meth:`Dataframe.query` 要求的字符串,
365
+ 用于缓存查询。 |联合查询条件| 不在此输出。
366
+
367
+ Note:
368
+ 这个方法主要用于 :class:`Dataframe` 的缓存查询。
369
+ 而 |联合查询条件| 可以由 :class:`Dataframe` 之间的运算直接完成。
370
+ 因此此处不作输出。
371
+ """
372
+ if self.__static_cond.empty:
373
+ return None
374
+ return _df2pandascond(self.__static_cond)
375
+
376
+ def __check_static_cond_valid(self, df):
377
+ """合并同字段的 |固定查询条件| ,如果条件间存在矛盾会抛出OpCombineError"""
378
+ df = df.groupby(['field'], as_index=False).apply(_op_agg).reset_index(drop=True)
379
+ if df.empty:
380
+ self.__static_cond = pd.DataFrame(columns=['field', 'op'])
381
+ else:
382
+ self.__static_cond = df
383
+
384
+ def __repr__(self): # pragma: no cover
385
+ return '\n'.join(self.to_sql())
386
+
387
+ def __copy__(self):
388
+ cp = SQLCondition(
389
+ fields=self.fields,
390
+ value_list=self.val_list.copy(),
391
+ quote_char=self.quote_char,
392
+ )
393
+ cp.__static_cond = self.__static_cond.copy()
394
+ return cp
395
+
396
+ def __and__(self, other):
397
+ """
398
+ 取出两个查询条件的“交集”,如果两个条件没有交,则报错。
399
+
400
+ Raises:
401
+ SqlCondError: |联合查询条件| 之间存在矛盾
402
+ OpCombineError: 联合vs固定,固定vs固定之间存在矛盾
403
+ """
404
+ fd_me = self.field_set
405
+ fd_other = other.field_set
406
+
407
+ if not fd_me:
408
+ val_list = other.val_list.copy()
409
+ elif not fd_other:
410
+ val_list = self.val_list.copy()
411
+ elif 1 == len(fd_me) == len(fd_other):
412
+ val_list = pd.DataFrame()
413
+ self.__static_cond = pd.concat([
414
+ self.__static_cond,
415
+ pd.DataFrame(data={
416
+ 'field': self.fields[0],
417
+ 'op': [OpFactory(IN, self.val_list.iloc[:, 0])]
418
+ })
419
+ ])
420
+ other.__static_cond = pd.concat([
421
+ other.__static_cond,
422
+ pd.DataFrame(data={
423
+ 'field': other.fields[0],
424
+ 'op': [OpFactory(IN, other.val_list.iloc[:, 0])]
425
+ })
426
+ ])
427
+ else:
428
+ # 字段越多,限制条件越多,一般数据量越小,定义为sub
429
+ if fd_me.issubset(fd_other):
430
+ main = self
431
+ sub = other
432
+ elif fd_me.issuperset(fd_other):
433
+ main = other
434
+ sub = self
435
+
436
+ else:
437
+ # 如果两个Condition的字段不存在包含关系,无法判断两个查询集的大小
438
+ raise SqlCondError("One of the condition's fields must be the other one's subset or superset.")
439
+ # todo 设置index,用join替代merge以提高性能
440
+ val_list = main.val_list.merge(sub.val_list, on=main.fields, how='inner')
441
+ if val_list.empty:
442
+ raise SqlCondError(
443
+ f"Failed to calculate:\n{main.val_list!r}"
444
+ f"\n>>> & <<<\n{sub.val_list!r}"
445
+ f"\nconfliction detected."
446
+ )
447
+
448
+ static_cond = pd.concat((self.__static_cond, other.__static_cond))
449
+ static_cond = static_cond.groupby('field', as_index=False).agg(_op_and).reset_index(drop=True)
450
+ val_list, static_cond = merge_conditions(val_list, static_cond)
451
+ if val_list.empty:
452
+ cond = SQLCondition([], pd.DataFrame(), quote_char=self.quote_char)
453
+ else:
454
+ cond = SQLCondition(val_list.columns.values, val_list, quote_char=self.quote_char)
455
+
456
+ cond.__static_cond = static_cond
457
+
458
+ return cond
459
+
460
+ def __eq__(self, other):
461
+ return self.serialized == other.serialized
462
+
463
+ def __le__(self, other: 'SQLCondition'):
464
+ try:
465
+ return (self & other) == self
466
+ except (SqlCondError, OpCombineError):
467
+ return False
468
+
469
+ @CachedProperty
470
+ def serialized(self):
471
+ fields = self.fields
472
+ val_list = self.val_list
473
+
474
+ if len(fields) == 1:
475
+ # 将条件归类至sta_cond
476
+ static_cond = pd.concat(
477
+ [
478
+ self.__static_cond,
479
+ pd.DataFrame(data={
480
+ 'field': fields,
481
+ 'op': [OpFactory(IN, val_list.iloc[:, 0])]
482
+ })
483
+ ]
484
+ ).sort_values('field').reset_index(drop=True)
485
+ val_list = pd.DataFrame()
486
+ else:
487
+ static_cond = self.__static_cond.sort_values('field')
488
+ if self.fields:
489
+ fields = sorted(fields)
490
+ val_list = val_list.sort_values(fields)[fields]
491
+ return _df2str(val_list) + _df2str(static_cond, strfunc=str)
492
+
493
+ def __getitem__(self, fields):
494
+ """
495
+ 根据字段取出当前条件的子条件
496
+
497
+ Args:
498
+ fields: 待查字段
499
+
500
+ Example:
501
+ >>> sc = SQLCondition(f1=range(1, 4), f2=range(4, 7), a__eq=1, b__in=(1, 2), c__lt=2)
502
+ >>> sc['a']
503
+ `a`=1
504
+ >>> sc['b', 'c', 'notin']
505
+ `b` IN (1, 2) AND `c`<2
506
+ >>> sc['f1']
507
+ `f1` IN (1,2,3)
508
+ >>> sc['f1', 'f2']
509
+ (f1=1 AND f2=4) OR (f1=2 AND f2=5) OR (f1=3 AND f2=6)
510
+
511
+ Raises:
512
+ KeyError: 传入的字段全都不在 :attr:`SQLCondition.all_fields` 中
513
+
514
+ Note:
515
+ 只要有待查字段在 :attr:`SQLCondition.all_fields` 中,调用就能成功。多余字段会被忽视。
516
+ """
517
+ if isinstance(fields, str):
518
+ fields = {fields}
519
+ else:
520
+ fields = set(fields)
521
+ valist_fields = []
522
+ fix_fields = []
523
+
524
+ for fld in fields:
525
+ if fld in self.field_set:
526
+ valist_fields.append(fld)
527
+ elif fld in self.all_fields:
528
+ fix_fields.append(fld)
529
+
530
+ if not valist_fields and not fix_fields:
531
+ raise KeyError(f"None of fields: {fields!r} is found.")
532
+
533
+ rtn = SQLCondition(
534
+ fields=valist_fields,
535
+ value_list=self.val_list[valist_fields].copy(),
536
+ quote_char=self.quote_char,
537
+ )
538
+ if fix_fields:
539
+ rtn.__static_cond = self.__static_cond[self.__static_cond['field'].isin(fix_fields)].copy()
540
+ return rtn
541
+
542
+
543
+ def update_cache(*attrs):
544
+ def deco(func):
545
+ @wraps(func)
546
+ def wrapper(self, *args, **kwargs):
547
+ for attr in attrs:
548
+ if attr in self.__dict__:
549
+ delattr(self, attr)
550
+ return func(self, *args, **kwargs)
551
+ return wrapper
552
+ return deco
553
+
554
+
555
+ class ConditionManager:
556
+ def __init__(self):
557
+ self.__main_conds = []
558
+ self.__tmp_conds = []
559
+ self.__altered = False
560
+ self.__valid = False
561
+
562
+ altered = property(lambda self: self.__altered)
563
+ valid = property(lambda self: self.__valid)
564
+
565
+ def mark_as_valid(self):
566
+ self.__valid = True
567
+
568
+ @update_cache('condition')
569
+ def add_main_cond(self, condition: SQLCondition):
570
+ self.__main_conds.append(condition)
571
+ self.__altered = True
572
+ self.__valid = False
573
+
574
+ @update_cache('condition')
575
+ def add_tmp_cond(self, condition: SQLCondition):
576
+ self.__tmp_conds.append(condition)
577
+ self.__valid = False
578
+
579
+ @update_cache('condition')
580
+ def clear_tmp(self):
581
+ self.__tmp_conds.clear()
582
+ self.__valid = False
583
+
584
+ @update_cache('condition')
585
+ def pop_main(self):
586
+ self.__main_conds.pop()
587
+ self.__altered = True
588
+ self.__valid = False
589
+
590
+ @cached_property
591
+ def condition(self):
592
+ if not self:
593
+ return None
594
+ conds = self.__main_conds + self.__tmp_conds
595
+ return reduce(lambda x, y: x & y, conds[1:], conds[0])
596
+
597
+ def __bool__(self):
598
+ return bool(self.__main_conds + self.__tmp_conds)
599
+
600
+ def has_main(self):
601
+ return bool(self.__main_conds)
602
+
603
+ @staticmethod
604
+ def any_changed(group):
605
+ changed = False
606
+ for cm in group:
607
+ changed = changed or cm.__altered
608
+ cm.__altered = False
609
+ return changed