mqttxx 2.0.3__py3-none-any.whl → 3.2.1__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.
- mqttxx/__init__.py +11 -6
- mqttxx/client.py +239 -183
- mqttxx/config.py +14 -0
- mqttxx/events.py +359 -0
- mqttxx/protocol.py +93 -22
- mqttxx/rpc.py +81 -22
- mqttxx-3.2.1.dist-info/METADATA +931 -0
- mqttxx-3.2.1.dist-info/RECORD +12 -0
- mqttxx/conventions.py +0 -145
- mqttxx-2.0.3.dist-info/METADATA +0 -490
- mqttxx-2.0.3.dist-info/RECORD +0 -12
- {mqttxx-2.0.3.dist-info → mqttxx-3.2.1.dist-info}/LICENSE +0 -0
- {mqttxx-2.0.3.dist-info → mqttxx-3.2.1.dist-info}/WHEEL +0 -0
- {mqttxx-2.0.3.dist-info → mqttxx-3.2.1.dist-info}/top_level.txt +0 -0
mqttxx/events.py
ADDED
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
# Event Channel 层 - 高吞吐、低耦合的事件广播通道
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import time
|
|
5
|
+
from typing import Callable, Any, Optional, overload
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from loguru import logger
|
|
8
|
+
|
|
9
|
+
from .client import MQTTClient
|
|
10
|
+
import json
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class EventMessage:
|
|
15
|
+
"""事件消息(可选的结构化格式)
|
|
16
|
+
|
|
17
|
+
这是一个**可选**的辅助工具类,用户可以选择:
|
|
18
|
+
1. 使用 EventMessage 发布(带时间戳、事件类型)
|
|
19
|
+
2. 直接发布原始字典(零开销)
|
|
20
|
+
|
|
21
|
+
订阅者会根据消息是否包含 "type": "event" 自动区分
|
|
22
|
+
|
|
23
|
+
Attributes:
|
|
24
|
+
type: 消息类型标识(固定为 "event")
|
|
25
|
+
event_type: 事件类型(如 "sensor.temperature", "user.login")
|
|
26
|
+
data: 事件数据(任意 JSON 可序列化对象)
|
|
27
|
+
timestamp: Unix 时间戳(秒,自动填充)
|
|
28
|
+
source: 事件源(可选,发布者标识)
|
|
29
|
+
|
|
30
|
+
示例:
|
|
31
|
+
# 创建事件消息
|
|
32
|
+
msg = EventMessage(
|
|
33
|
+
event_type="temperature.changed",
|
|
34
|
+
data={"value": 25.5, "unit": "C"},
|
|
35
|
+
source="sensor_001"
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# 序列化
|
|
39
|
+
data = msg.to_dict()
|
|
40
|
+
# {
|
|
41
|
+
# "type": "event",
|
|
42
|
+
# "event_type": "temperature.changed",
|
|
43
|
+
# "data": {"value": 25.5, "unit": "C"},
|
|
44
|
+
# "timestamp": 1673456789.123,
|
|
45
|
+
# "source": "sensor_001"
|
|
46
|
+
# }
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
# 固定字段(用于区分事件消息和 RPC 消息)
|
|
50
|
+
type: str = "event"
|
|
51
|
+
|
|
52
|
+
# 事件核心字段
|
|
53
|
+
event_type: str = "" # 事件类型
|
|
54
|
+
data: Any = None # 事件数据
|
|
55
|
+
|
|
56
|
+
# 元数据(自动填充)
|
|
57
|
+
timestamp: float = 0.0 # Unix 时间戳(秒)
|
|
58
|
+
source: str = "" # 事件源
|
|
59
|
+
|
|
60
|
+
def __post_init__(self):
|
|
61
|
+
"""自动填充时间戳"""
|
|
62
|
+
if self.timestamp == 0.0:
|
|
63
|
+
self.timestamp = time.time()
|
|
64
|
+
|
|
65
|
+
def to_dict(self) -> dict:
|
|
66
|
+
"""序列化为字典
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
包含所有字段的字典
|
|
70
|
+
"""
|
|
71
|
+
return {
|
|
72
|
+
"type": self.type,
|
|
73
|
+
"event_type": self.event_type,
|
|
74
|
+
"data": self.data,
|
|
75
|
+
"timestamp": self.timestamp,
|
|
76
|
+
"source": self.source,
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@classmethod
|
|
80
|
+
def from_dict(cls, data: dict) -> "EventMessage":
|
|
81
|
+
"""从字典反序列化
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
data: 包含事件字段的字典
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
EventMessage 实例
|
|
88
|
+
"""
|
|
89
|
+
return cls(
|
|
90
|
+
event_type=data.get("event_type", ""),
|
|
91
|
+
data=data.get("data"),
|
|
92
|
+
timestamp=data.get("timestamp", 0.0),
|
|
93
|
+
source=data.get("source", ""),
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
def encode(self) -> bytes:
|
|
97
|
+
"""编码为 bytes(JSON 格式)
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
UTF-8 编码的 JSON bytes
|
|
101
|
+
"""
|
|
102
|
+
return json.dumps(self.to_dict()).encode('utf-8')
|
|
103
|
+
|
|
104
|
+
@classmethod
|
|
105
|
+
def decode(cls, data: bytes) -> "EventMessage":
|
|
106
|
+
"""从 bytes 解码(JSON 格式)
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
data: UTF-8 编码的 JSON bytes
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
EventMessage 对象
|
|
113
|
+
|
|
114
|
+
Raises:
|
|
115
|
+
UnicodeDecodeError: UTF-8 解码失败
|
|
116
|
+
json.JSONDecodeError: JSON 解析失败
|
|
117
|
+
"""
|
|
118
|
+
obj = json.loads(data.decode('utf-8'))
|
|
119
|
+
return cls.from_dict(obj)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
# 事件处理器类型定义
|
|
123
|
+
# 参数:(topic: str, message: dict)
|
|
124
|
+
EventHandler = Callable[[str, dict], Any]
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class EventChannelManager:
|
|
128
|
+
"""Event Channel 管理器 - 极薄的发布订阅层
|
|
129
|
+
|
|
130
|
+
核心特性:
|
|
131
|
+
1. 发布:零包装,直接转发到 MQTT(可选 EventMessage 格式化)
|
|
132
|
+
2. 订阅:支持通配符,自动区分结构化/原始消息
|
|
133
|
+
3. 过滤:基于 topic 模式匹配(MQTT 原生支持)
|
|
134
|
+
4. 无返回值:明确告诉使用者"这不是 RPC"
|
|
135
|
+
|
|
136
|
+
与 RPC 的共存:
|
|
137
|
+
- RPC 消息:type = "rpc_request" | "rpc_response"
|
|
138
|
+
- Event 消息:type = "event" | 原始字典(无 type 字段)
|
|
139
|
+
- 通过 type 字段自动分流(在 MQTTClient._handle_message 中)
|
|
140
|
+
|
|
141
|
+
使用示例:
|
|
142
|
+
# 创建管理器
|
|
143
|
+
events = EventChannelManager(client)
|
|
144
|
+
|
|
145
|
+
# 订阅事件(支持通配符)
|
|
146
|
+
@events.subscribe("sensors/+/temperature")
|
|
147
|
+
async def on_temperature(topic, message):
|
|
148
|
+
print(f"温度更新: {topic} -> {message}")
|
|
149
|
+
|
|
150
|
+
# 发布结构化事件
|
|
151
|
+
await events.publish(
|
|
152
|
+
"sensors/room1/temperature",
|
|
153
|
+
EventMessage(
|
|
154
|
+
event_type="temperature.changed",
|
|
155
|
+
data={"value": 25.5, "unit": "C"}
|
|
156
|
+
)
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
# 发布原始事件(零开销)
|
|
160
|
+
await events.publish(
|
|
161
|
+
"sensors/room1/humidity",
|
|
162
|
+
{"value": 60.2, "unit": "%"}
|
|
163
|
+
)
|
|
164
|
+
"""
|
|
165
|
+
|
|
166
|
+
def __init__(self, client: MQTTClient):
|
|
167
|
+
"""初始化 Event Channel 管理器
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
client: MQTTClient 实例(必须已连接或准备连接)
|
|
171
|
+
"""
|
|
172
|
+
self._client = client
|
|
173
|
+
self._patterns: dict[str, list[EventHandler]] = {} # pattern → handlers
|
|
174
|
+
self._dispatchers: dict[str, Callable] = {} # 保存 dispatcher 引用(修复 P0-B)
|
|
175
|
+
|
|
176
|
+
logger.info("EventChannelManager 已初始化")
|
|
177
|
+
|
|
178
|
+
@overload
|
|
179
|
+
def subscribe(
|
|
180
|
+
self,
|
|
181
|
+
pattern: str,
|
|
182
|
+
handler: None = None
|
|
183
|
+
) -> Callable[[EventHandler], EventHandler]:
|
|
184
|
+
"""装饰器模式:返回装饰器函数"""
|
|
185
|
+
...
|
|
186
|
+
|
|
187
|
+
@overload
|
|
188
|
+
def subscribe(
|
|
189
|
+
self,
|
|
190
|
+
pattern: str,
|
|
191
|
+
handler: EventHandler
|
|
192
|
+
) -> EventHandler:
|
|
193
|
+
"""直接注册模式:返回处理器本身"""
|
|
194
|
+
...
|
|
195
|
+
|
|
196
|
+
def subscribe(self, pattern: str, handler: Optional[EventHandler] = None):
|
|
197
|
+
"""订阅事件主题(支持通配符)
|
|
198
|
+
|
|
199
|
+
核心设计:
|
|
200
|
+
1. 支持 MQTT 通配符(+ 和 #)
|
|
201
|
+
2. 一个 pattern 可以有多个处理器(广播模式)
|
|
202
|
+
3. 自动在 MQTTClient 层注册订阅
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
pattern: MQTT 主题模式(支持通配符)
|
|
206
|
+
- "+": 单级通配符(sensors/+/temperature)
|
|
207
|
+
- "#": 多级通配符(sensors/#)
|
|
208
|
+
handler: 事件处理器(可选,也可以用作装饰器)
|
|
209
|
+
|
|
210
|
+
返回:
|
|
211
|
+
装饰器函数(如果 handler 为 None)
|
|
212
|
+
|
|
213
|
+
使用示例:
|
|
214
|
+
# 方式 1: 装饰器
|
|
215
|
+
@events.subscribe("sensors/+/temperature")
|
|
216
|
+
async def on_temp(topic, message):
|
|
217
|
+
pass
|
|
218
|
+
|
|
219
|
+
# 方式 2: 直接注册
|
|
220
|
+
events.subscribe("sensors/+/temperature", on_temp)
|
|
221
|
+
"""
|
|
222
|
+
|
|
223
|
+
def decorator(func: EventHandler):
|
|
224
|
+
# 添加到处理器列表
|
|
225
|
+
if pattern not in self._patterns:
|
|
226
|
+
self._patterns[pattern] = []
|
|
227
|
+
|
|
228
|
+
# 第一次订阅这个 pattern,创建专属 dispatcher(修复 P0-B:避免闭包泄漏)
|
|
229
|
+
async def dispatcher(topic: str, payload: bytes):
|
|
230
|
+
"""专属 dispatcher(bytes → dict → handler)"""
|
|
231
|
+
try:
|
|
232
|
+
# 解码(JSON 格式)
|
|
233
|
+
data = json.loads(payload.decode('utf-8'))
|
|
234
|
+
|
|
235
|
+
# 分发到所有 handlers(使用 get 避免 KeyError)
|
|
236
|
+
for h in self._patterns.get(pattern, []):
|
|
237
|
+
try:
|
|
238
|
+
if asyncio.iscoroutinefunction(h):
|
|
239
|
+
await h(topic, data)
|
|
240
|
+
else:
|
|
241
|
+
h(topic, data)
|
|
242
|
+
except Exception as e:
|
|
243
|
+
logger.exception(
|
|
244
|
+
f"Event handler 异常 - pattern: {pattern}, error: {e}"
|
|
245
|
+
)
|
|
246
|
+
except Exception as e:
|
|
247
|
+
logger.error(f"事件消息解码失败 - topic: {topic}, error: {e}")
|
|
248
|
+
|
|
249
|
+
# 保存 dispatcher 引用(修复 P0-B)
|
|
250
|
+
self._dispatchers[pattern] = dispatcher
|
|
251
|
+
|
|
252
|
+
# 注册到 MQTTClient 层
|
|
253
|
+
self._client.subscribe(pattern, dispatcher)
|
|
254
|
+
|
|
255
|
+
self._patterns[pattern].append(func)
|
|
256
|
+
logger.debug(
|
|
257
|
+
f"事件订阅成功 - pattern: {pattern}, handlers: {len(self._patterns[pattern])}"
|
|
258
|
+
)
|
|
259
|
+
return func
|
|
260
|
+
|
|
261
|
+
if handler is None:
|
|
262
|
+
return decorator
|
|
263
|
+
else:
|
|
264
|
+
return decorator(handler)
|
|
265
|
+
|
|
266
|
+
def unsubscribe(self, pattern: str, handler: Optional[EventHandler] = None):
|
|
267
|
+
"""取消订阅(修复 P0-B:真正清理底层订阅)
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
pattern: MQTT 主题模式
|
|
271
|
+
handler: 要移除的处理器(None = 移除所有)
|
|
272
|
+
|
|
273
|
+
改进:
|
|
274
|
+
现在会真正调用 MQTTClient.unsubscribe 来清理底层 MQTT 订阅
|
|
275
|
+
避免内存泄漏
|
|
276
|
+
"""
|
|
277
|
+
if pattern not in self._patterns:
|
|
278
|
+
return
|
|
279
|
+
|
|
280
|
+
if handler is None:
|
|
281
|
+
# 移除所有处理器
|
|
282
|
+
del self._patterns[pattern]
|
|
283
|
+
|
|
284
|
+
# 清理 dispatcher(修复 P0-B)
|
|
285
|
+
dispatcher = self._dispatchers.pop(pattern, None)
|
|
286
|
+
if dispatcher:
|
|
287
|
+
self._client.unsubscribe(pattern, dispatcher)
|
|
288
|
+
|
|
289
|
+
logger.info(f"已取消订阅 - pattern: {pattern}")
|
|
290
|
+
else:
|
|
291
|
+
# 移除指定处理器
|
|
292
|
+
if handler in self._patterns[pattern]:
|
|
293
|
+
self._patterns[pattern].remove(handler)
|
|
294
|
+
|
|
295
|
+
# 如果没有处理器了,清理 dispatcher(修复 P0-B)
|
|
296
|
+
if not self._patterns[pattern]:
|
|
297
|
+
del self._patterns[pattern]
|
|
298
|
+
|
|
299
|
+
dispatcher = self._dispatchers.pop(pattern, None)
|
|
300
|
+
if dispatcher:
|
|
301
|
+
self._client.unsubscribe(pattern, dispatcher)
|
|
302
|
+
|
|
303
|
+
logger.info(f"已取消订阅(最后一个 handler)- pattern: {pattern}")
|
|
304
|
+
else:
|
|
305
|
+
logger.debug(
|
|
306
|
+
f"已移除处理器 - pattern: {pattern}, 剩余 {len(self._patterns[pattern])} 个"
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
async def publish(
|
|
310
|
+
self,
|
|
311
|
+
topic: str,
|
|
312
|
+
message: EventMessage | dict | Any,
|
|
313
|
+
qos: int = 0,
|
|
314
|
+
):
|
|
315
|
+
"""发布事件(极薄包装)
|
|
316
|
+
|
|
317
|
+
设计原则:
|
|
318
|
+
- 不创建 Future(无返回值)
|
|
319
|
+
- 不等待确认(fire-and-forget)
|
|
320
|
+
- 直接调用 client.publish()
|
|
321
|
+
|
|
322
|
+
Args:
|
|
323
|
+
topic: 目标主题
|
|
324
|
+
message: 事件消息
|
|
325
|
+
- EventMessage: 自动序列化为 JSON
|
|
326
|
+
- dict: 直接序列化为 JSON
|
|
327
|
+
- 其他类型: 包装为 {"data": message}
|
|
328
|
+
qos: QoS 等级(0 = 最多一次,1 = 至少一次,2 = 恰好一次)
|
|
329
|
+
|
|
330
|
+
使用示例:
|
|
331
|
+
# 结构化事件
|
|
332
|
+
await events.publish(
|
|
333
|
+
"sensors/room1/temperature",
|
|
334
|
+
EventMessage(event_type="temp.changed", data={"value": 25.5})
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
# 原始字典
|
|
338
|
+
await events.publish(
|
|
339
|
+
"sensors/room1/humidity",
|
|
340
|
+
{"value": 60.2, "unit": "%"}
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
# 简单值(自动包装)
|
|
344
|
+
await events.publish(
|
|
345
|
+
"alerts/fire",
|
|
346
|
+
"Fire detected in room 3!"
|
|
347
|
+
)
|
|
348
|
+
"""
|
|
349
|
+
# 编码消息
|
|
350
|
+
if isinstance(message, EventMessage):
|
|
351
|
+
payload = message.encode()
|
|
352
|
+
elif isinstance(message, dict):
|
|
353
|
+
payload = json.dumps(message).encode('utf-8')
|
|
354
|
+
else:
|
|
355
|
+
# 其他类型自动包装
|
|
356
|
+
payload = json.dumps({"data": message}).encode('utf-8')
|
|
357
|
+
|
|
358
|
+
# 直接发布(零开销)
|
|
359
|
+
await self._client.raw.publish(topic, payload, qos=qos)
|
mqttxx/protocol.py
CHANGED
|
@@ -1,11 +1,34 @@
|
|
|
1
1
|
# MQTT RPC 消息协议定义
|
|
2
2
|
|
|
3
|
+
import json
|
|
3
4
|
from dataclasses import dataclass
|
|
5
|
+
from dataclasses import asdict, is_dataclass
|
|
4
6
|
from typing import Any, Optional, Literal
|
|
5
|
-
|
|
6
7
|
from .exceptions import MessageError, ErrorCode
|
|
7
8
|
|
|
8
9
|
|
|
10
|
+
def _to_jsonable(obj: Any) -> Any:
|
|
11
|
+
if obj is None:
|
|
12
|
+
return None
|
|
13
|
+
|
|
14
|
+
if is_dataclass(obj):
|
|
15
|
+
return _to_jsonable(asdict(obj))
|
|
16
|
+
|
|
17
|
+
if isinstance(obj, dict):
|
|
18
|
+
return {k: _to_jsonable(v) for k, v in obj.items()}
|
|
19
|
+
|
|
20
|
+
if isinstance(obj, (list, tuple, set)):
|
|
21
|
+
return [_to_jsonable(v) for v in obj]
|
|
22
|
+
|
|
23
|
+
if hasattr(obj, "model_dump") and callable(getattr(obj, "model_dump")):
|
|
24
|
+
return _to_jsonable(obj.model_dump(mode="json"))
|
|
25
|
+
|
|
26
|
+
if hasattr(obj, "dict") and callable(getattr(obj, "dict")):
|
|
27
|
+
return _to_jsonable(obj.dict())
|
|
28
|
+
|
|
29
|
+
return obj
|
|
30
|
+
|
|
31
|
+
|
|
9
32
|
@dataclass
|
|
10
33
|
class RPCRequest:
|
|
11
34
|
"""RPC 请求消息
|
|
@@ -52,7 +75,7 @@ class RPCRequest:
|
|
|
52
75
|
"type": self.type,
|
|
53
76
|
"request_id": self.request_id,
|
|
54
77
|
"method": self.method,
|
|
55
|
-
"params": self.params,
|
|
78
|
+
"params": _to_jsonable(self.params),
|
|
56
79
|
"reply_to": self.reply_to,
|
|
57
80
|
"caller_id": self.caller_id,
|
|
58
81
|
}
|
|
@@ -80,10 +103,35 @@ class RPCRequest:
|
|
|
80
103
|
)
|
|
81
104
|
except KeyError as e:
|
|
82
105
|
raise MessageError(
|
|
83
|
-
f"RPC 请求缺少必需字段: {e}",
|
|
84
|
-
ErrorCode.MISSING_REQUIRED_FIELD
|
|
106
|
+
f"RPC 请求缺少必需字段: {e}", ErrorCode.MISSING_REQUIRED_FIELD
|
|
85
107
|
)
|
|
86
108
|
|
|
109
|
+
def encode(self) -> bytes:
|
|
110
|
+
"""编码为 bytes(JSON 格式)
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
UTF-8 编码的 JSON bytes
|
|
114
|
+
"""
|
|
115
|
+
return json.dumps(self.to_dict()).encode("utf-8")
|
|
116
|
+
|
|
117
|
+
@classmethod
|
|
118
|
+
def decode(cls, data: bytes) -> "RPCRequest":
|
|
119
|
+
"""从 bytes 解码(JSON 格式)
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
data: UTF-8 编码的 JSON bytes
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
RPCRequest 对象
|
|
126
|
+
|
|
127
|
+
Raises:
|
|
128
|
+
MessageError: 解码失败或缺少必需字段
|
|
129
|
+
UnicodeDecodeError: UTF-8 解码失败
|
|
130
|
+
json.JSONDecodeError: JSON 解析失败
|
|
131
|
+
"""
|
|
132
|
+
obj = json.loads(data.decode("utf-8"))
|
|
133
|
+
return cls.from_dict(obj)
|
|
134
|
+
|
|
87
135
|
|
|
88
136
|
@dataclass
|
|
89
137
|
class RPCResponse:
|
|
@@ -133,7 +181,7 @@ class RPCResponse:
|
|
|
133
181
|
if self.error is not None:
|
|
134
182
|
data["error"] = self.error
|
|
135
183
|
else:
|
|
136
|
-
data["result"] = self.result
|
|
184
|
+
data["result"] = _to_jsonable(self.result)
|
|
137
185
|
|
|
138
186
|
return data
|
|
139
187
|
|
|
@@ -158,28 +206,53 @@ class RPCResponse:
|
|
|
158
206
|
)
|
|
159
207
|
except KeyError as e:
|
|
160
208
|
raise MessageError(
|
|
161
|
-
f"RPC 响应缺少必需字段: {e}",
|
|
162
|
-
ErrorCode.MISSING_REQUIRED_FIELD
|
|
209
|
+
f"RPC 响应缺少必需字段: {e}", ErrorCode.MISSING_REQUIRED_FIELD
|
|
163
210
|
)
|
|
164
211
|
|
|
212
|
+
def encode(self) -> bytes:
|
|
213
|
+
"""编码为 bytes(JSON 格式)
|
|
165
214
|
|
|
166
|
-
|
|
167
|
-
|
|
215
|
+
Returns:
|
|
216
|
+
UTF-8 编码的 JSON bytes
|
|
217
|
+
"""
|
|
218
|
+
return json.dumps(self.to_dict()).encode("utf-8")
|
|
219
|
+
|
|
220
|
+
@classmethod
|
|
221
|
+
def decode(cls, data: bytes) -> "RPCResponse":
|
|
222
|
+
"""从 bytes 解码(JSON 格式)
|
|
168
223
|
|
|
169
|
-
|
|
224
|
+
Args:
|
|
225
|
+
data: UTF-8 编码的 JSON bytes
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
RPCResponse 对象
|
|
229
|
+
|
|
230
|
+
Raises:
|
|
231
|
+
MessageError: 解码失败或缺少必需字段
|
|
232
|
+
UnicodeDecodeError: UTF-8 解码失败
|
|
233
|
+
json.JSONDecodeError: JSON 解析失败
|
|
234
|
+
"""
|
|
235
|
+
obj = json.loads(data.decode("utf-8"))
|
|
236
|
+
return cls.from_dict(obj)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def parse_message_from_bytes(data: bytes) -> RPCRequest | RPCResponse:
|
|
240
|
+
"""从 bytes 解析 RPC 消息(JSON 格式)
|
|
170
241
|
|
|
171
242
|
Args:
|
|
172
|
-
data: JSON
|
|
243
|
+
data: UTF-8 编码的 JSON bytes
|
|
173
244
|
|
|
174
245
|
Returns:
|
|
175
|
-
RPCRequest 或 RPCResponse
|
|
246
|
+
RPCRequest 或 RPCResponse 对象
|
|
176
247
|
|
|
177
248
|
Raises:
|
|
178
|
-
MessageError:
|
|
249
|
+
MessageError: 解码失败或消息类型无效
|
|
250
|
+
UnicodeDecodeError: UTF-8 解码失败
|
|
251
|
+
json.JSONDecodeError: JSON 解析失败
|
|
179
252
|
|
|
180
253
|
示例:
|
|
181
|
-
|
|
182
|
-
message =
|
|
254
|
+
payload = b'{"type":"rpc_request","request_id":"123",...}'
|
|
255
|
+
message = parse_message_from_bytes(payload)
|
|
183
256
|
|
|
184
257
|
if isinstance(message, RPCRequest):
|
|
185
258
|
# 处理请求
|
|
@@ -188,14 +261,12 @@ def parse_message(data: dict) -> RPCRequest | RPCResponse:
|
|
|
188
261
|
# 处理响应
|
|
189
262
|
pass
|
|
190
263
|
"""
|
|
191
|
-
|
|
264
|
+
obj = json.loads(data.decode("utf-8"))
|
|
265
|
+
msg_type = obj.get("type")
|
|
192
266
|
|
|
193
267
|
if msg_type == "rpc_request":
|
|
194
|
-
return RPCRequest.from_dict(
|
|
268
|
+
return RPCRequest.from_dict(obj)
|
|
195
269
|
elif msg_type == "rpc_response":
|
|
196
|
-
return RPCResponse.from_dict(
|
|
270
|
+
return RPCResponse.from_dict(obj)
|
|
197
271
|
else:
|
|
198
|
-
raise MessageError(
|
|
199
|
-
f"未知消息类型: {msg_type}",
|
|
200
|
-
ErrorCode.INVALID_MESSAGE_TYPE
|
|
201
|
-
)
|
|
272
|
+
raise MessageError(f"未知消息类型: {msg_type}", ErrorCode.INVALID_MESSAGE_TYPE)
|