ErisPulse 2.1.13rc3__py3-none-any.whl → 2.1.14.dev2__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/__init__.py +8 -6
- ErisPulse/Core/adapter.py +18 -15
- ErisPulse/Core/config.py +0 -98
- ErisPulse/Core/env.py +1 -32
- ErisPulse/Core/erispulse_config.py +105 -0
- ErisPulse/Core/exceptions.py +108 -0
- ErisPulse/Core/logger.py +1 -1
- ErisPulse/Core/{server.py → router.py} +51 -70
- ErisPulse/__init__.py +21 -9
- ErisPulse/__main__.py +32 -21
- {erispulse-2.1.13rc3.dist-info → erispulse-2.1.14.dev2.dist-info}/METADATA +1 -1
- erispulse-2.1.14.dev2.dist-info/RECORD +16 -0
- ErisPulse/Core/raiserr.py +0 -181
- ErisPulse/Core/util.py +0 -123
- erispulse-2.1.13rc3.dist-info/RECORD +0 -16
- {erispulse-2.1.13rc3.dist-info → erispulse-2.1.14.dev2.dist-info}/WHEEL +0 -0
- {erispulse-2.1.13rc3.dist-info → erispulse-2.1.14.dev2.dist-info}/entry_points.txt +0 -0
- {erispulse-2.1.13rc3.dist-info → erispulse-2.1.14.dev2.dist-info}/licenses/LICENSE +0 -0
ErisPulse/Core/__init__.py
CHANGED
|
@@ -2,9 +2,10 @@ from .adapter import AdapterFather, SendDSL, adapter
|
|
|
2
2
|
from .env import env
|
|
3
3
|
from .logger import logger
|
|
4
4
|
from .mods import mods
|
|
5
|
-
from .
|
|
6
|
-
from .
|
|
7
|
-
from .
|
|
5
|
+
from .router import router, adapter_server
|
|
6
|
+
from .config import config
|
|
7
|
+
from . import exceptions
|
|
8
|
+
|
|
8
9
|
BaseAdapter = AdapterFather
|
|
9
10
|
|
|
10
11
|
__all__ = [
|
|
@@ -15,7 +16,8 @@ __all__ = [
|
|
|
15
16
|
'env',
|
|
16
17
|
'logger',
|
|
17
18
|
'mods',
|
|
18
|
-
'
|
|
19
|
-
'
|
|
20
|
-
'adapter_server'
|
|
19
|
+
'exceptions',
|
|
20
|
+
'router',
|
|
21
|
+
'adapter_server',
|
|
22
|
+
'config'
|
|
21
23
|
]
|
ErisPulse/Core/adapter.py
CHANGED
|
@@ -13,14 +13,12 @@ ErisPulse 适配器系统
|
|
|
13
13
|
|
|
14
14
|
import functools
|
|
15
15
|
import asyncio
|
|
16
|
-
import uuid
|
|
17
|
-
import time
|
|
18
16
|
from typing import (
|
|
19
17
|
Callable, Any, Dict, List, Type, Optional, Set,
|
|
20
|
-
Union, Awaitable
|
|
18
|
+
Union, Awaitable
|
|
21
19
|
)
|
|
22
20
|
from collections import defaultdict
|
|
23
|
-
|
|
21
|
+
from .logger import logger
|
|
24
22
|
|
|
25
23
|
class SendDSLBase:
|
|
26
24
|
"""
|
|
@@ -114,7 +112,6 @@ class BaseAdapter:
|
|
|
114
112
|
:example:
|
|
115
113
|
>>> await adapter.Send.To("123").Example("Hello")
|
|
116
114
|
"""
|
|
117
|
-
from .logger import logger
|
|
118
115
|
logger.debug(f"适配器 {self._adapter.__class__.__name__} 发送了实例类型的消息: {text}")
|
|
119
116
|
|
|
120
117
|
|
|
@@ -378,7 +375,7 @@ class AdapterManager:
|
|
|
378
375
|
for name in set(combinations):
|
|
379
376
|
setattr(self, name, instance)
|
|
380
377
|
else:
|
|
381
|
-
|
|
378
|
+
logger.warning(f"平台名 {platform} 过长,如果您是开发者,请考虑使用更短的名称")
|
|
382
379
|
setattr(self, platform.lower(), instance)
|
|
383
380
|
setattr(self, platform.upper(), instance)
|
|
384
381
|
setattr(self, platform.capitalize(), instance)
|
|
@@ -401,10 +398,18 @@ class AdapterManager:
|
|
|
401
398
|
"""
|
|
402
399
|
if platforms is None:
|
|
403
400
|
platforms = list(self._adapters.keys())
|
|
401
|
+
if not isinstance(platforms, list):
|
|
402
|
+
platforms = [platforms]
|
|
403
|
+
for platform in platforms:
|
|
404
|
+
if platform not in self._adapters:
|
|
405
|
+
raise ValueError(f"平台 {platform} 未注册")
|
|
406
|
+
|
|
407
|
+
logger.info(f"启动适配器 {platforms}")
|
|
404
408
|
|
|
405
|
-
from .
|
|
406
|
-
from .
|
|
409
|
+
from .router import adapter_server
|
|
410
|
+
from .erispulse_config import get_server_config
|
|
407
411
|
server_config = get_server_config()
|
|
412
|
+
|
|
408
413
|
host = server_config["host"]
|
|
409
414
|
port = server_config["port"]
|
|
410
415
|
ssl_cert = server_config.get("ssl_certfile", None)
|
|
@@ -441,16 +446,14 @@ class AdapterManager:
|
|
|
441
446
|
:param adapter: 适配器实例
|
|
442
447
|
:param platform: 平台名称
|
|
443
448
|
"""
|
|
444
|
-
from .. import sdk
|
|
445
449
|
|
|
446
|
-
# 加锁防止并发启动
|
|
447
450
|
if not getattr(adapter, "_starting_lock", None):
|
|
448
451
|
adapter._starting_lock = asyncio.Lock()
|
|
449
452
|
|
|
450
453
|
async with adapter._starting_lock:
|
|
451
454
|
# 再次确认是否已经被启动
|
|
452
455
|
if adapter in self._started_instances:
|
|
453
|
-
|
|
456
|
+
logger.info(f"适配器 {platform}(实例ID: {id(adapter)})已被其他协程启动,跳过")
|
|
454
457
|
return
|
|
455
458
|
|
|
456
459
|
retry_count = 0
|
|
@@ -464,12 +467,12 @@ class AdapterManager:
|
|
|
464
467
|
return
|
|
465
468
|
except Exception as e:
|
|
466
469
|
retry_count += 1
|
|
467
|
-
|
|
470
|
+
logger.error(f"平台 {platform} 启动失败(第{retry_count}次重试): {e}")
|
|
468
471
|
|
|
469
472
|
try:
|
|
470
473
|
await adapter.shutdown()
|
|
471
474
|
except Exception as stop_err:
|
|
472
|
-
|
|
475
|
+
logger.warning(f"停止适配器失败: {stop_err}")
|
|
473
476
|
|
|
474
477
|
# 计算等待时间
|
|
475
478
|
if retry_count <= len(backoff_intervals):
|
|
@@ -477,7 +480,7 @@ class AdapterManager:
|
|
|
477
480
|
else:
|
|
478
481
|
wait_time = fixed_delay
|
|
479
482
|
|
|
480
|
-
|
|
483
|
+
logger.info(f"将在 {wait_time // 60} 分钟后再次尝试重启 {platform}")
|
|
481
484
|
await asyncio.sleep(wait_time)
|
|
482
485
|
|
|
483
486
|
async def shutdown(self) -> None:
|
|
@@ -490,7 +493,7 @@ class AdapterManager:
|
|
|
490
493
|
for adapter in self._adapters.values():
|
|
491
494
|
await adapter.shutdown()
|
|
492
495
|
|
|
493
|
-
from .
|
|
496
|
+
from .router import adapter_server
|
|
494
497
|
await adapter_server.stop()
|
|
495
498
|
|
|
496
499
|
def get(self, platform: str) -> Optional[BaseAdapter]:
|
ErisPulse/Core/config.py
CHANGED
|
@@ -72,101 +72,3 @@ class ConfigManager:
|
|
|
72
72
|
return False
|
|
73
73
|
|
|
74
74
|
config = ConfigManager()
|
|
75
|
-
|
|
76
|
-
# 默认配置
|
|
77
|
-
DEFAULT_CONFIG = {
|
|
78
|
-
"server": {
|
|
79
|
-
"host": "0.0.0.0",
|
|
80
|
-
"port": 8000,
|
|
81
|
-
"ssl_certfile": None,
|
|
82
|
-
"ssl_keyfile": None
|
|
83
|
-
},
|
|
84
|
-
"logger": {
|
|
85
|
-
"level": "INFO",
|
|
86
|
-
"log_files": [],
|
|
87
|
-
"memory_limit": 1000
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
def _ensure_config_structure(config: Dict[str, Any]) -> Dict[str, Any]:
|
|
92
|
-
"""
|
|
93
|
-
确保配置结构完整,补全缺失的配置项
|
|
94
|
-
|
|
95
|
-
:param config: 当前配置
|
|
96
|
-
:return: 补全后的完整配置
|
|
97
|
-
"""
|
|
98
|
-
merged_config = DEFAULT_CONFIG.copy()
|
|
99
|
-
|
|
100
|
-
# 深度合并配置
|
|
101
|
-
for section, default_values in DEFAULT_CONFIG.items():
|
|
102
|
-
if section not in config:
|
|
103
|
-
config[section] = default_values.copy()
|
|
104
|
-
continue
|
|
105
|
-
|
|
106
|
-
if not isinstance(config[section], dict):
|
|
107
|
-
config[section] = default_values.copy()
|
|
108
|
-
continue
|
|
109
|
-
|
|
110
|
-
for key, default_value in default_values.items():
|
|
111
|
-
if key not in config[section]:
|
|
112
|
-
config[section][key] = default_value
|
|
113
|
-
|
|
114
|
-
return config
|
|
115
|
-
|
|
116
|
-
def get_config() -> Dict[str, Any]:
|
|
117
|
-
"""
|
|
118
|
-
获取当前配置,自动补全缺失的配置项并保存
|
|
119
|
-
|
|
120
|
-
:return: 完整的配置字典
|
|
121
|
-
"""
|
|
122
|
-
|
|
123
|
-
# 获取现有配置
|
|
124
|
-
current_config = config.getConfig("ErisPulse")
|
|
125
|
-
|
|
126
|
-
# 如果完全没有配置,设置默认配置
|
|
127
|
-
if current_config is None:
|
|
128
|
-
config.setConfig("ErisPulse", DEFAULT_CONFIG)
|
|
129
|
-
return DEFAULT_CONFIG
|
|
130
|
-
|
|
131
|
-
# 检查并补全缺失的配置项
|
|
132
|
-
complete_config = _ensure_config_structure(current_config)
|
|
133
|
-
|
|
134
|
-
# 如果配置有变化,更新到存储
|
|
135
|
-
if current_config != complete_config:
|
|
136
|
-
config.setConfig("ErisPulse", complete_config)
|
|
137
|
-
|
|
138
|
-
return complete_config
|
|
139
|
-
|
|
140
|
-
def update_config(new_config: Dict[str, Any]) -> bool:
|
|
141
|
-
"""
|
|
142
|
-
更新配置,自动补全缺失的配置项
|
|
143
|
-
|
|
144
|
-
:param new_config: 新的配置字典
|
|
145
|
-
:return: 是否更新成功
|
|
146
|
-
"""
|
|
147
|
-
# 获取当前配置并合并新配置
|
|
148
|
-
current = get_config()
|
|
149
|
-
merged = {**current, **new_config}
|
|
150
|
-
|
|
151
|
-
# 确保合并后的配置结构完整
|
|
152
|
-
complete_config = _ensure_config_structure(merged)
|
|
153
|
-
|
|
154
|
-
return config.setConfig("ErisPulse", complete_config)
|
|
155
|
-
|
|
156
|
-
def get_server_config() -> Dict[str, Any]:
|
|
157
|
-
"""
|
|
158
|
-
获取服务器配置,确保结构完整
|
|
159
|
-
|
|
160
|
-
:return: 服务器配置字典
|
|
161
|
-
"""
|
|
162
|
-
config = get_config()
|
|
163
|
-
return config["server"]
|
|
164
|
-
|
|
165
|
-
def get_logger_config() -> Dict[str, Any]:
|
|
166
|
-
"""
|
|
167
|
-
获取日志配置,确保结构完整
|
|
168
|
-
|
|
169
|
-
:return: 日志配置字典
|
|
170
|
-
"""
|
|
171
|
-
config = get_config()
|
|
172
|
-
return config["logger"]
|
ErisPulse/Core/env.py
CHANGED
|
@@ -13,14 +13,10 @@ ErisPulse 环境配置模块
|
|
|
13
13
|
import os
|
|
14
14
|
import json
|
|
15
15
|
import sqlite3
|
|
16
|
-
import importlib.util
|
|
17
16
|
import shutil
|
|
18
17
|
import time
|
|
19
|
-
import toml
|
|
20
|
-
from pathlib import Path
|
|
21
18
|
from datetime import datetime
|
|
22
|
-
from
|
|
23
|
-
from typing import List, Dict, Optional, Any, Set, Tuple, Union, Type, FrozenSet
|
|
19
|
+
from typing import List, Dict, Optional, Any, Tuple, Type
|
|
24
20
|
|
|
25
21
|
class EnvManager:
|
|
26
22
|
"""
|
|
@@ -39,7 +35,6 @@ class EnvManager:
|
|
|
39
35
|
db_path = os.path.join(os.path.dirname(__file__), "../data/config.db")
|
|
40
36
|
SNAPSHOT_DIR = os.path.join(os.path.dirname(__file__), "../data/snapshots")
|
|
41
37
|
|
|
42
|
-
CONFIG_FILE = "config.toml"
|
|
43
38
|
|
|
44
39
|
def __new__(cls, *args, **kwargs):
|
|
45
40
|
if not cls._instance:
|
|
@@ -192,8 +187,6 @@ class EnvManager:
|
|
|
192
187
|
from .config import config
|
|
193
188
|
return config.getConfig(key, default)
|
|
194
189
|
except Exception as e:
|
|
195
|
-
from . import logger
|
|
196
|
-
logger.error(f"读取配置文件 {self.CONFIG_FILE} 失败: {e}")
|
|
197
190
|
return default
|
|
198
191
|
|
|
199
192
|
def setConfig(self, key: str, value: Any) -> bool:
|
|
@@ -207,8 +200,6 @@ class EnvManager:
|
|
|
207
200
|
from .config import config
|
|
208
201
|
return config.setConfig(key, value)
|
|
209
202
|
except Exception as e:
|
|
210
|
-
from . import logger
|
|
211
|
-
logger.error(f"写入配置文件 {self.CONFIG_FILE} 失败: {e}")
|
|
212
203
|
return False
|
|
213
204
|
|
|
214
205
|
def delete(self, key: str) -> bool:
|
|
@@ -385,29 +376,7 @@ class EnvManager:
|
|
|
385
376
|
return True
|
|
386
377
|
except Exception as e:
|
|
387
378
|
return False
|
|
388
|
-
|
|
389
|
-
def load_env_file(self) -> bool:
|
|
390
|
-
"""
|
|
391
|
-
加载env.py文件中的配置项
|
|
392
379
|
|
|
393
|
-
:return: 操作是否成功
|
|
394
|
-
|
|
395
|
-
:example:
|
|
396
|
-
>>> env.load_env_file() # 加载env.py中的配置
|
|
397
|
-
"""
|
|
398
|
-
try:
|
|
399
|
-
env_file = Path("env.py")
|
|
400
|
-
if env_file.exists():
|
|
401
|
-
spec = importlib.util.spec_from_file_location("env_module", env_file)
|
|
402
|
-
env_module = importlib.util.module_from_spec(spec)
|
|
403
|
-
spec.loader.exec_module(env_module)
|
|
404
|
-
for key, value in vars(env_module).items():
|
|
405
|
-
if not key.startswith("__") and isinstance(value, (dict, list, str, int, float, bool)):
|
|
406
|
-
self.set(key, value)
|
|
407
|
-
return True
|
|
408
|
-
except Exception as e:
|
|
409
|
-
return False
|
|
410
|
-
|
|
411
380
|
def __getattr__(self, key: str) -> Any:
|
|
412
381
|
"""
|
|
413
382
|
通过属性访问配置项
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ErisPulse 框架配置管理
|
|
3
|
+
|
|
4
|
+
专门管理 ErisPulse 框架自身的配置项。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Dict, Any
|
|
8
|
+
from .config import config
|
|
9
|
+
|
|
10
|
+
# 默认配置
|
|
11
|
+
DEFAULT_ERISPULSE_CONFIG = {
|
|
12
|
+
"server": {
|
|
13
|
+
"host": "0.0.0.0",
|
|
14
|
+
"port": 8000,
|
|
15
|
+
"ssl_certfile": None,
|
|
16
|
+
"ssl_keyfile": None
|
|
17
|
+
},
|
|
18
|
+
"logger": {
|
|
19
|
+
"level": "INFO",
|
|
20
|
+
"log_files": [],
|
|
21
|
+
"memory_limit": 1000
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
def _ensure_erispulse_config_structure(config_dict: Dict[str, Any]) -> Dict[str, Any]:
|
|
26
|
+
"""
|
|
27
|
+
确保 ErisPulse 配置结构完整,补全缺失的配置项
|
|
28
|
+
|
|
29
|
+
:param config_dict: 当前配置
|
|
30
|
+
:return: 补全后的完整配置
|
|
31
|
+
"""
|
|
32
|
+
merged_config = DEFAULT_ERISPULSE_CONFIG.copy()
|
|
33
|
+
|
|
34
|
+
# 深度合并配置
|
|
35
|
+
for section, default_values in DEFAULT_ERISPULSE_CONFIG.items():
|
|
36
|
+
if section not in config_dict:
|
|
37
|
+
config_dict[section] = default_values.copy()
|
|
38
|
+
continue
|
|
39
|
+
|
|
40
|
+
if not isinstance(config_dict[section], dict):
|
|
41
|
+
config_dict[section] = default_values.copy()
|
|
42
|
+
continue
|
|
43
|
+
|
|
44
|
+
for key, default_value in default_values.items():
|
|
45
|
+
if key not in config_dict[section]:
|
|
46
|
+
config_dict[section][key] = default_value
|
|
47
|
+
|
|
48
|
+
return config_dict
|
|
49
|
+
|
|
50
|
+
def get_erispulse_config() -> Dict[str, Any]:
|
|
51
|
+
"""
|
|
52
|
+
获取 ErisPulse 框架配置,自动补全缺失的配置项并保存
|
|
53
|
+
|
|
54
|
+
:return: 完整的 ErisPulse 配置字典
|
|
55
|
+
"""
|
|
56
|
+
# 获取现有配置
|
|
57
|
+
current_config = config.getConfig("ErisPulse")
|
|
58
|
+
|
|
59
|
+
# 如果完全没有配置,设置默认配置
|
|
60
|
+
if current_config is None:
|
|
61
|
+
config.setConfig("ErisPulse", DEFAULT_ERISPULSE_CONFIG)
|
|
62
|
+
return DEFAULT_ERISPULSE_CONFIG
|
|
63
|
+
|
|
64
|
+
# 检查并补全缺失的配置项
|
|
65
|
+
complete_config = _ensure_erispulse_config_structure(current_config)
|
|
66
|
+
|
|
67
|
+
# 如果配置有变化,更新到存储
|
|
68
|
+
if current_config != complete_config:
|
|
69
|
+
config.setConfig("ErisPulse", complete_config)
|
|
70
|
+
|
|
71
|
+
return complete_config
|
|
72
|
+
|
|
73
|
+
def update_erispulse_config(new_config: Dict[str, Any]) -> bool:
|
|
74
|
+
"""
|
|
75
|
+
更新 ErisPulse 配置,自动补全缺失的配置项
|
|
76
|
+
|
|
77
|
+
:param new_config: 新的配置字典
|
|
78
|
+
:return: 是否更新成功
|
|
79
|
+
"""
|
|
80
|
+
# 获取当前配置并合并新配置
|
|
81
|
+
current = get_erispulse_config()
|
|
82
|
+
merged = {**current, **new_config}
|
|
83
|
+
|
|
84
|
+
# 确保合并后的配置结构完整
|
|
85
|
+
complete_config = _ensure_erispulse_config_structure(merged)
|
|
86
|
+
|
|
87
|
+
return config.setConfig("ErisPulse", complete_config)
|
|
88
|
+
|
|
89
|
+
def get_server_config() -> Dict[str, Any]:
|
|
90
|
+
"""
|
|
91
|
+
获取服务器配置,确保结构完整
|
|
92
|
+
|
|
93
|
+
:return: 服务器配置字典
|
|
94
|
+
"""
|
|
95
|
+
erispulse_config = get_erispulse_config()
|
|
96
|
+
return erispulse_config["server"]
|
|
97
|
+
|
|
98
|
+
def get_logger_config() -> Dict[str, Any]:
|
|
99
|
+
"""
|
|
100
|
+
获取日志配置,确保结构完整
|
|
101
|
+
|
|
102
|
+
:return: 日志配置字典
|
|
103
|
+
"""
|
|
104
|
+
erispulse_config = get_erispulse_config()
|
|
105
|
+
return erispulse_config["logger"]
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ErisPulse 全局异常处理系统
|
|
3
|
+
|
|
4
|
+
提供统一的异常捕获和格式化功能,支持同步和异步代码的异常处理。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
import traceback
|
|
9
|
+
import asyncio
|
|
10
|
+
import os
|
|
11
|
+
from typing import Dict, Any, Type
|
|
12
|
+
|
|
13
|
+
class ExceptionHandler:
|
|
14
|
+
@staticmethod
|
|
15
|
+
def format_exception(exc_type: Type[Exception], exc_value: Exception, exc_traceback: Any) -> str:
|
|
16
|
+
"""
|
|
17
|
+
:param exc_type: 异常类型
|
|
18
|
+
:param exc_value: 异常值
|
|
19
|
+
:param exc_traceback: 追踪信息
|
|
20
|
+
:return: 格式化后的异常信息
|
|
21
|
+
"""
|
|
22
|
+
tb_list = traceback.extract_tb(exc_traceback)
|
|
23
|
+
if tb_list:
|
|
24
|
+
last_frame = tb_list[-1]
|
|
25
|
+
filename = os.path.basename(last_frame.filename)
|
|
26
|
+
line_number = last_frame.lineno
|
|
27
|
+
function_name = last_frame.name
|
|
28
|
+
return f"ERROR: {filename}:{function_name}:{line_number}: {exc_type.__name__}: {exc_value}"
|
|
29
|
+
else:
|
|
30
|
+
return f"ERROR: {exc_type.__name__}: {exc_value}"
|
|
31
|
+
|
|
32
|
+
@staticmethod
|
|
33
|
+
def format_async_exception(exception: Exception) -> str:
|
|
34
|
+
"""
|
|
35
|
+
:param exception: 异常对象
|
|
36
|
+
:return: 格式化后的异常信息
|
|
37
|
+
"""
|
|
38
|
+
if exception.__traceback__:
|
|
39
|
+
tb_list = traceback.extract_tb(exception.__traceback__)
|
|
40
|
+
if tb_list:
|
|
41
|
+
last_frame = tb_list[-1]
|
|
42
|
+
filename = os.path.basename(last_frame.filename)
|
|
43
|
+
line_number = last_frame.lineno
|
|
44
|
+
function_name = last_frame.name
|
|
45
|
+
return f"ERROR: {filename}:{function_name}:{line_number}: {type(exception).__name__}: {exception}"
|
|
46
|
+
|
|
47
|
+
return f"ERROR: {type(exception).__name__}: {exception}"
|
|
48
|
+
|
|
49
|
+
def global_exception_handler(exc_type: Type[Exception], exc_value: Exception, exc_traceback: Any) -> None:
|
|
50
|
+
"""
|
|
51
|
+
全局异常处理器
|
|
52
|
+
|
|
53
|
+
:param exc_type: 异常类型
|
|
54
|
+
:param exc_value: 异常值
|
|
55
|
+
:param exc_traceback: 追踪信息
|
|
56
|
+
"""
|
|
57
|
+
try:
|
|
58
|
+
from ErisPulse import logger
|
|
59
|
+
err_logger = logger.error
|
|
60
|
+
except ImportError:
|
|
61
|
+
err_logger = sys.stderr.write
|
|
62
|
+
|
|
63
|
+
formatted_error = ExceptionHandler.format_exception(exc_type, exc_value, exc_traceback)
|
|
64
|
+
err_logger(formatted_error)
|
|
65
|
+
|
|
66
|
+
def async_exception_handler(loop: asyncio.AbstractEventLoop, context: Dict[str, Any]) -> None:
|
|
67
|
+
"""
|
|
68
|
+
异步异常处理器
|
|
69
|
+
|
|
70
|
+
:param loop: 事件循环
|
|
71
|
+
:param context: 上下文字典
|
|
72
|
+
"""
|
|
73
|
+
try:
|
|
74
|
+
from ErisPulse import logger
|
|
75
|
+
err_logger = logger.error
|
|
76
|
+
except ImportError:
|
|
77
|
+
err_logger = sys.stderr.write
|
|
78
|
+
|
|
79
|
+
exception = context.get('exception')
|
|
80
|
+
if exception:
|
|
81
|
+
try:
|
|
82
|
+
formatted_error = ExceptionHandler.format_async_exception(exception)
|
|
83
|
+
err_logger(formatted_error + '\n')
|
|
84
|
+
except Exception:
|
|
85
|
+
err_logger(f"ERROR: 捕捉器发生错误,原始异常信息:\n\n{exception}\n\n" + traceback.format_exc())
|
|
86
|
+
else:
|
|
87
|
+
msg = context.get('message', '未知异步错误')
|
|
88
|
+
err_logger(f"ERROR: 未处理的异步错误: {msg}\n")
|
|
89
|
+
|
|
90
|
+
def setup_async_loop(loop: asyncio.AbstractEventLoop = None) -> None:
|
|
91
|
+
"""
|
|
92
|
+
为指定的事件循环设置异常处理器
|
|
93
|
+
|
|
94
|
+
:param loop: 事件循环实例,如果为None则使用当前事件循环
|
|
95
|
+
"""
|
|
96
|
+
if loop is None:
|
|
97
|
+
try:
|
|
98
|
+
loop = asyncio.get_running_loop()
|
|
99
|
+
except RuntimeError:
|
|
100
|
+
loop = asyncio.get_event_loop()
|
|
101
|
+
|
|
102
|
+
loop.set_exception_handler(async_exception_handler)
|
|
103
|
+
|
|
104
|
+
sys.excepthook = global_exception_handler
|
|
105
|
+
try:
|
|
106
|
+
asyncio.get_event_loop().set_exception_handler(async_exception_handler)
|
|
107
|
+
except RuntimeError:
|
|
108
|
+
pass
|
ErisPulse/Core/logger.py
CHANGED
|
@@ -174,7 +174,7 @@ class Logger:
|
|
|
174
174
|
self._logs[ModuleName].append(msg)
|
|
175
175
|
|
|
176
176
|
def _setup_config(self):
|
|
177
|
-
from .
|
|
177
|
+
from .erispulse_config import get_logger_config
|
|
178
178
|
logger_config = get_logger_config()
|
|
179
179
|
if "level" in logger_config:
|
|
180
180
|
self.set_level(logger_config["level"])
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
# router.py (新文件名)
|
|
1
2
|
"""
|
|
2
|
-
ErisPulse
|
|
3
|
-
|
|
3
|
+
ErisPulse 路由系统
|
|
4
|
+
|
|
5
|
+
提供统一的HTTP和WebSocket路由管理,支持多适配器路由注册和生命周期管理。
|
|
4
6
|
|
|
5
7
|
{!--< tips >!--}
|
|
6
8
|
1. 适配器只需注册路由,无需自行管理服务器
|
|
@@ -19,9 +21,9 @@ from hypercorn.config import Config
|
|
|
19
21
|
from hypercorn.asyncio import serve
|
|
20
22
|
|
|
21
23
|
|
|
22
|
-
class
|
|
24
|
+
class RouterManager:
|
|
23
25
|
"""
|
|
24
|
-
|
|
26
|
+
路由管理器
|
|
25
27
|
|
|
26
28
|
{!--< tips >!--}
|
|
27
29
|
核心功能:
|
|
@@ -33,18 +35,18 @@ class AdapterServer:
|
|
|
33
35
|
|
|
34
36
|
def __init__(self):
|
|
35
37
|
"""
|
|
36
|
-
|
|
38
|
+
初始化路由管理器
|
|
37
39
|
|
|
38
40
|
{!--< tips >!--}
|
|
39
41
|
会自动创建FastAPI实例并设置核心路由
|
|
40
42
|
{!--< /tips >!--}
|
|
41
43
|
"""
|
|
42
44
|
self.app = FastAPI(
|
|
43
|
-
title="ErisPulse
|
|
44
|
-
description="
|
|
45
|
+
title="ErisPulse Router",
|
|
46
|
+
description="统一路由管理入口点",
|
|
45
47
|
version="1.0.0"
|
|
46
48
|
)
|
|
47
|
-
self.
|
|
49
|
+
self._http_routes: Dict[str, Dict[str, Callable]] = defaultdict(dict)
|
|
48
50
|
self._websocket_routes: Dict[str, Dict[str, Tuple[Callable, Optional[Callable]]]] = defaultdict(dict)
|
|
49
51
|
self.base_url = ""
|
|
50
52
|
self._server_task: Optional[asyncio.Task] = None
|
|
@@ -66,7 +68,7 @@ class AdapterServer:
|
|
|
66
68
|
:return:
|
|
67
69
|
Dict[str, str]: 包含服务状态的字典
|
|
68
70
|
"""
|
|
69
|
-
return {"status": "ok", "service": "ErisPulse
|
|
71
|
+
return {"status": "ok", "service": "ErisPulse Router"}
|
|
70
72
|
|
|
71
73
|
@self.app.get("/routes")
|
|
72
74
|
async def list_routes() -> Dict[str, Any]:
|
|
@@ -74,36 +76,23 @@ class AdapterServer:
|
|
|
74
76
|
列出所有已注册路由
|
|
75
77
|
|
|
76
78
|
:return:
|
|
77
|
-
Dict[str, Any]:
|
|
78
|
-
{
|
|
79
|
-
"http_routes": [
|
|
80
|
-
{
|
|
81
|
-
"path": "/adapter1/route1",
|
|
82
|
-
"adapter": "adapter1",
|
|
83
|
-
"methods": ["POST"]
|
|
84
|
-
},
|
|
85
|
-
...
|
|
86
|
-
],
|
|
87
|
-
"websocket_routes": [
|
|
88
|
-
{
|
|
89
|
-
"path": "/adapter1/ws",
|
|
90
|
-
"adapter": "adapter1",
|
|
91
|
-
"requires_auth": true
|
|
92
|
-
},
|
|
93
|
-
...
|
|
94
|
-
],
|
|
95
|
-
"base_url": self.base_url
|
|
96
|
-
}
|
|
79
|
+
Dict[str, Any]: 包含所有路由信息的字典
|
|
97
80
|
"""
|
|
98
81
|
http_routes = []
|
|
99
|
-
for adapter, routes in self.
|
|
82
|
+
for adapter, routes in self._http_routes.items():
|
|
100
83
|
for path, handler in routes.items():
|
|
101
|
-
|
|
102
|
-
|
|
84
|
+
# 查找对应的路由对象
|
|
85
|
+
route_obj = None
|
|
86
|
+
for route in self.app.router.routes:
|
|
87
|
+
if isinstance(route, APIRoute) and route.path == path:
|
|
88
|
+
route_obj = route
|
|
89
|
+
break
|
|
90
|
+
|
|
91
|
+
if route_obj:
|
|
103
92
|
http_routes.append({
|
|
104
93
|
"path": path,
|
|
105
94
|
"adapter": adapter,
|
|
106
|
-
"methods":
|
|
95
|
+
"methods": list(route_obj.methods)
|
|
107
96
|
})
|
|
108
97
|
|
|
109
98
|
websocket_routes = []
|
|
@@ -121,9 +110,9 @@ class AdapterServer:
|
|
|
121
110
|
"base_url": self.base_url
|
|
122
111
|
}
|
|
123
112
|
|
|
124
|
-
def
|
|
113
|
+
def register_http_route(
|
|
125
114
|
self,
|
|
126
|
-
|
|
115
|
+
module_name: str,
|
|
127
116
|
path: str,
|
|
128
117
|
handler: Callable,
|
|
129
118
|
methods: List[str] = ["POST"]
|
|
@@ -131,35 +120,37 @@ class AdapterServer:
|
|
|
131
120
|
"""
|
|
132
121
|
注册HTTP路由
|
|
133
122
|
|
|
134
|
-
:param
|
|
135
|
-
:param path: str 路由路径
|
|
123
|
+
:param module_name: str 模块名称
|
|
124
|
+
:param path: str 路由路径
|
|
136
125
|
:param handler: Callable 处理函数
|
|
137
126
|
:param methods: List[str] HTTP方法列表(默认["POST"])
|
|
138
127
|
|
|
139
128
|
:raises ValueError: 当路径已注册时抛出
|
|
140
|
-
|
|
141
|
-
{!--< tips >!--}
|
|
142
|
-
路径会自动添加适配器前缀,如:/adapter_name/path
|
|
143
|
-
{!--< /tips >!--}
|
|
144
129
|
"""
|
|
145
|
-
full_path = f"/{
|
|
130
|
+
full_path = f"/{module_name}{path}"
|
|
146
131
|
|
|
147
|
-
if full_path in self.
|
|
132
|
+
if full_path in self._http_routes[module_name]:
|
|
148
133
|
raise ValueError(f"路径 {full_path} 已注册")
|
|
149
134
|
|
|
150
135
|
route = APIRoute(
|
|
151
136
|
path=full_path,
|
|
152
137
|
endpoint=handler,
|
|
153
138
|
methods=methods,
|
|
154
|
-
name=f"{
|
|
139
|
+
name=f"{module_name}_{path.replace('/', '_')}"
|
|
155
140
|
)
|
|
156
141
|
self.app.router.routes.append(route)
|
|
157
|
-
self.
|
|
142
|
+
self._http_routes[module_name][full_path] = handler
|
|
158
143
|
logger.info(f"注册HTTP路由: {self.base_url}{full_path} 方法: {methods}")
|
|
159
144
|
|
|
145
|
+
def register_webhook(self, *args, **kwargs) -> None:
|
|
146
|
+
"""
|
|
147
|
+
兼容性方法:注册HTTP路由(适配器旧接口)
|
|
148
|
+
"""
|
|
149
|
+
return self.register_http_route(*args, **kwargs)
|
|
150
|
+
|
|
160
151
|
def register_websocket(
|
|
161
152
|
self,
|
|
162
|
-
|
|
153
|
+
module_name: str,
|
|
163
154
|
path: str,
|
|
164
155
|
handler: Callable[[WebSocket], Awaitable[Any]],
|
|
165
156
|
auth_handler: Optional[Callable[[WebSocket], Awaitable[bool]]] = None,
|
|
@@ -167,29 +158,21 @@ class AdapterServer:
|
|
|
167
158
|
"""
|
|
168
159
|
注册WebSocket路由
|
|
169
160
|
|
|
170
|
-
:param
|
|
171
|
-
:param path: str WebSocket路径
|
|
161
|
+
:param module_name: str 模块名称
|
|
162
|
+
:param path: str WebSocket路径
|
|
172
163
|
:param handler: Callable[[WebSocket], Awaitable[Any]] 主处理函数
|
|
173
164
|
:param auth_handler: Optional[Callable[[WebSocket], Awaitable[bool]]] 认证函数
|
|
174
165
|
|
|
175
166
|
:raises ValueError: 当路径已注册时抛出
|
|
176
|
-
|
|
177
|
-
{!--< tips >!--}
|
|
178
|
-
认证函数应返回布尔值,False将拒绝连接
|
|
179
|
-
{!--< /tips >!--}
|
|
180
167
|
"""
|
|
181
|
-
full_path = f"/{
|
|
168
|
+
full_path = f"/{module_name}{path}"
|
|
182
169
|
|
|
183
|
-
if full_path in self._websocket_routes[
|
|
170
|
+
if full_path in self._websocket_routes[module_name]:
|
|
184
171
|
raise ValueError(f"WebSocket路径 {full_path} 已注册")
|
|
185
172
|
|
|
186
173
|
async def websocket_endpoint(websocket: WebSocket) -> None:
|
|
187
174
|
"""
|
|
188
175
|
WebSocket端点包装器
|
|
189
|
-
|
|
190
|
-
{!--< internal-use >!--}
|
|
191
|
-
处理连接生命周期和错误处理
|
|
192
|
-
{!--< /internal-use >!--}
|
|
193
176
|
"""
|
|
194
177
|
await websocket.accept()
|
|
195
178
|
|
|
@@ -209,17 +192,16 @@ class AdapterServer:
|
|
|
209
192
|
self.app.add_api_websocket_route(
|
|
210
193
|
path=full_path,
|
|
211
194
|
endpoint=websocket_endpoint,
|
|
212
|
-
name=f"{
|
|
195
|
+
name=f"{module_name}_{path.replace('/', '_')}"
|
|
213
196
|
)
|
|
214
|
-
self._websocket_routes[
|
|
197
|
+
self._websocket_routes[module_name][full_path] = (handler, auth_handler)
|
|
215
198
|
logger.info(f"注册WebSocket: {self.base_url}{full_path} {'(需认证)' if auth_handler else ''}")
|
|
216
199
|
|
|
217
200
|
def get_app(self) -> FastAPI:
|
|
218
201
|
"""
|
|
219
202
|
获取FastAPI应用实例
|
|
220
203
|
|
|
221
|
-
:return:
|
|
222
|
-
FastAPI: FastAPI应用实例
|
|
204
|
+
:return: FastAPI应用实例
|
|
223
205
|
"""
|
|
224
206
|
return self.app
|
|
225
207
|
|
|
@@ -231,7 +213,7 @@ class AdapterServer:
|
|
|
231
213
|
ssl_keyfile: Optional[str] = None
|
|
232
214
|
) -> None:
|
|
233
215
|
"""
|
|
234
|
-
|
|
216
|
+
启动路由服务器
|
|
235
217
|
|
|
236
218
|
:param host: str 监听地址(默认"0.0.0.0")
|
|
237
219
|
:param port: int 监听端口(默认8000)
|
|
@@ -252,25 +234,24 @@ class AdapterServer:
|
|
|
252
234
|
config.keyfile = ssl_keyfile
|
|
253
235
|
|
|
254
236
|
self.base_url = f"http{'s' if ssl_certfile else ''}://{host}:{port}"
|
|
255
|
-
logger.info(f"
|
|
237
|
+
logger.info(f"启动路由服务器 {self.base_url}")
|
|
256
238
|
|
|
257
239
|
self._server_task = asyncio.create_task(serve(self.app, config))
|
|
258
240
|
|
|
259
241
|
async def stop(self) -> None:
|
|
260
242
|
"""
|
|
261
243
|
停止服务器
|
|
262
|
-
|
|
263
|
-
{!--< tips >!--}
|
|
264
|
-
会等待所有连接正常关闭
|
|
265
|
-
{!--< /tips >!--}
|
|
266
244
|
"""
|
|
267
245
|
if self._server_task:
|
|
268
246
|
self._server_task.cancel()
|
|
269
247
|
try:
|
|
270
248
|
await self._server_task
|
|
271
249
|
except asyncio.CancelledError:
|
|
272
|
-
logger.info("
|
|
250
|
+
logger.info("路由服务器已停止")
|
|
273
251
|
self._server_task = None
|
|
274
252
|
|
|
253
|
+
# 主要实例
|
|
254
|
+
router = RouterManager()
|
|
275
255
|
|
|
276
|
-
|
|
256
|
+
# 兼容性实例
|
|
257
|
+
adapter_server = router
|
ErisPulse/__init__.py
CHANGED
|
@@ -10,6 +10,9 @@ ErisPulse SDK 主模块
|
|
|
10
10
|
{!--< /tips >!--}
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
|
+
__version__ = "2.1.14dev1"
|
|
14
|
+
__author__ = "ErisPulse"
|
|
15
|
+
|
|
13
16
|
import os
|
|
14
17
|
import sys
|
|
15
18
|
import importlib
|
|
@@ -19,28 +22,36 @@ from typing import Dict, List, Tuple, Type, Any
|
|
|
19
22
|
from pathlib import Path
|
|
20
23
|
|
|
21
24
|
# BaseModules: SDK核心模块
|
|
22
|
-
from .Core import util
|
|
23
|
-
from .Core import raiserr
|
|
24
25
|
from .Core import logger
|
|
25
26
|
from .Core import env
|
|
26
27
|
from .Core import mods
|
|
27
28
|
from .Core import adapter, AdapterFather, SendDSL
|
|
28
|
-
from .Core import adapter_server
|
|
29
|
+
from .Core import router, adapter_server
|
|
30
|
+
from .Core import exceptions
|
|
31
|
+
from .Core import config
|
|
29
32
|
|
|
30
33
|
sdk = sys.modules[__name__]
|
|
31
34
|
|
|
32
35
|
BaseModules = {
|
|
33
|
-
"util": util,
|
|
34
36
|
"logger": logger,
|
|
35
|
-
"
|
|
37
|
+
"config": config,
|
|
38
|
+
"exceptions": exceptions,
|
|
36
39
|
"env": env,
|
|
37
40
|
"mods": mods,
|
|
38
41
|
"adapter": adapter,
|
|
42
|
+
"router": router,
|
|
43
|
+
"adapter_server": adapter_server,
|
|
39
44
|
"SendDSL": SendDSL,
|
|
40
45
|
"AdapterFather": AdapterFather,
|
|
41
46
|
"BaseAdapter": AdapterFather
|
|
42
47
|
}
|
|
43
48
|
|
|
49
|
+
import asyncio
|
|
50
|
+
|
|
51
|
+
asyncio_loop = asyncio.get_event_loop()
|
|
52
|
+
|
|
53
|
+
exceptions.setup_async_loop(asyncio_loop)
|
|
54
|
+
|
|
44
55
|
for module, moduleObj in BaseModules.items():
|
|
45
56
|
setattr(sdk, module, moduleObj)
|
|
46
57
|
|
|
@@ -664,23 +675,24 @@ def _prepare_environment() -> bool:
|
|
|
664
675
|
{!--< internal-use >!--}
|
|
665
676
|
准备运行环境
|
|
666
677
|
|
|
667
|
-
|
|
668
|
-
2. 加载环境变量配置
|
|
678
|
+
初始化项目环境文件
|
|
669
679
|
|
|
670
680
|
:return: bool 环境准备是否成功
|
|
671
681
|
"""
|
|
672
682
|
logger.info("[Init] 准备初始化环境...")
|
|
673
683
|
try:
|
|
684
|
+
from .Core.erispulse_config import get_erispulse_config
|
|
685
|
+
get_erispulse_config()
|
|
686
|
+
logger.info("[Init] 配置文件已加载")
|
|
687
|
+
|
|
674
688
|
main_init = init_progress()
|
|
675
689
|
if main_init:
|
|
676
690
|
logger.info("[Init] 项目入口已生成, 你可以在 main.py 中编写一些代码")
|
|
677
|
-
env.load_env_file()
|
|
678
691
|
return True
|
|
679
692
|
except Exception as e:
|
|
680
693
|
logger.error(f"环境准备失败: {e}")
|
|
681
694
|
return False
|
|
682
695
|
|
|
683
|
-
|
|
684
696
|
def init() -> bool:
|
|
685
697
|
"""
|
|
686
698
|
SDK初始化入口
|
ErisPulse/__main__.py
CHANGED
|
@@ -19,7 +19,6 @@ import json
|
|
|
19
19
|
import asyncio
|
|
20
20
|
from urllib.parse import urlparse
|
|
21
21
|
from typing import List, Dict, Tuple, Optional, Callable, Any
|
|
22
|
-
from importlib.metadata import version, PackageNotFoundError
|
|
23
22
|
from watchdog.observers import Observer
|
|
24
23
|
from watchdog.events import FileSystemEventHandler
|
|
25
24
|
|
|
@@ -188,26 +187,38 @@ class PackageManager:
|
|
|
188
187
|
|
|
189
188
|
try:
|
|
190
189
|
# 查找模块和适配器
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
190
|
+
entry_points = importlib.metadata.entry_points()
|
|
191
|
+
|
|
192
|
+
# 处理模块
|
|
193
|
+
if hasattr(entry_points, 'select'):
|
|
194
|
+
module_entries = entry_points.select(group='erispulse.module')
|
|
195
|
+
else:
|
|
196
|
+
module_entries = entry_points.get('erispulse.module', [])
|
|
197
|
+
|
|
198
|
+
for entry in module_entries:
|
|
199
|
+
dist = entry.dist
|
|
200
|
+
packages["modules"][entry.name] = {
|
|
201
|
+
"package": dist.metadata["Name"],
|
|
202
|
+
"version": dist.version,
|
|
203
|
+
"summary": dist.metadata["Summary"],
|
|
204
|
+
"enabled": self._is_module_enabled(entry.name)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
# 处理适配器
|
|
208
|
+
if hasattr(entry_points, 'select'):
|
|
209
|
+
adapter_entries = entry_points.select(group='erispulse.adapter')
|
|
210
|
+
else:
|
|
211
|
+
adapter_entries = entry_points.get('erispulse.adapter', [])
|
|
212
|
+
|
|
213
|
+
for entry in adapter_entries:
|
|
214
|
+
dist = entry.dist
|
|
215
|
+
packages["adapters"][entry.name] = {
|
|
216
|
+
"package": dist.metadata["Name"],
|
|
217
|
+
"version": dist.version,
|
|
218
|
+
"summary": dist.metadata["Summary"]
|
|
219
|
+
}
|
|
208
220
|
|
|
209
221
|
# 查找CLI扩展
|
|
210
|
-
entry_points = importlib.metadata.entry_points()
|
|
211
222
|
if hasattr(entry_points, 'select'):
|
|
212
223
|
cli_entries = entry_points.select(group='erispulse.cli')
|
|
213
224
|
else:
|
|
@@ -222,9 +233,9 @@ class PackageManager:
|
|
|
222
233
|
}
|
|
223
234
|
|
|
224
235
|
except Exception as e:
|
|
225
|
-
|
|
236
|
+
print(f"[error] 获取已安装包信息失败: {e}")
|
|
226
237
|
import traceback
|
|
227
|
-
|
|
238
|
+
print(traceback.format_exc())
|
|
228
239
|
|
|
229
240
|
return packages
|
|
230
241
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
ErisPulse/__init__.py,sha256=YoqCrqbR1dxjajBii6D4iebuVUl2bZaZYbpPXEgGBz8,26428
|
|
2
|
+
ErisPulse/__main__.py,sha256=aDYN5_11PdL3tj2ruhNoXwNc9TmAUnBtFujQgnEf_sI,37573
|
|
3
|
+
ErisPulse/Core/__init__.py,sha256=hX2yEt9VSD3JubiofoQdcY4v1lnQUU02dhuVADkMTVo,437
|
|
4
|
+
ErisPulse/Core/adapter.py,sha256=oBJOp6SS8sm8NgIxQwetGsHu24wHNXz7ESQ5yKJSo7Q,18234
|
|
5
|
+
ErisPulse/Core/config.py,sha256=2BRWINOqKtHSCP4KfhuiRpGwR96jWGKV7gjZSi_VQHE,2397
|
|
6
|
+
ErisPulse/Core/env.py,sha256=U45f9WtriVyd3tW1N8to-ZvpzcF9gD8DJzNTC1jY2cM,17665
|
|
7
|
+
ErisPulse/Core/erispulse_config.py,sha256=QDx401hNX9JcSHqCSVK33X6VTubl6HI1znAK3T_J0K0,3034
|
|
8
|
+
ErisPulse/Core/exceptions.py,sha256=zuTREGczwGzbYT4Z6dACqHwgNRpiJeLFR8aCxFdOg7k,3667
|
|
9
|
+
ErisPulse/Core/logger.py,sha256=HY6jkTQpztbc07mewHbMpaFNv_2-fX-87G78UEa3DTo,8306
|
|
10
|
+
ErisPulse/Core/mods.py,sha256=2yIq8t9Ca9CBPRiZU0yr8Lc0XGmmkB7LlH-5FWqXjw4,7023
|
|
11
|
+
ErisPulse/Core/router.py,sha256=66hT8VC2dVNX-dANldoOPDcqQ94hidFkNnvKgAPemGQ,8491
|
|
12
|
+
erispulse-2.1.14.dev2.dist-info/METADATA,sha256=h-m8vZWF0_WsUwiSj83NmYEZz0zEA91-cdR39EkyKpc,6264
|
|
13
|
+
erispulse-2.1.14.dev2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
14
|
+
erispulse-2.1.14.dev2.dist-info/entry_points.txt,sha256=Jss71M6nEha0TA-DyVZugPYdcL14s9QpiOeIlgWxzOc,182
|
|
15
|
+
erispulse-2.1.14.dev2.dist-info/licenses/LICENSE,sha256=4jyqikiB0G0n06CEEMMTzTXjE4IShghSlB74skMSPQs,1464
|
|
16
|
+
erispulse-2.1.14.dev2.dist-info/RECORD,,
|
ErisPulse/Core/raiserr.py
DELETED
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
ErisPulse 错误管理系统
|
|
3
|
-
|
|
4
|
-
提供全局异常捕获功能。不再推荐使用自定义错误注册功能。
|
|
5
|
-
|
|
6
|
-
{!--< tips >!--}
|
|
7
|
-
1. 请使用Python原生异常抛出方法
|
|
8
|
-
2. 系统会自动捕获并格式化所有未处理异常
|
|
9
|
-
3. 注册功能已标记为弃用,将在未来版本移除
|
|
10
|
-
{!--< /tips >!--}
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
import sys
|
|
14
|
-
import traceback
|
|
15
|
-
import asyncio
|
|
16
|
-
from typing import Dict, Any, Optional, Type, Callable, List, Set, Tuple, Union
|
|
17
|
-
|
|
18
|
-
class Error:
|
|
19
|
-
"""
|
|
20
|
-
错误管理器
|
|
21
|
-
|
|
22
|
-
{!--< deprecated >!--} 请使用Python原生异常抛出方法 | 2025-07-18
|
|
23
|
-
|
|
24
|
-
{!--< tips >!--}
|
|
25
|
-
1. 注册功能将在未来版本移除
|
|
26
|
-
2. 请直接使用raise Exception("message")方式抛出异常
|
|
27
|
-
{!--< /tips >!--}
|
|
28
|
-
"""
|
|
29
|
-
|
|
30
|
-
def __init__(self):
|
|
31
|
-
self._types = {}
|
|
32
|
-
|
|
33
|
-
def register(self, name: str, doc: str = "", base: Type[Exception] = Exception) -> Type[Exception]:
|
|
34
|
-
"""
|
|
35
|
-
注册新的错误类型
|
|
36
|
-
|
|
37
|
-
{!--< deprecated >!--} 请使用Python原生异常抛出方法 | 2025-07-18
|
|
38
|
-
|
|
39
|
-
:param name: 错误类型名称
|
|
40
|
-
:param doc: 错误描述文档
|
|
41
|
-
:param base: 基础异常类
|
|
42
|
-
:return: 注册的错误类
|
|
43
|
-
"""
|
|
44
|
-
if name not in self._types:
|
|
45
|
-
err_cls = type(name, (base,), {"__doc__": doc})
|
|
46
|
-
self._types[name] = err_cls
|
|
47
|
-
return self._types[name]
|
|
48
|
-
|
|
49
|
-
def __getattr__(self, name: str) -> Callable[..., None]:
|
|
50
|
-
"""
|
|
51
|
-
动态获取错误抛出函数
|
|
52
|
-
|
|
53
|
-
{!--< deprecated >!--} 请使用Python原生异常抛出方法 | 2025-07-18
|
|
54
|
-
|
|
55
|
-
:param name: 错误类型名称
|
|
56
|
-
:return: 错误抛出函数
|
|
57
|
-
|
|
58
|
-
:raises AttributeError: 当错误类型未注册时抛出
|
|
59
|
-
"""
|
|
60
|
-
def raiser(msg: str, exit: bool = False) -> None:
|
|
61
|
-
"""
|
|
62
|
-
错误抛出函数
|
|
63
|
-
|
|
64
|
-
:param msg: 错误消息
|
|
65
|
-
:param exit: 是否退出程序
|
|
66
|
-
"""
|
|
67
|
-
from .logger import logger
|
|
68
|
-
err_cls = self._types.get(name) or self.register(name)
|
|
69
|
-
exc = err_cls(msg)
|
|
70
|
-
|
|
71
|
-
red = '\033[91m'
|
|
72
|
-
reset = '\033[0m'
|
|
73
|
-
|
|
74
|
-
logger.error(f"{red}{name}: {msg} | {err_cls.__doc__}{reset}")
|
|
75
|
-
logger.error(f"{red}{ ''.join(traceback.format_stack()) }{reset}")
|
|
76
|
-
|
|
77
|
-
if exit:
|
|
78
|
-
raise exc
|
|
79
|
-
return raiser
|
|
80
|
-
|
|
81
|
-
def info(self, name: Optional[str] = None) -> Dict[str, Any]:
|
|
82
|
-
"""
|
|
83
|
-
获取错误信息
|
|
84
|
-
|
|
85
|
-
{!--< deprecated >!--} 此功能将在未来版本移除 | 2025-07-18
|
|
86
|
-
|
|
87
|
-
:param name: 错误类型名称(可选)
|
|
88
|
-
:return: 错误信息字典
|
|
89
|
-
"""
|
|
90
|
-
result = {}
|
|
91
|
-
for err_name, err_cls in self._types.items():
|
|
92
|
-
result[err_name] = {
|
|
93
|
-
"type": err_name,
|
|
94
|
-
"doc": getattr(err_cls, "__doc__", ""),
|
|
95
|
-
"class": err_cls,
|
|
96
|
-
}
|
|
97
|
-
if name is None:
|
|
98
|
-
return result
|
|
99
|
-
err_cls = self._types.get(name)
|
|
100
|
-
if not err_cls:
|
|
101
|
-
return {
|
|
102
|
-
"type": None,
|
|
103
|
-
"doc": None,
|
|
104
|
-
"class": None,
|
|
105
|
-
}
|
|
106
|
-
return {
|
|
107
|
-
"type": name,
|
|
108
|
-
"doc": getattr(err_cls, "__doc__", ""),
|
|
109
|
-
"class": err_cls,
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
raiserr = Error()
|
|
114
|
-
|
|
115
|
-
def global_exception_handler(exc_type: Type[Exception], exc_value: Exception, exc_traceback: Any) -> None:
|
|
116
|
-
"""
|
|
117
|
-
全局异常处理器
|
|
118
|
-
|
|
119
|
-
:param exc_type: 异常类型
|
|
120
|
-
:param exc_value: 异常值
|
|
121
|
-
:param exc_traceback: 追踪信息
|
|
122
|
-
"""
|
|
123
|
-
RED = '\033[91m'
|
|
124
|
-
YELLOW = '\033[93m'
|
|
125
|
-
BLUE = '\033[94m'
|
|
126
|
-
RESET = '\033[0m'
|
|
127
|
-
|
|
128
|
-
error_title = f"{RED}{exc_type.__name__}{RESET}: {YELLOW}{exc_value}{RESET}"
|
|
129
|
-
traceback_lines = traceback.format_exception(exc_type, exc_value, exc_traceback)
|
|
130
|
-
|
|
131
|
-
colored_traceback = []
|
|
132
|
-
for line in traceback_lines:
|
|
133
|
-
if "File " in line and ", line " in line:
|
|
134
|
-
parts = line.split(', line ')
|
|
135
|
-
colored_line = f"{BLUE}{parts[0]}{RESET}, line {parts[1]}"
|
|
136
|
-
colored_traceback.append(colored_line)
|
|
137
|
-
else:
|
|
138
|
-
colored_traceback.append(f"{RED}{line}{RESET}")
|
|
139
|
-
|
|
140
|
-
full_error = f"""
|
|
141
|
-
{error_title}
|
|
142
|
-
{RED}Traceback:{RESET}
|
|
143
|
-
{colored_traceback}"""
|
|
144
|
-
|
|
145
|
-
sys.stderr.write(full_error)
|
|
146
|
-
|
|
147
|
-
def async_exception_handler(loop: asyncio.AbstractEventLoop, context: Dict[str, Any]) -> None:
|
|
148
|
-
"""
|
|
149
|
-
异步异常处理器
|
|
150
|
-
|
|
151
|
-
:param loop: 事件循环
|
|
152
|
-
:param context: 上下文字典
|
|
153
|
-
"""
|
|
154
|
-
RED = '\033[91m'
|
|
155
|
-
YELLOW = '\033[93m'
|
|
156
|
-
BLUE = '\033[94m'
|
|
157
|
-
RESET = '\033[0m'
|
|
158
|
-
|
|
159
|
-
exception = context.get('exception')
|
|
160
|
-
if exception:
|
|
161
|
-
tb = ''.join(traceback.format_exception(type(exception), exception, exception.__traceback__))
|
|
162
|
-
|
|
163
|
-
colored_tb = []
|
|
164
|
-
for line in tb.split('\n'):
|
|
165
|
-
if "File " in line and ", line " in line:
|
|
166
|
-
parts = line.split(', line ')
|
|
167
|
-
colored_line = f"{BLUE}{parts[0]}{RESET}, line {parts[1]}"
|
|
168
|
-
colored_tb.append(colored_line)
|
|
169
|
-
else:
|
|
170
|
-
colored_tb.append(f"{RED}{line}{RESET}")
|
|
171
|
-
|
|
172
|
-
error_msg = f"""{RED}{type(exception).__name__}{RESET}: {YELLOW}{exception}{RESET}
|
|
173
|
-
{RED}Traceback:{RESET}
|
|
174
|
-
{colored_tb}"""
|
|
175
|
-
sys.stderr.write(error_msg)
|
|
176
|
-
else:
|
|
177
|
-
msg = context.get('message', 'Unknown async error')
|
|
178
|
-
sys.stderr.write(f"{RED}Async Error{RESET}: {YELLOW}{msg}{RESET}")
|
|
179
|
-
|
|
180
|
-
sys.excepthook = global_exception_handler
|
|
181
|
-
asyncio.get_event_loop().set_exception_handler(async_exception_handler)
|
ErisPulse/Core/util.py
DELETED
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
ErisPulse 工具函数集合
|
|
3
|
-
|
|
4
|
-
提供常用工具函数,包括拓扑排序、缓存装饰器、异步执行等实用功能。
|
|
5
|
-
|
|
6
|
-
{!--< tips >!--}
|
|
7
|
-
1. 使用@cache装饰器缓存函数结果
|
|
8
|
-
2. 使用@run_in_executor在独立线程中运行同步函数
|
|
9
|
-
3. 使用@retry实现自动重试机制
|
|
10
|
-
{!--< /tips >!--}
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
import time
|
|
14
|
-
import asyncio
|
|
15
|
-
import functools
|
|
16
|
-
import traceback
|
|
17
|
-
from concurrent.futures import ThreadPoolExecutor
|
|
18
|
-
from collections import defaultdict, deque
|
|
19
|
-
from typing import List, Dict, Type, Callable, Any, Optional, Set
|
|
20
|
-
|
|
21
|
-
executor = ThreadPoolExecutor()
|
|
22
|
-
|
|
23
|
-
class Util:
|
|
24
|
-
"""
|
|
25
|
-
工具函数集合
|
|
26
|
-
|
|
27
|
-
提供各种实用功能,简化开发流程
|
|
28
|
-
|
|
29
|
-
{!--< tips >!--}
|
|
30
|
-
1. 拓扑排序用于解决依赖关系
|
|
31
|
-
2. 装饰器简化常见模式实现
|
|
32
|
-
3. 异步执行提升性能
|
|
33
|
-
{!--< /tips >!--}
|
|
34
|
-
"""
|
|
35
|
-
def ExecAsync(self, async_func: Callable, *args: Any, **kwargs: Any) -> Any:
|
|
36
|
-
"""
|
|
37
|
-
异步执行函数
|
|
38
|
-
|
|
39
|
-
:param async_func: 异步函数
|
|
40
|
-
:param args: 位置参数
|
|
41
|
-
:param kwargs: 关键字参数
|
|
42
|
-
:return: 函数执行结果
|
|
43
|
-
|
|
44
|
-
:example:
|
|
45
|
-
>>> result = util.ExecAsync(my_async_func, arg1, arg2)
|
|
46
|
-
"""
|
|
47
|
-
loop = asyncio.get_event_loop()
|
|
48
|
-
return loop.run_in_executor(executor, lambda: asyncio.run(async_func(*args, **kwargs)))
|
|
49
|
-
|
|
50
|
-
def cache(self, func: Callable) -> Callable:
|
|
51
|
-
"""
|
|
52
|
-
缓存装饰器
|
|
53
|
-
|
|
54
|
-
:param func: 被装饰函数
|
|
55
|
-
:return: 装饰后的函数
|
|
56
|
-
|
|
57
|
-
:example:
|
|
58
|
-
>>> @util.cache
|
|
59
|
-
>>> def expensive_operation(param):
|
|
60
|
-
>>> return heavy_computation(param)
|
|
61
|
-
"""
|
|
62
|
-
cache_dict = {}
|
|
63
|
-
@functools.wraps(func)
|
|
64
|
-
def wrapper(*args, **kwargs):
|
|
65
|
-
key = (args, tuple(sorted(kwargs.items())))
|
|
66
|
-
if key not in cache_dict:
|
|
67
|
-
cache_dict[key] = func(*args, **kwargs)
|
|
68
|
-
return cache_dict[key]
|
|
69
|
-
return wrapper
|
|
70
|
-
|
|
71
|
-
def run_in_executor(self, func: Callable) -> Callable:
|
|
72
|
-
"""
|
|
73
|
-
在独立线程中执行同步函数的装饰器
|
|
74
|
-
|
|
75
|
-
:param func: 被装饰的同步函数
|
|
76
|
-
:return: 可等待的协程函数
|
|
77
|
-
|
|
78
|
-
:example:
|
|
79
|
-
>>> @util.run_in_executor
|
|
80
|
-
>>> def blocking_io():
|
|
81
|
-
>>> # 执行阻塞IO操作
|
|
82
|
-
>>> return result
|
|
83
|
-
"""
|
|
84
|
-
@functools.wraps(func)
|
|
85
|
-
async def wrapper(*args, **kwargs):
|
|
86
|
-
loop = asyncio.get_event_loop()
|
|
87
|
-
try:
|
|
88
|
-
return await loop.run_in_executor(None, lambda: func(*args, **kwargs))
|
|
89
|
-
except Exception as e:
|
|
90
|
-
from . import logger
|
|
91
|
-
logger.error(f"线程内发生未处理异常:\n{''.join(traceback.format_exc())}")
|
|
92
|
-
return wrapper
|
|
93
|
-
|
|
94
|
-
def retry(self, max_attempts: int = 3, delay: int = 1) -> Callable:
|
|
95
|
-
"""
|
|
96
|
-
自动重试装饰器
|
|
97
|
-
|
|
98
|
-
:param max_attempts: 最大重试次数 (默认: 3)
|
|
99
|
-
:param delay: 重试间隔(秒) (默认: 1)
|
|
100
|
-
:return: 装饰器函数
|
|
101
|
-
|
|
102
|
-
:example:
|
|
103
|
-
>>> @util.retry(max_attempts=5, delay=2)
|
|
104
|
-
>>> def unreliable_operation():
|
|
105
|
-
>>> # 可能失败的操作
|
|
106
|
-
"""
|
|
107
|
-
def decorator(func: Callable) -> Callable:
|
|
108
|
-
@functools.wraps(func)
|
|
109
|
-
def wrapper(*args, **kwargs):
|
|
110
|
-
attempts = 0
|
|
111
|
-
while attempts < max_attempts:
|
|
112
|
-
try:
|
|
113
|
-
return func(*args, **kwargs)
|
|
114
|
-
except Exception as e:
|
|
115
|
-
attempts += 1
|
|
116
|
-
if attempts == max_attempts:
|
|
117
|
-
raise
|
|
118
|
-
time.sleep(delay)
|
|
119
|
-
return wrapper
|
|
120
|
-
return decorator
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
util = Util()
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
ErisPulse/__init__.py,sha256=T-N56UQyBmpvlqwH2wGi5ptPzaJpbgF5tKkJt0DlkaM,26099
|
|
2
|
-
ErisPulse/__main__.py,sha256=kGpk-BeztjhrdNwWL2mZxaVy05y4XicCS6SZGtQERho,37409
|
|
3
|
-
ErisPulse/Core/__init__.py,sha256=tBYPahQ7-w7U6M69xy8DW0_ECa9ja-Q0y_SQqVswYBo,409
|
|
4
|
-
ErisPulse/Core/adapter.py,sha256=ZK81dibJ471FowL0MRXkS113iBcMgj_VzpTw0PzhAEo,18102
|
|
5
|
-
ErisPulse/Core/config.py,sha256=ZmwGdtHSOE7K5uOGzLYcyl3ZF3sAmeWAntqcdfDzhpM,5027
|
|
6
|
-
ErisPulse/Core/env.py,sha256=HGkzsdbxh8c1GSDJhnGP9B09Sz2ZeNbRxWieaFmAcug,18870
|
|
7
|
-
ErisPulse/Core/logger.py,sha256=cJzNXF-EmdWxwgiHg5Itmkwsva2Jhe9l9X4rXKiXHgc,8296
|
|
8
|
-
ErisPulse/Core/mods.py,sha256=2yIq8t9Ca9CBPRiZU0yr8Lc0XGmmkB7LlH-5FWqXjw4,7023
|
|
9
|
-
ErisPulse/Core/raiserr.py,sha256=vlyaaiOIYkyqm9dAqSW9E54JBzX-9roHDp5_r6I0yUU,5591
|
|
10
|
-
ErisPulse/Core/server.py,sha256=FkDTeLuHD5IBnWVxvYU8pHb6yCt8GzyvC1bpOiJ7G7I,9217
|
|
11
|
-
ErisPulse/Core/util.py,sha256=7rdMmn6sBFqYd4znxBCcJjuv2eyTExdeKyZopgds868,3796
|
|
12
|
-
erispulse-2.1.13rc3.dist-info/METADATA,sha256=ppMW2h_uDFhvpfP9u30vbaHgQA9fr_oPdDzeWaLQRf4,6262
|
|
13
|
-
erispulse-2.1.13rc3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
14
|
-
erispulse-2.1.13rc3.dist-info/entry_points.txt,sha256=Jss71M6nEha0TA-DyVZugPYdcL14s9QpiOeIlgWxzOc,182
|
|
15
|
-
erispulse-2.1.13rc3.dist-info/licenses/LICENSE,sha256=4jyqikiB0G0n06CEEMMTzTXjE4IShghSlB74skMSPQs,1464
|
|
16
|
-
erispulse-2.1.13rc3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|