mqttxx 2.0.2__py3-none-any.whl → 3.1.2__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/rpc.py CHANGED
@@ -1,9 +1,8 @@
1
1
  # MQTT RPC 模块 - 基于 aiomqtt 的双向对等 RPC 调用
2
2
 
3
3
  import asyncio
4
- import json
5
4
  import uuid
6
- from typing import Any, Callable, Optional
5
+ from typing import Any, Callable, Optional, Type
7
6
  from loguru import logger
8
7
 
9
8
  from .client import MQTTClient
@@ -14,8 +13,9 @@ from .exceptions import (
14
13
  TooManyConcurrentCallsError,
15
14
  MQTTXError,
16
15
  ErrorCode,
16
+ MessageError,
17
17
  )
18
- from .protocol import RPCRequest, RPCResponse
18
+ from .protocol import RPCRequest, RPCResponse, parse_message_from_bytes, Codec, JSONCodec
19
19
 
20
20
 
21
21
  # 权限检查回调类型
@@ -75,7 +75,8 @@ class RPCManager:
75
75
  self,
76
76
  client: MQTTClient,
77
77
  config: Optional[RPCConfig] = None,
78
- auth_callback: Optional[AuthCallback] = None
78
+ auth_callback: Optional[AuthCallback] = None,
79
+ codec: Type[Codec] = JSONCodec, # 新增:可插拔编解码器
79
80
  ):
80
81
  """初始化 RPC 管理器
81
82
 
@@ -85,6 +86,7 @@ class RPCManager:
85
86
  auth_callback: 权限检查回调函数(可选)
86
87
  签名:async def auth_callback(caller_id: str, method: str, request: RPCRequest) -> bool
87
88
  返回:True = 允许,False = 拒绝
89
+ codec: 编解码器(默认 JSONCodec)
88
90
 
89
91
  使用示例:
90
92
  client = MQTTClient(...)
@@ -92,6 +94,7 @@ class RPCManager:
92
94
 
93
95
  # 基础用法(无权限控制)
94
96
  rpc = RPCManager(client)
97
+ rpc.setup("my/rpc/responses") # 设置响应主题
95
98
 
96
99
  # 带权限控制
97
100
  async def auth_check(caller_id, method, request):
@@ -102,14 +105,51 @@ class RPCManager:
102
105
  self._client = client
103
106
  self.config = config or RPCConfig()
104
107
  self._auth_callback = auth_callback
108
+ self._codec = codec # 保存编解码器
105
109
 
106
110
  # RPC 状态
107
111
  self._pending_calls: dict[str, asyncio.Future] = {} # request_id → Future
108
112
  self._handlers: dict[str, Callable] = {} # method_name → handler
109
- self._pending_calls_lock = asyncio.Lock() # 修复 P0-1:保护 _pending_calls 并发访问
113
+ self._pending_calls_lock = (
114
+ asyncio.Lock()
115
+ ) # 修复 P0-1:保护 _pending_calls 并发访问
110
116
 
111
117
  logger.info("RPCManager 已初始化")
112
118
 
119
+ def setup(self, reply_topic: str):
120
+ """设置 RPC 响应主题并自动订阅
121
+
122
+ 这个方法会:
123
+ 1. 订阅 reply_topic(接收 RPC 响应)
124
+ 2. 注册消息处理器(自动解码 + 分发)
125
+
126
+ Args:
127
+ reply_topic: 响应主题(例如:server/rpc_responses)
128
+
129
+ 示例:
130
+ rpc = RPCManager(client)
131
+ rpc.setup("my/rpc/responses")
132
+ """
133
+ async def handle_bytes(topic: str, payload: bytes):
134
+ """bytes → RPC message → handle"""
135
+ try:
136
+ # 解码
137
+ message = parse_message_from_bytes(payload, self._codec)
138
+
139
+ # 路由
140
+ if isinstance(message, RPCRequest):
141
+ await self._handle_request(topic, message)
142
+ elif isinstance(message, RPCResponse):
143
+ await self._handle_response(topic, message)
144
+ except MessageError as e:
145
+ logger.debug(f"非 RPC 消息 - topic: {topic}, reason: {e}")
146
+ except Exception as e:
147
+ logger.exception(f"RPC 消息处理失败: {e}")
148
+
149
+ # 订阅 raw bytes
150
+ self._client.raw.subscribe(reply_topic, handle_bytes)
151
+
152
+
113
153
  def register(self, method_name: str):
114
154
  """装饰器:注册本地 RPC 方法供远程调用
115
155
 
@@ -129,9 +169,10 @@ class RPCManager:
129
169
  # 同步方法也支持
130
170
  return {"result": "ok"}
131
171
  """
172
+
132
173
  def decorator(func: Callable):
133
174
  self._handlers[method_name] = func
134
- logger.success(f"RPC 方法已注册: {method_name}")
175
+ # logger.debug(f"RPC 方法已注册: {method_name}") 无需输出
135
176
  return func
136
177
 
137
178
  return decorator
@@ -171,12 +212,12 @@ class RPCManager:
171
212
  if isinstance(message, RPCRequest):
172
213
  asyncio.create_task(
173
214
  self._handle_request(topic, message),
174
- name=f"rpc_req_{message.request_id[:8]}"
215
+ name=f"rpc_req_{message.request_id[:8]}",
175
216
  )
176
217
  elif isinstance(message, RPCResponse):
177
218
  asyncio.create_task(
178
219
  self._handle_response(topic, message),
179
- name=f"rpc_resp_{message.request_id[:8]}"
220
+ name=f"rpc_resp_{message.request_id[:8]}",
180
221
  )
181
222
 
182
223
  async def call(
@@ -254,8 +295,9 @@ class RPCManager:
254
295
  future = asyncio.get_event_loop().create_future()
255
296
  self._pending_calls[request_id] = future
256
297
 
257
- # 发送请求
258
- await self._client.publish(topic, json.dumps(request.to_dict()), qos=1)
298
+ # 发送请求(使用编解码器)
299
+ payload = request.encode(self._codec)
300
+ await self._client.raw.publish(topic, payload, qos=1)
259
301
  logger.debug(f"RPC 请求已发送 - method: {method}, request_id: {request_id[:8]}")
260
302
 
261
303
  try:
@@ -316,8 +358,7 @@ class RPCManager:
316
358
  f"RPC 权限拒绝 - caller: {message.caller_id}, method: {message.method}"
317
359
  )
318
360
  response = RPCResponse(
319
- request_id=message.request_id,
320
- error="Permission denied"
361
+ request_id=message.request_id, error="Permission denied"
321
362
  )
322
363
  await self._send_response(message.reply_to, response)
323
364
  return
@@ -328,8 +369,7 @@ class RPCManager:
328
369
  if not handler:
329
370
  logger.warning(f"方法未找到 - method: {message.method}")
330
371
  response = RPCResponse(
331
- request_id=message.request_id,
332
- error=f"方法未找到: {message.method}"
372
+ request_id=message.request_id, error=f"方法未找到: {message.method}"
333
373
  )
334
374
  else:
335
375
  # 执行方法
@@ -353,22 +393,20 @@ class RPCManager:
353
393
  try:
354
394
  if asyncio.iscoroutinefunction(self._auth_callback):
355
395
  allowed = await self._auth_callback(
356
- message.caller_id,
357
- message.method,
358
- message
396
+ message.caller_id, message.method, message
359
397
  )
360
398
  else:
361
399
  allowed = self._auth_callback(
362
- message.caller_id,
363
- message.method,
364
- message
400
+ message.caller_id, message.method, message
365
401
  )
366
402
  return bool(allowed)
367
403
  except Exception as e:
368
404
  logger.exception(f"权限检查失败: {e}")
369
405
  return False # 默认拒绝
370
406
 
371
- async def _execute_handler(self, handler: Callable, message: RPCRequest) -> RPCResponse:
407
+ async def _execute_handler(
408
+ self, handler: Callable, message: RPCRequest
409
+ ) -> RPCResponse:
372
410
  """执行 RPC 方法处理器
373
411
 
374
412
  Args:
@@ -390,10 +428,7 @@ class RPCManager:
390
428
  result = handler(message.params)
391
429
 
392
430
  logger.debug(f"RPC 方法执行成功 - method: {message.method}")
393
- return RPCResponse(
394
- request_id=message.request_id,
395
- result=result
396
- )
431
+ return RPCResponse(request_id=message.request_id, result=result)
397
432
 
398
433
  except asyncio.CancelledError:
399
434
  # 任务被取消,向上传播
@@ -401,10 +436,7 @@ class RPCManager:
401
436
 
402
437
  except Exception as e:
403
438
  logger.exception(f"RPC 方法执行失败 - method: {message.method}")
404
- return RPCResponse(
405
- request_id=message.request_id,
406
- error=str(e)
407
- )
439
+ return RPCResponse(request_id=message.request_id, error=str(e))
408
440
 
409
441
  async def _send_response(self, topic: str, response: RPCResponse):
410
442
  """发送 RPC 响应
@@ -413,5 +445,6 @@ class RPCManager:
413
445
  topic: 响应主题
414
446
  response: RPC 响应消息
415
447
  """
416
- await self._client.publish(topic, json.dumps(response.to_dict()), qos=1)
448
+ payload = response.encode(self._codec)
449
+ await self._client.raw.publish(topic, payload, qos=1)
417
450
  logger.debug(f"RPC 响应已发送 - request_id: {response.request_id[:8]}")