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.
- deepfos/__init__.py +6 -0
- deepfos/_version.py +21 -0
- deepfos/algo/__init__.py +0 -0
- deepfos/algo/graph.py +171 -0
- deepfos/algo/segtree.py +31 -0
- deepfos/api/V1_1/__init__.py +0 -0
- deepfos/api/V1_1/business_model.py +119 -0
- deepfos/api/V1_1/dimension.py +599 -0
- deepfos/api/V1_1/models/__init__.py +0 -0
- deepfos/api/V1_1/models/business_model.py +1033 -0
- deepfos/api/V1_1/models/dimension.py +2768 -0
- deepfos/api/V1_2/__init__.py +0 -0
- deepfos/api/V1_2/dimension.py +285 -0
- deepfos/api/V1_2/models/__init__.py +0 -0
- deepfos/api/V1_2/models/dimension.py +2923 -0
- deepfos/api/__init__.py +0 -0
- deepfos/api/account.py +167 -0
- deepfos/api/accounting_engines.py +147 -0
- deepfos/api/app.py +626 -0
- deepfos/api/approval_process.py +198 -0
- deepfos/api/base.py +983 -0
- deepfos/api/business_model.py +160 -0
- deepfos/api/consolidation.py +129 -0
- deepfos/api/consolidation_process.py +106 -0
- deepfos/api/datatable.py +341 -0
- deepfos/api/deep_pipeline.py +61 -0
- deepfos/api/deepconnector.py +36 -0
- deepfos/api/deepfos_task.py +92 -0
- deepfos/api/deepmodel.py +188 -0
- deepfos/api/dimension.py +486 -0
- deepfos/api/financial_model.py +319 -0
- deepfos/api/journal_model.py +119 -0
- deepfos/api/journal_template.py +132 -0
- deepfos/api/memory_financial_model.py +98 -0
- deepfos/api/models/__init__.py +3 -0
- deepfos/api/models/account.py +483 -0
- deepfos/api/models/accounting_engines.py +756 -0
- deepfos/api/models/app.py +1338 -0
- deepfos/api/models/approval_process.py +1043 -0
- deepfos/api/models/base.py +234 -0
- deepfos/api/models/business_model.py +805 -0
- deepfos/api/models/consolidation.py +711 -0
- deepfos/api/models/consolidation_process.py +248 -0
- deepfos/api/models/datatable_mysql.py +427 -0
- deepfos/api/models/deep_pipeline.py +55 -0
- deepfos/api/models/deepconnector.py +28 -0
- deepfos/api/models/deepfos_task.py +386 -0
- deepfos/api/models/deepmodel.py +308 -0
- deepfos/api/models/dimension.py +1576 -0
- deepfos/api/models/financial_model.py +1796 -0
- deepfos/api/models/journal_model.py +341 -0
- deepfos/api/models/journal_template.py +854 -0
- deepfos/api/models/memory_financial_model.py +478 -0
- deepfos/api/models/platform.py +178 -0
- deepfos/api/models/python.py +221 -0
- deepfos/api/models/reconciliation_engine.py +411 -0
- deepfos/api/models/reconciliation_report.py +161 -0
- deepfos/api/models/role_strategy.py +884 -0
- deepfos/api/models/smartlist.py +237 -0
- deepfos/api/models/space.py +1137 -0
- deepfos/api/models/system.py +1065 -0
- deepfos/api/models/variable.py +463 -0
- deepfos/api/models/workflow.py +946 -0
- deepfos/api/platform.py +199 -0
- deepfos/api/python.py +90 -0
- deepfos/api/reconciliation_engine.py +181 -0
- deepfos/api/reconciliation_report.py +64 -0
- deepfos/api/role_strategy.py +234 -0
- deepfos/api/smartlist.py +69 -0
- deepfos/api/space.py +582 -0
- deepfos/api/system.py +372 -0
- deepfos/api/variable.py +154 -0
- deepfos/api/workflow.py +264 -0
- deepfos/boost/__init__.py +6 -0
- deepfos/boost/py_jstream.py +89 -0
- deepfos/boost/py_pandas.py +20 -0
- deepfos/cache.py +121 -0
- deepfos/config.py +6 -0
- deepfos/core/__init__.py +27 -0
- deepfos/core/cube/__init__.py +10 -0
- deepfos/core/cube/_base.py +462 -0
- deepfos/core/cube/constants.py +21 -0
- deepfos/core/cube/cube.py +408 -0
- deepfos/core/cube/formula.py +707 -0
- deepfos/core/cube/syscube.py +532 -0
- deepfos/core/cube/typing.py +7 -0
- deepfos/core/cube/utils.py +238 -0
- deepfos/core/dimension/__init__.py +11 -0
- deepfos/core/dimension/_base.py +506 -0
- deepfos/core/dimension/dimcreator.py +184 -0
- deepfos/core/dimension/dimension.py +472 -0
- deepfos/core/dimension/dimexpr.py +271 -0
- deepfos/core/dimension/dimmember.py +155 -0
- deepfos/core/dimension/eledimension.py +22 -0
- deepfos/core/dimension/filters.py +99 -0
- deepfos/core/dimension/sysdimension.py +168 -0
- deepfos/core/logictable/__init__.py +5 -0
- deepfos/core/logictable/_cache.py +141 -0
- deepfos/core/logictable/_operator.py +663 -0
- deepfos/core/logictable/nodemixin.py +673 -0
- deepfos/core/logictable/sqlcondition.py +609 -0
- deepfos/core/logictable/tablemodel.py +497 -0
- deepfos/db/__init__.py +36 -0
- deepfos/db/cipher.py +660 -0
- deepfos/db/clickhouse.py +191 -0
- deepfos/db/connector.py +195 -0
- deepfos/db/daclickhouse.py +171 -0
- deepfos/db/dameng.py +101 -0
- deepfos/db/damysql.py +189 -0
- deepfos/db/dbkits.py +358 -0
- deepfos/db/deepengine.py +99 -0
- deepfos/db/deepmodel.py +82 -0
- deepfos/db/deepmodel_kingbase.py +83 -0
- deepfos/db/edb.py +214 -0
- deepfos/db/gauss.py +83 -0
- deepfos/db/kingbase.py +83 -0
- deepfos/db/mysql.py +184 -0
- deepfos/db/oracle.py +131 -0
- deepfos/db/postgresql.py +192 -0
- deepfos/db/sqlserver.py +99 -0
- deepfos/db/utils.py +135 -0
- deepfos/element/__init__.py +89 -0
- deepfos/element/accounting.py +348 -0
- deepfos/element/apvlprocess.py +215 -0
- deepfos/element/base.py +398 -0
- deepfos/element/bizmodel.py +1269 -0
- deepfos/element/datatable.py +2467 -0
- deepfos/element/deep_pipeline.py +186 -0
- deepfos/element/deepconnector.py +59 -0
- deepfos/element/deepmodel.py +1806 -0
- deepfos/element/dimension.py +1254 -0
- deepfos/element/fact_table.py +427 -0
- deepfos/element/finmodel.py +1485 -0
- deepfos/element/journal.py +840 -0
- deepfos/element/journal_template.py +943 -0
- deepfos/element/pyscript.py +412 -0
- deepfos/element/reconciliation.py +553 -0
- deepfos/element/rolestrategy.py +243 -0
- deepfos/element/smartlist.py +457 -0
- deepfos/element/variable.py +756 -0
- deepfos/element/workflow.py +560 -0
- deepfos/exceptions/__init__.py +239 -0
- deepfos/exceptions/hook.py +86 -0
- deepfos/lazy.py +104 -0
- deepfos/lazy_import.py +84 -0
- deepfos/lib/__init__.py +0 -0
- deepfos/lib/_javaobj.py +366 -0
- deepfos/lib/asynchronous.py +879 -0
- deepfos/lib/concurrency.py +107 -0
- deepfos/lib/constant.py +39 -0
- deepfos/lib/decorator.py +310 -0
- deepfos/lib/deepchart.py +778 -0
- deepfos/lib/deepux.py +477 -0
- deepfos/lib/discovery.py +273 -0
- deepfos/lib/edb_lexer.py +789 -0
- deepfos/lib/eureka.py +156 -0
- deepfos/lib/filterparser.py +751 -0
- deepfos/lib/httpcli.py +106 -0
- deepfos/lib/jsonstreamer.py +80 -0
- deepfos/lib/msg.py +394 -0
- deepfos/lib/nacos.py +225 -0
- deepfos/lib/patch.py +92 -0
- deepfos/lib/redis.py +241 -0
- deepfos/lib/serutils.py +181 -0
- deepfos/lib/stopwatch.py +99 -0
- deepfos/lib/subtask.py +572 -0
- deepfos/lib/sysutils.py +703 -0
- deepfos/lib/utils.py +1003 -0
- deepfos/local.py +160 -0
- deepfos/options.py +670 -0
- deepfos/translation.py +237 -0
- deepfos-1.1.60.dist-info/METADATA +33 -0
- deepfos-1.1.60.dist-info/RECORD +175 -0
- deepfos-1.1.60.dist-info/WHEEL +5 -0
- deepfos-1.1.60.dist-info/top_level.txt +1 -0
deepfos/db/clickhouse.py
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"""数据库连接类"""
|
|
2
|
+
import asyncio
|
|
3
|
+
from typing import Union, List, Iterable, TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from cachetools import TTLCache
|
|
7
|
+
|
|
8
|
+
from deepfos.api.datatable import ClickHouseAPI
|
|
9
|
+
from deepfos.cache import Manager
|
|
10
|
+
from deepfos.lib.utils import cachedclass, split_dataframe
|
|
11
|
+
from deepfos.lib.decorator import singleton, cached_property
|
|
12
|
+
from deepfos.options import OPTION
|
|
13
|
+
from .dbkits import BaseSqlParser, SyncMeta, T_DataInfo
|
|
14
|
+
from .connector import ClickHouseAPIConnector, ClickHouseDirectAccess
|
|
15
|
+
from .mysql import _AbsAsyncMySQLClient, MySQLConvertor # noqa
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
'ClickHouseClient',
|
|
20
|
+
'AsyncClickHouseClient',
|
|
21
|
+
'ClickHouseConvertor',
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@singleton
|
|
26
|
+
class SqlParser(BaseSqlParser):
|
|
27
|
+
api_cls = ClickHouseAPI
|
|
28
|
+
|
|
29
|
+
@cached_property
|
|
30
|
+
def datatable_cls(self):
|
|
31
|
+
from deepfos.element.datatable import AsyncDataTableClickHouse
|
|
32
|
+
return AsyncDataTableClickHouse
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ClickHouseConvertor(MySQLConvertor):
|
|
36
|
+
def build_sql(
|
|
37
|
+
self,
|
|
38
|
+
columns: str,
|
|
39
|
+
values_in_line: Iterable[str],
|
|
40
|
+
tablename: str,
|
|
41
|
+
updatecol: Iterable[str] = None,
|
|
42
|
+
**opts
|
|
43
|
+
):
|
|
44
|
+
if updatecol is not None:
|
|
45
|
+
raise NotImplementedError("`updatecol` is not yet implemented for ClickHouseDB.")
|
|
46
|
+
|
|
47
|
+
return super().build_sql(columns, values_in_line, tablename, updatecol, **opts)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# -----------------------------------------------------------------------------
|
|
51
|
+
# core
|
|
52
|
+
class APIBasedClient(_AbsAsyncMySQLClient):
|
|
53
|
+
convertor = ClickHouseConvertor(quote_char='`')
|
|
54
|
+
|
|
55
|
+
def __init__(self, version: Union[float, str] = None): # noqa
|
|
56
|
+
self.parser = SqlParser()
|
|
57
|
+
self.connector = ClickHouseAPIConnector(version)
|
|
58
|
+
self.connector.trxn_execute = self.connector.execute_many
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class DirectAccessClient:
|
|
62
|
+
def __init__(self, version: Union[float, str] = None):
|
|
63
|
+
self.parser = SqlParser()
|
|
64
|
+
self.connector = ClickHouseDirectAccess(version)
|
|
65
|
+
|
|
66
|
+
async def exec_sqls(
|
|
67
|
+
self,
|
|
68
|
+
sqls: Union[str, Iterable[str]],
|
|
69
|
+
table_info: T_DataInfo = None
|
|
70
|
+
):
|
|
71
|
+
"""执行多句sql
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
sqls: 要执行的sql语句,str或多句sql的list
|
|
75
|
+
table_info: sql中表名占位符对应的数据表元素信息。
|
|
76
|
+
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
if isinstance(sqls, str):
|
|
80
|
+
sqls = [sqls]
|
|
81
|
+
|
|
82
|
+
parsed_sql = await self.parser.parse(sqls, table_info)
|
|
83
|
+
return await self.connector.execute_many(parsed_sql)
|
|
84
|
+
|
|
85
|
+
async def query_dfs(
|
|
86
|
+
self,
|
|
87
|
+
sqls: Union[str, Iterable[str]],
|
|
88
|
+
table_info: T_DataInfo = None
|
|
89
|
+
) -> Union[pd.DataFrame, List[pd.DataFrame]]:
|
|
90
|
+
"""执行sql查询语句
|
|
91
|
+
|
|
92
|
+
获取DataFrame格式的二维表
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
sqls: 执行的sql,表名可以通过${table_name}占位
|
|
96
|
+
table_info: sql中表名占位符对应的数据表元素信息。
|
|
97
|
+
|
|
98
|
+
Notes:
|
|
99
|
+
如果执行的sql中没有任何表名占位符,sql将直接执行。
|
|
100
|
+
如果有占位符, 例如 ``${table1}``,那么要求 ``table_info`` 有
|
|
101
|
+
key值为 ``table1`` , 对应键值为包含
|
|
102
|
+
``elementName, elementType, folderId/path`` 的字典,
|
|
103
|
+
或者 :class:`DataTableMySQL` 类型,或者 :class:`ElementModel` 类型
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
:class:`DataFrame` 格式的二维数据表
|
|
107
|
+
|
|
108
|
+
"""
|
|
109
|
+
if isinstance(sqls, str):
|
|
110
|
+
sql_list = await self.parser.parse([sqls], table_info)
|
|
111
|
+
return await self.connector.query_dataframe(sql_list[0])
|
|
112
|
+
else:
|
|
113
|
+
sqls = await self.parser.parse(sqls, table_info)
|
|
114
|
+
return await asyncio.gather(*[
|
|
115
|
+
self.connector.query_dataframe(sql) for sql in sqls
|
|
116
|
+
])
|
|
117
|
+
|
|
118
|
+
async def insert_df(
|
|
119
|
+
self,
|
|
120
|
+
dataframe: pd.DataFrame,
|
|
121
|
+
element_name: str = None,
|
|
122
|
+
table_name: str = None,
|
|
123
|
+
table_info: T_DataInfo = None,
|
|
124
|
+
chunksize: int = None,
|
|
125
|
+
):
|
|
126
|
+
"""将 :class:`DataFrame` 的插入数据表
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
dataframe: 入库数据
|
|
130
|
+
element_name: 数据表元素名
|
|
131
|
+
table_name: 数据表的 **实际表名**
|
|
132
|
+
table_info: 数据表元素信息,使用table
|
|
133
|
+
chunksize: 单次插库的数据行数
|
|
134
|
+
|
|
135
|
+
"""
|
|
136
|
+
if table_name is not None:
|
|
137
|
+
tbl_name = table_name
|
|
138
|
+
elif element_name is not None:
|
|
139
|
+
tbl_name = (await self.parser.parse(["${%s}" % element_name], table_info))[0]
|
|
140
|
+
else:
|
|
141
|
+
raise ValueError("Either 'element_name' or 'table_name' must be presented.")
|
|
142
|
+
|
|
143
|
+
return await asyncio.gather(*[
|
|
144
|
+
self.connector.insert_dataframe(tbl_name, df)
|
|
145
|
+
for df in split_dataframe(dataframe, chunksize)
|
|
146
|
+
])
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
if OPTION.general.db_direct_access:
|
|
150
|
+
ClientBase = DirectAccessClient
|
|
151
|
+
else:
|
|
152
|
+
ClientBase = APIBasedClient
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
@cachedclass(Manager.create_cache(TTLCache, maxsize=5, ttl=3600))
|
|
156
|
+
class AsyncClickHouseClient(ClientBase):
|
|
157
|
+
pass
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
@cachedclass(Manager.create_cache(TTLCache, maxsize=5, ttl=3600))
|
|
161
|
+
class ClickHouseClient(ClientBase, metaclass=SyncMeta):
|
|
162
|
+
synchronize = (
|
|
163
|
+
'exec_sqls',
|
|
164
|
+
'query_dfs',
|
|
165
|
+
'insert_df',
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
169
|
+
def exec_sqls(
|
|
170
|
+
self,
|
|
171
|
+
sqls: Union[str, Iterable[str]],
|
|
172
|
+
table_info: T_DataInfo = None
|
|
173
|
+
):
|
|
174
|
+
...
|
|
175
|
+
|
|
176
|
+
def query_dfs(
|
|
177
|
+
self,
|
|
178
|
+
sqls: Union[str, Iterable[str]],
|
|
179
|
+
table_info: T_DataInfo = None
|
|
180
|
+
) -> Union[pd.DataFrame, List[pd.DataFrame]]:
|
|
181
|
+
...
|
|
182
|
+
|
|
183
|
+
def insert_df(
|
|
184
|
+
self,
|
|
185
|
+
dataframe: pd.DataFrame,
|
|
186
|
+
element_name: str = None,
|
|
187
|
+
table_name: str = None,
|
|
188
|
+
table_info: T_DataInfo = None,
|
|
189
|
+
chunksize: int = None,
|
|
190
|
+
):
|
|
191
|
+
...
|
deepfos/db/connector.py
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
from typing import Union, List
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
|
|
7
|
+
from deepfos.api.datatable import MySQLAPI, ClickHouseAPI, OracleAPI, SQLServerAPI, KingBaseAPI, GaussAPI, DaMengAPI, \
|
|
8
|
+
PostgreSQLAPI, DeepEngineAPI, DeepModelAPI, DeepModelKingBaseAPI
|
|
9
|
+
from deepfos.lib.asynchronous import cache_async
|
|
10
|
+
from .dbkits import APIFinder
|
|
11
|
+
from . import daclickhouse
|
|
12
|
+
from . import damysql
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
'AbstractConnector',
|
|
16
|
+
'MySQLAPIConnector',
|
|
17
|
+
'MySQLDirectAccess',
|
|
18
|
+
'ClickHouseAPIConnector',
|
|
19
|
+
'ClickHouseDirectAccess',
|
|
20
|
+
'SQLServerAPIConnector',
|
|
21
|
+
'OracleAPIConnector',
|
|
22
|
+
'GaussAPIConnector',
|
|
23
|
+
'KingBaseAPIConnector',
|
|
24
|
+
'DaMengAPIConnector',
|
|
25
|
+
'PostgreSQLAPIConnector',
|
|
26
|
+
'DeepEngineAPIConnector',
|
|
27
|
+
'DeepModelAPIConnector',
|
|
28
|
+
'DeepModelKingBaseAPIConnector',
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class AbstractConnector(ABC): # pragma: no cover
|
|
33
|
+
def __init__(self, *args, **kwargs):
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
@abstractmethod
|
|
37
|
+
async def execute(self, sql: str):
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
@abstractmethod
|
|
41
|
+
async def execute_many(self, sqls: List[str]):
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
@abstractmethod
|
|
45
|
+
async def trxn_execute(self, sqls: List[str]):
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
@abstractmethod
|
|
49
|
+
async def select(self, sql: str):
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
@abstractmethod
|
|
53
|
+
async def query_dataframe(self, sql: str) -> pd.DataFrame:
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class MySQLAPIConnector(AbstractConnector):
|
|
58
|
+
def __init__( # noqa
|
|
59
|
+
self,
|
|
60
|
+
version: Union[float, str] = None,
|
|
61
|
+
):
|
|
62
|
+
self._v = version
|
|
63
|
+
|
|
64
|
+
@cache_async
|
|
65
|
+
async def build_api(self) -> MySQLAPI:
|
|
66
|
+
return await APIFinder().find_api(MySQLAPI, version=self._v)
|
|
67
|
+
|
|
68
|
+
async def execute(self, sql: str):
|
|
69
|
+
api = await self.build_api()
|
|
70
|
+
return await api.dml.run_sql(sql)
|
|
71
|
+
|
|
72
|
+
async def execute_many(self, sqls: List[str]):
|
|
73
|
+
api = await self.build_api()
|
|
74
|
+
return await asyncio.gather(
|
|
75
|
+
*(api.dml.run_sql(sql) for sql in sqls)
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
async def trxn_execute(self, sqls: List[str]):
|
|
79
|
+
api = await self.build_api()
|
|
80
|
+
return await api.dml.execute_batch_sql(sqls)
|
|
81
|
+
|
|
82
|
+
select = execute
|
|
83
|
+
|
|
84
|
+
async def query_dataframe(self, sql: str) -> pd.DataFrame:
|
|
85
|
+
api = await self.build_api()
|
|
86
|
+
resp = await api.dml.run_sql(sql)
|
|
87
|
+
select_data = [item or {} for item in resp.selectResult]
|
|
88
|
+
return pd.DataFrame.from_records(select_data)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class ClickHouseAPIConnector(MySQLAPIConnector):
|
|
92
|
+
async def trxn_execute(self, sqls: List[str]):
|
|
93
|
+
raise NotImplementedError('ClickHouse does not support transaction')
|
|
94
|
+
|
|
95
|
+
@cache_async
|
|
96
|
+
async def build_api(self) -> ClickHouseAPI:
|
|
97
|
+
return await APIFinder().find_api(ClickHouseAPI, version=self._v)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class MySQLDirectAccess(AbstractConnector):
|
|
101
|
+
async def execute(self, sql: str):
|
|
102
|
+
return await damysql.execute(sql)
|
|
103
|
+
|
|
104
|
+
async def execute_many(self, sqls: List[str]):
|
|
105
|
+
return await asyncio.gather(
|
|
106
|
+
*(damysql.execute(sql) for sql in sqls)
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
async def trxn_execute(self, sqls: List[str]):
|
|
110
|
+
return await damysql.trxn_execute(sqls)
|
|
111
|
+
|
|
112
|
+
async def select(self, sql: str):
|
|
113
|
+
return await damysql.select(sql)
|
|
114
|
+
|
|
115
|
+
async def query_dataframe(self, sql: str) -> pd.DataFrame:
|
|
116
|
+
return await damysql.query_dataframe(sql)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class ClickHouseDirectAccess(AbstractConnector):
|
|
120
|
+
async def execute(self, sql: str):
|
|
121
|
+
return await daclickhouse.execute(sql)
|
|
122
|
+
|
|
123
|
+
async def execute_many(self, sqls: List[str]):
|
|
124
|
+
return await asyncio.gather(
|
|
125
|
+
*(daclickhouse.execute(sql) for sql in sqls)
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
async def trxn_execute(self, sqls: List[str]):
|
|
129
|
+
raise NotImplementedError('ClickHouse does not support transaction')
|
|
130
|
+
|
|
131
|
+
async def select(self, sql: str):
|
|
132
|
+
return await daclickhouse.select(sql)
|
|
133
|
+
|
|
134
|
+
async def query_dataframe(self, sql: str) -> pd.DataFrame:
|
|
135
|
+
return await daclickhouse.query_dataframe(sql)
|
|
136
|
+
|
|
137
|
+
async def insert_dataframe(self, table: str, dataframe: pd.DataFrame): # noqa
|
|
138
|
+
return await daclickhouse.insert_dataframe(table, dataframe)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class SQLServerAPIConnector(MySQLAPIConnector):
|
|
142
|
+
@cache_async
|
|
143
|
+
async def build_api(self) -> SQLServerAPI:
|
|
144
|
+
return await APIFinder().find_api(SQLServerAPI, version=self._v)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class OracleAPIConnector(MySQLAPIConnector):
|
|
148
|
+
@cache_async
|
|
149
|
+
async def build_api(self) -> OracleAPI:
|
|
150
|
+
return await APIFinder().find_api(OracleAPI, version=self._v)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class KingBaseAPIConnector(MySQLAPIConnector):
|
|
154
|
+
@cache_async
|
|
155
|
+
async def build_api(self) -> KingBaseAPI:
|
|
156
|
+
return await APIFinder().find_api(KingBaseAPI, version=self._v)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class GaussAPIConnector(MySQLAPIConnector):
|
|
160
|
+
@cache_async
|
|
161
|
+
async def build_api(self) -> GaussAPI:
|
|
162
|
+
return await APIFinder().find_api(GaussAPI, version=self._v)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class DaMengAPIConnector(MySQLAPIConnector):
|
|
166
|
+
@cache_async
|
|
167
|
+
async def build_api(self) -> DaMengAPI:
|
|
168
|
+
return await APIFinder().find_api(DaMengAPI, version=self._v)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class PostgreSQLAPIConnector(MySQLAPIConnector):
|
|
172
|
+
@cache_async
|
|
173
|
+
async def build_api(self) -> PostgreSQLAPI:
|
|
174
|
+
return await APIFinder().find_api(PostgreSQLAPI, version=self._v)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
class DeepEngineAPIConnector(ClickHouseAPIConnector):
|
|
178
|
+
@cache_async
|
|
179
|
+
async def build_api(self) -> DeepEngineAPI:
|
|
180
|
+
return await APIFinder().find_api(DeepEngineAPI, version=self._v)
|
|
181
|
+
|
|
182
|
+
async def trxn_execute(self, sqls: List[str]):
|
|
183
|
+
raise NotImplementedError('DeepEngine does not support transaction')
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class DeepModelAPIConnector(MySQLAPIConnector):
|
|
187
|
+
@cache_async
|
|
188
|
+
async def build_api(self) -> DeepModelAPI:
|
|
189
|
+
return await APIFinder().find_api(DeepModelAPI, version=self._v)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class DeepModelKingBaseAPIConnector(KingBaseAPIConnector):
|
|
193
|
+
@cache_async
|
|
194
|
+
async def build_api(self) -> DeepModelKingBaseAPI:
|
|
195
|
+
return await APIFinder().find_api(DeepModelKingBaseAPI, version=self._v)
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from reprlib import aRepr
|
|
3
|
+
import warnings
|
|
4
|
+
|
|
5
|
+
from loguru import logger
|
|
6
|
+
from cachetools import TTLCache
|
|
7
|
+
import clickhouse_driver
|
|
8
|
+
import threading
|
|
9
|
+
|
|
10
|
+
from deepfos import OPTION
|
|
11
|
+
from deepfos.api.datatable import ClickHouseAPI
|
|
12
|
+
from deepfos.lib.constant import UNSET
|
|
13
|
+
from deepfos.lib.asynchronous import register_on_loop_shutdown
|
|
14
|
+
from .utils import (
|
|
15
|
+
decrypt, AbsLeaseManager, ACCOUNT_EXPIRE,
|
|
16
|
+
PENDING, INITIALIZED, INITIALIZING, DBConnecetionError
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
aRepr.maxstring = 200
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# cdef class ClientCH:
|
|
23
|
+
# cdef:
|
|
24
|
+
# object conn
|
|
25
|
+
# int status
|
|
26
|
+
# object inited
|
|
27
|
+
# object lease
|
|
28
|
+
# bytes secret
|
|
29
|
+
|
|
30
|
+
class ClientCH:
|
|
31
|
+
def __init__(self):
|
|
32
|
+
self.conn = UNSET
|
|
33
|
+
self.status = PENDING
|
|
34
|
+
self.inited = threading.Event()
|
|
35
|
+
self.lease = LeaseManagerCH(ACCOUNT_EXPIRE / 3)
|
|
36
|
+
self.secret = "!ABCD-EFGH-IJKL@".encode()
|
|
37
|
+
|
|
38
|
+
async def establish_conn(self):
|
|
39
|
+
if self.status == INITIALIZED:
|
|
40
|
+
return self.conn
|
|
41
|
+
|
|
42
|
+
if self.status == PENDING:
|
|
43
|
+
self.status = INITIALIZING
|
|
44
|
+
else:
|
|
45
|
+
self.inited.wait()
|
|
46
|
+
if self.status != INITIALIZED:
|
|
47
|
+
raise RuntimeError("Failed to initialze connection pool.")
|
|
48
|
+
return self.conn
|
|
49
|
+
|
|
50
|
+
conf = await self.lease.renew()
|
|
51
|
+
self.lease.schedule(slow_start=True)
|
|
52
|
+
register_on_loop_shutdown(self.close)
|
|
53
|
+
|
|
54
|
+
conn_info = {
|
|
55
|
+
'host': conf.host,
|
|
56
|
+
'database': conf.dbName,
|
|
57
|
+
'user': conf.name,
|
|
58
|
+
'password': decrypt(self.secret, conf.password),
|
|
59
|
+
'port': conf.port,
|
|
60
|
+
'settings': {'use_numpy': True}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
self.conn = clickhouse_driver.Client(**conn_info)
|
|
65
|
+
self.conn.connection.force_connect()
|
|
66
|
+
except Exception as e:
|
|
67
|
+
self.status = PENDING
|
|
68
|
+
raise DBConnecetionError(e) from None
|
|
69
|
+
if not self.conn.connection.connected:
|
|
70
|
+
self.status = PENDING
|
|
71
|
+
raise DBConnecetionError("Not a valid connection")
|
|
72
|
+
|
|
73
|
+
self.status = INITIALIZED
|
|
74
|
+
self.inited.set()
|
|
75
|
+
return self.conn
|
|
76
|
+
|
|
77
|
+
async def ensure_connected(self):
|
|
78
|
+
max_retry = 3
|
|
79
|
+
retries = 0
|
|
80
|
+
interval = 0.5
|
|
81
|
+
|
|
82
|
+
while self.conn is UNSET or not self.conn.connection.connected:
|
|
83
|
+
try:
|
|
84
|
+
await self.establish_conn()
|
|
85
|
+
except DBConnecetionError:
|
|
86
|
+
retries += 1
|
|
87
|
+
if retries > max_retry:
|
|
88
|
+
self.inited.set()
|
|
89
|
+
raise
|
|
90
|
+
logger.exception(f'Failed to establish connection, '
|
|
91
|
+
f'starting {retries} times retry.')
|
|
92
|
+
time.sleep(interval)
|
|
93
|
+
interval *= 2
|
|
94
|
+
except Exception:
|
|
95
|
+
self.inited.set()
|
|
96
|
+
self.status = PENDING
|
|
97
|
+
raise
|
|
98
|
+
|
|
99
|
+
return self.conn
|
|
100
|
+
|
|
101
|
+
async def execute(self, sql):
|
|
102
|
+
conn = await self.ensure_connected()
|
|
103
|
+
logger.opt(lazy=True).debug("Run sql: {sql}", sql=lambda: aRepr.repr(sql))
|
|
104
|
+
return conn.execute(sql)
|
|
105
|
+
|
|
106
|
+
async def select(self, sql):
|
|
107
|
+
conn = await self.ensure_connected()
|
|
108
|
+
logger.opt(lazy=True).debug("Run sql: {sql}", sql=lambda: aRepr.repr(sql))
|
|
109
|
+
return conn.execute(sql)
|
|
110
|
+
|
|
111
|
+
async def query_dataframe(self, sql):
|
|
112
|
+
conn = await self.ensure_connected()
|
|
113
|
+
logger.opt(lazy=True).debug("Run sql: {sql}", sql=lambda: aRepr.repr(sql))
|
|
114
|
+
with warnings.catch_warnings():
|
|
115
|
+
warnings.simplefilter("ignore", category=RuntimeWarning)
|
|
116
|
+
return conn.query_dataframe(sql)
|
|
117
|
+
|
|
118
|
+
async def insert_dataframe(self, table, dataframe):
|
|
119
|
+
query = f"INSERT INTO `{table}` VALUES"
|
|
120
|
+
conn = await self.ensure_connected()
|
|
121
|
+
logger.debug(f"Run sql : {query} ...")
|
|
122
|
+
with warnings.catch_warnings():
|
|
123
|
+
warnings.simplefilter("ignore", category=RuntimeWarning)
|
|
124
|
+
return conn.insert_dataframe(query, dataframe)
|
|
125
|
+
|
|
126
|
+
def close(self):
|
|
127
|
+
self.lease.cancel()
|
|
128
|
+
self.status = PENDING
|
|
129
|
+
|
|
130
|
+
if self.conn is not UNSET:
|
|
131
|
+
self.conn.disconnect()
|
|
132
|
+
self.conn = UNSET
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
# cdef class ClientManager:
|
|
136
|
+
class ClientManager:
|
|
137
|
+
cache = TTLCache(maxsize=10, ttl=1800)
|
|
138
|
+
|
|
139
|
+
def new_client(self):
|
|
140
|
+
key = (
|
|
141
|
+
OPTION.api.header.get('space'),
|
|
142
|
+
OPTION.api.header.get('app'),
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
if key not in self.cache:
|
|
146
|
+
self.cache[key] = ClientCH()
|
|
147
|
+
|
|
148
|
+
return self.cache[key]
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class LeaseManagerCH(AbsLeaseManager):
|
|
152
|
+
DB = 'clickhouse'
|
|
153
|
+
|
|
154
|
+
async def new_api(self):
|
|
155
|
+
return await ClickHouseAPI(version=1.0, sync=False)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
async def select(sql):
|
|
159
|
+
return await ClientManager().new_client().select(sql)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
async def execute(sql):
|
|
163
|
+
return await ClientManager().new_client().execute(sql)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
async def query_dataframe(sql):
|
|
167
|
+
return await ClientManager().new_client().query_dataframe(sql)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
async def insert_dataframe(table, dataframe):
|
|
171
|
+
return await ClientManager().new_client().insert_dataframe(table, dataframe)
|
deepfos/db/dameng.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""Dameng客户端"""
|
|
2
|
+
import asyncio
|
|
3
|
+
from typing import Union, List, Iterable, TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
|
|
7
|
+
from deepfos.api.datatable import DaMengAPI
|
|
8
|
+
from deepfos.cache import Manager, SpaceSeperatedTTLCache
|
|
9
|
+
from deepfos.lib.utils import cachedclass
|
|
10
|
+
from deepfos.lib.decorator import singleton, cached_property
|
|
11
|
+
from deepfos.db.oracle import OracleDFSQLConvertor
|
|
12
|
+
from .dbkits import SyncMeta, T_DataInfo, BaseSqlParser
|
|
13
|
+
from .connector import DaMengAPIConnector
|
|
14
|
+
from .mysql import _AbsAsyncMySQLClient # noqa
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
'DaMengClient',
|
|
19
|
+
'AsyncDaMengClient',
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@singleton
|
|
24
|
+
class SqlParser(BaseSqlParser):
|
|
25
|
+
api_cls = DaMengAPI
|
|
26
|
+
|
|
27
|
+
@cached_property
|
|
28
|
+
def datatable_cls(self):
|
|
29
|
+
from deepfos.element.datatable import AsyncDataTableDaMeng
|
|
30
|
+
return AsyncDataTableDaMeng
|
|
31
|
+
|
|
32
|
+
@staticmethod
|
|
33
|
+
async def query_table_names(api: DaMengAPI, query_table): # pragma: no cover
|
|
34
|
+
async def query_single(tbl_ele):
|
|
35
|
+
return tbl_ele.elementName, await api.dml.get_tablename(tbl_ele)
|
|
36
|
+
|
|
37
|
+
tablenames = await asyncio.gather(*(
|
|
38
|
+
query_single(table)
|
|
39
|
+
for table in query_table
|
|
40
|
+
))
|
|
41
|
+
|
|
42
|
+
if len(tablenames) != len(query_table):
|
|
43
|
+
missing = set(t.elementName for t in query_table).difference(
|
|
44
|
+
set(t[0] for t in tablenames))
|
|
45
|
+
|
|
46
|
+
raise ValueError(f"Cannot resolve actual table names for element: {missing}")
|
|
47
|
+
return dict(tablenames)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# -----------------------------------------------------------------------------
|
|
51
|
+
# core
|
|
52
|
+
class _AsyncDaMengClient(_AbsAsyncMySQLClient):
|
|
53
|
+
convertor = OracleDFSQLConvertor(quote_char='"')
|
|
54
|
+
|
|
55
|
+
def __init__(self, version: Union[float, str] = None): # noqa
|
|
56
|
+
self.parser = SqlParser()
|
|
57
|
+
self.connector = DaMengAPIConnector(version)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class _DaMengClient(_AsyncDaMengClient, metaclass=SyncMeta):
|
|
61
|
+
synchronize = (
|
|
62
|
+
'exec_sqls',
|
|
63
|
+
'query_dfs',
|
|
64
|
+
'insert_df',
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
68
|
+
def exec_sqls(
|
|
69
|
+
self,
|
|
70
|
+
sqls: Union[str, Iterable[str]],
|
|
71
|
+
table_info: T_DataInfo = None
|
|
72
|
+
):
|
|
73
|
+
...
|
|
74
|
+
|
|
75
|
+
def query_dfs(
|
|
76
|
+
self,
|
|
77
|
+
sqls: Union[str, Iterable[str]],
|
|
78
|
+
table_info: T_DataInfo = None
|
|
79
|
+
) -> Union[pd.DataFrame, List[pd.DataFrame]]:
|
|
80
|
+
...
|
|
81
|
+
|
|
82
|
+
def insert_df(
|
|
83
|
+
self,
|
|
84
|
+
dataframe: pd.DataFrame,
|
|
85
|
+
element_name: str = None,
|
|
86
|
+
table_name: str = None,
|
|
87
|
+
updatecol: Iterable[str] = None,
|
|
88
|
+
table_info: T_DataInfo = None,
|
|
89
|
+
chunksize: int = None,
|
|
90
|
+
):
|
|
91
|
+
...
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@cachedclass(Manager.create_cache(SpaceSeperatedTTLCache, maxsize=5, ttl=3600))
|
|
95
|
+
class AsyncDaMengClient(_AsyncDaMengClient):
|
|
96
|
+
pass
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@cachedclass(Manager.create_cache(SpaceSeperatedTTLCache, maxsize=5, ttl=3600))
|
|
100
|
+
class DaMengClient(_DaMengClient):
|
|
101
|
+
pass
|