ErisPulse 2.7.0.dev5__tar.gz → 2.7.1.dev0__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.
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/PKG-INFO +1 -1
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/pyproject.toml +1 -1
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/CLI/commands/create.py +46 -13
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/Core/Bases/config_schema.py +52 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/Core/Event/wrapper.py +8 -7
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/Core/adapter.py +9 -23
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/Core/config.py +24 -14
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/Core/constants.py +13 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/Core/module.py +9 -21
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/runtime/__init__.py +6 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/runtime/frame_config.py +4 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/sdk.py +41 -11
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/.gitignore +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/LICENSE +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/README.pypi.md +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/CLI/__init__.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/CLI/base.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/CLI/cli.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/CLI/commands/__init__.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/CLI/commands/doctor.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/CLI/commands/init.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/CLI/commands/install.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/CLI/commands/language.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/CLI/commands/list.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/CLI/commands/list_remote.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/CLI/commands/run.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/CLI/commands/self_update.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/CLI/commands/types.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/CLI/commands/uninstall.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/CLI/commands/upgrade.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/CLI/console.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/CLI/constants.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/CLI/hints.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/CLI/i18n/__init__.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/CLI/i18n/locales/__init__.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/CLI/i18n/locales/en.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/CLI/i18n/locales/ja.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/CLI/i18n/locales/ru.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/CLI/i18n/locales/zh_cn.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/CLI/i18n/locales/zh_tw.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/CLI/registry.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/CLI/utils/__init__.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/CLI/utils/display.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/CLI/utils/file_watcher.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/CLI/utils/package_manager.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/CLI/utils/scaffold_text.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/Core/Bases/__init__.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/Core/Bases/adapter.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/Core/Bases/client.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/Core/Bases/converter.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/Core/Bases/errors.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/Core/Bases/i18n_schema.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/Core/Bases/kv_builder.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/Core/Bases/manager.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/Core/Bases/module.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/Core/Bases/router.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/Core/Bases/send_builder.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/Core/Bases/send_rules.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/Core/Bases/storage.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/Core/Bases/websocket.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/Core/Event/__init__.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/Core/Event/base.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/Core/Event/command.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/Core/Event/message.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/Core/Event/message_builder.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/Core/Event/meta.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/Core/Event/notice.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/Core/Event/request.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/Core/Event/session_type.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/Core/__init__.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/Core/assets/__init__.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/Core/assets/error.css +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/Core/assets/error.html +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/Core/assets/root.css +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/Core/assets/root.html +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/Core/client.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/Core/i18n/__init__.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/Core/i18n/constants.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/Core/i18n/locales/__init__.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/Core/i18n/locales/en.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/Core/i18n/locales/ja.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/Core/i18n/locales/ru.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/Core/i18n/locales/zh_cn.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/Core/i18n/locales/zh_tw.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/Core/lifecycle.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/Core/logger.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/Core/master.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/Core/router.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/Core/storage.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/__init__.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/__main__.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/finders/__init__.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/finders/adapter.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/finders/bases/__init__.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/finders/bases/finder.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/finders/module.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/loaders/__init__.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/loaders/adapter.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/loaders/bases/__init__.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/loaders/bases/loader.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/loaders/module.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/loaders/strategy.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/loaders/strict.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/py.typed +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/runtime/config_schema.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/runtime/context.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/runtime/diagnostics.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/runtime/exceptions.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/runtime/hints.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/runtime/memory.py +0 -0
- {erispulse-2.7.0.dev5 → erispulse-2.7.1.dev0}/src/ErisPulse/runtime/tasks.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ErisPulse
|
|
3
|
-
Version: 2.7.
|
|
3
|
+
Version: 2.7.1.dev0
|
|
4
4
|
Summary: Event-driven modular async bot framework — write once, deploy to any platform | 事件驱动的模块化异步机器人框架,一次编写,多平台部署
|
|
5
5
|
Project-URL: Homepage, https://github.com/ErisPulse/ErisPulse
|
|
6
6
|
Project-URL: Documentation, https://www.erisdev.com#docs
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ErisPulse"
|
|
7
|
-
version = "2.7.
|
|
7
|
+
version = "2.7.1-dev.0"
|
|
8
8
|
description = "Event-driven modular async bot framework — write once, deploy to any platform | 事件驱动的模块化异步机器人框架,一次编写,多平台部署"
|
|
9
9
|
readme = "README.pypi.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -462,33 +462,50 @@ class {converter_name}(BaseConverter):
|
|
|
462
462
|
return None
|
|
463
463
|
"""
|
|
464
464
|
|
|
465
|
-
_README_MODULE = """
|
|
465
|
+
_README_MODULE = """<div align="center">
|
|
466
466
|
|
|
467
|
-
{
|
|
467
|
+
<img src="https://raw.githubusercontent.com/ErisPulse/ErisPulse/main/.github/assets/ErisPulseLogo.png" width="180" alt="{name}" />
|
|
468
|
+
|
|
469
|
+
# {name}
|
|
470
|
+
|
|
471
|
+
**{description}**
|
|
472
|
+
|
|
473
|
+
<p>
|
|
474
|
+
<a href="https://pypi.org/project/ErisPulse-{name}/"><img src="https://img.shields.io/pypi/v/ErisPulse-{name}?style=for-the-badge&logo=pypi&logoColor=white" alt="PyPI"></a>
|
|
475
|
+
<a href="https://pypi.org/project/ErisPulse-{name}/"><img src="https://img.shields.io/badge/Python-3.10+-FFD43B?style=for-the-badge&logo=python&logoColor=blue" alt="Python"></a>
|
|
476
|
+
<a href="./LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue?style=for-the-badge" alt="License"></a>
|
|
477
|
+
<a href="https://github.com/ErisPulse/ErisPulse"><img src="https://img.shields.io/badge/Powered_by-ErisPulse-FF6B9D?style=for-the-badge&logo=bookstack&logoColor=white" alt="ErisPulse"></a>
|
|
478
|
+
</p>
|
|
479
|
+
|
|
480
|
+
</div>
|
|
481
|
+
|
|
482
|
+
---
|
|
468
483
|
|
|
469
484
|
## 安装
|
|
470
485
|
|
|
471
486
|
```bash
|
|
472
487
|
epsdk install ErisPulse-{name}
|
|
473
488
|
```
|
|
489
|
+
"""
|
|
474
490
|
|
|
475
|
-
|
|
491
|
+
_README_ADAPTER = """<div align="center">
|
|
476
492
|
|
|
477
|
-
|
|
493
|
+
<img src="https://raw.githubusercontent.com/ErisPulse/ErisPulse/main/.github/assets/ErisPulseLogo.png" width="180" alt="{name}" />
|
|
478
494
|
|
|
479
|
-
|
|
495
|
+
# {name}
|
|
480
496
|
|
|
481
|
-
|
|
497
|
+
**{description}**
|
|
482
498
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
"""
|
|
499
|
+
<p>
|
|
500
|
+
<a href="https://pypi.org/project/ErisPulse-{name}/"><img src="https://img.shields.io/pypi/v/ErisPulse-{name}?style=for-the-badge&logo=pypi&logoColor=white" alt="PyPI"></a>
|
|
501
|
+
<a href="https://pypi.org/project/ErisPulse-{name}/"><img src="https://img.shields.io/badge/Python-3.10+-FFD43B?style=for-the-badge&logo=python&logoColor=blue" alt="Python"></a>
|
|
502
|
+
<a href="./LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue?style=for-the-badge" alt="License"></a>
|
|
503
|
+
<a href="https://github.com/ErisPulse/ErisPulse"><img src="https://img.shields.io/badge/Powered_by-ErisPulse-FF6B9D?style=for-the-badge&logo=bookstack&logoColor=white" alt="ErisPulse"></a>
|
|
504
|
+
</p>
|
|
488
505
|
|
|
489
|
-
|
|
506
|
+
</div>
|
|
490
507
|
|
|
491
|
-
|
|
508
|
+
---
|
|
492
509
|
|
|
493
510
|
## 安装
|
|
494
511
|
|
|
@@ -561,6 +578,22 @@ def _validate_name(name: str) -> bool:
|
|
|
561
578
|
return all(c.isalnum() or c == "_" for c in name)
|
|
562
579
|
|
|
563
580
|
|
|
581
|
+
def _copy_erispulse_logo(project_dir: Path) -> None:
|
|
582
|
+
"""
|
|
583
|
+
将 ErisPulseLogo.png 拷贝到项目的 .github/assets/ 目录
|
|
584
|
+
|
|
585
|
+
:param project_dir: [Path] 项目根目录
|
|
586
|
+
"""
|
|
587
|
+
import shutil
|
|
588
|
+
|
|
589
|
+
logo_src = Path(__file__).parent.parent / "assets" / "ErisPulseLogo.png"
|
|
590
|
+
if not logo_src.exists():
|
|
591
|
+
return
|
|
592
|
+
dest = project_dir / ".github" / "assets" / "ErisPulseLogo.png"
|
|
593
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
594
|
+
shutil.copy2(logo_src, dest)
|
|
595
|
+
|
|
596
|
+
|
|
564
597
|
def _scaffold_text(name: str) -> dict:
|
|
565
598
|
"""
|
|
566
599
|
构建当前语言的脚手架文案映射,并预填充 {name} 占位符
|
|
@@ -424,6 +424,58 @@ def dict_to_dataclass(config_class: type, data: dict):
|
|
|
424
424
|
return config_class(**kwargs)
|
|
425
425
|
|
|
426
426
|
|
|
427
|
+
def _notify_instance_config_update(
|
|
428
|
+
instance: Any,
|
|
429
|
+
old_dict: dict | None,
|
|
430
|
+
new_dict: dict | None,
|
|
431
|
+
*,
|
|
432
|
+
i18n_key: str,
|
|
433
|
+
log_params: dict,
|
|
434
|
+
) -> None:
|
|
435
|
+
"""
|
|
436
|
+
调用实例的 ``on_config_update`` 回调,传入类型安全的配置对象
|
|
437
|
+
|
|
438
|
+
若实例声明了 ``ConfigClass``,则将字典通过 :func:`dict_to_dataclass`
|
|
439
|
+
转换为 dataclass 实例;否则原样传入字典。回调中抛出的异常会被捕获
|
|
440
|
+
并按指定的 i18n 键记录日志,不会向上传播。
|
|
441
|
+
|
|
442
|
+
供 ``ModuleManager`` 与 ``AdapterManager`` 的配置热更新路由共用,
|
|
443
|
+
避免在两处重复实现字典→dataclass 转换 + 异常兜底逻辑。
|
|
444
|
+
|
|
445
|
+
{!--< internal-use >!--}
|
|
446
|
+
{!--< /internal-use >!--}
|
|
447
|
+
|
|
448
|
+
:param instance: 模块/适配器实例(需实现 ``on_config_update``)
|
|
449
|
+
:param old_dict: 变更前的配置字典(可能为 None)
|
|
450
|
+
:param new_dict: 变更后的配置字典(可能为 None)
|
|
451
|
+
:param i18n_key: 回调异常日志的 i18n 键(如 ``core.module.config_update_failed``)
|
|
452
|
+
:param log_params: 异常日志的额外格式化参数(如 ``{"name": "MyModule"}``)
|
|
453
|
+
"""
|
|
454
|
+
config_class = getattr(instance, "ConfigClass", None)
|
|
455
|
+
try:
|
|
456
|
+
if config_class is not None:
|
|
457
|
+
old_config = (
|
|
458
|
+
dict_to_dataclass(config_class, old_dict) if old_dict else None
|
|
459
|
+
)
|
|
460
|
+
new_config = (
|
|
461
|
+
dict_to_dataclass(config_class, new_dict) if new_dict else None
|
|
462
|
+
)
|
|
463
|
+
else:
|
|
464
|
+
old_config = old_dict
|
|
465
|
+
new_config = new_dict
|
|
466
|
+
instance.on_config_update(old_config, new_config)
|
|
467
|
+
except Exception as e:
|
|
468
|
+
try:
|
|
469
|
+
from ErisPulse.Core.i18n import i18n
|
|
470
|
+
from ErisPulse.Core.logger import logger
|
|
471
|
+
|
|
472
|
+
params = dict(log_params)
|
|
473
|
+
params["error"] = e
|
|
474
|
+
logger.error(i18n.t(i18n_key, **params))
|
|
475
|
+
except Exception:
|
|
476
|
+
pass
|
|
477
|
+
|
|
478
|
+
|
|
427
479
|
def validate_config(instance) -> list[str]:
|
|
428
480
|
"""
|
|
429
481
|
校验 dataclass 实例
|
|
@@ -1576,8 +1576,8 @@ class Event(dict):
|
|
|
1576
1576
|
future.set_result(raw)
|
|
1577
1577
|
except asyncio.InvalidStateError:
|
|
1578
1578
|
pass
|
|
1579
|
-
except Exception:
|
|
1580
|
-
|
|
1579
|
+
except Exception as _e:
|
|
1580
|
+
logger.trace(f"[Event] onebot wait handler 异常: {_e}")
|
|
1581
1581
|
|
|
1582
1582
|
handler_wrapper = {"func": _temp_handler, "platform": None}
|
|
1583
1583
|
adapter._onebot_handlers[event_type].append(handler_wrapper)
|
|
@@ -1919,7 +1919,8 @@ class Conversation:
|
|
|
1919
1919
|
try:
|
|
1920
1920
|
if not cond(self.context):
|
|
1921
1921
|
continue
|
|
1922
|
-
except Exception:
|
|
1922
|
+
except Exception as _e:
|
|
1923
|
+
logger.trace(f"[Conversation] 字段 condition 执行异常,跳过: {_e}")
|
|
1923
1924
|
continue
|
|
1924
1925
|
filtered_fields.append(f)
|
|
1925
1926
|
|
|
@@ -2115,8 +2116,8 @@ class Conversation:
|
|
|
2115
2116
|
if event:
|
|
2116
2117
|
self._event = event
|
|
2117
2118
|
return True
|
|
2118
|
-
except Exception:
|
|
2119
|
-
|
|
2119
|
+
except Exception as _e:
|
|
2120
|
+
logger.trace(f"[Conversation] resume 失败: {_e}")
|
|
2120
2121
|
return False
|
|
2121
2122
|
|
|
2122
2123
|
async def clear_saved(self):
|
|
@@ -2133,8 +2134,8 @@ class Conversation:
|
|
|
2133
2134
|
platform = self._event.get_platform()
|
|
2134
2135
|
key = f"{CONVERSATION_KEY_PREFIX}:{platform}:{user_id}"
|
|
2135
2136
|
storage.delete(key)
|
|
2136
|
-
except Exception:
|
|
2137
|
-
|
|
2137
|
+
except Exception as _e:
|
|
2138
|
+
logger.trace(f"[Conversation] clear_saved 失败: {_e}")
|
|
2138
2139
|
|
|
2139
2140
|
|
|
2140
2141
|
__all__ = [
|
|
@@ -235,29 +235,15 @@ class AdapterManager(ManagerBase):
|
|
|
235
235
|
{!--< internal-use >!--}
|
|
236
236
|
调用适配器的 on_config_update 回调,传入类型安全的配置对象
|
|
237
237
|
"""
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
dict_to_dataclass(config_class, new_dict) if new_dict else None
|
|
248
|
-
)
|
|
249
|
-
else:
|
|
250
|
-
old_config = old_dict
|
|
251
|
-
new_config = new_dict
|
|
252
|
-
instance.on_config_update(old_config, new_config)
|
|
253
|
-
except Exception as e:
|
|
254
|
-
logger.error(
|
|
255
|
-
i18n.t(
|
|
256
|
-
"core.adapter.config_update_failed",
|
|
257
|
-
platform=platform or instance.__class__.__name__,
|
|
258
|
-
error=e,
|
|
259
|
-
)
|
|
260
|
-
)
|
|
238
|
+
from ..Core.Bases.config_schema import _notify_instance_config_update
|
|
239
|
+
|
|
240
|
+
_notify_instance_config_update(
|
|
241
|
+
instance,
|
|
242
|
+
old_dict,
|
|
243
|
+
new_dict,
|
|
244
|
+
i18n_key="core.adapter.config_update_failed",
|
|
245
|
+
log_params={"platform": platform or instance.__class__.__name__},
|
|
246
|
+
)
|
|
261
247
|
|
|
262
248
|
# ==================== 适配器注册与管理 ====================
|
|
263
249
|
|
|
@@ -50,6 +50,7 @@ class ConfigManager:
|
|
|
50
50
|
self._lock = threading.RLock() # 线程安全锁
|
|
51
51
|
self._file_lock = threading.RLock() # 文件操作锁
|
|
52
52
|
self._atexit_registered = False # atexit 钩子注册标记
|
|
53
|
+
self._last_self_write_mtime: float = 0.0 # 框架自身最后一次刷盘的 mtime
|
|
53
54
|
self._migrate_config() # 迁移旧配置文件
|
|
54
55
|
self._load_config() # 初始化时加载配置
|
|
55
56
|
self._watch_config_file() # 记录配置文件 mtime 以便后续监听
|
|
@@ -77,19 +78,21 @@ class ConfigManager:
|
|
|
77
78
|
if self._watcher_stop.is_set():
|
|
78
79
|
break
|
|
79
80
|
try:
|
|
80
|
-
|
|
81
|
-
#
|
|
82
|
-
#
|
|
83
|
-
if self.
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
81
|
+
with self._lock:
|
|
82
|
+
# _check_file_change 已能区分"框架自身刷盘"与"外部修改":
|
|
83
|
+
# 自身刷盘的 mtime 与 _last_self_write_mtime 一致 → 返回 False
|
|
84
|
+
if not self._check_file_change():
|
|
85
|
+
continue
|
|
86
|
+
# 真正的外部修改:重载文件到缓存。
|
|
87
|
+
# 不清空 _dirty_keys、不取消 _write_timer —— 待写键会在
|
|
88
|
+
# 下次 _flush_config 时与外部内容合并(脏键优先),
|
|
89
|
+
# 避免丢失本进程尚未落盘的写入。
|
|
90
|
+
old_cache = self._cache.copy() if self._cache else {}
|
|
91
|
+
loaded = self._load_config()
|
|
92
|
+
# 仅在成功加载时广播变更,半成品/语法错误的 TOML
|
|
93
|
+
# 不再以空配置形式触发 config.updated
|
|
94
|
+
if loaded:
|
|
95
|
+
self._emit_config_updated(old_cache)
|
|
93
96
|
except Exception as e:
|
|
94
97
|
# 监听异常不再静默吞掉,便于排查 watcher 故障
|
|
95
98
|
self._log_config_error(
|
|
@@ -365,6 +368,7 @@ class ConfigManager:
|
|
|
365
368
|
# 从而重复触发 config.updated(与 config.set 路由重复调用 on_config_update)
|
|
366
369
|
try:
|
|
367
370
|
self._config_mtime = Path(self.CONFIG_FILE).stat().st_mtime
|
|
371
|
+
self._last_self_write_mtime = self._config_mtime
|
|
368
372
|
except OSError:
|
|
369
373
|
pass
|
|
370
374
|
|
|
@@ -504,8 +508,10 @@ class ConfigManager:
|
|
|
504
508
|
检测配置文件是否被外部程序或用户手动编辑
|
|
505
509
|
|
|
506
510
|
对比记录的 mtime 与当前文件 mtime,若不一致说明文件已被外部修改。
|
|
511
|
+
若变化后的 mtime 与框架自身最后一次刷盘的 mtime(``_last_self_write_mtime``)
|
|
512
|
+
一致,则判定为框架自身的写入而非外部修改,返回 False。
|
|
507
513
|
|
|
508
|
-
:return: bool
|
|
514
|
+
:return: bool 文件是否被外部修改
|
|
509
515
|
|
|
510
516
|
{!--< internal-use >!--}
|
|
511
517
|
{!--< /internal-use >!--}
|
|
@@ -517,6 +523,10 @@ class ConfigManager:
|
|
|
517
523
|
current_mtime = config_path.stat().st_mtime
|
|
518
524
|
if current_mtime != self._config_mtime:
|
|
519
525
|
self._config_mtime = current_mtime
|
|
526
|
+
# 若 mtime 与框架自身最后一次刷盘一致,说明是 _flush_config 的
|
|
527
|
+
# 写入,不算外部修改(避免 watcher 误触发重载)
|
|
528
|
+
if current_mtime == self._last_self_write_mtime:
|
|
529
|
+
return False
|
|
520
530
|
return True
|
|
521
531
|
except OSError:
|
|
522
532
|
pass
|
|
@@ -633,6 +633,17 @@ DEFAULT_OFFLINE_BOT_EXPIRY_SECS: Final[int] = 3600
|
|
|
633
633
|
# 修改影响: 设大减少 CPU 中断但内存峰值更高,设小更频繁回收但增加开销。0 表示禁用。
|
|
634
634
|
DEFAULT_PROACTIVE_GC_INTERVAL_SECS: Final[int] = 300
|
|
635
635
|
|
|
636
|
+
# 主动 GC 分代(generation)。
|
|
637
|
+
# 运行时行为。gc.collect(generation) 只回收指定代及更年轻的对象:0=最年轻代(最轻量),
|
|
638
|
+
# 1=中年代,2=最老代(覆盖最全)。默认 2 兼顾覆盖面与频率可控。
|
|
639
|
+
# 修改影响: 设低减少单次扫描开销但回收不彻底,设高(2)覆盖更全但单次更重。
|
|
640
|
+
DEFAULT_PROACTIVE_GC_GENERATION: Final[int] = 2
|
|
641
|
+
|
|
642
|
+
# 主动 GC 全量收集周期(每 N 轮做一次全量 gc.collect())。
|
|
643
|
+
# 运行时行为。0 表示禁用(仅按 generation 回收),N>0 表示每 N 轮额外触发一次全量回收。
|
|
644
|
+
# 修改影响: 设低(如 5)确保周期性深度回收,设高或 0 减少长尾暂停。
|
|
645
|
+
DEFAULT_PROACTIVE_GC_FULL_EVERY: Final[int] = 0
|
|
646
|
+
|
|
636
647
|
# shutdown 时等待在途事件处理器完成的最长时间(秒)。
|
|
637
648
|
# 运行时行为。适配器关闭时取消所有 pending handler tasks 后等待它们退出的耐心时间。
|
|
638
649
|
# 修改影响: 设大确保处理器完整结束,设小加速关闭流程。
|
|
@@ -743,6 +754,8 @@ __all__ = [
|
|
|
743
754
|
"DEFAULT_MODULE_ENABLED",
|
|
744
755
|
"DEFAULT_MODULE_PRIORITY",
|
|
745
756
|
"DEFAULT_OFFLINE_BOT_EXPIRY_SECS",
|
|
757
|
+
"DEFAULT_PROACTIVE_GC_FULL_EVERY",
|
|
758
|
+
"DEFAULT_PROACTIVE_GC_GENERATION",
|
|
746
759
|
"DEFAULT_PROACTIVE_GC_INTERVAL_SECS",
|
|
747
760
|
"DEFAULT_RATE_LIMIT_CLEANUP_INTERVAL_SECS",
|
|
748
761
|
"DEFAULT_RATE_LIMIT_MAX_REQUESTS",
|
|
@@ -184,27 +184,15 @@ class ModuleManager(ManagerBase):
|
|
|
184
184
|
:param old_dict: 变更前的配置字典(可能为 None)
|
|
185
185
|
:param new_dict: 变更后的配置字典(可能为 None)
|
|
186
186
|
"""
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
dict_to_dataclass(config_class, new_dict) if new_dict else None
|
|
197
|
-
)
|
|
198
|
-
else:
|
|
199
|
-
old_config = old_dict
|
|
200
|
-
new_config = new_dict
|
|
201
|
-
instance.on_config_update(old_config, new_config)
|
|
202
|
-
except Exception as e:
|
|
203
|
-
logger.error(
|
|
204
|
-
i18n.t(
|
|
205
|
-
"core.module.config_update_failed", name=module_name, error=e
|
|
206
|
-
)
|
|
207
|
-
)
|
|
187
|
+
from ..Core.Bases.config_schema import _notify_instance_config_update
|
|
188
|
+
|
|
189
|
+
_notify_instance_config_update(
|
|
190
|
+
instance,
|
|
191
|
+
old_dict,
|
|
192
|
+
new_dict,
|
|
193
|
+
i18n_key="core.module.config_update_failed",
|
|
194
|
+
log_params={"name": module_name},
|
|
195
|
+
)
|
|
208
196
|
|
|
209
197
|
# ==================== 模块注册与管理 ====================
|
|
210
198
|
|
|
@@ -51,6 +51,8 @@ __all__ = [
|
|
|
51
51
|
# 通用配置 Schema(适配器/模块/外部项目均可使用)
|
|
52
52
|
"BaseConfig",
|
|
53
53
|
"BotAccountConfig",
|
|
54
|
+
"I18nConfig",
|
|
55
|
+
"SECRET_REDACTED",
|
|
54
56
|
# 异常处理
|
|
55
57
|
"ExceptionHandler",
|
|
56
58
|
"async_exception_handler",
|
|
@@ -78,6 +80,7 @@ __all__ = [
|
|
|
78
80
|
# 异常诊断
|
|
79
81
|
"log_diagnostic",
|
|
80
82
|
"log_snapshot",
|
|
83
|
+
"redact_secret",
|
|
81
84
|
"register_config_i18n",
|
|
82
85
|
"resolve_config_schema",
|
|
83
86
|
"setup_exception_handling",
|
|
@@ -99,10 +102,13 @@ _LAZY_FROM_CONFIG_SCHEMA = {
|
|
|
99
102
|
"AdapterConfig",
|
|
100
103
|
"BaseConfig",
|
|
101
104
|
"BotAccountConfig",
|
|
105
|
+
"I18nConfig",
|
|
106
|
+
"SECRET_REDACTED",
|
|
102
107
|
"dataclass_to_defaults_dict",
|
|
103
108
|
"dataclass_to_toml_with_comments",
|
|
104
109
|
"dict_to_dataclass",
|
|
105
110
|
"get_config_schema",
|
|
111
|
+
"redact_secret",
|
|
106
112
|
"register_config_i18n",
|
|
107
113
|
"resolve_config_schema",
|
|
108
114
|
"validate_config",
|
|
@@ -21,6 +21,8 @@ from ..Core.constants import (
|
|
|
21
21
|
DEFAULT_LOG_MEMORY_LIMIT,
|
|
22
22
|
DEFAULT_MESSAGE_IGNORE_SELF,
|
|
23
23
|
DEFAULT_OFFLINE_BOT_EXPIRY_SECS,
|
|
24
|
+
DEFAULT_PROACTIVE_GC_FULL_EVERY,
|
|
25
|
+
DEFAULT_PROACTIVE_GC_GENERATION,
|
|
24
26
|
DEFAULT_PROACTIVE_GC_INTERVAL_SECS,
|
|
25
27
|
DEFAULT_SERVER_AUTO_START,
|
|
26
28
|
DEFAULT_SERVER_HOST,
|
|
@@ -79,6 +81,8 @@ DEFAULT_ERISPULSE_CONFIG = {
|
|
|
79
81
|
# 性能优化与主动 GC 配置
|
|
80
82
|
"handler_max_concurrency": DEFAULT_HANDLER_MAX_CONCURRENCY,
|
|
81
83
|
"proactive_gc_interval": DEFAULT_PROACTIVE_GC_INTERVAL_SECS,
|
|
84
|
+
"proactive_gc_generation": DEFAULT_PROACTIVE_GC_GENERATION,
|
|
85
|
+
"proactive_gc_full_every": DEFAULT_PROACTIVE_GC_FULL_EVERY,
|
|
82
86
|
"offline_bot_expiry": DEFAULT_OFFLINE_BOT_EXPIRY_SECS,
|
|
83
87
|
},
|
|
84
88
|
"i18n": {
|
|
@@ -23,6 +23,8 @@ from collections.abc import Callable
|
|
|
23
23
|
from typing import TYPE_CHECKING, Any
|
|
24
24
|
|
|
25
25
|
from .Core.constants import (
|
|
26
|
+
DEFAULT_PROACTIVE_GC_FULL_EVERY,
|
|
27
|
+
DEFAULT_PROACTIVE_GC_GENERATION,
|
|
26
28
|
DEFAULT_PROACTIVE_GC_INTERVAL_SECS,
|
|
27
29
|
DEFAULT_UNINIT_TIMEOUT_SECS,
|
|
28
30
|
HARD_RESTART_EXIT_CODE,
|
|
@@ -569,6 +571,16 @@ class SDK:
|
|
|
569
571
|
},
|
|
570
572
|
)
|
|
571
573
|
|
|
574
|
+
# 冻结框架对象:将当前所有已跟踪对象移入永久代,
|
|
575
|
+
# 后续 gc.collect() 不再扫描框架单例/缓存/模块/适配器实例,
|
|
576
|
+
# 仅回收运行期新建的临时对象,降低 GC 暂停与误回收风险。
|
|
577
|
+
import gc as _gc
|
|
578
|
+
|
|
579
|
+
try:
|
|
580
|
+
_gc.freeze()
|
|
581
|
+
except Exception:
|
|
582
|
+
pass
|
|
583
|
+
|
|
572
584
|
# 启动主动 GC 后台任务
|
|
573
585
|
self._sdk._start_proactive_gc()
|
|
574
586
|
|
|
@@ -834,39 +846,57 @@ class SDK:
|
|
|
834
846
|
启动主动 GC 后台任务
|
|
835
847
|
|
|
836
848
|
定期执行 Python GC 和内部资源回收(离线 Bot 清理等),
|
|
837
|
-
|
|
849
|
+
防止长期运行时的内存增长。
|
|
850
|
+
|
|
851
|
+
GC 行为由三项框架配置控制(均支持热更新):
|
|
852
|
+
|
|
853
|
+
- ``proactive_gc_interval``: 回收间隔秒数(0 禁用)
|
|
854
|
+
- ``proactive_gc_generation``: 回收分代(0/1/2,默认 2=最老代)
|
|
855
|
+
- ``proactive_gc_full_every``: 每 N 轮额外做一次全量回收(0 禁用)
|
|
856
|
+
|
|
857
|
+
初始化阶段已调用 ``gc.freeze()`` 将框架对象移入永久代,
|
|
858
|
+
此处 ``gc.collect()`` 仅扫描运行期新建对象。
|
|
838
859
|
"""
|
|
839
860
|
# 停止已有的 GC 任务
|
|
840
861
|
self._stop_proactive_gc()
|
|
841
862
|
|
|
842
|
-
def
|
|
863
|
+
def _read_gc_config() -> tuple[int, int, int]:
|
|
843
864
|
interval = DEFAULT_PROACTIVE_GC_INTERVAL_SECS
|
|
865
|
+
generation = DEFAULT_PROACTIVE_GC_GENERATION
|
|
866
|
+
full_every = DEFAULT_PROACTIVE_GC_FULL_EVERY
|
|
844
867
|
try:
|
|
845
868
|
from .runtime import get_framework_config
|
|
846
869
|
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
)
|
|
870
|
+
fw = get_framework_config()
|
|
871
|
+
interval = fw.get("proactive_gc_interval", interval)
|
|
872
|
+
generation = fw.get("proactive_gc_generation", generation)
|
|
873
|
+
full_every = fw.get("proactive_gc_full_every", full_every)
|
|
850
874
|
except Exception:
|
|
851
875
|
pass
|
|
852
|
-
return interval
|
|
876
|
+
return interval, generation, full_every
|
|
853
877
|
|
|
854
|
-
if
|
|
878
|
+
if _read_gc_config()[0] <= 0:
|
|
855
879
|
return # 配置禁用
|
|
856
880
|
|
|
857
881
|
async def _gc_loop():
|
|
858
882
|
import gc
|
|
859
883
|
|
|
884
|
+
round_count = 0
|
|
860
885
|
while True:
|
|
861
886
|
try:
|
|
862
|
-
#
|
|
863
|
-
interval =
|
|
887
|
+
# 每轮重新读取配置,支持热更新
|
|
888
|
+
interval, generation, full_every = _read_gc_config()
|
|
864
889
|
if interval <= 0:
|
|
865
890
|
# 配置运行时禁用了 GC,停止循环
|
|
866
891
|
break
|
|
867
892
|
await asyncio.sleep(interval)
|
|
868
|
-
|
|
869
|
-
|
|
893
|
+
round_count += 1
|
|
894
|
+
# 1. Python GC —— 按分代回收,可选周期性全量
|
|
895
|
+
do_full = full_every > 0 and round_count % full_every == 0
|
|
896
|
+
if do_full:
|
|
897
|
+
collected = gc.collect()
|
|
898
|
+
else:
|
|
899
|
+
collected = gc.collect(generation)
|
|
870
900
|
# 2. 内部资源回收
|
|
871
901
|
try:
|
|
872
902
|
adapter_mgr = self.adapter
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|