ErisPulse 2.1.14.dev1__py3-none-any.whl → 2.1.15.dev3__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.
@@ -0,0 +1,547 @@
1
+ """
2
+ ErisPulse 存储管理模块
3
+
4
+ 提供键值存储、事务支持、快照和恢复功能,用于管理框架运行时数据。
5
+ 基于SQLite实现持久化存储,支持复杂数据类型和原子操作。
6
+
7
+ {!--< tips >!--}
8
+ 1. 支持JSON序列化存储复杂数据类型
9
+ 2. 提供事务支持确保数据一致性
10
+ 3. 自动快照功能防止数据丢失
11
+ {!--< /tips >!--}
12
+ """
13
+
14
+ import os
15
+ import json
16
+ import sqlite3
17
+ import shutil
18
+ import time
19
+ from datetime import datetime
20
+ from typing import List, Dict, Optional, Any, Tuple, Type
21
+
22
+ class StorageManager:
23
+ """
24
+ 存储管理器
25
+
26
+ 单例模式实现,提供键值存储的增删改查、事务和快照管理
27
+
28
+ {!--< tips >!--}
29
+ 1. 使用get/set方法操作存储项
30
+ 2. 使用transaction上下文管理事务
31
+ 3. 使用snapshot/restore管理数据快照
32
+ {!--< /tips >!--}
33
+ """
34
+
35
+ _instance = None
36
+ db_path = os.path.join(os.path.dirname(__file__), "../data/config.db")
37
+ SNAPSHOT_DIR = os.path.join(os.path.dirname(__file__), "../data/snapshots")
38
+
39
+ def __new__(cls, *args, **kwargs):
40
+ if not cls._instance:
41
+ cls._instance = super().__new__(cls)
42
+ return cls._instance
43
+
44
+ def __init__(self):
45
+ if not hasattr(self, "_initialized"):
46
+ # 确保关键属性在初始化时都有默认值
47
+ self._last_snapshot_time = time.time()
48
+ self._snapshot_interval = 3600
49
+ self._init_db()
50
+ self._initialized = True
51
+
52
+ def _init_db(self) -> None:
53
+ """
54
+ {!--< internal-use >!--}
55
+ 初始化数据库
56
+ """
57
+ os.makedirs(os.path.dirname(self.db_path), exist_ok=True)
58
+ os.makedirs(self.SNAPSHOT_DIR, exist_ok=True)
59
+ conn = sqlite3.connect(self.db_path)
60
+
61
+ # 启用WAL模式提高并发性能
62
+ conn.execute("PRAGMA journal_mode=WAL")
63
+ conn.execute("PRAGMA synchronous=NORMAL")
64
+
65
+ cursor = conn.cursor()
66
+ cursor.execute("""
67
+ CREATE TABLE IF NOT EXISTS config (
68
+ key TEXT PRIMARY KEY,
69
+ value TEXT NOT NULL
70
+ )
71
+ """)
72
+ conn.commit()
73
+ conn.close()
74
+
75
+ # 初始化自动快照调度器
76
+ self._last_snapshot_time = time.time() # 初始化为当前时间
77
+ self._snapshot_interval = 3600 # 默认每小时自动快照
78
+
79
+ def get(self, key: str, default: Any = None) -> Any:
80
+ """
81
+ 获取存储项的值
82
+
83
+ :param key: 存储项键名
84
+ :param default: 默认值(当键不存在时返回)
85
+ :return: 存储项的值
86
+
87
+ :example:
88
+ >>> timeout = storage.get("network.timeout", 30)
89
+ >>> user_settings = storage.get("user.settings", {})
90
+ """
91
+ try:
92
+ with sqlite3.connect(self.db_path) as conn:
93
+ cursor = conn.cursor()
94
+ cursor.execute("SELECT value FROM config WHERE key = ?", (key,))
95
+ result = cursor.fetchone()
96
+ if result:
97
+ try:
98
+ return json.loads(result[0])
99
+ except json.JSONDecodeError:
100
+ return result[0]
101
+ return default
102
+ except sqlite3.OperationalError as e:
103
+ if "no such table" in str(e):
104
+ self._init_db()
105
+ return self.get(key, default)
106
+ else:
107
+ from . import logger
108
+ logger.error(f"数据库操作错误: {e}")
109
+
110
+ def get_all_keys(self) -> List[str]:
111
+ """
112
+ 获取所有存储项的键名
113
+
114
+ :return: 键名列表
115
+
116
+ :example:
117
+ >>> all_keys = storage.get_all_keys()
118
+ >>> print(f"共有 {len(all_keys)} 个存储项")
119
+ """
120
+ with sqlite3.connect(self.db_path) as conn:
121
+ cursor = conn.cursor()
122
+ cursor.execute("SELECT key FROM config")
123
+ return [row[0] for row in cursor.fetchall()]
124
+
125
+ def set(self, key: str, value: Any) -> bool:
126
+ """
127
+ 设置存储项的值
128
+
129
+ :param key: 存储项键名
130
+ :param value: 存储项的值
131
+ :return: 操作是否成功
132
+
133
+ :example:
134
+ >>> storage.set("app.name", "MyApp")
135
+ >>> storage.set("user.settings", {"theme": "dark"})
136
+ """
137
+ try:
138
+ serialized_value = json.dumps(value) if isinstance(value, (dict, list)) else str(value)
139
+ with self.transaction():
140
+ conn = sqlite3.connect(self.db_path)
141
+ cursor = conn.cursor()
142
+ cursor.execute("INSERT OR REPLACE INTO config (key, value) VALUES (?, ?)", (key, serialized_value))
143
+ conn.commit()
144
+ conn.close()
145
+
146
+ self._check_auto_snapshot()
147
+ return True
148
+ except Exception as e:
149
+ return False
150
+
151
+ def set_multi(self, items: Dict[str, Any]) -> bool:
152
+ """
153
+ 批量设置多个存储项
154
+
155
+ :param items: 键值对字典
156
+ :return: 操作是否成功
157
+
158
+ :example:
159
+ >>> storage.set_multi({
160
+ >>> "app.name": "MyApp",
161
+ >>> "app.version": "1.0.0",
162
+ >>> "app.debug": True
163
+ >>> })
164
+ """
165
+ try:
166
+ with self.transaction():
167
+ conn = sqlite3.connect(self.db_path)
168
+ cursor = conn.cursor()
169
+ for key, value in items.items():
170
+ serialized_value = json.dumps(value) if isinstance(value, (dict, list)) else str(value)
171
+ cursor.execute("INSERT OR REPLACE INTO config (key, value) VALUES (?, ?)",
172
+ (key, serialized_value))
173
+ conn.commit()
174
+ conn.close()
175
+
176
+ self._check_auto_snapshot()
177
+ return True
178
+ except Exception as e:
179
+ return False
180
+
181
+ def getConfig(self, key: str, default: Any = None) -> Any:
182
+ """
183
+ 获取模块/适配器配置项(委托给config模块)
184
+ :param key: 配置项的键(支持点分隔符如"module.sub.key")
185
+ :param default: 默认值
186
+ :return: 配置项的值
187
+ """
188
+ try:
189
+ from .config import config
190
+ return config.getConfig(key, default)
191
+ except Exception as e:
192
+ return default
193
+
194
+ def setConfig(self, key: str, value: Any) -> bool:
195
+ """
196
+ 设置模块/适配器配置(委托给config模块)
197
+ :param key: 配置项键名(支持点分隔符如"module.sub.key")
198
+ :param value: 配置项值
199
+ :return: 操作是否成功
200
+ """
201
+ try:
202
+ from .config import config
203
+ return config.setConfig(key, value)
204
+ except Exception as e:
205
+ return False
206
+
207
+ def delete(self, key: str) -> bool:
208
+ """
209
+ 删除存储项
210
+
211
+ :param key: 存储项键名
212
+ :return: 操作是否成功
213
+
214
+ :example:
215
+ >>> storage.delete("temp.session")
216
+ """
217
+ try:
218
+ with self.transaction():
219
+ conn = sqlite3.connect(self.db_path)
220
+ cursor = conn.cursor()
221
+ cursor.execute("DELETE FROM config WHERE key = ?", (key,))
222
+ conn.commit()
223
+ conn.close()
224
+
225
+ self._check_auto_snapshot()
226
+ return True
227
+ except Exception as e:
228
+ return False
229
+
230
+ def delete_multi(self, keys: List[str]) -> bool:
231
+ """
232
+ 批量删除多个存储项
233
+
234
+ :param keys: 键名列表
235
+ :return: 操作是否成功
236
+
237
+ :example:
238
+ >>> storage.delete_multi(["temp.key1", "temp.key2"])
239
+ """
240
+ try:
241
+ with self.transaction():
242
+ conn = sqlite3.connect(self.db_path)
243
+ cursor = conn.cursor()
244
+ cursor.executemany("DELETE FROM config WHERE key = ?", [(k,) for k in keys])
245
+ conn.commit()
246
+ conn.close()
247
+
248
+ self._check_auto_snapshot()
249
+ return True
250
+ except Exception as e:
251
+ return False
252
+
253
+ def get_multi(self, keys: List[str]) -> Dict[str, Any]:
254
+ """
255
+ 批量获取多个存储项的值
256
+
257
+ :param keys: 键名列表
258
+ :return: 键值对字典
259
+
260
+ :example:
261
+ >>> settings = storage.get_multi(["app.name", "app.version"])
262
+ """
263
+ conn = sqlite3.connect(self.db_path)
264
+ cursor = conn.cursor()
265
+ placeholders = ','.join(['?'] * len(keys))
266
+ cursor.execute(f"SELECT key, value FROM config WHERE key IN ({placeholders})", keys)
267
+ results = {row[0]: json.loads(row[1]) if row[1].startswith(('{', '[')) else row[1]
268
+ for row in cursor.fetchall()}
269
+ conn.close()
270
+ return results
271
+
272
+ def transaction(self) -> 'StorageManager._Transaction':
273
+ """
274
+ 创建事务上下文
275
+
276
+ :return: 事务上下文管理器
277
+
278
+ :example:
279
+ >>> with storage.transaction():
280
+ >>> storage.set("key1", "value1")
281
+ >>> storage.set("key2", "value2")
282
+ """
283
+ return self._Transaction(self)
284
+
285
+ class _Transaction:
286
+ """
287
+ 事务上下文管理器
288
+
289
+ {!--< internal-use >!--}
290
+ 确保多个操作的原子性
291
+ """
292
+
293
+ def __init__(self, storage_manager: 'StorageManager'):
294
+ self.storage_manager = storage_manager
295
+ self.conn = None
296
+ self.cursor = None
297
+
298
+ def __enter__(self) -> 'StorageManager._Transaction':
299
+ """
300
+ 进入事务上下文
301
+ """
302
+ self.conn = sqlite3.connect(self.storage_manager.db_path)
303
+ self.cursor = self.conn.cursor()
304
+ self.cursor.execute("BEGIN TRANSACTION")
305
+ return self
306
+
307
+ def __exit__(self, exc_type: Type[Exception], exc_val: Exception, exc_tb: Any) -> None:
308
+ """
309
+ 退出事务上下文
310
+ """
311
+ if exc_type is None:
312
+ self.conn.commit()
313
+ else:
314
+ self.conn.rollback()
315
+ from .logger import logger
316
+ logger.error(f"事务执行失败: {exc_val}")
317
+ self.conn.close()
318
+
319
+ def _check_auto_snapshot(self) -> None:
320
+ """
321
+ {!--< internal-use >!--}
322
+ 检查并执行自动快照
323
+ """
324
+ from .logger import logger
325
+
326
+ if not hasattr(self, '_last_snapshot_time') or self._last_snapshot_time is None:
327
+ self._last_snapshot_time = time.time()
328
+
329
+ if not hasattr(self, '_snapshot_interval') or self._snapshot_interval is None:
330
+ self._snapshot_interval = 3600
331
+
332
+ current_time = time.time()
333
+
334
+ try:
335
+ time_diff = current_time - self._last_snapshot_time
336
+ if not isinstance(time_diff, (int, float)):
337
+ raise ValueError("时间差应为数值类型")
338
+
339
+ if not isinstance(self._snapshot_interval, (int, float)):
340
+ raise ValueError("快照间隔应为数值类型")
341
+
342
+ if time_diff > self._snapshot_interval:
343
+ self._last_snapshot_time = current_time
344
+ self.snapshot(f"auto_{datetime.now().strftime('%Y%m%d_%H%M%S')}")
345
+
346
+ except Exception as e:
347
+ logger.error(f"自动快照检查失败: {e}")
348
+ self._last_snapshot_time = current_time
349
+ self._snapshot_interval = 3600
350
+
351
+ def set_snapshot_interval(self, seconds: int) -> None:
352
+ """
353
+ 设置自动快照间隔
354
+
355
+ :param seconds: 间隔秒数
356
+
357
+ :example:
358
+ >>> # 每30分钟自动快照
359
+ >>> storage.set_snapshot_interval(1800)
360
+ """
361
+ self._snapshot_interval = seconds
362
+
363
+ def clear(self) -> bool:
364
+ """
365
+ 清空所有存储项
366
+
367
+ :return: 操作是否成功
368
+
369
+ :example:
370
+ >>> storage.clear() # 清空所有存储
371
+ """
372
+ try:
373
+ conn = sqlite3.connect(self.db_path)
374
+ cursor = conn.cursor()
375
+ cursor.execute("DELETE FROM config")
376
+ conn.commit()
377
+ conn.close()
378
+ return True
379
+ except Exception as e:
380
+ return False
381
+
382
+ def __getattr__(self, key: str) -> Any:
383
+ """
384
+ 通过属性访问存储项
385
+
386
+ :param key: 存储项键名
387
+ :return: 存储项的值
388
+
389
+ :raises KeyError: 当存储项不存在时抛出
390
+
391
+ :example:
392
+ >>> app_name = storage.app_name
393
+ """
394
+ try:
395
+ return self.get(key)
396
+ except KeyError:
397
+ from . import logger
398
+ logger.error(f"存储项 {key} 不存在")
399
+
400
+ def __setattr__(self, key: str, value: Any) -> None:
401
+ """
402
+ 通过属性设置存储项
403
+
404
+ :param key: 存储项键名
405
+ :param value: 存储项的值
406
+
407
+ :example:
408
+ >>> storage.app_name = "MyApp"
409
+ """
410
+ try:
411
+ self.set(key, value)
412
+ except Exception as e:
413
+ from . import logger
414
+ logger.error(f"设置存储项 {key} 失败: {e}")
415
+
416
+ def snapshot(self, name: Optional[str] = None) -> str:
417
+ """
418
+ 创建数据库快照
419
+
420
+ :param name: 快照名称(可选)
421
+ :return: 快照文件路径
422
+
423
+ :example:
424
+ >>> # 创建命名快照
425
+ >>> snapshot_path = storage.snapshot("before_update")
426
+ >>> # 创建时间戳快照
427
+ >>> snapshot_path = storage.snapshot()
428
+ """
429
+ if not name:
430
+ name = datetime.now().strftime("%Y%m%d_%H%M%S")
431
+ snapshot_path = os.path.join(self.SNAPSHOT_DIR, f"{name}.db")
432
+
433
+ try:
434
+ # 快照目录
435
+ os.makedirs(self.SNAPSHOT_DIR, exist_ok=True)
436
+
437
+ # 安全关闭连接
438
+ if hasattr(self, "_conn") and self._conn is not None:
439
+ try:
440
+ self._conn.close()
441
+ except Exception as e:
442
+ from . import logger
443
+ logger.warning(f"关闭数据库连接时出错: {e}")
444
+
445
+ # 创建快照
446
+ shutil.copy2(self.db_path, snapshot_path)
447
+ from . import logger
448
+ logger.info(f"数据库快照已创建: {snapshot_path}")
449
+ return snapshot_path
450
+ except Exception as e:
451
+ from . import logger
452
+ logger.error(f"创建快照失败: {e}")
453
+ raise
454
+
455
+ def restore(self, snapshot_name: str) -> bool:
456
+ """
457
+ 从快照恢复数据库
458
+
459
+ :param snapshot_name: 快照名称或路径
460
+ :return: 恢复是否成功
461
+
462
+ :example:
463
+ >>> storage.restore("before_update")
464
+ """
465
+ snapshot_path = os.path.join(self.SNAPSHOT_DIR, f"{snapshot_name}.db") \
466
+ if not snapshot_name.endswith('.db') else snapshot_name
467
+
468
+ if not os.path.exists(snapshot_path):
469
+ from . import logger
470
+ logger.error(f"快照文件不存在: {snapshot_path}")
471
+ return False
472
+
473
+ try:
474
+ # 安全关闭连接
475
+ if hasattr(self, "_conn") and self._conn is not None:
476
+ try:
477
+ self._conn.close()
478
+ except Exception as e:
479
+ from . import logger
480
+ logger.warning(f"关闭数据库连接时出错: {e}")
481
+
482
+ # 执行恢复操作
483
+ shutil.copy2(snapshot_path, self.db_path)
484
+ self._init_db() # 恢复后重新初始化数据库连接
485
+ from . import logger
486
+ logger.info(f"数据库已从快照恢复: {snapshot_path}")
487
+ return True
488
+ except Exception as e:
489
+ from . import logger
490
+ logger.error(f"恢复快照失败: {e}")
491
+ return False
492
+
493
+ def list_snapshots(self) -> List[Tuple[str, datetime, int]]:
494
+ """
495
+ 列出所有可用的快照
496
+
497
+ :return: 快照信息列表(名称, 创建时间, 大小)
498
+
499
+ :example:
500
+ >>> for name, date, size in storage.list_snapshots():
501
+ >>> print(f"{name} - {date} ({size} bytes)")
502
+ """
503
+ snapshots = []
504
+ for f in os.listdir(self.SNAPSHOT_DIR):
505
+ if f.endswith('.db'):
506
+ path = os.path.join(self.SNAPSHOT_DIR, f)
507
+ stat = os.stat(path)
508
+ snapshots.append((
509
+ f[:-3], # 去掉.db后缀
510
+ datetime.fromtimestamp(stat.st_ctime),
511
+ stat.st_size
512
+ ))
513
+ return sorted(snapshots, key=lambda x: x[1], reverse=True)
514
+
515
+ def delete_snapshot(self, snapshot_name: str) -> bool:
516
+ """
517
+ 删除指定的快照
518
+
519
+ :param snapshot_name: 快照名称
520
+ :return: 删除是否成功
521
+
522
+ :example:
523
+ >>> storage.delete_snapshot("old_backup")
524
+ """
525
+ snapshot_path = os.path.join(self.SNAPSHOT_DIR, f"{snapshot_name}.db") \
526
+ if not snapshot_name.endswith('.db') else snapshot_name
527
+
528
+ if not os.path.exists(snapshot_path):
529
+ from . import logger
530
+ logger.error(f"快照文件不存在: {snapshot_path}")
531
+ return False
532
+
533
+ try:
534
+ os.remove(snapshot_path)
535
+ from . import logger
536
+ logger.info(f"快照已删除: {snapshot_path}")
537
+ return True
538
+ except Exception as e:
539
+ from . import logger
540
+ logger.error(f"删除快照失败: {e}")
541
+ return False
542
+
543
+ storage = StorageManager()
544
+
545
+ __all__ = [
546
+ "storage"
547
+ ]
ErisPulse/__init__.py CHANGED
@@ -10,30 +10,35 @@ ErisPulse SDK 主模块
10
10
  {!--< /tips >!--}
11
11
  """
12
12
 
13
- __version__ = "2.1.14dev1"
13
+ __version__ = "2.1.15-dev.3"
14
14
  __author__ = "ErisPulse"
15
15
 
16
16
  import os
17
17
  import sys
18
18
  import importlib
19
+ import asyncio
19
20
  import inspect
20
21
  import importlib.metadata
21
22
  from typing import Dict, List, Tuple, Type, Any
22
23
  from pathlib import Path
23
24
 
24
25
  # BaseModules: SDK核心模块
25
- from .Core import exceptions
26
26
  from .Core import logger
27
+ from .Core import storage
27
28
  from .Core import env
28
29
  from .Core import mods
29
30
  from .Core import adapter, AdapterFather, SendDSL
30
31
  from .Core import router, adapter_server
32
+ from .Core import exceptions
33
+ from .Core import config
31
34
 
32
35
  sdk = sys.modules[__name__]
33
36
 
34
37
  BaseModules = {
35
38
  "logger": logger,
36
- "raiserr": exceptions,
39
+ "config": config,
40
+ "exceptions": exceptions,
41
+ "storage": storage,
37
42
  "env": env,
38
43
  "mods": mods,
39
44
  "adapter": adapter,
@@ -44,6 +49,10 @@ BaseModules = {
44
49
  "BaseAdapter": AdapterFather
45
50
  }
46
51
 
52
+ asyncio_loop = asyncio.get_event_loop()
53
+
54
+ exceptions.setup_async_loop(asyncio_loop)
55
+
47
56
  for module, moduleObj in BaseModules.items():
48
57
  setattr(sdk, module, moduleObj)
49
58
 
@@ -637,7 +646,12 @@ from ErisPulse import sdk
637
646
 
638
647
  async def main():
639
648
  try:
640
- sdk.init()
649
+ isInit = await sdk.init_task()
650
+
651
+ if not isInit:
652
+ sdk.logger.error("ErisPulse 初始化失败,请检查日志")
653
+ return
654
+
641
655
  await sdk.adapter.startup()
642
656
 
643
657
  # 保持程序运行(不建议修改)
@@ -667,47 +681,51 @@ def _prepare_environment() -> bool:
667
681
  {!--< internal-use >!--}
668
682
  准备运行环境
669
683
 
670
- 1. 初始化项目环境文件
671
- 2. 加载环境变量配置
684
+ 初始化项目环境文件
672
685
 
673
686
  :return: bool 环境准备是否成功
674
687
  """
675
688
  logger.info("[Init] 准备初始化环境...")
676
689
  try:
690
+ from .Core.erispulse_config import get_erispulse_config
691
+ get_erispulse_config()
692
+ logger.info("[Init] 配置文件已加载")
693
+
677
694
  main_init = init_progress()
678
695
  if main_init:
679
696
  logger.info("[Init] 项目入口已生成, 你可以在 main.py 中编写一些代码")
680
- env.load_env_file()
681
697
  return True
682
698
  except Exception as e:
683
699
  logger.error(f"环境准备失败: {e}")
684
700
  return False
685
701
 
686
-
687
702
  def init() -> bool:
688
703
  """
689
704
  SDK初始化入口
690
705
 
691
- 执行步骤:
692
- 1. 准备运行环境
693
- 2. 初始化所有模块和适配器
694
-
695
706
  :return: bool SDK初始化是否成功
696
-
697
- {!--< tips >!--}
698
- 1. 这是SDK的主要入口函数
699
- 2. 如果初始化失败会抛出InitError异常
700
- 3. 建议在main.py中调用此函数
701
- {!--< /tips >!--}
702
-
703
- :raises InitError: 当初始化失败时抛出
704
707
  """
705
-
706
708
  if not _prepare_environment():
707
709
  return False
708
-
709
710
  return ModuleInitializer.init()
710
711
 
712
+ def init_task() -> asyncio.Task:
713
+ """
714
+ SDK初始化入口,返回Task对象
715
+
716
+ :return: asyncio.Task 初始化任务
717
+ """
718
+ async def _async_init():
719
+ if not _prepare_environment():
720
+ return False
721
+ return ModuleInitializer.init()
722
+
723
+ try:
724
+ return asyncio.create_task(_async_init())
725
+ except RuntimeError:
726
+ loop = asyncio.new_event_loop()
727
+ asyncio.set_event_loop(loop)
728
+ return loop.create_task(_async_init())
711
729
 
712
730
  def load_module(module_name: str) -> bool:
713
731
  """