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,107 @@
1
+ import contextvars
2
+ import threading
3
+ from collections import defaultdict
4
+ from concurrent.futures import ThreadPoolExecutor
5
+ import copy
6
+
7
+ from deepfos.lib.constant import UNSET
8
+ from deepfos.local import Proxy
9
+
10
+
11
+ class ThreadCtxExecutor(ThreadPoolExecutor):
12
+ def __init__(
13
+ self,
14
+ max_workers=None,
15
+ thread_name_prefix='',
16
+ initargs=()
17
+ ):
18
+ self.context = contextvars.copy_context()
19
+ super().__init__(
20
+ max_workers=max_workers,
21
+ thread_name_prefix=thread_name_prefix,
22
+ initializer=self._set_context,
23
+ initargs=initargs
24
+ )
25
+
26
+ def _set_context(self):
27
+ for var, value in self.context.items():
28
+ var.set(value)
29
+
30
+
31
+ class _Local:
32
+ __slots__ = ('_thread_local', 'default')
33
+
34
+ def __init__(self):
35
+ self.default = None
36
+ self._thread_local = {}
37
+
38
+ def set_current(self, value):
39
+ key = threading.get_ident()
40
+ self._thread_local[key] = value
41
+
42
+ def get_current(self):
43
+ key = threading.get_ident()
44
+ if key not in self._thread_local:
45
+ self._thread_local[key] = copy.deepcopy(self.default)
46
+ return self._thread_local[key]
47
+
48
+ def set_default(self, value):
49
+ if threading.get_ident() not in self._thread_local:
50
+ self.default = value
51
+
52
+
53
+ class _LocalMemo:
54
+ __slots__ = '_memo'
55
+
56
+ def __init__(self):
57
+ self._memo = defaultdict(_Local)
58
+
59
+ def get(self, attr):
60
+ if attr not in self._memo:
61
+ raise AttributeError(attr)
62
+ return self._memo[attr]
63
+
64
+ def set(self, attr, value):
65
+ tlocal = self._memo[attr]
66
+ tlocal.set_default(copy.deepcopy(value))
67
+ tlocal.set_current(value)
68
+
69
+ def __contains__(self, item):
70
+ return item in self._memo
71
+
72
+
73
+ class ThreadLocal:
74
+ """定义线程隔离的变量"""
75
+ def __init__(self):
76
+ self.__dict__['_memo'] = _LocalMemo()
77
+
78
+ def __setattr__(self, key, value):
79
+ self._memo.set(key, value)
80
+
81
+ def __getattr__(self, item):
82
+ _local = self._memo.get(item)
83
+ return _local.get_current()
84
+
85
+
86
+ class ParallelProxy(Proxy):
87
+ def __init__(self, payload):
88
+ object.__setattr__(self, '_ParallelProxy__result', UNSET)
89
+ object.__setattr__(self, '_ParallelProxy__payload', payload)
90
+
91
+ def __await__(self):
92
+ coro = self.__payload
93
+ return coro.__await__()
94
+
95
+ def _get_current_object(self):
96
+ if self.__result is not UNSET:
97
+ return self.__result
98
+
99
+ object.__setattr__(self, '_ParallelProxy__result', self.__payload.result())
100
+ return self.__result
101
+
102
+
103
+ def compute(obj):
104
+ try:
105
+ return obj._get_current_object() # noqa
106
+ except AttributeError:
107
+ return obj
@@ -0,0 +1,39 @@
1
+ from requests.structures import CaseInsensitiveDict
2
+ import re
3
+
4
+ UNSET = object()
5
+ DFLT_DATA_COLUMN = 'data'
6
+ COLUMN_USAGE_FIELD = 'VirtualMeasure_220922'
7
+ DFLT_COMMENT_COLUMN = 'VirtualMeasure_220922'
8
+ USED_FOR_COMMENT = 'comment'
9
+ USED_FOR_DATA = 'data'
10
+ DFLT_NAME_COLUMN = 'name'
11
+ DFLT_PNAME_COLUMN = 'parent_name'
12
+ SHAREDMEMBER = 'sharedmember'
13
+
14
+ SHAREDMEMBERV12 = 'sharedMember'
15
+ DFLT_PNAME_COLUMN_V12 = 'parentName'
16
+
17
+
18
+ VIEW = "View"
19
+ VIEW_DICT = CaseInsensitiveDict(view=VIEW)
20
+ HIERARCHY = CaseInsensitiveDict(
21
+ Base="Base",
22
+ IBase="IBase",
23
+ Children="Children",
24
+ IChildren="IChildren",
25
+ Descendant="Descendant",
26
+ IDescendant="IDescendant",
27
+ )
28
+ ROOT = "#root"
29
+ ACCEPT_LANS = [
30
+ 'zh-cn',
31
+ 'en'
32
+ ]
33
+ RE_DIMNAME_PARSER = re.compile('(?P<name>.*){(?P<body>.*)}')
34
+ RE_SERVER_NAME_PARSER = re.compile('^[a-z-]+(?P<ver>[0-9-]+)$', re.IGNORECASE)
35
+ RE_MODULEID_PARSER = re.compile('^[A-Z]+(?P<ver>[0-9_]+)$', re.IGNORECASE)
36
+ RE_SYS_SERVER_PARSER = re.compile('^(?:https?://)?([a-z-]+-server)$', re.IGNORECASE)
37
+ DECIMAL_COL = 'decimal_val'
38
+ STRING_COL = 'string_val'
39
+ INDEX_FIELD = 'index'
@@ -0,0 +1,310 @@
1
+ """装饰器"""
2
+
3
+ import threading
4
+ import functools
5
+ import warnings
6
+ import weakref
7
+ import inspect
8
+
9
+ from typing import Any, Callable, TypeVar, Tuple, Generic
10
+
11
+ from cachetools import LRUCache
12
+ from cachetools.keys import hashkey
13
+
14
+ from deepfos.cache import Manager
15
+ from deepfos.exceptions import eliminate_from_traceback
16
+ from deepfos.lib.asynchronous import evloop
17
+ from deepfos.lib.constant import UNSET
18
+ from deepfos.lib.utils import repr_version
19
+
20
+ __all__ = [
21
+ 'cached_property',
22
+ 'cached_class_property',
23
+ 'singleton',
24
+ 'flagmethod',
25
+ 'deprecated',
26
+ 'lru_cache',
27
+ ]
28
+ F = TypeVar('F', bound=Callable[..., Any])
29
+ RT = TypeVar('RT')
30
+
31
+
32
+ # noinspection PyPep8Naming
33
+ @eliminate_from_traceback
34
+ class cached_property(Generic[RT]): # pragma: no cover
35
+ def __init__(self, func: Callable[[Any], RT]):
36
+ self.__doc__ = getattr(func, "__doc__")
37
+ self.func = func
38
+ self.attrname = None
39
+ self.lock = threading.RLock()
40
+
41
+ def __set_name__(self, owner, name):
42
+ self.attrname = name
43
+
44
+ def __get__(self, obj, cls) -> RT:
45
+ if obj is None:
46
+ return self
47
+
48
+ try:
49
+ cache = obj.__dict__
50
+ except AttributeError:
51
+ raise TypeError(
52
+ f"No '__dict__' attribute on {type(obj).__name__!r} "
53
+ f"instance to cache {self.attrname!r} property.") from None
54
+
55
+ val = cache.get(self.attrname, UNSET)
56
+ if val is UNSET:
57
+ # If in different thread,
58
+ # deadlock may happen when there are future properties in function
59
+ # while being used in other thread in the same time
60
+ if evloop.in_same_thread():
61
+ with self.lock:
62
+ # check if another thread filled cache while we awaited lock
63
+ val = cache.get(self.attrname, UNSET)
64
+ if val is UNSET:
65
+ val = self.func(obj)
66
+ setattr(obj, self.attrname, val)
67
+ else:
68
+ val = self.func(obj)
69
+ setattr(obj, self.attrname, val)
70
+ return val
71
+
72
+
73
+ # noinspection PyPep8Naming
74
+ class cached_class_property(Generic[RT]): # pragma: no cover
75
+ def __init__(self, func: Callable[[Any], RT]):
76
+ self.__doc__ = getattr(func, "__doc__")
77
+ self.func = func
78
+
79
+ def __get__(self, obj, cls) -> RT:
80
+ value = self.func(obj)
81
+ # set attribute to class
82
+ setattr(cls, self.func.__name__, value)
83
+ return value
84
+
85
+
86
+ # noinspection PydanticTypeChecker
87
+ def singleton(cls):
88
+ """单例"""
89
+ cls.__new_original__ = cls.__new__
90
+ singleton_lock = threading.Lock()
91
+
92
+ @functools.wraps(cls.__new__)
93
+ def singleton_new(cls_, *args, **kwargs):
94
+ with singleton_lock:
95
+ it = cls_.__dict__.get('__it__')
96
+ if it is not None:
97
+ return it
98
+
99
+ cls_.__it__ = it = cls_.__new_original__(cls_)
100
+ it.__init_original__(*args, **kwargs)
101
+ return it
102
+
103
+ cls.__new__ = singleton_new
104
+ cls.__init_original__ = cls.__init__
105
+ cls.__init__ = object.__init__
106
+ return cls
107
+
108
+
109
+ class FlagMethod:
110
+ """描述符。进出方法时设置flag。
111
+
112
+ 类似于 :func:`flagmethod`,但是支持嵌套调用。
113
+
114
+ Args:
115
+ flag: 作为标识的属性名
116
+ method: 需要装饰的方法
117
+
118
+ >>> def nested_flagmethod(flag):
119
+ ... def wrapper(method):
120
+ ... return FlagMethod(flag, method)
121
+ ... return wrapper
122
+
123
+ >>> class Example:
124
+ ... def __init__(self):
125
+ ... self.flag = False
126
+ ...
127
+ ... @nested_flagmethod('flag')
128
+ ... def foo(self, arg):
129
+ ... pass
130
+ ...
131
+ ... def bar(self):
132
+ ... print(self.flag)
133
+ ...
134
+ >>> example = Example()
135
+ >>> example.foo(example.bar())
136
+ True
137
+ >>> example.flag
138
+ False
139
+
140
+ """
141
+
142
+ def __init__(self, flag: str, method):
143
+ """
144
+
145
+ Args:
146
+ flag:
147
+ method:
148
+ """
149
+ self.flag = flag
150
+ self.method = method
151
+
152
+ def __get__(self, instance, owner):
153
+ if instance is None:
154
+ return self
155
+ setattr(instance, self.flag, True)
156
+ self.obj = weakref.ref(instance)
157
+ return self
158
+
159
+ def __call__(self, *args, **kwargs):
160
+ try:
161
+ rslt = self.method(self.obj(), *args, **kwargs)
162
+ finally:
163
+ setattr(self.obj(), self.flag, False)
164
+ return rslt
165
+
166
+
167
+ def flagmethod(flag: str) -> Callable[[F], F]:
168
+ """进出方法时设置flag
169
+
170
+ 用于method的装饰器,当调用被装饰的方法时,会将 `self.flag`
171
+ 置为 `True`,结束调用时(包括异常退出),置为 `False`。
172
+
173
+ Args:
174
+ flag: 作为标识的属性名
175
+
176
+ >>> class Example:
177
+ ... def __init__(self):
178
+ ... self.flag = False
179
+ ...
180
+ ... @flagmethod('flag')
181
+ ... def foo(self, arg):
182
+ ... self.bar()
183
+ ... pass
184
+ ...
185
+ ... @flagmethod('flag')
186
+ ... async def async_foo(self, arg):
187
+ ... self.bar()
188
+ ... pass
189
+ ...
190
+ ... def bar(self):
191
+ ... print(self.flag)
192
+ ...
193
+ >>> example = Example()
194
+ >>> example.foo(1)
195
+ True
196
+ >>> import asyncio
197
+ >>> asyncio.run(example.async_foo(1))
198
+ True
199
+ >>> example.flag
200
+ False
201
+
202
+ """
203
+
204
+ def deco(method):
205
+ if inspect.iscoroutinefunction(method):
206
+ async def wrapper(self, *args, **kwargs):
207
+ setattr(self, flag, True)
208
+ try:
209
+ rslt = await method(self, *args, **kwargs)
210
+ finally:
211
+ setattr(self, flag, False)
212
+ return rslt
213
+ else:
214
+ def wrapper(self, *args, **kwargs):
215
+ setattr(self, flag, True)
216
+ try:
217
+ rslt = method(self, *args, **kwargs)
218
+ finally:
219
+ setattr(self, flag, False)
220
+ return rslt
221
+ return functools.wraps(method)(wrapper)
222
+
223
+ return deco
224
+
225
+
226
+ def deprecated(
227
+ replacement: str = None,
228
+ version: Tuple[int, int, int] = None,
229
+ reason: str = None
230
+ ):
231
+ """弃用方法装饰器
232
+
233
+ Args:
234
+ replacement: 弃用方法的替代方法名
235
+ version: 开始弃用的版本
236
+ reason: 弃用原因
237
+
238
+
239
+ """
240
+
241
+ def deco(method):
242
+ msg = "This method is deprecated and will be removed in near future"
243
+ docs = "\n" + " " * 8 + f".. deprecated:: {repr_version(version)}\n"
244
+
245
+ if replacement:
246
+ msg += f", use [{replacement}] instead."
247
+ docs += f"\n" + " " * 12 + f"请使用 :meth:`{replacement}`\n"
248
+ if reason:
249
+ msg += f"Deprecated reason: {reason}. "
250
+ if version:
251
+ msg += f"Deprecated version: {repr_version(version)}. "
252
+
253
+ if method.__doc__:
254
+ method.__doc__ += docs
255
+ else:
256
+ method.__doc__ = docs
257
+
258
+ if inspect.iscoroutinefunction(method):
259
+ @functools.wraps(method)
260
+ async def wrapper(*args, **kwargs):
261
+ warnings.warn(msg, DeprecationWarning)
262
+ return await method(*args, **kwargs)
263
+ else:
264
+ @functools.wraps(method)
265
+ def wrapper(*args, **kwargs):
266
+ warnings.warn(msg, DeprecationWarning)
267
+ return method(*args, **kwargs)
268
+
269
+ return wrapper
270
+
271
+ return deco
272
+
273
+
274
+ def lru_cache(maxsize=128, cache_factory=LRUCache):
275
+ assert isinstance(maxsize, int), 'Expected maxsize to be an integer'
276
+
277
+ if maxsize == 0:
278
+ def dummy_deco(fun):
279
+ return fun
280
+ return dummy_deco
281
+
282
+ cache = Manager.create_cache(cache_factory, maxsize=maxsize)
283
+
284
+ def deco_fun(func):
285
+ if inspect.iscoroutinefunction(func):
286
+ @functools.wraps(func)
287
+ async def wrapper(*args, **kwargs):
288
+ key = hashkey(*args, **kwargs)
289
+
290
+ if key in cache:
291
+ return cache[key]
292
+
293
+ result = await func(*args, **kwargs)
294
+ cache[key] = result
295
+ return result
296
+ else:
297
+ @functools.wraps(func)
298
+ def wrapper(*args, **kwargs):
299
+ key = hashkey(*args, **kwargs)
300
+
301
+ if key in cache:
302
+ return cache[key]
303
+
304
+ result = func(*args, **kwargs)
305
+ cache[key] = result
306
+ return result
307
+
308
+ return wrapper
309
+
310
+ return deco_fun