ErisPulse 2.4.2.dev0__py3-none-any.whl → 2.4.2.dev1__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.
- ErisPulse/Core/adapter.py +141 -125
- ErisPulse/Core/adapter.pyi +11 -3
- ErisPulse/Core/config.py +89 -39
- ErisPulse/Core/config.pyi +17 -0
- ErisPulse/Core/module.py +30 -19
- ErisPulse/Core/module.pyi +18 -5
- ErisPulse/Core/router.py +8 -3
- ErisPulse/loaders/module.py +12 -4
- ErisPulse/sdk.py +85 -53
- ErisPulse/sdk.pyi +39 -8
- {erispulse-2.4.2.dev0.dist-info → erispulse-2.4.2.dev1.dist-info}/METADATA +1 -1
- {erispulse-2.4.2.dev0.dist-info → erispulse-2.4.2.dev1.dist-info}/RECORD +15 -15
- {erispulse-2.4.2.dev0.dist-info → erispulse-2.4.2.dev1.dist-info}/WHEEL +0 -0
- {erispulse-2.4.2.dev0.dist-info → erispulse-2.4.2.dev1.dist-info}/entry_points.txt +0 -0
- {erispulse-2.4.2.dev0.dist-info → erispulse-2.4.2.dev1.dist-info}/licenses/LICENSE +0 -0
ErisPulse/Core/adapter.py
CHANGED
|
@@ -51,6 +51,9 @@ class AdapterManager(ManagerBase):
|
|
|
51
51
|
# Bot状态存储 - {platform: {bot_id: {"status": str, "last_active": float, "info": dict}}}
|
|
52
52
|
self._bots: dict[str, dict[str, dict]] = {}
|
|
53
53
|
|
|
54
|
+
# 标记是否正在关闭,避免重复提交离线事件
|
|
55
|
+
self._is_being_shutdown = False
|
|
56
|
+
|
|
54
57
|
def set_sdk_ref(self, sdk) -> bool:
|
|
55
58
|
"""
|
|
56
59
|
设置 SDK 引用
|
|
@@ -287,127 +290,134 @@ class AdapterManager(ManagerBase):
|
|
|
287
290
|
>>> # 关闭多个适配器
|
|
288
291
|
>>> await adapter.shutdown(["Platform1", "Platform2"])
|
|
289
292
|
"""
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
if not isinstance(platforms, list):
|
|
293
|
-
platforms = [platforms]
|
|
294
|
-
for platform in platforms:
|
|
295
|
-
if platform not in self._adapters:
|
|
296
|
-
raise ValueError(f"平台 {platform} 未注册")
|
|
297
|
-
|
|
298
|
-
logger.info(f"关闭适配器 {platforms}")
|
|
299
|
-
|
|
300
|
-
# 提交适配器关闭开始事件
|
|
301
|
-
await lifecycle.submit_event(
|
|
302
|
-
"adapter.stop", msg="开始关闭适配器", data={"platforms": platforms}
|
|
303
|
-
)
|
|
304
|
-
|
|
305
|
-
from .router import router
|
|
306
|
-
|
|
307
|
-
# 需要收集受影响的 adapter 实例(因为多个平台可能共享同一个实例)
|
|
308
|
-
affected_adapters = set()
|
|
309
|
-
bots_to_offline = [] # [(platform, bot_id), ...]
|
|
293
|
+
# 设置关闭标志,避免重复提交离线事件
|
|
294
|
+
self._is_being_shutdown = True
|
|
310
295
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
if
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
adapter_instance = self._adapters[platform]
|
|
320
|
-
affected_adapters.add(adapter_instance)
|
|
321
|
-
|
|
322
|
-
# 收集该平台下需要标记为离线的 Bot
|
|
323
|
-
if platform in self._bots:
|
|
324
|
-
for bot_id, bot_info in self._bots[platform].items():
|
|
325
|
-
if bot_info.get("status") != "offline":
|
|
326
|
-
bots_to_offline.append((platform, bot_id))
|
|
296
|
+
try:
|
|
297
|
+
if platforms is None:
|
|
298
|
+
platforms = list(self._adapters.keys())
|
|
299
|
+
if not isinstance(platforms, list):
|
|
300
|
+
platforms = [platforms]
|
|
301
|
+
for platform in platforms:
|
|
302
|
+
if platform not in self._adapters:
|
|
303
|
+
raise ValueError(f"平台 {platform} 未注册")
|
|
327
304
|
|
|
328
|
-
|
|
329
|
-
for adapter_instance in affected_adapters:
|
|
330
|
-
if adapter_instance in self._started_instances:
|
|
331
|
-
# 找到该实例对应的平台名(用于事件提交)
|
|
332
|
-
instance_platforms = [
|
|
333
|
-
p for p, a in self._adapters.items() if a is adapter_instance
|
|
334
|
-
]
|
|
335
|
-
platform_label = (
|
|
336
|
-
instance_platforms[0]
|
|
337
|
-
if instance_platforms
|
|
338
|
-
else str(id(adapter_instance))
|
|
339
|
-
)
|
|
305
|
+
logger.info(f"关闭适配器 {platforms}")
|
|
340
306
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
"adapter.status.change",
|
|
346
|
-
msg=f"适配器 {p} 状态变化: stopping",
|
|
347
|
-
data={"platform": p, "status": "stopping"},
|
|
348
|
-
)
|
|
307
|
+
# 提交适配器关闭开始事件
|
|
308
|
+
await lifecycle.submit_event(
|
|
309
|
+
"adapter.stop", msg="开始关闭适配器", data={"platforms": platforms}
|
|
310
|
+
)
|
|
349
311
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
312
|
+
from .router import router
|
|
313
|
+
|
|
314
|
+
# 需要收集受影响的 adapter 实例(因为多个平台可能共享同一个实例)
|
|
315
|
+
affected_adapters = set()
|
|
316
|
+
bots_to_offline = [] # [(platform, bot_id), ...]
|
|
317
|
+
|
|
318
|
+
# 取消目标平台的后台启动任务
|
|
319
|
+
for platform in platforms:
|
|
320
|
+
task = self._adapter_tasks.pop(platform, None)
|
|
321
|
+
if task and not task.done():
|
|
322
|
+
task.cancel()
|
|
323
|
+
logger.debug(f"已取消平台 {platform} 的后台启动任务")
|
|
324
|
+
|
|
325
|
+
for platform in platforms:
|
|
326
|
+
adapter_instance = self._adapters[platform]
|
|
327
|
+
affected_adapters.add(adapter_instance)
|
|
328
|
+
|
|
329
|
+
# 收集该平台下需要标记为离线的 Bot
|
|
330
|
+
if platform in self._bots:
|
|
331
|
+
for bot_id, bot_info in self._bots[platform].items():
|
|
332
|
+
if bot_info.get("status") != "offline":
|
|
333
|
+
bots_to_offline.append((platform, bot_id))
|
|
334
|
+
|
|
335
|
+
# 对每个受影响的 adapter 实例执行 shutdown(如果尚未关闭)
|
|
336
|
+
for adapter_instance in affected_adapters:
|
|
337
|
+
if adapter_instance in self._started_instances:
|
|
338
|
+
# 找到该实例对应的平台名(用于事件提交)
|
|
339
|
+
instance_platforms = [
|
|
340
|
+
p for p, a in self._adapters.items() if a is adapter_instance
|
|
341
|
+
]
|
|
342
|
+
platform_label = (
|
|
343
|
+
instance_platforms[0]
|
|
344
|
+
if instance_platforms
|
|
345
|
+
else str(id(adapter_instance))
|
|
346
|
+
)
|
|
353
347
|
|
|
354
|
-
# 提交适配器状态变化事件(
|
|
348
|
+
# 提交适配器状态变化事件(stopping)
|
|
355
349
|
for p in instance_platforms:
|
|
356
350
|
if p in platforms:
|
|
357
351
|
await lifecycle.submit_event(
|
|
358
352
|
"adapter.status.change",
|
|
359
|
-
msg=f"适配器 {p} 状态变化:
|
|
360
|
-
data={"platform": p, "status": "
|
|
353
|
+
msg=f"适配器 {p} 状态变化: stopping",
|
|
354
|
+
data={"platform": p, "status": "stopping"},
|
|
361
355
|
)
|
|
362
|
-
except Exception as e:
|
|
363
|
-
logger.error(f"关闭适配器实例 {id(adapter_instance)} 失败: {e}")
|
|
364
356
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
"status"
|
|
374
|
-
"
|
|
375
|
-
|
|
376
|
-
|
|
357
|
+
try:
|
|
358
|
+
await adapter_instance.shutdown()
|
|
359
|
+
self._started_instances.remove(adapter_instance)
|
|
360
|
+
|
|
361
|
+
# 提交适配器状态变化事件(stopped)
|
|
362
|
+
for p in instance_platforms:
|
|
363
|
+
if p in platforms:
|
|
364
|
+
await lifecycle.submit_event(
|
|
365
|
+
"adapter.status.change",
|
|
366
|
+
msg=f"适配器 {p} 状态变化: stopped",
|
|
367
|
+
data={"platform": p, "status": "stopped"},
|
|
368
|
+
)
|
|
369
|
+
except Exception as e:
|
|
370
|
+
logger.error(f"关闭适配器实例 {id(adapter_instance)} 失败: {e}")
|
|
371
|
+
|
|
372
|
+
# 提交适配器状态变化事件(stop_failed)
|
|
373
|
+
for p in instance_platforms:
|
|
374
|
+
if p in platforms:
|
|
375
|
+
await lifecycle.submit_event(
|
|
376
|
+
"adapter.status.change",
|
|
377
|
+
msg=f"适配器 {p} 状态变化: stop_failed",
|
|
378
|
+
data={
|
|
379
|
+
"platform": p,
|
|
380
|
+
"status": "stop_failed",
|
|
381
|
+
"error": str(e),
|
|
382
|
+
},
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
# 清理被关闭平台的路由
|
|
386
|
+
for platform in platforms:
|
|
387
|
+
result = router.unregister_all_by_namespace(platform)
|
|
388
|
+
if result["http_count"] > 0 or result["websocket_count"] > 0:
|
|
389
|
+
logger.debug(
|
|
390
|
+
f"已清理平台 {platform} 的路由: HTTP={result['http_count']}, WebSocket={result['websocket_count']}"
|
|
391
|
+
)
|
|
377
392
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
if result["http_count"] > 0 or result["websocket_count"] > 0:
|
|
382
|
-
logger.debug(
|
|
383
|
-
f"已清理平台 {platform} 的路由: HTTP={result['http_count']}, WebSocket={result['websocket_count']}"
|
|
384
|
-
)
|
|
393
|
+
# 停止路由器(仅当所有适配器都关闭时)
|
|
394
|
+
if not self._started_instances:
|
|
395
|
+
await router.stop()
|
|
385
396
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
"adapter.bot.offline",
|
|
397
|
-
msg=f"Bot {platform}/{bot_id} 离线",
|
|
398
|
-
data={"platform": platform, "bot_id": bot_id, "status": "offline"},
|
|
399
|
-
)
|
|
397
|
+
# 将相关 Bot 标记为离线
|
|
398
|
+
for platform, bot_id in bots_to_offline:
|
|
399
|
+
if platform in self._bots and bot_id in self._bots[platform]:
|
|
400
|
+
self._bots[platform][bot_id]["status"] = "offline"
|
|
401
|
+
# 提交 Bot 离线事件
|
|
402
|
+
await lifecycle.submit_event(
|
|
403
|
+
"adapter.bot.offline",
|
|
404
|
+
msg=f"Bot {platform}/{bot_id} 离线",
|
|
405
|
+
data={"platform": platform, "bot_id": bot_id, "status": "offline"},
|
|
406
|
+
)
|
|
400
407
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
408
|
+
# 如果所有适配器都关闭了,清理事件处理器
|
|
409
|
+
if not self._started_instances:
|
|
410
|
+
self._onebot_handlers.clear()
|
|
411
|
+
self._raw_handlers.clear()
|
|
412
|
+
self._onebot_middlewares.clear()
|
|
406
413
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
414
|
+
# 提交适配器关闭完成事件
|
|
415
|
+
await lifecycle.submit_event(
|
|
416
|
+
"adapter.stopped", msg="适配器关闭完成", data={"platforms": platforms}
|
|
417
|
+
)
|
|
418
|
+
finally:
|
|
419
|
+
# 清除关闭标志
|
|
420
|
+
self._is_being_shutdown = False
|
|
411
421
|
|
|
412
422
|
def clear(self) -> None:
|
|
413
423
|
"""
|
|
@@ -473,12 +483,11 @@ class AdapterManager(ManagerBase):
|
|
|
473
483
|
|
|
474
484
|
def exists(self, platform: str) -> bool:
|
|
475
485
|
"""
|
|
476
|
-
|
|
486
|
+
检查平台是否已注册
|
|
477
487
|
|
|
478
488
|
:param platform: 平台名称
|
|
479
|
-
:return:
|
|
489
|
+
:return: 平台是否已注册(即 adapter.register() 已被调用)
|
|
480
490
|
"""
|
|
481
|
-
# 检查平台是否已注册(在 _adapters 中)
|
|
482
491
|
return platform in self._adapters
|
|
483
492
|
|
|
484
493
|
def is_enabled(self, platform: str) -> bool:
|
|
@@ -486,20 +495,25 @@ class AdapterManager(ManagerBase):
|
|
|
486
495
|
检查平台适配器是否启用
|
|
487
496
|
|
|
488
497
|
:param platform: 平台名称
|
|
489
|
-
:return:
|
|
498
|
+
:return: 平台适配器是否启用
|
|
499
|
+
|
|
500
|
+
{!--< tips >!--}
|
|
501
|
+
适配器启用条件:
|
|
502
|
+
1. 适配器在配置文件中(ErisPulse.adapters.status.{platform} 存在)
|
|
503
|
+
2. 配置值为启用状态
|
|
504
|
+
|
|
505
|
+
如果适配器未在配置中,返回 False
|
|
506
|
+
{!--< /tips >!--}
|
|
490
507
|
"""
|
|
491
|
-
|
|
508
|
+
from .config import parse_bool_config
|
|
509
|
+
|
|
492
510
|
status = config.getConfig(f"ErisPulse.adapters.status.{platform}")
|
|
493
511
|
|
|
494
|
-
#
|
|
512
|
+
# 适配器未在配置中,返回 False
|
|
495
513
|
if status is None:
|
|
496
|
-
return False
|
|
497
|
-
|
|
498
|
-
# 处理字符串形式的布尔值
|
|
499
|
-
if isinstance(status, str):
|
|
500
|
-
return status.lower() not in ("false", "0", "no", "off")
|
|
514
|
+
return False
|
|
501
515
|
|
|
502
|
-
return
|
|
516
|
+
return parse_bool_config(status)
|
|
503
517
|
|
|
504
518
|
def enable(self, platform: str) -> bool:
|
|
505
519
|
"""
|
|
@@ -541,7 +555,7 @@ class AdapterManager(ManagerBase):
|
|
|
541
555
|
:return: 是否取消成功
|
|
542
556
|
|
|
543
557
|
{!--< internal-use >!--}
|
|
544
|
-
|
|
558
|
+
注意: 此方法仅取消注册, 不关闭已启动的适配器
|
|
545
559
|
{!--< /internal-use >!--}
|
|
546
560
|
"""
|
|
547
561
|
if platform not in self._adapters:
|
|
@@ -837,13 +851,15 @@ class AdapterManager(ManagerBase):
|
|
|
837
851
|
)
|
|
838
852
|
|
|
839
853
|
if status == "offline":
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
854
|
+
# 只有在非主动关闭的情况下才提交事件(避免与 shutdown() 重复)
|
|
855
|
+
if not self._is_being_shutdown:
|
|
856
|
+
asyncio.ensure_future(
|
|
857
|
+
lifecycle.submit_event(
|
|
858
|
+
"adapter.bot.offline",
|
|
859
|
+
msg=f"Bot {platform}/{bot_id} 离线",
|
|
860
|
+
data={"platform": platform, "bot_id": bot_id, "status": "offline"},
|
|
861
|
+
)
|
|
845
862
|
)
|
|
846
|
-
)
|
|
847
863
|
|
|
848
864
|
def _update_bot_heartbeat(self, platform: str, self_info: dict) -> None:
|
|
849
865
|
"""
|
ErisPulse/Core/adapter.pyi
CHANGED
|
@@ -104,10 +104,10 @@ class AdapterManager(ManagerBase):
|
|
|
104
104
|
...
|
|
105
105
|
def exists(self: object, platform: str) -> bool:
|
|
106
106
|
"""
|
|
107
|
-
|
|
107
|
+
检查平台是否已注册
|
|
108
108
|
|
|
109
109
|
:param platform: 平台名称
|
|
110
|
-
:return:
|
|
110
|
+
:return: 平台是否已注册(即 adapter.register() 已被调用)
|
|
111
111
|
"""
|
|
112
112
|
...
|
|
113
113
|
def is_enabled(self: object, platform: str) -> bool:
|
|
@@ -115,7 +115,15 @@ class AdapterManager(ManagerBase):
|
|
|
115
115
|
检查平台适配器是否启用
|
|
116
116
|
|
|
117
117
|
:param platform: 平台名称
|
|
118
|
-
:return:
|
|
118
|
+
:return: 平台适配器是否启用
|
|
119
|
+
|
|
120
|
+
{!--< tips >!--}
|
|
121
|
+
适配器启用条件:
|
|
122
|
+
1. 适配器在配置文件中(ErisPulse.adapters.status.{platform} 存在)
|
|
123
|
+
2. 配置值为启用状态
|
|
124
|
+
|
|
125
|
+
如果适配器未在配置中,返回 False
|
|
126
|
+
{!--< /tips >!--}
|
|
119
127
|
"""
|
|
120
128
|
...
|
|
121
129
|
def enable(self: object, platform: str) -> bool:
|
ErisPulse/Core/config.py
CHANGED
|
@@ -24,8 +24,9 @@ class ConfigManager:
|
|
|
24
24
|
self._cache_timestamp = 0 # 缓存时间戳
|
|
25
25
|
self._cache_timeout = 60 # 缓存超时时间(秒)
|
|
26
26
|
self._write_delay = 5 # 写入延迟(秒)
|
|
27
|
-
self._write_timer = None # 写入定时器
|
|
27
|
+
self._write_timer: threading.Timer | None = None # 写入定时器
|
|
28
28
|
self._lock = threading.RLock() # 线程安全锁
|
|
29
|
+
self._file_lock = threading.RLock() # 文件操作锁,确保原子性
|
|
29
30
|
self._migrate_config() # 迁移旧配置文件
|
|
30
31
|
self._load_config() # 初始化时加载配置
|
|
31
32
|
|
|
@@ -138,56 +139,79 @@ class ConfigManager:
|
|
|
138
139
|
def _flush_config(self) -> None:
|
|
139
140
|
"""
|
|
140
141
|
将待写入的配置刷新到文件
|
|
142
|
+
|
|
143
|
+
使用文件锁确保多线程环境下的原子性操作
|
|
141
144
|
"""
|
|
142
145
|
with self._lock:
|
|
143
146
|
if not self._dirty_keys:
|
|
144
147
|
return # 没有需要写入的内容
|
|
145
148
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
149
|
+
with self._file_lock: # 确保文件操作原子性
|
|
150
|
+
try:
|
|
151
|
+
# 从文件读取完整配置
|
|
152
|
+
if os.path.exists(self.CONFIG_FILE):
|
|
153
|
+
with open(self.CONFIG_FILE, "r", encoding="utf-8") as f:
|
|
154
|
+
config = toml.load(f)
|
|
155
|
+
else:
|
|
156
|
+
config = {}
|
|
157
|
+
|
|
158
|
+
# 应用待写入的更改
|
|
159
|
+
for key, value in self._dirty_keys.items():
|
|
160
|
+
keys = key.split(".")
|
|
161
|
+
current = config
|
|
162
|
+
for k in keys[:-1]:
|
|
163
|
+
if k not in current:
|
|
164
|
+
current[k] = {}
|
|
165
|
+
current = current[k]
|
|
166
|
+
current[keys[-1]] = value
|
|
167
|
+
|
|
168
|
+
# 对配置字典进行排序,确保同一模块的配置项排列在一起
|
|
169
|
+
sorted_config = self._sort_config_dict(config)
|
|
170
|
+
|
|
171
|
+
# 写入临时文件,确保原子性
|
|
172
|
+
temp_file = self.CONFIG_FILE + ".tmp"
|
|
173
|
+
with open(temp_file, "w", encoding="utf-8") as f:
|
|
174
|
+
toml.dump(sorted_config, f)
|
|
175
|
+
|
|
176
|
+
# 原子性重命名(跨平台兼容)
|
|
177
|
+
if os.name == 'nt': # Windows
|
|
178
|
+
if os.path.exists(self.CONFIG_FILE):
|
|
179
|
+
os.replace(temp_file, self.CONFIG_FILE)
|
|
180
|
+
else:
|
|
181
|
+
os.rename(temp_file, self.CONFIG_FILE)
|
|
182
|
+
else: # Unix/Linux/macOS
|
|
183
|
+
os.rename(temp_file, self.CONFIG_FILE)
|
|
184
|
+
|
|
185
|
+
# 更新缓存并清除待写入队列
|
|
186
|
+
self._cache = sorted_config
|
|
187
|
+
self._cache_timestamp = time.time()
|
|
188
|
+
self._dirty_keys.clear()
|
|
175
189
|
|
|
176
|
-
|
|
177
|
-
|
|
190
|
+
except Exception as e:
|
|
191
|
+
from .logger import logger
|
|
178
192
|
|
|
179
|
-
|
|
193
|
+
logger.error(f"写入配置文件 {self.CONFIG_FILE} 失败: {e}")
|
|
194
|
+
# 清理临时文件
|
|
195
|
+
temp_file = self.CONFIG_FILE + ".tmp"
|
|
196
|
+
if os.path.exists(temp_file):
|
|
197
|
+
try:
|
|
198
|
+
os.remove(temp_file)
|
|
199
|
+
except Exception:
|
|
200
|
+
pass
|
|
180
201
|
|
|
181
202
|
def _schedule_write(self) -> None:
|
|
182
203
|
"""
|
|
183
204
|
安排延迟写入
|
|
205
|
+
|
|
206
|
+
线程安全:使用锁保护 Timer 的取消和创建
|
|
184
207
|
"""
|
|
185
|
-
|
|
186
|
-
self._write_timer
|
|
208
|
+
with self._lock:
|
|
209
|
+
if self._write_timer:
|
|
210
|
+
self._write_timer.cancel()
|
|
187
211
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
212
|
+
self._write_timer = threading.Timer(self._write_delay, self._flush_config)
|
|
213
|
+
self._write_timer.daemon = True
|
|
214
|
+
self._write_timer.start()
|
|
191
215
|
|
|
192
216
|
def _check_cache_validity(self) -> None:
|
|
193
217
|
"""
|
|
@@ -268,4 +292,30 @@ class ConfigManager:
|
|
|
268
292
|
|
|
269
293
|
config: ConfigManager = ConfigManager()
|
|
270
294
|
|
|
271
|
-
|
|
295
|
+
|
|
296
|
+
def parse_bool_config(value: Any) -> bool:
|
|
297
|
+
"""
|
|
298
|
+
解析配置中的布尔值
|
|
299
|
+
|
|
300
|
+
:param value: 配置值(可以是 bool, int, str 等)
|
|
301
|
+
:return: 解析后的布尔值
|
|
302
|
+
|
|
303
|
+
支持的值:
|
|
304
|
+
- True: True, 1, "true", "True", "1", "yes", "Yes", "on", "On"
|
|
305
|
+
- False: False, 0, "false", "False", "0", "no", "No", "off", "Off"
|
|
306
|
+
"""
|
|
307
|
+
if isinstance(value, bool):
|
|
308
|
+
return value
|
|
309
|
+
|
|
310
|
+
if isinstance(value, int):
|
|
311
|
+
return value != 0
|
|
312
|
+
|
|
313
|
+
if isinstance(value, str):
|
|
314
|
+
normalized = value.lower().strip()
|
|
315
|
+
return normalized in ("true", "1", "yes", "on")
|
|
316
|
+
|
|
317
|
+
# 其他类型尝试转换为布尔值
|
|
318
|
+
return bool(value)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
__all__ = ["config", "parse_bool_config"]
|
ErisPulse/Core/config.pyi
CHANGED
|
@@ -46,11 +46,15 @@ class ConfigManager:
|
|
|
46
46
|
def _flush_config(self: object) -> None:
|
|
47
47
|
"""
|
|
48
48
|
将待写入的配置刷新到文件
|
|
49
|
+
|
|
50
|
+
使用文件锁确保多线程环境下的原子性操作
|
|
49
51
|
"""
|
|
50
52
|
...
|
|
51
53
|
def _schedule_write(self: object) -> None:
|
|
52
54
|
"""
|
|
53
55
|
安排延迟写入
|
|
56
|
+
|
|
57
|
+
线程安全:使用锁保护 Timer 的取消和创建
|
|
54
58
|
"""
|
|
55
59
|
...
|
|
56
60
|
def _check_cache_validity(self: object) -> None:
|
|
@@ -87,3 +91,16 @@ class ConfigManager:
|
|
|
87
91
|
...
|
|
88
92
|
|
|
89
93
|
config: ConfigManager
|
|
94
|
+
|
|
95
|
+
def parse_bool_config(value: Any) -> bool:
|
|
96
|
+
"""
|
|
97
|
+
解析配置中的布尔值
|
|
98
|
+
|
|
99
|
+
:param value: 配置值(可以是 bool, int, str 等)
|
|
100
|
+
:return: 解析后的布尔值
|
|
101
|
+
|
|
102
|
+
支持的值:
|
|
103
|
+
- True: True, 1, "true", "True", "1", "yes", "Yes", "on", "On"
|
|
104
|
+
- False: False, 0, "false", "False", "0", "no", "No", "off", "Off"
|
|
105
|
+
"""
|
|
106
|
+
...
|
ErisPulse/Core/module.py
CHANGED
|
@@ -272,15 +272,17 @@ class ModuleManager(ManagerBase):
|
|
|
272
272
|
|
|
273
273
|
def exists(self, module_name: str) -> bool:
|
|
274
274
|
"""
|
|
275
|
-
|
|
275
|
+
检查模块是否已注册
|
|
276
276
|
|
|
277
|
-
:param module_name:
|
|
278
|
-
:return:
|
|
277
|
+
:param module_name: 模块名称
|
|
278
|
+
:return: 模块是否已注册(即 module.register() 已被调用)
|
|
279
|
+
|
|
280
|
+
{!--< tips >!--}
|
|
281
|
+
exists() 只检查模块类是否已注册到管理器,用于验证模块是否可以加载。
|
|
282
|
+
如需检查模块是否启用,请使用 is_enabled()。
|
|
283
|
+
{!--< /tips >!--}
|
|
279
284
|
"""
|
|
280
|
-
|
|
281
|
-
return True
|
|
282
|
-
module_statuses = config.getConfig("ErisPulse.modules.status", {})
|
|
283
|
-
return module_name in module_statuses
|
|
285
|
+
return module_name in self._module_classes
|
|
284
286
|
|
|
285
287
|
def is_loaded(self, module_name: str) -> bool:
|
|
286
288
|
"""
|
|
@@ -350,9 +352,9 @@ class ModuleManager(ManagerBase):
|
|
|
350
352
|
{!--< internal-use >!--}
|
|
351
353
|
此方法仅供内部使用
|
|
352
354
|
|
|
353
|
-
:param module_name:
|
|
354
|
-
:param enabled:
|
|
355
|
-
:return:
|
|
355
|
+
:param module_name: 模块名称
|
|
356
|
+
:param enabled: 是否启用模块 (默认: False)
|
|
357
|
+
:return: 操作是否成功
|
|
356
358
|
"""
|
|
357
359
|
if self.exists(module_name):
|
|
358
360
|
return True
|
|
@@ -367,18 +369,27 @@ class ModuleManager(ManagerBase):
|
|
|
367
369
|
"""
|
|
368
370
|
检查模块是否启用
|
|
369
371
|
|
|
370
|
-
:param module_name:
|
|
371
|
-
:return:
|
|
372
|
+
:param module_name: 模块名称
|
|
373
|
+
:return: 模块是否启用
|
|
374
|
+
|
|
375
|
+
{!--< tips >!--}
|
|
376
|
+
模块启用条件:
|
|
377
|
+
1. 模块在配置文件中(ErisPulse.modules.status.{module_name} 存在)
|
|
378
|
+
2. 配置值为启用状态
|
|
379
|
+
|
|
380
|
+
如果模块未在配置中,返回 False
|
|
381
|
+
{!--< /tips >!--}
|
|
372
382
|
"""
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
return False
|
|
383
|
+
from .config import parse_bool_config
|
|
384
|
+
|
|
385
|
+
status = config.getConfig(f"ErisPulse.modules.status.{module_name}")
|
|
377
386
|
|
|
378
|
-
|
|
379
|
-
|
|
387
|
+
# 模块未在配置中,返回 False
|
|
388
|
+
if status is None:
|
|
389
|
+
return False
|
|
380
390
|
|
|
381
|
-
|
|
391
|
+
# 解析配置值
|
|
392
|
+
return parse_bool_config(status)
|
|
382
393
|
|
|
383
394
|
def enable(self, module_name: str) -> bool:
|
|
384
395
|
"""
|
ErisPulse/Core/module.pyi
CHANGED
|
@@ -91,10 +91,15 @@ class ModuleManager(ManagerBase):
|
|
|
91
91
|
...
|
|
92
92
|
def exists(self: object, module_name: str) -> bool:
|
|
93
93
|
"""
|
|
94
|
-
|
|
94
|
+
检查模块是否已注册
|
|
95
95
|
|
|
96
|
-
:param module_name:
|
|
97
|
-
:return:
|
|
96
|
+
:param module_name: 模块名称
|
|
97
|
+
:return: 模块是否已注册(即 module.register() 已被调用)
|
|
98
|
+
|
|
99
|
+
{!--< tips >!--}
|
|
100
|
+
exists() 只检查模块类是否已注册到管理器,用于验证模块是否可以加载。
|
|
101
|
+
如需检查模块是否启用,请使用 is_enabled()。
|
|
102
|
+
{!--< /tips >!--}
|
|
98
103
|
"""
|
|
99
104
|
...
|
|
100
105
|
def is_loaded(self: object, module_name: str) -> bool:
|
|
@@ -155,8 +160,16 @@ class ModuleManager(ManagerBase):
|
|
|
155
160
|
"""
|
|
156
161
|
检查模块是否启用
|
|
157
162
|
|
|
158
|
-
:param module_name:
|
|
159
|
-
:return:
|
|
163
|
+
:param module_name: 模块名称
|
|
164
|
+
:return: 模块是否启用
|
|
165
|
+
|
|
166
|
+
{!--< tips >!--}
|
|
167
|
+
模块启用条件:
|
|
168
|
+
1. 模块在配置文件中(ErisPulse.modules.status.{module_name} 存在)
|
|
169
|
+
2. 配置值为启用状态
|
|
170
|
+
|
|
171
|
+
如果模块未在配置中,返回 False
|
|
172
|
+
{!--< /tips >!--}
|
|
160
173
|
"""
|
|
161
174
|
...
|
|
162
175
|
def enable(self: object, module_name: str) -> bool:
|
ErisPulse/Core/router.py
CHANGED
|
@@ -485,9 +485,14 @@ class RouterManager:
|
|
|
485
485
|
self._server_task.cancel()
|
|
486
486
|
try:
|
|
487
487
|
await asyncio.wait_for(self._server_task, timeout=5.0)
|
|
488
|
-
except
|
|
489
|
-
|
|
490
|
-
|
|
488
|
+
except asyncio.CancelledError:
|
|
489
|
+
logger.info("路由服务器已被取消")
|
|
490
|
+
except asyncio.TimeoutError:
|
|
491
|
+
logger.warning("路由服务器停止超时,强制终止")
|
|
492
|
+
except Exception as e:
|
|
493
|
+
logger.error(f"路由服务器停止时发生错误: {e}", exc_info=True)
|
|
494
|
+
finally:
|
|
495
|
+
self._server_task = None
|
|
491
496
|
|
|
492
497
|
# 清理所有注册的路由
|
|
493
498
|
logger.debug("清理所有注册的路由...")
|
ErisPulse/loaders/module.py
CHANGED
|
@@ -549,11 +549,15 @@ class LazyModule:
|
|
|
549
549
|
"""
|
|
550
550
|
确保模块已初始化
|
|
551
551
|
|
|
552
|
-
:raises RuntimeError: 当模块需要异步初始化时抛出
|
|
553
|
-
|
|
554
552
|
{!--< internal-use >!--}
|
|
555
553
|
内部方法,检查并确保模块已初始化
|
|
556
554
|
{!--< /internal-use >!--}
|
|
555
|
+
|
|
556
|
+
设计说明:
|
|
557
|
+
- 支持同步/异步透明的懒加载机制,用户无需感知差异
|
|
558
|
+
- BaseModule 在同步上下文中使用 asyncio.run() 确保初始化完成
|
|
559
|
+
- 非 BaseModule 保持原有逻辑,支持同步初始化
|
|
560
|
+
{!--< internal-use >!--}
|
|
557
561
|
"""
|
|
558
562
|
if not object.__getattribute__(self, "_initialized"):
|
|
559
563
|
try:
|
|
@@ -561,8 +565,12 @@ class LazyModule:
|
|
|
561
565
|
|
|
562
566
|
if object.__getattribute__(self, "_is_base_module"):
|
|
563
567
|
# BaseModule 必须通过 manager.load() 异步初始化
|
|
564
|
-
#
|
|
565
|
-
loop.create_task(
|
|
568
|
+
# 在同步上下文中,使用 asyncio.run() 确保初始化完成
|
|
569
|
+
# 在异步上下文中,使用 loop.create_task() 避免阻塞
|
|
570
|
+
if loop.is_running():
|
|
571
|
+
loop.create_task(self._initialize())
|
|
572
|
+
else:
|
|
573
|
+
asyncio.run(self._initialize())
|
|
566
574
|
return
|
|
567
575
|
|
|
568
576
|
init_method = getattr(
|
ErisPulse/sdk.py
CHANGED
|
@@ -144,8 +144,17 @@ class SDK:
|
|
|
144
144
|
return object.__getattribute__(self, name)
|
|
145
145
|
except AttributeError:
|
|
146
146
|
from .Core.logger import logger as _logger
|
|
147
|
-
|
|
148
|
-
|
|
147
|
+
|
|
148
|
+
# 区分不同场景,提供更准确的错误提示
|
|
149
|
+
if not name.startswith('_'):
|
|
150
|
+
if name in self.module._module_classes:
|
|
151
|
+
_logger.error(f"[SDK] 模块 '{name}' 已注册但未加载或未启用,请检查模块配置")
|
|
152
|
+
elif name in self.adapter._adapters:
|
|
153
|
+
_logger.error(f"[SDK] 适配器 '{name}' 已注册但未启用,请检查适配器配置")
|
|
154
|
+
else:
|
|
155
|
+
_logger.error(f"[SDK] 未找到属性或模块/适配器 '{name}',请检查名称是否正确")
|
|
156
|
+
|
|
157
|
+
raise AttributeError(f"ErisPulse SDK has no attribute '{name}'")
|
|
149
158
|
|
|
150
159
|
def __repr__(self) -> str:
|
|
151
160
|
"""
|
|
@@ -363,55 +372,37 @@ class SDK:
|
|
|
363
372
|
loaded_modules = module_manager.list_loaded()
|
|
364
373
|
if loaded_modules:
|
|
365
374
|
await module_manager.unload()
|
|
366
|
-
|
|
375
|
+
|
|
367
376
|
# 3. 收集 SDK 对象上的模块属性(在 clear 之前)
|
|
368
377
|
instance_dict = object.__getattribute__(self._sdk, '__dict__')
|
|
369
378
|
module_properties_to_clear = set()
|
|
370
|
-
|
|
379
|
+
|
|
371
380
|
# 收集已加载模块的属性名
|
|
372
381
|
for module_name in loaded_modules:
|
|
373
382
|
if module_name in instance_dict:
|
|
374
383
|
module_properties_to_clear.add(module_name)
|
|
375
|
-
|
|
376
|
-
#
|
|
384
|
+
|
|
385
|
+
# 处理已初始化的 LazyModule(已访问过,有实例)
|
|
377
386
|
for attr_name, attr_value in list(instance_dict.items()):
|
|
378
387
|
if attr_name.startswith('_'):
|
|
379
388
|
continue
|
|
380
389
|
from .loaders.module import LazyModule
|
|
381
390
|
if isinstance(attr_value, LazyModule):
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
lm_initialized = object.__getattribute__(attr_value, '_initialized')
|
|
390
|
-
lm_is_base = object.__getattribute__(attr_value, '_is_base_module')
|
|
391
|
-
|
|
392
|
-
if lm_is_base and not lm_initialized:
|
|
391
|
+
# 只处理已初始化的 LazyModule
|
|
392
|
+
lm_initialized = object.__getattribute__(attr_value, '_initialized')
|
|
393
|
+
if lm_initialized:
|
|
394
|
+
lm_name = object.__getattribute__(attr_value, '_module_name')
|
|
395
|
+
instance = object.__getattribute__(attr_value, '_instance')
|
|
396
|
+
if hasattr(instance, 'on_unload'):
|
|
397
|
+
try:
|
|
393
398
|
import inspect
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
tmp_instance = lm_class(lm_sdk) if params else lm_class()
|
|
402
|
-
if tmp_instance is None:
|
|
403
|
-
raise ValueError(f"模块 {lm_name} __init__ 返回 None")
|
|
404
|
-
if hasattr(tmp_instance, 'on_unload'):
|
|
405
|
-
if inspect.iscoroutinefunction(tmp_instance.on_unload):
|
|
406
|
-
await tmp_instance.on_unload({"module_name": lm_name})
|
|
407
|
-
else:
|
|
408
|
-
tmp_instance.on_unload({"module_name": lm_name})
|
|
409
|
-
except Exception as e:
|
|
410
|
-
logger.warning(f"清理未初始化懒加载模块 {lm_name} 的 on_unload 失败: {e}")
|
|
411
|
-
except Exception as e:
|
|
412
|
-
logger.warning(f"清理未初始化懒加载模块 {lm_name} 失败: {e}")
|
|
413
|
-
|
|
414
|
-
module_properties_to_clear.add(attr_name)
|
|
399
|
+
if inspect.iscoroutinefunction(instance.on_unload):
|
|
400
|
+
await instance.on_unload({"module_name": lm_name})
|
|
401
|
+
else:
|
|
402
|
+
instance.on_unload({"module_name": lm_name})
|
|
403
|
+
except Exception as e:
|
|
404
|
+
logger.warning(f"清理懒加载模块 {lm_name} 的 on_unload 失败: {e}")
|
|
405
|
+
module_properties_to_clear.add(attr_name)
|
|
415
406
|
|
|
416
407
|
# 4. 清理所有事件处理器
|
|
417
408
|
Event._clear_all_handlers()
|
|
@@ -460,10 +451,13 @@ class SDK:
|
|
|
460
451
|
"module_properties_to_clear": list(module_properties_to_clear),
|
|
461
452
|
},
|
|
462
453
|
)
|
|
463
|
-
|
|
464
|
-
#
|
|
454
|
+
|
|
455
|
+
# 等待一小段时间,确保事件处理完成
|
|
456
|
+
await asyncio.sleep(0.1)
|
|
457
|
+
|
|
458
|
+
# 9. 清理生命周期事件处理器(在所有事件完成之后)
|
|
465
459
|
lifecycle._handlers.clear()
|
|
466
|
-
|
|
460
|
+
|
|
467
461
|
logger.info(f"SDK反初始化成功 (耗时: {duration_str})")
|
|
468
462
|
return True
|
|
469
463
|
|
|
@@ -478,6 +472,13 @@ class SDK:
|
|
|
478
472
|
"error": str(e),
|
|
479
473
|
},
|
|
480
474
|
)
|
|
475
|
+
|
|
476
|
+
# 等待一小段时间,确保事件处理完成
|
|
477
|
+
await asyncio.sleep(0.1)
|
|
478
|
+
|
|
479
|
+
# 清理生命周期事件处理器(即使在失败时也要清理)
|
|
480
|
+
lifecycle._handlers.clear()
|
|
481
|
+
|
|
481
482
|
if "attached to a different loop" in str(e):
|
|
482
483
|
# 这是一个常见的错误,通常是由于SDK在另一个事件循环中运行而导致的。
|
|
483
484
|
# 在这种情况下,我们直接返回True即可
|
|
@@ -734,20 +735,51 @@ if __name__ == "__main__":
|
|
|
734
735
|
async def restart(self) -> bool:
|
|
735
736
|
"""
|
|
736
737
|
SDK 重新启动
|
|
737
|
-
|
|
738
|
-
执行完整的反初始化后再初始化过程,并重新启动适配器
|
|
739
|
-
|
|
740
|
-
{!--< tips >!--}
|
|
741
|
-
使用 asyncio.ensure_future 将重启任务注册到事件循环调度器,
|
|
742
|
-
与调用栈完全解耦,确保即使调用方被取消,重启流程也能完整执行。
|
|
743
738
|
|
|
744
|
-
|
|
739
|
+
执行完整的反初始化后再初始化过程,并重新启动适配器。
|
|
740
|
+
|
|
741
|
+
{!--< tips >!--}
|
|
742
|
+
**重要设计说明**:
|
|
743
|
+
|
|
744
|
+
此方法使用 `asyncio.ensure_future()` 将重启任务注册到事件循环调度器,
|
|
745
|
+
与调用栈完全解耦。这是有意为之的设计,原因如下:
|
|
746
|
+
|
|
747
|
+
1. **事件链路保护**:如果模块在事件处理器内部调用 `restart()`,而重启过程
|
|
748
|
+
是同步等待的,那么重启会中断当前事件链路,导致事件处理不完整。
|
|
749
|
+
|
|
750
|
+
2. **后台执行**:重启是一个耗时操作(需要关闭适配器、卸载模块、重新加载),
|
|
751
|
+
使用 `ensure_future` 可以让它在后台执行,不阻塞调用者。
|
|
752
|
+
|
|
753
|
+
3. **返回值语义**:方法立即返回 `True` 表示"重启任务已成功调度",
|
|
754
|
+
而不是"重启已完成"。实际的重启过程在后台进行。
|
|
755
|
+
|
|
756
|
+
**使用场景示例**:
|
|
757
|
+
|
|
758
|
+
>>> # 场景1: 在模块的事件处理器中调用重启
|
|
759
|
+
>>> @Event.on("message")
|
|
760
|
+
>>> async def handle_reload_command(event):
|
|
761
|
+
>>> if event["message"] == "/reload":
|
|
762
|
+
>>> # 使用 ensure_future 确保事件链路不被中断
|
|
763
|
+
>>> await sdk.restart() # ✅ 正确
|
|
764
|
+
>>> # 不要使用 await sdk.restart(),这会导致事件链路中断
|
|
765
|
+
>>>
|
|
766
|
+
>>> # 场景2: 等待重启完成
|
|
767
|
+
>>> # 如果需要等待重启完成,可以使用生命周期事件监听
|
|
768
|
+
>>> @lifecycle.on("core.init.complete")
|
|
769
|
+
>>> async def on_restart_complete(event):
|
|
770
|
+
>>> if event["data"]["success"]:
|
|
771
|
+
>>> logger.info("重启成功!")
|
|
772
|
+
>>>
|
|
773
|
+
>>> # 场景3: 命令触发重启
|
|
774
|
+
>>> @command("restart")
|
|
775
|
+
>>> async def restart_command():
|
|
776
|
+
>>> logger.info("正在重启 SDK...")
|
|
777
|
+
>>> await sdk.restart()
|
|
778
|
+
>>> logger.info("重启任务已调度,将在后台执行")
|
|
745
779
|
{!--< /tips >!--}
|
|
746
|
-
|
|
747
|
-
:return: bool
|
|
748
|
-
|
|
749
|
-
:raises RuntimeError: 当初始化失败时抛出
|
|
750
|
-
|
|
780
|
+
|
|
781
|
+
:return: bool 重启任务是否成功调度(并非重启是否完成)
|
|
782
|
+
|
|
751
783
|
:example:
|
|
752
784
|
>>> await sdk.restart()
|
|
753
785
|
"""
|
ErisPulse/sdk.pyi
CHANGED
|
@@ -141,18 +141,49 @@ class SDK:
|
|
|
141
141
|
"""
|
|
142
142
|
SDK 重新启动
|
|
143
143
|
|
|
144
|
-
|
|
144
|
+
执行完整的反初始化后再初始化过程,并重新启动适配器。
|
|
145
145
|
|
|
146
146
|
{!--< tips >!--}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
147
|
+
**重要设计说明**:
|
|
148
|
+
|
|
149
|
+
此方法使用 `asyncio.ensure_future()` 将重启任务注册到事件循环调度器,
|
|
150
|
+
与调用栈完全解耦。这是有意为之的设计,原因如下:
|
|
151
|
+
|
|
152
|
+
1. **事件链路保护**:如果模块在事件处理器内部调用 `restart()`,而重启过程
|
|
153
|
+
是同步等待的,那么重启会中断当前事件链路,导致事件处理不完整。
|
|
154
|
+
|
|
155
|
+
2. **后台执行**:重启是一个耗时操作(需要关闭适配器、卸载模块、重新加载),
|
|
156
|
+
使用 `ensure_future` 可以让它在后台执行,不阻塞调用者。
|
|
157
|
+
|
|
158
|
+
3. **返回值语义**:方法立即返回 `True` 表示"重启任务已成功调度",
|
|
159
|
+
而不是"重启已完成"。实际的重启过程在后台进行。
|
|
160
|
+
|
|
161
|
+
**使用场景示例**:
|
|
162
|
+
|
|
163
|
+
>>> # 场景1: 在模块的事件处理器中调用重启
|
|
164
|
+
>>> @Event.on("message")
|
|
165
|
+
>>> async def handle_reload_command(event):
|
|
166
|
+
>>> if event["message"] == "/reload":
|
|
167
|
+
>>> # 使用 ensure_future 确保事件链路不被中断
|
|
168
|
+
>>> await sdk.restart() # ✅ 正确
|
|
169
|
+
>>> # 不要使用 await sdk.restart(),这会导致事件链路中断
|
|
170
|
+
>>>
|
|
171
|
+
>>> # 场景2: 等待重启完成
|
|
172
|
+
>>> # 如果需要等待重启完成,可以使用生命周期事件监听
|
|
173
|
+
>>> @lifecycle.on("core.init.complete")
|
|
174
|
+
>>> async def on_restart_complete(event):
|
|
175
|
+
>>> if event["data"]["success"]:
|
|
176
|
+
>>> logger.info("重启成功!")
|
|
177
|
+
>>>
|
|
178
|
+
>>> # 场景3: 命令触发重启
|
|
179
|
+
>>> @command("restart")
|
|
180
|
+
>>> async def restart_command():
|
|
181
|
+
>>> logger.info("正在重启 SDK...")
|
|
182
|
+
>>> await sdk.restart()
|
|
183
|
+
>>> logger.info("重启任务已调度,将在后台执行")
|
|
151
184
|
{!--< /tips >!--}
|
|
152
185
|
|
|
153
|
-
:return: bool
|
|
154
|
-
|
|
155
|
-
:raises RuntimeError: 当初始化失败时抛出
|
|
186
|
+
:return: bool 重启任务是否成功调度(并非重启是否完成)
|
|
156
187
|
|
|
157
188
|
:example:
|
|
158
189
|
>>> await sdk.restart()
|
|
@@ -2,8 +2,8 @@ ErisPulse/__init__.py,sha256=rIEPa-80J1wlolAE5IavBEAkKh3S5EGpW4TTlbQmDEM,1504
|
|
|
2
2
|
ErisPulse/__init__.pyi,sha256=P5aOKFk9OttVVNQFqENMxGT4A4DneWa6BpVls4ktUMw,656
|
|
3
3
|
ErisPulse/__main__.py,sha256=7tGYe9fBPUWFYH0wZEnk5M2OhcQI6SvbUTo9HvXNsdk,359
|
|
4
4
|
ErisPulse/__main__.pyi,sha256=ShpCRAsyNREan4WzZUe5ImB5az453DokFzZrcnVM2LM,431
|
|
5
|
-
ErisPulse/sdk.py,sha256=
|
|
6
|
-
ErisPulse/sdk.pyi,sha256=
|
|
5
|
+
ErisPulse/sdk.py,sha256=7bYuAcC8FC-QcQiTSW6s3Yh4UYPTg4nvz2FTdza7dQ4,29419
|
|
6
|
+
ErisPulse/sdk.pyi,sha256=WT9Rbo9alhO4ztu1k2_X_RktwmE7UGHvVPozxuRrntA,6641
|
|
7
7
|
ErisPulse/CLI/__init__.py,sha256=LcGh1t4r_YBt0IPrTI9WdsOcWb3hIhGvLHaX4Tl0-QI,94
|
|
8
8
|
ErisPulse/CLI/__init__.pyi,sha256=MwfRkbzA9MSDQpVl5HYnLq-BN2snw1LfM2asW7-K5xg,192
|
|
9
9
|
ErisPulse/CLI/base.py,sha256=G3IXDGlsbILoEze0mJcjvvjWDcIxYsKgPJ-t8CwGgDk,1103
|
|
@@ -38,17 +38,17 @@ ErisPulse/CLI/utils/package_manager.py,sha256=Sc4UwP4Rbzye9g3lL05kzo5AsfJ_pHFQOl
|
|
|
38
38
|
ErisPulse/CLI/utils/package_manager.pyi,sha256=rinvIBxjR5gn3bhCEJ6K0iLyrCXYo2ttMmtJulDHXiM,8147
|
|
39
39
|
ErisPulse/Core/__init__.py,sha256=eFY-q8j8fiN9_ie-syvglcy_tPQ5qrIufPC2n7LZXpc,1958
|
|
40
40
|
ErisPulse/Core/__init__.pyi,sha256=8ADqzF23pS8uGZLpRu2dGOjhHsl7WaI0kLm0TvF3PXY,736
|
|
41
|
-
ErisPulse/Core/adapter.py,sha256=
|
|
42
|
-
ErisPulse/Core/adapter.pyi,sha256=
|
|
43
|
-
ErisPulse/Core/config.py,sha256=
|
|
44
|
-
ErisPulse/Core/config.pyi,sha256=
|
|
41
|
+
ErisPulse/Core/adapter.py,sha256=w4HmnBmhlM4E3hpTWFi7MXboISk8tVPWUwB7E82ofBU,42572
|
|
42
|
+
ErisPulse/Core/adapter.pyi,sha256=YQz5JXZ-76JFG3xUhacSWVUc11ChY7GYZoFnSnSrkPM,12876
|
|
43
|
+
ErisPulse/Core/config.py,sha256=ylv9F4sVhYOsI_x1bVbI0rLWw4K6bE08-JAjNMLQlws,10699
|
|
44
|
+
ErisPulse/Core/config.pyi,sha256=hMPispd42iVB-_0rJbtzo3I_wvOJ7VKdysjsB6XZOH0,3092
|
|
45
45
|
ErisPulse/Core/lifecycle.py,sha256=lJOGL2AI8UkEksX5tQ4hJRm3pT5_IBCVs5AyYUtNv-I,6444
|
|
46
46
|
ErisPulse/Core/lifecycle.pyi,sha256=nvsgCPy5179OW_J8__7cxX_OPBAJcGqKEC36sIvK0RU,2678
|
|
47
47
|
ErisPulse/Core/logger.py,sha256=rrof9Iaap9EwX56NfqKqvyKzErmlxlcl9EbgJ9uehRM,15522
|
|
48
48
|
ErisPulse/Core/logger.pyi,sha256=bobxpD0abXiwC8FD4RaPxGYBa7H2vEY7oNMwJdtAQAM,9117
|
|
49
|
-
ErisPulse/Core/module.py,sha256=
|
|
50
|
-
ErisPulse/Core/module.pyi,sha256=
|
|
51
|
-
ErisPulse/Core/router.py,sha256=
|
|
49
|
+
ErisPulse/Core/module.py,sha256=nSSB2-MUIFIYZpJXnl1oAKYvBFUw54NQVS50F14UM2U,18524
|
|
50
|
+
ErisPulse/Core/module.pyi,sha256=OxFR_y8hcLeMY5c4sRikri0zYd1wApG_1no_UtxhL_Q,7516
|
|
51
|
+
ErisPulse/Core/router.py,sha256=RW8bDVzzD_AJ8BQUeCSqTjODuqFH3AADH6QhIKzOtCw,18675
|
|
52
52
|
ErisPulse/Core/router.pyi,sha256=zU0Y8uRzU8t_us_zA6S0Yd1_0dqk1CV9V3YsjhuC07c,5316
|
|
53
53
|
ErisPulse/Core/storage.py,sha256=f2W_yJHONEwHTDYIHYdXiJZj-LDEIbnv8Was1JWyACA,17478
|
|
54
54
|
ErisPulse/Core/storage.pyi,sha256=1MHAVo-luTn8Bn3I2mMdHBFN6fbf9HXTxNgQnoFiAgE,6178
|
|
@@ -94,7 +94,7 @@ ErisPulse/loaders/__init__.py,sha256=LGNzCHnCRKDgIYtWjCbsk2hZ020TKner48dGCIlDQXM
|
|
|
94
94
|
ErisPulse/loaders/__init__.pyi,sha256=rwvQW2XJTRR5woGsoOyOFgQEdjEfwye6QZARj0Q8QbY,424
|
|
95
95
|
ErisPulse/loaders/adapter.py,sha256=Ai-78zXTdpE-xLBdgNngL8C5F3XpJ96QtbCY4YaiTBc,8336
|
|
96
96
|
ErisPulse/loaders/adapter.pyi,sha256=RBmlw1CG-g9FowLWC8QfHah18WOJMEIGfGRuhfAeAiE,3062
|
|
97
|
-
ErisPulse/loaders/module.py,sha256=
|
|
97
|
+
ErisPulse/loaders/module.py,sha256=npTZ4EH5rZNguaj9CRhmpK8mItGgZRzfa6sQJE1BX9E,28292
|
|
98
98
|
ErisPulse/loaders/module.pyi,sha256=UUxcQx7fa_-FgtE_WB1MtpvYWb9L0qWwFdxH2X6ozoQ,6020
|
|
99
99
|
ErisPulse/loaders/strategy.py,sha256=T7pPKlsc4RHs5dbg_JDU-PjIa0x4w7hYlN8ihH3rW8Y,3343
|
|
100
100
|
ErisPulse/loaders/strategy.pyi,sha256=jVEAgOeR1zNXdX60fHQcCQECgQDYP-J564mrkBBfFgQ,2409
|
|
@@ -110,8 +110,8 @@ ErisPulse/runtime/exceptions.py,sha256=ODQuetLnWUdq4qHgk4b-SxgGri-GrgYs9sALCB_qu
|
|
|
110
110
|
ErisPulse/runtime/exceptions.pyi,sha256=I9N3S4sNYMjdqVfUCOG6Jwq0nbMHh6biHh7BfY6Ts1o,1588
|
|
111
111
|
ErisPulse/runtime/frame_config.py,sha256=GN4-e72KU9SVIuXQCDFHZJR8HWXHb5nxzvtQk8ezOsc,5573
|
|
112
112
|
ErisPulse/runtime/frame_config.pyi,sha256=x_lrPZAfl9hHDs_9NUTom430SaDLcevpjIkomssiJ78,2011
|
|
113
|
-
erispulse-2.4.2.
|
|
114
|
-
erispulse-2.4.2.
|
|
115
|
-
erispulse-2.4.2.
|
|
116
|
-
erispulse-2.4.2.
|
|
117
|
-
erispulse-2.4.2.
|
|
113
|
+
erispulse-2.4.2.dev1.dist-info/METADATA,sha256=PDkmHxqAb45pGhwHbC9P8H0iWiSwb22WdMvyT4gHBkw,10625
|
|
114
|
+
erispulse-2.4.2.dev1.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
115
|
+
erispulse-2.4.2.dev1.dist-info/entry_points.txt,sha256=NiOwT6-XQ7KIH1r6J8odjRO-uaKHfr_Vz_UIG96EWXg,187
|
|
116
|
+
erispulse-2.4.2.dev1.dist-info/licenses/LICENSE,sha256=c2XbbDpZFu8YVuWV5mBZnGxE1IlYyelE1qW9TJADHV4,1071
|
|
117
|
+
erispulse-2.4.2.dev1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|