dataops-sdk 0.4.1__tar.gz → 0.4.2__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.4.1 → dataops_sdk-0.4.2}/PKG-INFO +2 -1
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/pyproject.toml +2 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/__init__.py +1 -1
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/runner/__init__.py +2 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/runner/utils/__init__.py +2 -0
- dataops_sdk-0.4.2/src/dataops_sdk/runner/utils/websocket_client.py +240 -0
- dataops_sdk-0.4.2/src/dataops_sdk/schemas/sql_query_ast.py +628 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/services/__init__.py +1 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/services/datasource.py +5 -2
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/services/dict_.py +29 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/services/skill.py +2 -14
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk.egg-info/PKG-INFO +2 -1
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk.egg-info/SOURCES.txt +4 -1
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk.egg-info/requires.txt +1 -0
- dataops_sdk-0.4.2/tests/test_websocket_client.py +118 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/README.md +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/setup.cfg +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/constants.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/exceptions.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/runner/datasource_factory.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/runner/schemas/nacos/__init__.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/runner/schemas/nacos/config.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/runner/schemas/nacos/constants.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/runner/schemas/nacos/instance.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/runner/schemas/nacos/nacos_exception.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/runner/schemas/nacos/naming_param.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/runner/schemas/nacos/naming_request.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/runner/schemas/nacos/naming_response.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/runner/schemas/nacos/preserved_metadata_key.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/runner/schemas/nacos/service.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/runner/utils/common.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/runner/utils/dbConn_base.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/runner/utils/dbConn_sync.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/runner/utils/duckdb_manager.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/runner/utils/file_transfer_base.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/runner/utils/ftp_file_transfer.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/runner/utils/nacos.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/runner/utils/neo4j_sync.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/runner/utils/queue_base.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/runner/utils/queue_kafka.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/runner/utils/sftp_file_transfer.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/schemas/__init__.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/schemas/alarm.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/schemas/api.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/schemas/common.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/schemas/data_table.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/schemas/datasource.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/schemas/dict_.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/schemas/enums.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/schemas/group.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/schemas/hazard.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/schemas/meta.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/schemas/query.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/schemas/report.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/schemas/skill.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/schemas/table.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/schemas/user.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/schemas/widget.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/schemas/workflow.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/services/alarm.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/services/api.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/services/auth.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/services/data_table.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/services/dataset.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/services/group.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/services/hazard.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/services/meta.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/services/metrics.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/services/ref.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/services/report.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/services/table.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/services/widget.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/services/workflow.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/session.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/tiptap/__init__.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/tiptap/builder.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/tiptap/constants.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/tiptap/markdown_parser.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/tiptap/models.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/tiptap/parse_utils.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/tiptap/render_utils.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/tiptap/renderers.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/tiptap/tool.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/utils/__init__.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/utils/common.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/utils/log.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk/utils/utils.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk.egg-info/dependency_links.txt +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/src/dataops_sdk.egg-info/top_level.txt +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/tests/test_constants.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/tests/test_exceptions.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/tests/test_session.py +0 -0
- {dataops_sdk-0.4.1 → dataops_sdk-0.4.2}/tests/test_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dataops-sdk
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
4
4
|
Summary: Python SDK for the DataOps platform.
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -21,6 +21,7 @@ Requires-Dist: doris-python>=1.0.3; extra == "runner"
|
|
|
21
21
|
Requires-Dist: oracledb~=4.0.1; extra == "runner"
|
|
22
22
|
Requires-Dist: neo4j; extra == "runner"
|
|
23
23
|
Requires-Dist: kafka-python>3; extra == "runner"
|
|
24
|
+
Requires-Dist: websockets>=13; extra == "runner"
|
|
24
25
|
Provides-Extra: dev
|
|
25
26
|
Requires-Dist: pytest; extra == "dev"
|
|
26
27
|
Requires-Dist: pytest-mock; extra == "dev"
|
|
@@ -19,6 +19,7 @@ from .utils import (
|
|
|
19
19
|
KafkaQueue,
|
|
20
20
|
Neo4jConn,
|
|
21
21
|
SFTPFileTransfer,
|
|
22
|
+
WebSocketConn,
|
|
22
23
|
decrypt_body,
|
|
23
24
|
encrypt_body,
|
|
24
25
|
generate_uuid,
|
|
@@ -32,6 +33,7 @@ __all__ = [
|
|
|
32
33
|
"FTPFileTransfer",
|
|
33
34
|
"KafkaQueue",
|
|
34
35
|
"SFTPFileTransfer",
|
|
36
|
+
"WebSocketConn",
|
|
35
37
|
"Neo4jConn",
|
|
36
38
|
"decrypt_body",
|
|
37
39
|
"encrypt_body",
|
|
@@ -33,6 +33,7 @@ from .nacos import get_service_instance
|
|
|
33
33
|
from .neo4j_sync import Neo4jConn
|
|
34
34
|
from .queue_kafka import KafkaQueue
|
|
35
35
|
from .sftp_file_transfer import SFTPFileTransfer
|
|
36
|
+
from .websocket_client import WebSocketConn
|
|
36
37
|
|
|
37
38
|
__all__ = [
|
|
38
39
|
"decrypt_body",
|
|
@@ -43,6 +44,7 @@ __all__ = [
|
|
|
43
44
|
"Neo4jConn",
|
|
44
45
|
"get_service_instance",
|
|
45
46
|
"KafkaQueue",
|
|
47
|
+
"WebSocketConn",
|
|
46
48
|
"FTPFileTransfer",
|
|
47
49
|
"SFTPFileTransfer",
|
|
48
50
|
"parse_args",
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
"""WebSocket 客户端工具类。
|
|
2
|
+
|
|
3
|
+
基于 websockets 的同步客户端实现,提供发送/接收数据(文本或二进制帧)能力。
|
|
4
|
+
作为脚本内独立工具类使用,不接入平台数据源体系。
|
|
5
|
+
|
|
6
|
+
同步 API 说明:
|
|
7
|
+
- 构造即连接,连接失败抛出 ConnectionError;
|
|
8
|
+
- receive 在超时或连接关闭时返回 None(对齐 KafkaQueue.recv 语义);
|
|
9
|
+
- 不做自动重连(单次脚本运行生命周期),留作后续扩展。
|
|
10
|
+
|
|
11
|
+
注意:
|
|
12
|
+
- receive(timeout=None) 会永久阻塞,脚本中拉取循环务必传入数值 timeout,
|
|
13
|
+
最终由执行器的子进程超时兜底;
|
|
14
|
+
- max_size 默认 1MB,超限帧会抛出 ConnectionClosedError(1009) 并断连,
|
|
15
|
+
需要接收大帧时请调大 max_size;
|
|
16
|
+
- receive_json 假定接收文本帧,二进制帧请使用 receive()。
|
|
17
|
+
|
|
18
|
+
Example:
|
|
19
|
+
>>> with WebSocketConn(endpoint="example.com/ws", headers={"Authorization": "Bearer x"}, use_ssl=True) as ws:
|
|
20
|
+
... ws.send({"action": "subscribe", "channel": "trades"})
|
|
21
|
+
... while not ws.closed:
|
|
22
|
+
... msg = ws.receive(timeout=5)
|
|
23
|
+
... if msg is not None:
|
|
24
|
+
... print(msg)
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
import json
|
|
28
|
+
import os
|
|
29
|
+
from logging import Logger
|
|
30
|
+
from typing import Any
|
|
31
|
+
|
|
32
|
+
from websockets import Subprotocol
|
|
33
|
+
from websockets.exceptions import ConnectionClosed
|
|
34
|
+
from websockets.protocol import State
|
|
35
|
+
from websockets.sync.client import connect as _ws_connect
|
|
36
|
+
|
|
37
|
+
from dataops_sdk.utils.log import getLogger
|
|
38
|
+
|
|
39
|
+
__all__ = ["WebSocketConn"]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class WebSocketConn:
|
|
43
|
+
"""同步 WebSocket 客户端连接。
|
|
44
|
+
|
|
45
|
+
构造即建立连接,提供 send / receive / send_json / receive_json 方法,
|
|
46
|
+
支持上下文管理器(退出时自动关闭)。
|
|
47
|
+
|
|
48
|
+
Attributes:
|
|
49
|
+
logger: 日志记录器,用于记录连接操作日志。
|
|
50
|
+
url: WebSocket 服务端地址。
|
|
51
|
+
|
|
52
|
+
Example:
|
|
53
|
+
>>> ws = WebSocketConn(endpoint="example.com/ws", use_ssl=True)
|
|
54
|
+
>>> ws.send({"action": "subscribe"})
|
|
55
|
+
>>> msg = ws.receive(timeout=5)
|
|
56
|
+
>>> ws.close()
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
def __init__(
|
|
60
|
+
self,
|
|
61
|
+
endpoint: str,
|
|
62
|
+
*,
|
|
63
|
+
use_ssl: bool = False,
|
|
64
|
+
headers: dict[str, str] | None = None,
|
|
65
|
+
protocols: list[Subprotocol] | None = None,
|
|
66
|
+
open_timeout: float | None = 10.0,
|
|
67
|
+
ping_interval: float | None = 20.0,
|
|
68
|
+
ping_timeout: float | None = 20.0,
|
|
69
|
+
close_timeout: float | None = 10.0,
|
|
70
|
+
max_size: int | None = 2**20,
|
|
71
|
+
task_id: str | None = None,
|
|
72
|
+
):
|
|
73
|
+
"""初始化并建立 WebSocket 连接。
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
endpoint: WebSocket 服务端地址,只需输入主机:端口(可带路径),
|
|
77
|
+
如 "host:9001" 或 "host:9001/ws";若已含协议前缀则按原样使用。
|
|
78
|
+
use_ssl: 是否使用 TLS(wss://),默认 False 使用 ws://。
|
|
79
|
+
headers: 附加请求头(如 Authorization),可选。
|
|
80
|
+
protocols: 子协议列表,可选。
|
|
81
|
+
open_timeout: 建立连接超时(秒)。
|
|
82
|
+
ping_interval: 心跳间隔(秒)。
|
|
83
|
+
ping_timeout: 心跳超时(秒)。
|
|
84
|
+
close_timeout: 关闭连接超时(秒)。
|
|
85
|
+
max_size: 单帧最大字节数,超限帧将被关闭连接。
|
|
86
|
+
task_id: 任务 ID,用于日志标识;缺省时使用 TASK_ID 环境变量。
|
|
87
|
+
|
|
88
|
+
Raises:
|
|
89
|
+
ConnectionError: 连接失败时抛出。
|
|
90
|
+
"""
|
|
91
|
+
self._endpoint = endpoint
|
|
92
|
+
if "://" in endpoint:
|
|
93
|
+
self._url = endpoint
|
|
94
|
+
else:
|
|
95
|
+
scheme = "wss" if use_ssl else "ws"
|
|
96
|
+
self._url = f"{scheme}://{endpoint}"
|
|
97
|
+
# 先初始化为 None,避免构造失败时 __del__ 调用 close 访问不存在的属性
|
|
98
|
+
self._conn = None
|
|
99
|
+
self._logger = getLogger(task_id or os.getenv("TASK_ID") or type(self).__name__)
|
|
100
|
+
try:
|
|
101
|
+
self._conn = _ws_connect(
|
|
102
|
+
self._url,
|
|
103
|
+
subprotocols=protocols,
|
|
104
|
+
additional_headers=headers,
|
|
105
|
+
open_timeout=open_timeout,
|
|
106
|
+
ping_interval=ping_interval,
|
|
107
|
+
ping_timeout=ping_timeout,
|
|
108
|
+
close_timeout=close_timeout,
|
|
109
|
+
max_size=max_size,
|
|
110
|
+
)
|
|
111
|
+
except Exception as e:
|
|
112
|
+
self._logger.error(f"WebSocket 连接失败: {self._url}: {e}")
|
|
113
|
+
raise ConnectionError(f"WebSocket connect failed: {self._url}: {e}") from e
|
|
114
|
+
self._logger.info(f"WebSocket 已连接: {self._url}")
|
|
115
|
+
|
|
116
|
+
def send(self, data: str | bytes | dict | list) -> None:
|
|
117
|
+
"""发送消息。
|
|
118
|
+
|
|
119
|
+
str 发送为文本帧,bytes 发送为二进制帧;
|
|
120
|
+
dict / list 自动序列化为 JSON 文本帧。
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
data: 待发送数据。
|
|
124
|
+
|
|
125
|
+
Raises:
|
|
126
|
+
ConnectionError: 连接已关闭时抛出。
|
|
127
|
+
"""
|
|
128
|
+
if self._conn is None or self._conn.state is State.CLOSED:
|
|
129
|
+
raise ConnectionError(f"WebSocket 连接已关闭,无法发送数据: {self._url}")
|
|
130
|
+
if isinstance(data, (dict, list)):
|
|
131
|
+
data = json.dumps(data, ensure_ascii=False)
|
|
132
|
+
try:
|
|
133
|
+
self._conn.send(data)
|
|
134
|
+
except ConnectionClosed as e:
|
|
135
|
+
self._logger.error(f"WebSocket 发送失败(连接已关闭): {e}")
|
|
136
|
+
raise
|
|
137
|
+
except Exception as e:
|
|
138
|
+
self._logger.error(f"WebSocket 发送失败: {e}")
|
|
139
|
+
raise
|
|
140
|
+
|
|
141
|
+
def receive(self, timeout: float | None = None) -> str | bytes | None:
|
|
142
|
+
"""接收一条消息。
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
timeout: 等待超时(秒),None 表示阻塞等待。
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
str | bytes | None: 文本帧返回 str,二进制帧返回 bytes;
|
|
149
|
+
超时或连接关闭时返回 None。
|
|
150
|
+
"""
|
|
151
|
+
if self._conn is None or self._conn.state is State.CLOSED:
|
|
152
|
+
return None
|
|
153
|
+
try:
|
|
154
|
+
return self._conn.recv(timeout=timeout)
|
|
155
|
+
except TimeoutError:
|
|
156
|
+
return None
|
|
157
|
+
except ConnectionClosed as e:
|
|
158
|
+
self._logger.warning(f"WebSocket 连接已关闭: {e}")
|
|
159
|
+
return None
|
|
160
|
+
|
|
161
|
+
def send_json(self, obj: Any) -> None:
|
|
162
|
+
"""发送 JSON 消息。
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
obj: 任意可 JSON 序列化的对象。
|
|
166
|
+
"""
|
|
167
|
+
self.send(json.dumps(obj, ensure_ascii=False))
|
|
168
|
+
|
|
169
|
+
def receive_json(self, timeout: float | None = None) -> Any | None:
|
|
170
|
+
"""接收并解析一条 JSON 文本帧。
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
timeout: 等待超时(秒),None 表示阻塞等待。
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
Any | None: 解析后的对象;超时或连接关闭时返回 None。
|
|
177
|
+
"""
|
|
178
|
+
raw = self.receive(timeout=timeout)
|
|
179
|
+
if raw is None:
|
|
180
|
+
return None
|
|
181
|
+
return json.loads(raw)
|
|
182
|
+
|
|
183
|
+
def close(self) -> None:
|
|
184
|
+
"""关闭连接。
|
|
185
|
+
|
|
186
|
+
幂等,可多次调用。
|
|
187
|
+
"""
|
|
188
|
+
if self._conn is None:
|
|
189
|
+
return
|
|
190
|
+
try:
|
|
191
|
+
if self._conn.state is not State.CLOSED:
|
|
192
|
+
self._conn.close()
|
|
193
|
+
self._logger.info("WebSocket 已关闭")
|
|
194
|
+
except Exception as e:
|
|
195
|
+
self._logger.error(f"WebSocket 关闭失败: {e}")
|
|
196
|
+
finally:
|
|
197
|
+
self._conn = None
|
|
198
|
+
|
|
199
|
+
@property
|
|
200
|
+
def logger(self) -> Logger:
|
|
201
|
+
"""日志记录器。"""
|
|
202
|
+
return self._logger
|
|
203
|
+
|
|
204
|
+
@property
|
|
205
|
+
def url(self) -> str:
|
|
206
|
+
"""完整的 WebSocket 地址(含协议前缀)。"""
|
|
207
|
+
return self._url
|
|
208
|
+
|
|
209
|
+
@property
|
|
210
|
+
def endpoint(self) -> str:
|
|
211
|
+
"""WebSocket 服务端地址(不含协议前缀)。"""
|
|
212
|
+
return self._endpoint
|
|
213
|
+
|
|
214
|
+
@property
|
|
215
|
+
def closed(self) -> bool:
|
|
216
|
+
"""连接是否已关闭。"""
|
|
217
|
+
return self._conn is None or self._conn.state is State.CLOSED
|
|
218
|
+
|
|
219
|
+
@property
|
|
220
|
+
def connected(self) -> bool:
|
|
221
|
+
"""连接是否处于打开状态。"""
|
|
222
|
+
return self._conn is not None and self._conn.state is State.OPEN
|
|
223
|
+
|
|
224
|
+
def __enter__(self) -> "WebSocketConn":
|
|
225
|
+
"""上下文管理器入口。
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
WebSocketConn: 当前实例。
|
|
229
|
+
"""
|
|
230
|
+
return self
|
|
231
|
+
|
|
232
|
+
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
233
|
+
"""上下文管理器退出,自动关闭连接。"""
|
|
234
|
+
if exc_type:
|
|
235
|
+
self._logger.exception(exc_val)
|
|
236
|
+
self.close()
|
|
237
|
+
|
|
238
|
+
def __del__(self):
|
|
239
|
+
"""析构时兜底关闭连接。"""
|
|
240
|
+
self.close()
|