dataops-sdk 0.5.0__tar.gz → 0.5.1__tar.gz
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.
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/PKG-INFO +6 -3
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/pyproject.toml +5 -2
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/__init__.py +2 -2
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/runner/__init__.py +2 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/runner/datasource_factory.py +31 -5
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/runner/utils/__init__.py +3 -0
- dataops_sdk-0.5.1/src/dataops_sdk/runner/utils/dbConn_async.py +484 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/runner/utils/dbConn_base.py +255 -43
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/runner/utils/dbConn_sync.py +3 -2
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/runner/utils/nacos.py +6 -1
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/schemas/__init__.py +13 -1
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/schemas/ontology.py +54 -15
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/schemas/skill.py +7 -0
- dataops_sdk-0.5.1/src/dataops_sdk/schemas/wiki.py +122 -0
- dataops_sdk-0.5.1/src/dataops_sdk/services/__init__.py +359 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/services/ontology.py +208 -222
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/services/skill.py +53 -12
- dataops_sdk-0.5.1/src/dataops_sdk/services/wiki.py +291 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/services/workflow.py +4 -2
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk.egg-info/PKG-INFO +6 -3
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk.egg-info/SOURCES.txt +5 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk.egg-info/requires.txt +7 -2
- dataops_sdk-0.5.1/tests/test_datasource_factory.py +73 -0
- dataops_sdk-0.5.1/tests/test_db_conn_async.py +169 -0
- dataops_sdk-0.5.0/src/dataops_sdk/services/__init__.py +0 -176
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/README.md +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/setup.cfg +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/constants.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/exceptions.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/runner/schemas/nacos/__init__.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/runner/schemas/nacos/config.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/runner/schemas/nacos/constants.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/runner/schemas/nacos/instance.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/runner/schemas/nacos/nacos_exception.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/runner/schemas/nacos/naming_param.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/runner/schemas/nacos/naming_request.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/runner/schemas/nacos/naming_response.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/runner/schemas/nacos/preserved_metadata_key.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/runner/schemas/nacos/service.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/runner/utils/common.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/runner/utils/duckdb_manager.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/runner/utils/file_transfer_base.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/runner/utils/ftp_file_transfer.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/runner/utils/neo4j_sync.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/runner/utils/queue_base.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/runner/utils/queue_kafka.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/runner/utils/sftp_file_transfer.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/runner/utils/websocket_client.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/schemas/alarm.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/schemas/api.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/schemas/common.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/schemas/data_table.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/schemas/datasource.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/schemas/dict_.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/schemas/enums.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/schemas/group.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/schemas/hazard.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/schemas/meta.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/schemas/query.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/schemas/report.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/schemas/table.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/schemas/tag.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/schemas/user.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/schemas/widget.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/schemas/workflow.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/services/alarm.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/services/api.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/services/auth.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/services/data_table.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/services/dataset.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/services/datasource.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/services/dict_.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/services/group.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/services/hazard.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/services/meta.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/services/metrics.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/services/ref.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/services/report.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/services/table.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/services/tag.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/services/widget.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/session.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/tiptap/__init__.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/tiptap/builder.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/tiptap/constants.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/tiptap/markdown_parser.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/tiptap/models.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/tiptap/parse_utils.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/tiptap/render_utils.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/tiptap/renderers.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/tiptap/tool.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/utils/__init__.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/utils/common.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/utils/log.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk/utils/utils.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk.egg-info/dependency_links.txt +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/src/dataops_sdk.egg-info/top_level.txt +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/tests/test_constants.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/tests/test_exceptions.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/tests/test_session.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/tests/test_utils.py +0 -0
- {dataops_sdk-0.5.0 → dataops_sdk-0.5.1}/tests/test_websocket_client.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dataops-sdk
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.1
|
|
4
4
|
Summary: Python SDK for the DataOps platform.
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -16,8 +16,11 @@ Provides-Extra: runner
|
|
|
16
16
|
Requires-Dist: paramiko>4; extra == "runner"
|
|
17
17
|
Requires-Dist: pandas; extra == "runner"
|
|
18
18
|
Requires-Dist: sqlalchemy; extra == "runner"
|
|
19
|
-
Requires-Dist:
|
|
20
|
-
Requires-Dist:
|
|
19
|
+
Requires-Dist: mariadb[binary]; extra == "runner"
|
|
20
|
+
Requires-Dist: psycopg[binary]>=3; extra == "runner"
|
|
21
|
+
Requires-Dist: dmpython~=2.5.32; platform_system != "Darwin" and extra == "runner"
|
|
22
|
+
Requires-Dist: dmsqlalchemy<3,>2; platform_system != "Darwin" and extra == "runner"
|
|
23
|
+
Requires-Dist: doris-python>=1.0.4; extra == "runner"
|
|
21
24
|
Requires-Dist: oracledb~=4.0.1; extra == "runner"
|
|
22
25
|
Requires-Dist: neo4j; extra == "runner"
|
|
23
26
|
Requires-Dist: kafka-python>3; extra == "runner"
|
|
@@ -27,8 +27,11 @@ runner = [
|
|
|
27
27
|
"pandas",
|
|
28
28
|
# 关系数据库
|
|
29
29
|
"sqlalchemy",
|
|
30
|
-
"
|
|
31
|
-
"
|
|
30
|
+
"mariadb[binary]",
|
|
31
|
+
"psycopg[binary]>=3",
|
|
32
|
+
"dmpython~=2.5.32; platform_system != 'Darwin'",
|
|
33
|
+
"dmsqlalchemy>2,<3; platform_system != 'Darwin'",
|
|
34
|
+
"doris-python>=1.0.4",
|
|
32
35
|
"oracledb~=4.0.1",
|
|
33
36
|
# 图数据库
|
|
34
37
|
"neo4j",
|
|
@@ -6,7 +6,7 @@ Nacos 集成能力,未安装时仍可独立导入本包核心功能。
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
from . import runner, schemas, utils
|
|
9
|
-
from .runner import service_get_datasource_instent
|
|
9
|
+
from .runner import service_get_datasource_instent, get_service_instance
|
|
10
10
|
from .services import ( # noqa: F401
|
|
11
11
|
release_list_create,
|
|
12
12
|
release_list_get_by_code,
|
|
@@ -111,4 +111,4 @@ from .services import ( # noqa: F401
|
|
|
111
111
|
)
|
|
112
112
|
from .session import dataops_base_url_context, request_context
|
|
113
113
|
|
|
114
|
-
__version__ = "0.5.
|
|
114
|
+
__version__ = "0.5.1"
|
|
@@ -15,6 +15,7 @@ from .datasource_factory import service_get_datasource_instent
|
|
|
15
15
|
from .schemas.nacos import NacosSettings
|
|
16
16
|
from .utils import (
|
|
17
17
|
DatabaseConn,
|
|
18
|
+
DatabaseConnAsync,
|
|
18
19
|
FTPFileTransfer,
|
|
19
20
|
KafkaQueue,
|
|
20
21
|
Neo4jConn,
|
|
@@ -30,6 +31,7 @@ from .utils import (
|
|
|
30
31
|
|
|
31
32
|
__all__ = [
|
|
32
33
|
"DatabaseConn",
|
|
34
|
+
"DatabaseConnAsync",
|
|
33
35
|
"FTPFileTransfer",
|
|
34
36
|
"KafkaQueue",
|
|
35
37
|
"SFTPFileTransfer",
|
|
@@ -6,24 +6,33 @@
|
|
|
6
6
|
from dataops_sdk.services.datasource import service_get_datasource_by_name
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
def service_get_datasource_instent(
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
def service_get_datasource_instent(
|
|
10
|
+
datasource: str,
|
|
11
|
+
log_sql: bool | None = None,
|
|
12
|
+
use_async: bool = False,
|
|
13
|
+
**kwargs,
|
|
14
|
+
):
|
|
15
|
+
"""根据数据源名称获取对应的连接实例.
|
|
12
16
|
|
|
13
17
|
通过数据源名称从平台查询元数据,根据数据源的 category(数据库/消息队列)
|
|
14
18
|
和 type(具体类型)动态创建并返回相应的连接实例。
|
|
15
19
|
|
|
20
|
+
``use_async=True`` 时,关系型数据库返回 ``DatabaseConnAsync``。
|
|
21
|
+
Neo4j、Kafka 以及达梦(dm)尚无异步实现。
|
|
22
|
+
|
|
16
23
|
Args:
|
|
17
24
|
datasource: 数据源名称,用于从平台查询数据源元数据。
|
|
18
25
|
log_sql: 是否记录 SQL 执行日志,传递给数据库连接实例。
|
|
26
|
+
use_async: 为 True 时返回异步连接。默认为 False,保持同步连接。
|
|
27
|
+
**kwargs: 预留的额外参数,当前不会传给连接实例。
|
|
19
28
|
|
|
20
29
|
Returns:
|
|
21
|
-
DatabaseConn: 通用数据库连接实例(MySQL、PostgreSQL 等)。
|
|
30
|
+
DatabaseConn | DatabaseConnAsync: 通用数据库连接实例(MySQL、PostgreSQL 等)。
|
|
22
31
|
Neo4jConn: Neo4j 图数据库连接实例。
|
|
23
32
|
KafkaQueue: Kafka 消息队列连接实例。
|
|
24
33
|
|
|
25
34
|
Raises:
|
|
26
|
-
NotImplementedError:
|
|
35
|
+
NotImplementedError: 当数据源类型未被支持,或该类型没有异步实现时抛出。
|
|
27
36
|
|
|
28
37
|
Note:
|
|
29
38
|
当前支持的 category:
|
|
@@ -39,6 +48,8 @@ def service_get_datasource_instent(datasource: str, log_sql: bool | None = None,
|
|
|
39
48
|
if meta.data.type == "kafka":
|
|
40
49
|
# Kafka 连接需要 bootstrap_servers
|
|
41
50
|
assert meta.data.bootstrap_servers, "bootstrap_servers is required, but not provided"
|
|
51
|
+
if use_async:
|
|
52
|
+
raise NotImplementedError("kafka does not support async connection")
|
|
42
53
|
from dataops_sdk.runner import KafkaQueue
|
|
43
54
|
|
|
44
55
|
return KafkaQueue(meta.data.bootstrap_servers)
|
|
@@ -54,6 +65,8 @@ def service_get_datasource_instent(datasource: str, log_sql: bool | None = None,
|
|
|
54
65
|
assert meta.data.database, "database is required, but not provided"
|
|
55
66
|
|
|
56
67
|
if meta.data.type == "neo4j":
|
|
68
|
+
if use_async:
|
|
69
|
+
raise NotImplementedError("Neo4j does not support async connection")
|
|
57
70
|
# Neo4j 图数据库使用专用连接类
|
|
58
71
|
from dataops_sdk.runner import Neo4jConn
|
|
59
72
|
|
|
@@ -68,6 +81,19 @@ def service_get_datasource_instent(datasource: str, log_sql: bool | None = None,
|
|
|
68
81
|
)
|
|
69
82
|
else:
|
|
70
83
|
# 通用数据库连接,支持 MySQL、PostgreSQL 等
|
|
84
|
+
if use_async:
|
|
85
|
+
from dataops_sdk.runner import DatabaseConnAsync
|
|
86
|
+
|
|
87
|
+
return DatabaseConnAsync(
|
|
88
|
+
host=meta.data.ip,
|
|
89
|
+
port=meta.data.port,
|
|
90
|
+
user=meta.data.username,
|
|
91
|
+
password=meta.data.password,
|
|
92
|
+
db=meta.data.database,
|
|
93
|
+
db_type=meta.data.type,
|
|
94
|
+
log_sql=log_sql,
|
|
95
|
+
name=meta.title,
|
|
96
|
+
)
|
|
71
97
|
from dataops_sdk.runner import DatabaseConn
|
|
72
98
|
|
|
73
99
|
return DatabaseConn(
|
|
@@ -10,6 +10,7 @@ Functions:
|
|
|
10
10
|
|
|
11
11
|
Classes:
|
|
12
12
|
DatabaseConn: 数据库连接管理类.
|
|
13
|
+
DatabaseConnAsync: 异步数据库连接管理类.
|
|
13
14
|
Neo4jConn: Neo4j 图数据库连接类.
|
|
14
15
|
FTPFileTransfer: FTP 文件传输类.
|
|
15
16
|
SFTPFileTransfer: SFTP 文件传输类.
|
|
@@ -27,6 +28,7 @@ from .common import (
|
|
|
27
28
|
parse_trigger_time,
|
|
28
29
|
print_json,
|
|
29
30
|
)
|
|
31
|
+
from .dbConn_async import DatabaseConnAsync
|
|
30
32
|
from .dbConn_sync import DatabaseConn
|
|
31
33
|
from .ftp_file_transfer import FTPFileTransfer
|
|
32
34
|
from .nacos import get_service_instance
|
|
@@ -41,6 +43,7 @@ __all__ = [
|
|
|
41
43
|
"generate_uuid",
|
|
42
44
|
"print_json",
|
|
43
45
|
"DatabaseConn",
|
|
46
|
+
"DatabaseConnAsync",
|
|
44
47
|
"Neo4jConn",
|
|
45
48
|
"get_service_instance",
|
|
46
49
|
"KafkaQueue",
|
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
"""基于 SQLAlchemy 的异步数据库连接实现.
|
|
2
|
+
|
|
3
|
+
继承自 DatabaseConnBaseAsync,提供与 DatabaseConn 对齐的异步接口。
|
|
4
|
+
使用 SQLAlchemy AsyncEngine 和 AsyncSession 作为底层实现。
|
|
5
|
+
|
|
6
|
+
Classes:
|
|
7
|
+
DatabaseConnAsync: 基于 SQLAlchemy 的异步数据库连接类.
|
|
8
|
+
DatabaseConnDataOpsAsync: 通过数据源名称动态创建的 DatabaseConnAsync 子类.
|
|
9
|
+
|
|
10
|
+
Example:
|
|
11
|
+
>>> async with DatabaseConnAsync(host="localhost", port=3306,
|
|
12
|
+
... user="root", password="pass", db="test") as conn:
|
|
13
|
+
... result = await conn.select("SELECT * FROM users WHERE id = %s", (1,))
|
|
14
|
+
... await conn.execute("INSERT INTO users (name) VALUES (%s)", ("Alice",))
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import re
|
|
18
|
+
from collections.abc import Mapping
|
|
19
|
+
from typing import Any, Final, Literal, overload
|
|
20
|
+
|
|
21
|
+
from sqlalchemy import URL, text
|
|
22
|
+
from sqlalchemy.ext.asyncio import (
|
|
23
|
+
AsyncEngine,
|
|
24
|
+
AsyncSession,
|
|
25
|
+
async_sessionmaker,
|
|
26
|
+
create_async_engine,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
from .dbConn_base import DatabaseConnBaseAsync
|
|
30
|
+
|
|
31
|
+
# 数据库类型到异步驱动程序的映射
|
|
32
|
+
ASYNC_DBTYPE_DRIVER: Final[Mapping[str, str]] = {
|
|
33
|
+
"mysql": "mariadbconnector",
|
|
34
|
+
"mariadb": "mariadbconnector",
|
|
35
|
+
"postgresql": "psycopg",
|
|
36
|
+
"oracle": "oracledb",
|
|
37
|
+
"doris": "mariadbconnector",
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def build_async_url(
|
|
42
|
+
host: str,
|
|
43
|
+
port: int | str,
|
|
44
|
+
user: str,
|
|
45
|
+
password: str,
|
|
46
|
+
db: str,
|
|
47
|
+
db_type: str,
|
|
48
|
+
) -> URL:
|
|
49
|
+
"""构建异步数据库连接 URL.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
host: 数据库服务器主机地址.
|
|
53
|
+
port: 数据库服务器端口号.
|
|
54
|
+
user: 数据库用户名.
|
|
55
|
+
password: 数据库密码.
|
|
56
|
+
db: 数据库名称.
|
|
57
|
+
db_type: 数据库类型.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
URL: SQLAlchemy 异步连接 URL.
|
|
61
|
+
|
|
62
|
+
Raises:
|
|
63
|
+
NotImplementedError: 当指定的 db_type 没有异步驱动时抛出.
|
|
64
|
+
"""
|
|
65
|
+
if db_type not in ASYNC_DBTYPE_DRIVER:
|
|
66
|
+
raise NotImplementedError(f"{db_type} 异步驱动尚未支持,请联系管理员")
|
|
67
|
+
|
|
68
|
+
query: dict[str, str] = {}
|
|
69
|
+
if db_type in {"mysql", "mariadb", "doris"}:
|
|
70
|
+
query["local_infile"] = "True"
|
|
71
|
+
query["ssl_disabled"] = "True"
|
|
72
|
+
|
|
73
|
+
driver = ASYNC_DBTYPE_DRIVER[db_type]
|
|
74
|
+
return URL.create(
|
|
75
|
+
drivername=f"{db_type}+{driver}",
|
|
76
|
+
username=user,
|
|
77
|
+
password=password,
|
|
78
|
+
host=host,
|
|
79
|
+
port=int(port),
|
|
80
|
+
database=db,
|
|
81
|
+
query=query,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class DatabaseConnAsync(DatabaseConnBaseAsync):
|
|
86
|
+
"""基于 SQLAlchemy 的异步数据库连接类.
|
|
87
|
+
|
|
88
|
+
使用 AsyncEngine 和 AsyncSession 实现数据库操作,支持单条执行、批量执行、
|
|
89
|
+
查询、手动事务以及异步上下文管理器。
|
|
90
|
+
|
|
91
|
+
Attributes:
|
|
92
|
+
engine: SQLAlchemy AsyncEngine 对象.
|
|
93
|
+
session: 当前事务的 AsyncSession,未开启事务时为 None.
|
|
94
|
+
session_factory: SQLAlchemy 异步 Session 工厂.
|
|
95
|
+
logger: 日志记录器.
|
|
96
|
+
|
|
97
|
+
Example:
|
|
98
|
+
>>> async with DatabaseConnAsync(
|
|
99
|
+
... host="localhost", port=3306, user="root", password="pass",
|
|
100
|
+
... db="test", db_type="mysql",
|
|
101
|
+
... ) as conn:
|
|
102
|
+
... rows = await conn.select("SELECT * FROM users LIMIT %s", (10,))
|
|
103
|
+
... await conn.execute(
|
|
104
|
+
... "INSERT INTO users (name) VALUES (%s)", ("Alice",)
|
|
105
|
+
... )
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
def post_init(self):
|
|
109
|
+
"""初始化 SQLAlchemy 异步 Engine 和 Session 工厂.
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
None
|
|
113
|
+
"""
|
|
114
|
+
self._url = build_async_url(
|
|
115
|
+
host=self._host,
|
|
116
|
+
port=self._port,
|
|
117
|
+
user=self._user,
|
|
118
|
+
password=self._password,
|
|
119
|
+
db=self._db,
|
|
120
|
+
db_type=self._db_type,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
if self._log_sql:
|
|
124
|
+
self._logger.info(
|
|
125
|
+
f"Creating SQLAlchemy async Engine for {self._host}:{self._port}/{self._db}"
|
|
126
|
+
)
|
|
127
|
+
self._engine: AsyncEngine | None = create_async_engine_safe(self._db_type, self._url)
|
|
128
|
+
self._session_factory = async_sessionmaker(
|
|
129
|
+
bind=self._engine,
|
|
130
|
+
autoflush=False,
|
|
131
|
+
expire_on_commit=False,
|
|
132
|
+
)
|
|
133
|
+
self._session: AsyncSession | None = None
|
|
134
|
+
|
|
135
|
+
def _get_log_sql(self, log_sql: bool | None) -> bool:
|
|
136
|
+
"""获取是否记录 SQL 的布尔值.
|
|
137
|
+
|
|
138
|
+
优先级:方法参数 log_sql > 实例级 self._log_sql.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
log_sql: 方法级 log_sql 参数.
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
bool: 是否应记录 SQL.
|
|
145
|
+
"""
|
|
146
|
+
return bool(log_sql) if log_sql is not None else self._log_sql
|
|
147
|
+
|
|
148
|
+
@staticmethod
|
|
149
|
+
def render_sql(sql: str, args: tuple | list | dict | None) -> str:
|
|
150
|
+
"""渲染 SQL 语句,仅用于日志展示.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
sql: SQL 语句模板.
|
|
154
|
+
args: SQL 语句参数.
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
str: 渲染后的 SQL 语句.
|
|
158
|
+
"""
|
|
159
|
+
if not args:
|
|
160
|
+
return sql
|
|
161
|
+
if isinstance(args, (list, tuple)):
|
|
162
|
+
return sql.replace("%s", "{}").format(*args)
|
|
163
|
+
sql = re.sub(r"\%\((\w+)\)s", r"\{\1\}", sql)
|
|
164
|
+
return sql.format(**args)
|
|
165
|
+
|
|
166
|
+
async def executemany(
|
|
167
|
+
self,
|
|
168
|
+
sql: str,
|
|
169
|
+
datas: list[tuple | list],
|
|
170
|
+
*,
|
|
171
|
+
auto_commit: bool = True,
|
|
172
|
+
log_sql: bool | None = None,
|
|
173
|
+
**kwargs,
|
|
174
|
+
):
|
|
175
|
+
"""执行多条 SQL 语句(批量执行).
|
|
176
|
+
|
|
177
|
+
已通过 begin() 开启手动事务时,使用该事务执行,auto_commit 被忽略。
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
sql: 待执行的 SQL 语句模板,支持 %s 或 :name 占位符.
|
|
181
|
+
datas: SQL 语句参数的列表,每一项对应一条 SQL 执行.
|
|
182
|
+
auto_commit: 是否自动提交事务,默认为 True. 存在活跃事务时此参数被忽略.
|
|
183
|
+
log_sql: 是否打印 SQL 日志,为 None 时使用实例级 self._log_sql.
|
|
184
|
+
**kwargs: 兼容性的额外参数,传入时会输出警告日志.
|
|
185
|
+
"""
|
|
186
|
+
if kwargs:
|
|
187
|
+
self.logger.warning(f"尚未支持的参数: {kwargs}")
|
|
188
|
+
|
|
189
|
+
should_log = self._get_log_sql(log_sql)
|
|
190
|
+
sql, args = self.process_input(sql, datas)
|
|
191
|
+
|
|
192
|
+
if should_log and args:
|
|
193
|
+
self.logger.info(self.render_sql(sql, args[0]))
|
|
194
|
+
|
|
195
|
+
if self._session is not None:
|
|
196
|
+
await self._session.execute(text(sql), args)
|
|
197
|
+
return
|
|
198
|
+
|
|
199
|
+
async with self.session_factory() as session:
|
|
200
|
+
await session.execute(text(sql), args)
|
|
201
|
+
if auto_commit:
|
|
202
|
+
await session.commit()
|
|
203
|
+
|
|
204
|
+
async def execute(
|
|
205
|
+
self,
|
|
206
|
+
sql: str,
|
|
207
|
+
args: tuple | list | dict | None = None,
|
|
208
|
+
*,
|
|
209
|
+
auto_commit: bool = True,
|
|
210
|
+
log_sql: bool | None = None,
|
|
211
|
+
**kwargs,
|
|
212
|
+
):
|
|
213
|
+
"""执行单条 SQL 语句.
|
|
214
|
+
|
|
215
|
+
已通过 begin() 开启手动事务时,使用该事务执行,auto_commit 被忽略。
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
sql: 待执行的 SQL 语句.
|
|
219
|
+
args: SQL 语句参数,支持元组、列表或字典格式.
|
|
220
|
+
auto_commit: 是否自动提交事务,默认为 True. 存在活跃事务时此参数被忽略.
|
|
221
|
+
log_sql: 是否打印 SQL 日志,为 None 时使用实例级 self._log_sql.
|
|
222
|
+
**kwargs: 兼容性的额外参数,传入时会输出警告日志.
|
|
223
|
+
"""
|
|
224
|
+
if kwargs:
|
|
225
|
+
self.logger.warning(f"尚未支持的参数: {kwargs}")
|
|
226
|
+
|
|
227
|
+
should_log = self._get_log_sql(log_sql)
|
|
228
|
+
sql, args = self.process_input(sql, args)
|
|
229
|
+
|
|
230
|
+
if should_log:
|
|
231
|
+
self.logger.info(self.render_sql(sql, args))
|
|
232
|
+
|
|
233
|
+
if self._session is not None:
|
|
234
|
+
await self._session.execute(text(sql), args)
|
|
235
|
+
return
|
|
236
|
+
|
|
237
|
+
async with self.session_factory() as session:
|
|
238
|
+
await session.execute(text(sql), args)
|
|
239
|
+
if auto_commit:
|
|
240
|
+
await session.commit()
|
|
241
|
+
|
|
242
|
+
@overload
|
|
243
|
+
async def select(
|
|
244
|
+
self,
|
|
245
|
+
sql: str,
|
|
246
|
+
args: tuple | list | dict | None = None,
|
|
247
|
+
size: int | None = None,
|
|
248
|
+
mapping: Literal[False] = False,
|
|
249
|
+
*,
|
|
250
|
+
log_sql: bool | None = None,
|
|
251
|
+
) -> list[tuple[Any, ...]]: ...
|
|
252
|
+
|
|
253
|
+
@overload
|
|
254
|
+
async def select(
|
|
255
|
+
self,
|
|
256
|
+
sql: str,
|
|
257
|
+
args: tuple | list | dict | None = None,
|
|
258
|
+
size: int | None = None,
|
|
259
|
+
mapping: Literal[True] = ...,
|
|
260
|
+
*,
|
|
261
|
+
log_sql: bool | None = None,
|
|
262
|
+
) -> list[dict[str, Any]]: ...
|
|
263
|
+
|
|
264
|
+
async def select(
|
|
265
|
+
self,
|
|
266
|
+
sql: str,
|
|
267
|
+
args: tuple | list | dict | None = None,
|
|
268
|
+
size: int | None = None,
|
|
269
|
+
mapping: bool = False,
|
|
270
|
+
*,
|
|
271
|
+
log_sql: bool | None = None,
|
|
272
|
+
) -> list[dict[str, Any] | tuple[Any, ...]]:
|
|
273
|
+
"""执行查询语句并返回结果.
|
|
274
|
+
|
|
275
|
+
Args:
|
|
276
|
+
sql: 待执行的查询 SQL 语句.
|
|
277
|
+
args: SQL 语句参数,支持元组、列表或字典格式.
|
|
278
|
+
size: 限制返回的行数,None 或小于等于 0 时不限制.
|
|
279
|
+
mapping: 是否以字典形式返回每行(列名 -> 值),默认为 False.
|
|
280
|
+
log_sql: 是否打印 SQL 日志,为 None 时使用实例级 self._log_sql.
|
|
281
|
+
|
|
282
|
+
Returns:
|
|
283
|
+
list[dict[str, Any] | tuple[Any, ...]]: 查询结果列表;
|
|
284
|
+
当 mapping=True 时元素为字典,否则元素为元组.
|
|
285
|
+
"""
|
|
286
|
+
should_log = self._get_log_sql(log_sql)
|
|
287
|
+
|
|
288
|
+
if isinstance(sql, str):
|
|
289
|
+
sql, args = self.process_input(sql, args)
|
|
290
|
+
|
|
291
|
+
if should_log:
|
|
292
|
+
self.logger.info(self.render_sql(sql, args))
|
|
293
|
+
|
|
294
|
+
async with self.session_factory() as session:
|
|
295
|
+
connection = await session.connection()
|
|
296
|
+
if isinstance(sql, str):
|
|
297
|
+
result = await connection.exec_driver_sql(sql, args)
|
|
298
|
+
else:
|
|
299
|
+
result = await connection.execute(sql)
|
|
300
|
+
|
|
301
|
+
if mapping:
|
|
302
|
+
result = result.mappings()
|
|
303
|
+
rows = result.fetchall() if size is None or size <= 0 else result.fetchmany(size)
|
|
304
|
+
|
|
305
|
+
if mapping:
|
|
306
|
+
return [dict(row) for row in rows]
|
|
307
|
+
return [row._t._data for row in rows]
|
|
308
|
+
|
|
309
|
+
@staticmethod
|
|
310
|
+
def process_input(sql: str, args: dict | tuple | list | None):
|
|
311
|
+
"""预处理 SQL 参数.
|
|
312
|
+
|
|
313
|
+
将列表形式的参数规范化为 SQLAlchemy 接受的元组形式。
|
|
314
|
+
|
|
315
|
+
Args:
|
|
316
|
+
sql: SQL 语句模板.
|
|
317
|
+
args: SQL 参数,支持 None、元组、列表或字典.
|
|
318
|
+
|
|
319
|
+
Returns:
|
|
320
|
+
tuple[str, Any]: 处理后的 (sql, args) 元组.
|
|
321
|
+
"""
|
|
322
|
+
if args is None:
|
|
323
|
+
return sql, None
|
|
324
|
+
if isinstance(args, list):
|
|
325
|
+
if len(args) > 1 and isinstance(args[0], list):
|
|
326
|
+
return sql, list(map(tuple, args))
|
|
327
|
+
return sql, tuple(args)
|
|
328
|
+
return sql, args
|
|
329
|
+
|
|
330
|
+
async def close(self):
|
|
331
|
+
"""关闭数据库连接和活跃事务.
|
|
332
|
+
|
|
333
|
+
如果存在未提交的事务,先执行回滚,然后关闭 Session 和 Engine。
|
|
334
|
+
支持多次调用。
|
|
335
|
+
"""
|
|
336
|
+
if getattr(self, "_session", None) is not None:
|
|
337
|
+
try:
|
|
338
|
+
await self._session.rollback()
|
|
339
|
+
except Exception:
|
|
340
|
+
pass
|
|
341
|
+
finally:
|
|
342
|
+
await self._session.close()
|
|
343
|
+
self._session = None
|
|
344
|
+
|
|
345
|
+
if getattr(self, "_engine", None) is not None:
|
|
346
|
+
await self._engine.dispose()
|
|
347
|
+
self._engine = None
|
|
348
|
+
self._logger.info("closed")
|
|
349
|
+
|
|
350
|
+
@property
|
|
351
|
+
def session_factory(self):
|
|
352
|
+
"""获取 SQLAlchemy 异步 Session 工厂.
|
|
353
|
+
|
|
354
|
+
Returns:
|
|
355
|
+
async_sessionmaker: 已绑定 AsyncEngine 的 Session 工厂.
|
|
356
|
+
"""
|
|
357
|
+
return self._session_factory
|
|
358
|
+
|
|
359
|
+
@property
|
|
360
|
+
def engine(self) -> AsyncEngine | None:
|
|
361
|
+
"""获取 SQLAlchemy AsyncEngine 对象.
|
|
362
|
+
|
|
363
|
+
Returns:
|
|
364
|
+
AsyncEngine | None: 异步 Engine,未初始化时为 None.
|
|
365
|
+
"""
|
|
366
|
+
return self._engine
|
|
367
|
+
|
|
368
|
+
async def commit(self):
|
|
369
|
+
"""提交当前手动事务.
|
|
370
|
+
|
|
371
|
+
仅在已通过 begin() 开启事务后调用。提交后事务结束,session 会被关闭。
|
|
372
|
+
"""
|
|
373
|
+
if self._session is None:
|
|
374
|
+
self._logger.warning("commit called but no active transaction")
|
|
375
|
+
return
|
|
376
|
+
self._logger.debug("committing transaction")
|
|
377
|
+
await self._session.commit()
|
|
378
|
+
await self._session.close()
|
|
379
|
+
self._session = None
|
|
380
|
+
|
|
381
|
+
async def rollback(self):
|
|
382
|
+
"""回滚当前手动事务.
|
|
383
|
+
|
|
384
|
+
仅在已通过 begin() 开启事务后调用。回滚后事务结束,session 会被关闭。
|
|
385
|
+
"""
|
|
386
|
+
if self._session is None:
|
|
387
|
+
self._logger.warning("rollback called but no active transaction")
|
|
388
|
+
return
|
|
389
|
+
self._logger.warning("rolling back transaction")
|
|
390
|
+
await self._session.rollback()
|
|
391
|
+
await self._session.close()
|
|
392
|
+
self._session = None
|
|
393
|
+
|
|
394
|
+
async def begin(self):
|
|
395
|
+
"""开启一个新的手动事务.
|
|
396
|
+
|
|
397
|
+
开启后,后续的 execute() 和 executemany() 将在该事务中执行。
|
|
398
|
+
调用 commit() 或 rollback() 会结束当前事务。
|
|
399
|
+
|
|
400
|
+
Raises:
|
|
401
|
+
RuntimeError: 如果已有活跃事务未关闭时再次调用。
|
|
402
|
+
"""
|
|
403
|
+
if self._session is not None:
|
|
404
|
+
raise RuntimeError(
|
|
405
|
+
"Transaction already active. Commit or rollback before starting a new one."
|
|
406
|
+
)
|
|
407
|
+
self._logger.debug("beginning transaction")
|
|
408
|
+
self._session = self.session_factory()
|
|
409
|
+
await self._session.begin()
|
|
410
|
+
|
|
411
|
+
@property
|
|
412
|
+
def session(self) -> AsyncSession | None:
|
|
413
|
+
"""获取当前事务的 Session 对象.
|
|
414
|
+
|
|
415
|
+
Returns:
|
|
416
|
+
AsyncSession | None: 当前事务的 Session;未开启事务时返回 None.
|
|
417
|
+
"""
|
|
418
|
+
return self._session
|
|
419
|
+
|
|
420
|
+
@property
|
|
421
|
+
def in_transaction(self) -> bool:
|
|
422
|
+
"""检查是否存在活跃的手动事务.
|
|
423
|
+
|
|
424
|
+
Returns:
|
|
425
|
+
bool: 如果存在活跃事务则返回 True,否则返回 False.
|
|
426
|
+
"""
|
|
427
|
+
return self._session is not None
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
class DatabaseConnDataOpsAsync(DatabaseConnAsync):
|
|
431
|
+
"""基于数据源名称动态创建的 DatabaseConnAsync 子类.
|
|
432
|
+
|
|
433
|
+
通过平台元数据服务根据 datasource 名称获取连接信息,
|
|
434
|
+
自动构造 DatabaseConnAsync 实例。
|
|
435
|
+
|
|
436
|
+
Attributes:
|
|
437
|
+
datasource: 数据源名称.
|
|
438
|
+
log_sql: 是否记录 SQL 执行日志.
|
|
439
|
+
"""
|
|
440
|
+
|
|
441
|
+
def __init__(self, datasource: str, log_sql: bool | None = None):
|
|
442
|
+
"""初始化 DatabaseConnDataOpsAsync.
|
|
443
|
+
|
|
444
|
+
Args:
|
|
445
|
+
datasource: 数据源名称.
|
|
446
|
+
log_sql: 是否记录 SQL 执行日志,默认为 None.
|
|
447
|
+
"""
|
|
448
|
+
from dataops_sdk.services.meta import Meta, release_list_get_by_name
|
|
449
|
+
|
|
450
|
+
datasource_meta = Meta.model_validate(release_list_get_by_name("datasource", datasource))
|
|
451
|
+
|
|
452
|
+
super().__init__(
|
|
453
|
+
host=datasource_meta.data["ip"],
|
|
454
|
+
port=datasource_meta.data["port"],
|
|
455
|
+
user=datasource_meta.data["username"],
|
|
456
|
+
password=datasource_meta.data["password"],
|
|
457
|
+
db=datasource_meta.data["database"],
|
|
458
|
+
db_type=datasource_meta.data["type"],
|
|
459
|
+
log_sql=log_sql,
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
def create_async_engine_safe(db_type: str, url: str | URL, **kwargs: Any) -> AsyncEngine:
|
|
464
|
+
"""安全创建 SQLAlchemy 异步 Engine.
|
|
465
|
+
|
|
466
|
+
Args:
|
|
467
|
+
db_type: 数据库类型,如 mysql、postgresql、oracle、doris.
|
|
468
|
+
url: 数据库连接 URL.
|
|
469
|
+
**kwargs: 传递给 create_async_engine 的额外参数.
|
|
470
|
+
|
|
471
|
+
Returns:
|
|
472
|
+
AsyncEngine: 配置好的 SQLAlchemy 异步 Engine 实例.
|
|
473
|
+
|
|
474
|
+
Raises:
|
|
475
|
+
NotImplementedError: 当数据库类型不在异步支持列表中时抛出.
|
|
476
|
+
"""
|
|
477
|
+
if db_type not in ASYNC_DBTYPE_DRIVER:
|
|
478
|
+
raise NotImplementedError(f"{db_type} 异步驱动尚未支持,请联系管理员")
|
|
479
|
+
|
|
480
|
+
kwargs.setdefault("pool_pre_ping", True)
|
|
481
|
+
if db_type in ("mysql", "mariadb", "doris"):
|
|
482
|
+
kwargs.setdefault("pool_recycle", 3600)
|
|
483
|
+
|
|
484
|
+
return create_async_engine(url, **kwargs)
|