ErisPulse 2.3.4.dev2__py3-none-any.whl → 2.3.4.dev114514__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/Bases/module.py +1 -53
- ErisPulse/Core/Bases/module.pyi +0 -43
- ErisPulse/Core/Event/command.py +1 -6
- ErisPulse/Core/_self_config.py +1 -1
- ErisPulse/Core/adapter.py +10 -70
- ErisPulse/Core/adapter.pyi +1 -18
- ErisPulse/Core/exceptions.py +2 -4
- ErisPulse/Core/lifecycle.py +0 -9
- ErisPulse/Core/logger.py +15 -21
- ErisPulse/Core/logger.pyi +1 -2
- ErisPulse/Core/module.py +9 -57
- ErisPulse/Core/module.pyi +1 -12
- ErisPulse/Core/router.py +5 -13
- ErisPulse/Core/storage.py +256 -94
- ErisPulse/Core/storage.pyi +66 -13
- ErisPulse/__init__.py +1237 -35
- ErisPulse/__init__.pyi +290 -3
- ErisPulse/sdk_protocol.py +143 -0
- ErisPulse/sdk_protocol.pyi +97 -0
- {erispulse-2.3.4.dev2.dist-info → erispulse-2.3.4.dev114514.dist-info}/METADATA +1 -1
- {erispulse-2.3.4.dev2.dist-info → erispulse-2.3.4.dev114514.dist-info}/RECORD +24 -38
- ErisPulse/Core/Bases/manager.py +0 -136
- ErisPulse/Core/Bases/manager.pyi +0 -108
- ErisPulse/loaders/__init__.py +0 -22
- ErisPulse/loaders/__init__.pyi +0 -21
- ErisPulse/loaders/adapter_loader.py +0 -187
- ErisPulse/loaders/adapter_loader.pyi +0 -82
- ErisPulse/loaders/base_loader.py +0 -162
- ErisPulse/loaders/base_loader.pyi +0 -23
- ErisPulse/loaders/initializer.py +0 -150
- ErisPulse/loaders/initializer.pyi +0 -60
- ErisPulse/loaders/module_loader.py +0 -618
- ErisPulse/loaders/module_loader.pyi +0 -179
- ErisPulse/loaders/strategy.py +0 -129
- ErisPulse/loaders/strategy.pyi +0 -90
- ErisPulse/sdk.py +0 -435
- ErisPulse/sdk.pyi +0 -158
- {erispulse-2.3.4.dev2.dist-info → erispulse-2.3.4.dev114514.dist-info}/WHEEL +0 -0
- {erispulse-2.3.4.dev2.dist-info → erispulse-2.3.4.dev114514.dist-info}/entry_points.txt +0 -0
- {erispulse-2.3.4.dev2.dist-info → erispulse-2.3.4.dev114514.dist-info}/licenses/LICENSE +0 -0
ErisPulse/Core/storage.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
ErisPulse 存储管理模块
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
提供键值存储、事务支持、快照和恢复功能,用于管理框架运行时数据。
|
|
5
5
|
基于SQLite实现持久化存储,支持复杂数据类型和原子操作。
|
|
6
6
|
|
|
7
7
|
支持两种数据库模式:
|
|
@@ -17,21 +17,23 @@ use_global_db = true
|
|
|
17
17
|
{!--< tips >!--}
|
|
18
18
|
1. 支持JSON序列化存储复杂数据类型
|
|
19
19
|
2. 提供事务支持确保数据一致性
|
|
20
|
+
3. 自动快照功能防止数据丢失
|
|
20
21
|
{!--< /tips >!--}
|
|
21
22
|
"""
|
|
22
23
|
|
|
23
24
|
import os
|
|
24
25
|
import json
|
|
25
26
|
import sqlite3
|
|
26
|
-
import
|
|
27
|
-
|
|
28
|
-
from
|
|
27
|
+
import shutil
|
|
28
|
+
import time
|
|
29
|
+
from datetime import datetime
|
|
30
|
+
from typing import List, Dict, Optional, Any, Tuple, Type
|
|
29
31
|
|
|
30
32
|
class StorageManager:
|
|
31
33
|
"""
|
|
32
34
|
存储管理器
|
|
33
35
|
|
|
34
|
-
|
|
36
|
+
单例模式实现,提供键值存储的增删改查、事务和快照管理
|
|
35
37
|
|
|
36
38
|
支持两种数据库模式:
|
|
37
39
|
1. 项目数据库(默认):位于项目目录下的 config/config.db
|
|
@@ -46,6 +48,7 @@ class StorageManager:
|
|
|
46
48
|
{!--< tips >!--}
|
|
47
49
|
1. 使用get/set方法操作存储项
|
|
48
50
|
2. 使用transaction上下文管理事务
|
|
51
|
+
3. 使用snapshot/restore管理数据快照
|
|
49
52
|
{!--< /tips >!--}
|
|
50
53
|
"""
|
|
51
54
|
|
|
@@ -54,8 +57,7 @@ class StorageManager:
|
|
|
54
57
|
DEFAULT_PROJECT_DB_PATH = os.path.join(os.getcwd(), "config", "config.db")
|
|
55
58
|
# 包内全局数据库路径
|
|
56
59
|
GLOBAL_DB_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), "../data/config.db"))
|
|
57
|
-
|
|
58
|
-
_local = threading.local()
|
|
60
|
+
SNAPSHOT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "../data/snapshots"))
|
|
59
61
|
|
|
60
62
|
def __new__(cls, *args, **kwargs):
|
|
61
63
|
if not cls._instance:
|
|
@@ -71,41 +73,20 @@ class StorageManager:
|
|
|
71
73
|
self._ensure_directories()
|
|
72
74
|
|
|
73
75
|
# 根据配置决定使用哪个数据库
|
|
74
|
-
from .
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
use_global_db = storage_config.get("use_global_db", False)
|
|
76
|
+
from .config import config
|
|
77
|
+
use_global_db = config.getConfig("ErisPulse.storage.use_global_db", False)
|
|
78
78
|
|
|
79
79
|
if use_global_db and os.path.exists(self.GLOBAL_DB_PATH):
|
|
80
80
|
self.db_path = self.GLOBAL_DB_PATH
|
|
81
81
|
else:
|
|
82
82
|
self.db_path = self.DEFAULT_PROJECT_DB_PATH
|
|
83
83
|
|
|
84
|
+
self._last_snapshot_time = time.time()
|
|
85
|
+
self._snapshot_interval = 3600
|
|
86
|
+
|
|
84
87
|
self._init_db()
|
|
85
88
|
self._initialized = True
|
|
86
89
|
|
|
87
|
-
@contextmanager
|
|
88
|
-
def _get_connection(self):
|
|
89
|
-
"""
|
|
90
|
-
获取数据库连接(支持事务)
|
|
91
|
-
|
|
92
|
-
如果在事务中,返回事务的连接
|
|
93
|
-
否则创建新连接
|
|
94
|
-
"""
|
|
95
|
-
# 检查是否在线程本地存储中有活动事务连接
|
|
96
|
-
if hasattr(self._local, 'transaction_conn') and self._local.transaction_conn is not None:
|
|
97
|
-
conn = self._local.transaction_conn
|
|
98
|
-
should_close = False
|
|
99
|
-
else:
|
|
100
|
-
conn = sqlite3.connect(self.db_path)
|
|
101
|
-
should_close = True
|
|
102
|
-
|
|
103
|
-
try:
|
|
104
|
-
yield conn
|
|
105
|
-
finally:
|
|
106
|
-
if should_close:
|
|
107
|
-
conn.close()
|
|
108
|
-
|
|
109
90
|
def _ensure_directories(self) -> None:
|
|
110
91
|
"""
|
|
111
92
|
确保必要的目录存在
|
|
@@ -115,6 +96,12 @@ class StorageManager:
|
|
|
115
96
|
os.makedirs(os.path.dirname(self.DEFAULT_PROJECT_DB_PATH), exist_ok=True)
|
|
116
97
|
except Exception:
|
|
117
98
|
pass # 如果无法创建项目目录,则跳过
|
|
99
|
+
|
|
100
|
+
# 确保快照目录存在
|
|
101
|
+
try:
|
|
102
|
+
os.makedirs(self.SNAPSHOT_DIR, exist_ok=True)
|
|
103
|
+
except Exception:
|
|
104
|
+
pass # 如果无法创建快照目录,则跳过
|
|
118
105
|
|
|
119
106
|
def _init_db(self) -> None:
|
|
120
107
|
"""
|
|
@@ -153,6 +140,10 @@ class StorageManager:
|
|
|
153
140
|
except Exception as e:
|
|
154
141
|
logger.error(f"初始化数据库时发生未知错误: {e}")
|
|
155
142
|
raise
|
|
143
|
+
|
|
144
|
+
# 初始化自动快照调度器
|
|
145
|
+
self._last_snapshot_time = time.time() # 初始化为当前时间
|
|
146
|
+
self._snapshot_interval = 3600 # 默认每小时自动快照
|
|
156
147
|
|
|
157
148
|
def get(self, key: str, default: Any = None) -> Any:
|
|
158
149
|
"""
|
|
@@ -235,14 +226,15 @@ class StorageManager:
|
|
|
235
226
|
return False
|
|
236
227
|
|
|
237
228
|
try:
|
|
238
|
-
serialized_value = json.dumps(value)
|
|
239
|
-
with self.
|
|
229
|
+
serialized_value = json.dumps(value) if isinstance(value, (dict, list)) else str(value)
|
|
230
|
+
with self.transaction():
|
|
231
|
+
conn = sqlite3.connect(self.db_path)
|
|
240
232
|
cursor = conn.cursor()
|
|
241
233
|
cursor.execute("INSERT OR REPLACE INTO config (key, value) VALUES (?, ?)", (key, serialized_value))
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
conn.commit()
|
|
234
|
+
conn.commit()
|
|
235
|
+
conn.close()
|
|
245
236
|
|
|
237
|
+
self._check_auto_snapshot()
|
|
246
238
|
return True
|
|
247
239
|
except Exception as e:
|
|
248
240
|
from .logger import logger
|
|
@@ -268,16 +260,17 @@ class StorageManager:
|
|
|
268
260
|
return False
|
|
269
261
|
|
|
270
262
|
try:
|
|
271
|
-
with self.
|
|
263
|
+
with self.transaction():
|
|
264
|
+
conn = sqlite3.connect(self.db_path)
|
|
272
265
|
cursor = conn.cursor()
|
|
273
266
|
for key, value in items.items():
|
|
274
|
-
serialized_value = json.dumps(value)
|
|
267
|
+
serialized_value = json.dumps(value) if isinstance(value, (dict, list)) else str(value)
|
|
275
268
|
cursor.execute("INSERT OR REPLACE INTO config (key, value) VALUES (?, ?)",
|
|
276
269
|
(key, serialized_value))
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
conn.commit()
|
|
270
|
+
conn.commit()
|
|
271
|
+
conn.close()
|
|
280
272
|
|
|
273
|
+
self._check_auto_snapshot()
|
|
281
274
|
return True
|
|
282
275
|
except Exception:
|
|
283
276
|
return False
|
|
@@ -323,13 +316,14 @@ class StorageManager:
|
|
|
323
316
|
return False
|
|
324
317
|
|
|
325
318
|
try:
|
|
326
|
-
with self.
|
|
319
|
+
with self.transaction():
|
|
320
|
+
conn = sqlite3.connect(self.db_path)
|
|
327
321
|
cursor = conn.cursor()
|
|
328
322
|
cursor.execute("DELETE FROM config WHERE key = ?", (key,))
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
conn.commit()
|
|
323
|
+
conn.commit()
|
|
324
|
+
conn.close()
|
|
332
325
|
|
|
326
|
+
self._check_auto_snapshot()
|
|
333
327
|
return True
|
|
334
328
|
except Exception:
|
|
335
329
|
return False
|
|
@@ -349,13 +343,14 @@ class StorageManager:
|
|
|
349
343
|
return False
|
|
350
344
|
|
|
351
345
|
try:
|
|
352
|
-
with self.
|
|
346
|
+
with self.transaction():
|
|
347
|
+
conn = sqlite3.connect(self.db_path)
|
|
353
348
|
cursor = conn.cursor()
|
|
354
349
|
cursor.executemany("DELETE FROM config WHERE key = ?", [(k,) for k in keys])
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
conn.commit()
|
|
350
|
+
conn.commit()
|
|
351
|
+
conn.close()
|
|
358
352
|
|
|
353
|
+
self._check_auto_snapshot()
|
|
359
354
|
return True
|
|
360
355
|
except Exception:
|
|
361
356
|
return False
|
|
@@ -379,12 +374,8 @@ class StorageManager:
|
|
|
379
374
|
cursor = conn.cursor()
|
|
380
375
|
placeholders = ','.join(['?'] * len(keys))
|
|
381
376
|
cursor.execute(f"SELECT key, value FROM config WHERE key IN ({placeholders})", keys)
|
|
382
|
-
results = {
|
|
383
|
-
|
|
384
|
-
try:
|
|
385
|
-
results[row[0]] = json.loads(row[1])
|
|
386
|
-
except json.JSONDecodeError:
|
|
387
|
-
results[row[0]] = row[1]
|
|
377
|
+
results = {row[0]: json.loads(row[1]) if row[1].startswith(('{', '[')) else row[1]
|
|
378
|
+
for row in cursor.fetchall()}
|
|
388
379
|
conn.close()
|
|
389
380
|
return results
|
|
390
381
|
except Exception as e:
|
|
@@ -412,15 +403,6 @@ class StorageManager:
|
|
|
412
403
|
def __exit__(self, *args):
|
|
413
404
|
pass
|
|
414
405
|
return EmptyTransaction()
|
|
415
|
-
|
|
416
|
-
# 如果已经在事务中(嵌套事务),返回一个空事务,复用现有连接
|
|
417
|
-
if hasattr(self._local, 'transaction_conn') and self._local.transaction_conn is not None:
|
|
418
|
-
class NestedTransaction:
|
|
419
|
-
def __enter__(self):
|
|
420
|
-
return self
|
|
421
|
-
def __exit__(self, *args):
|
|
422
|
-
pass
|
|
423
|
-
return NestedTransaction()
|
|
424
406
|
|
|
425
407
|
return self._Transaction(self)
|
|
426
408
|
|
|
@@ -444,18 +426,12 @@ class StorageManager:
|
|
|
444
426
|
self.conn = sqlite3.connect(self.storage_manager.db_path)
|
|
445
427
|
self.cursor = self.conn.cursor()
|
|
446
428
|
self.cursor.execute("BEGIN TRANSACTION")
|
|
447
|
-
# 将连接存储到线程本地存储,供其他方法复用
|
|
448
|
-
self.storage_manager._local.transaction_conn = self.conn
|
|
449
429
|
return self
|
|
450
430
|
|
|
451
431
|
def __exit__(self, exc_type: Type[Exception], exc_val: Exception, exc_tb: Any) -> None:
|
|
452
432
|
"""
|
|
453
433
|
退出事务上下文
|
|
454
434
|
"""
|
|
455
|
-
# 清除线程本地存储中的连接引用
|
|
456
|
-
if hasattr(self.storage_manager._local, 'transaction_conn'):
|
|
457
|
-
self.storage_manager._local.transaction_conn = None
|
|
458
|
-
|
|
459
435
|
if self.conn is not None:
|
|
460
436
|
try:
|
|
461
437
|
if exc_type is None:
|
|
@@ -470,6 +446,58 @@ class StorageManager:
|
|
|
470
446
|
if hasattr(self.conn, 'close'):
|
|
471
447
|
self.conn.close()
|
|
472
448
|
|
|
449
|
+
def _check_auto_snapshot(self) -> None:
|
|
450
|
+
"""
|
|
451
|
+
{!--< internal-use >!--}
|
|
452
|
+
检查并执行自动快照
|
|
453
|
+
"""
|
|
454
|
+
# 避免在初始化过程中调用此方法导致问题
|
|
455
|
+
if not hasattr(self, '_initialized') or not self._initialized:
|
|
456
|
+
return
|
|
457
|
+
|
|
458
|
+
from .logger import logger
|
|
459
|
+
|
|
460
|
+
if not hasattr(self, '_last_snapshot_time') or self._last_snapshot_time is None:
|
|
461
|
+
self._last_snapshot_time = time.time()
|
|
462
|
+
|
|
463
|
+
if not hasattr(self, '_snapshot_interval') or self._snapshot_interval is None:
|
|
464
|
+
self._snapshot_interval = 3600
|
|
465
|
+
|
|
466
|
+
current_time = time.time()
|
|
467
|
+
|
|
468
|
+
try:
|
|
469
|
+
time_diff = current_time - self._last_snapshot_time
|
|
470
|
+
if not isinstance(time_diff, (int, float)):
|
|
471
|
+
raise ValueError("时间差应为数值类型")
|
|
472
|
+
|
|
473
|
+
if not isinstance(self._snapshot_interval, (int, float)):
|
|
474
|
+
raise ValueError("快照间隔应为数值类型")
|
|
475
|
+
|
|
476
|
+
if time_diff > self._snapshot_interval:
|
|
477
|
+
self._last_snapshot_time = current_time
|
|
478
|
+
self.snapshot(f"auto_{datetime.now().strftime('%Y%m%d_%H%M%S')}")
|
|
479
|
+
|
|
480
|
+
except Exception as e:
|
|
481
|
+
logger.error(f"自动快照检查失败: {e}")
|
|
482
|
+
self._last_snapshot_time = current_time
|
|
483
|
+
self._snapshot_interval = 3600
|
|
484
|
+
|
|
485
|
+
def set_snapshot_interval(self, seconds: int) -> None:
|
|
486
|
+
"""
|
|
487
|
+
设置自动快照间隔
|
|
488
|
+
|
|
489
|
+
:param seconds: 间隔秒数
|
|
490
|
+
|
|
491
|
+
:example:
|
|
492
|
+
>>> # 每30分钟自动快照
|
|
493
|
+
>>> storage.set_snapshot_interval(1800)
|
|
494
|
+
"""
|
|
495
|
+
# 避免在初始化过程中调用此方法导致问题
|
|
496
|
+
if not hasattr(self, '_initialized') or not self._initialized:
|
|
497
|
+
return
|
|
498
|
+
|
|
499
|
+
self._snapshot_interval = seconds
|
|
500
|
+
|
|
473
501
|
def clear(self) -> bool:
|
|
474
502
|
"""
|
|
475
503
|
清空所有存储项
|
|
@@ -484,13 +512,11 @@ class StorageManager:
|
|
|
484
512
|
return False
|
|
485
513
|
|
|
486
514
|
try:
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
conn.commit()
|
|
493
|
-
|
|
515
|
+
conn = sqlite3.connect(self.db_path)
|
|
516
|
+
cursor = conn.cursor()
|
|
517
|
+
cursor.execute("DELETE FROM config")
|
|
518
|
+
conn.commit()
|
|
519
|
+
conn.close()
|
|
494
520
|
return True
|
|
495
521
|
except Exception:
|
|
496
522
|
return False
|
|
@@ -514,24 +540,11 @@ class StorageManager:
|
|
|
514
540
|
# 避免在初始化过程中调用此方法导致问题
|
|
515
541
|
if not hasattr(self, '_initialized') or not self._initialized:
|
|
516
542
|
raise AttributeError(f"存储尚未初始化完成: {key}")
|
|
517
|
-
|
|
518
|
-
# 检查键是否存在
|
|
543
|
+
|
|
519
544
|
try:
|
|
520
|
-
|
|
521
|
-
cursor = conn.cursor()
|
|
522
|
-
cursor.execute("SELECT value FROM config WHERE key = ?", (key,))
|
|
523
|
-
result = cursor.fetchone()
|
|
545
|
+
return self.get(key)
|
|
524
546
|
except Exception:
|
|
525
547
|
raise AttributeError(f"存储项 {key} 不存在或访问出错")
|
|
526
|
-
|
|
527
|
-
if result is None:
|
|
528
|
-
raise AttributeError(f"存储项 {key} 不存在")
|
|
529
|
-
|
|
530
|
-
# 解析并返回值
|
|
531
|
-
try:
|
|
532
|
-
return json.loads(result[0])
|
|
533
|
-
except json.JSONDecodeError:
|
|
534
|
-
return result[0]
|
|
535
548
|
|
|
536
549
|
def __setattr__(self, key: str, value: Any) -> None:
|
|
537
550
|
"""
|
|
@@ -559,8 +572,157 @@ class StorageManager:
|
|
|
559
572
|
from .logger import logger
|
|
560
573
|
logger.error(f"设置存储项 {key} 失败: {e}")
|
|
561
574
|
|
|
575
|
+
def snapshot(self, name: Optional[str] = None) -> str:
|
|
576
|
+
"""
|
|
577
|
+
创建数据库快照
|
|
578
|
+
|
|
579
|
+
:param name: 快照名称(可选)
|
|
580
|
+
:return: 快照文件路径
|
|
581
|
+
|
|
582
|
+
:example:
|
|
583
|
+
>>> # 创建命名快照
|
|
584
|
+
>>> snapshot_path = storage.snapshot("before_update")
|
|
585
|
+
>>> # 创建时间戳快照
|
|
586
|
+
>>> snapshot_path = storage.snapshot()
|
|
587
|
+
"""
|
|
588
|
+
# 避免在初始化过程中调用此方法导致问题
|
|
589
|
+
if not hasattr(self, '_initialized') or not self._initialized:
|
|
590
|
+
raise RuntimeError("存储尚未初始化完成")
|
|
591
|
+
|
|
592
|
+
if not name:
|
|
593
|
+
name = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
594
|
+
snapshot_path = os.path.join(self.SNAPSHOT_DIR, f"{name}.db")
|
|
595
|
+
|
|
596
|
+
try:
|
|
597
|
+
# 快照目录
|
|
598
|
+
os.makedirs(self.SNAPSHOT_DIR, exist_ok=True)
|
|
599
|
+
|
|
600
|
+
# 安全关闭连接
|
|
601
|
+
if hasattr(self, "_conn") and self._conn is not None:
|
|
602
|
+
try:
|
|
603
|
+
self._conn.close()
|
|
604
|
+
except Exception as e:
|
|
605
|
+
from .logger import logger
|
|
606
|
+
logger.warning(f"关闭数据库连接时出错: {e}")
|
|
607
|
+
|
|
608
|
+
# 创建快照
|
|
609
|
+
shutil.copy2(self.db_path, snapshot_path)
|
|
610
|
+
from .logger import logger
|
|
611
|
+
logger.info(f"数据库快照已创建: {snapshot_path}")
|
|
612
|
+
return snapshot_path
|
|
613
|
+
except Exception as e:
|
|
614
|
+
from .logger import logger
|
|
615
|
+
logger.error(f"创建快照失败: {e}")
|
|
616
|
+
raise
|
|
617
|
+
|
|
618
|
+
def restore(self, snapshot_name: str) -> bool:
|
|
619
|
+
"""
|
|
620
|
+
从快照恢复数据库
|
|
621
|
+
|
|
622
|
+
:param snapshot_name: 快照名称或路径
|
|
623
|
+
:return: 恢复是否成功
|
|
624
|
+
|
|
625
|
+
:example:
|
|
626
|
+
>>> storage.restore("before_update")
|
|
627
|
+
"""
|
|
628
|
+
# 避免在初始化过程中调用此方法导致问题
|
|
629
|
+
if not hasattr(self, '_initialized') or not self._initialized:
|
|
630
|
+
return False
|
|
631
|
+
|
|
632
|
+
snapshot_path = os.path.join(self.SNAPSHOT_DIR, f"{snapshot_name}.db") \
|
|
633
|
+
if not snapshot_name.endswith('.db') else snapshot_name
|
|
634
|
+
|
|
635
|
+
if not os.path.exists(snapshot_path):
|
|
636
|
+
from .logger import logger
|
|
637
|
+
logger.error(f"快照文件不存在: {snapshot_path}")
|
|
638
|
+
return False
|
|
639
|
+
|
|
640
|
+
try:
|
|
641
|
+
# 安全关闭连接
|
|
642
|
+
if hasattr(self, "_conn") and self._conn is not None:
|
|
643
|
+
try:
|
|
644
|
+
self._conn.close()
|
|
645
|
+
except Exception as e:
|
|
646
|
+
from .logger import logger
|
|
647
|
+
logger.warning(f"关闭数据库连接时出错: {e}")
|
|
648
|
+
|
|
649
|
+
# 执行恢复操作
|
|
650
|
+
shutil.copy2(snapshot_path, self.db_path)
|
|
651
|
+
self._init_db() # 恢复后重新初始化数据库连接
|
|
652
|
+
from .logger import logger
|
|
653
|
+
logger.info(f"数据库已从快照恢复: {snapshot_path}")
|
|
654
|
+
return True
|
|
655
|
+
except Exception as e:
|
|
656
|
+
from .logger import logger
|
|
657
|
+
logger.error(f"恢复快照失败: {e}")
|
|
658
|
+
return False
|
|
659
|
+
|
|
660
|
+
def list_snapshots(self) -> List[Tuple[str, datetime, int]]:
|
|
661
|
+
"""
|
|
662
|
+
列出所有可用的快照
|
|
663
|
+
|
|
664
|
+
:return: 快照信息列表(名称, 创建时间, 大小)
|
|
665
|
+
|
|
666
|
+
:example:
|
|
667
|
+
>>> for name, date, size in storage.list_snapshots():
|
|
668
|
+
>>> print(f"{name} - {date} ({size} bytes)")
|
|
669
|
+
"""
|
|
670
|
+
# 避免在初始化过程中调用此方法导致问题
|
|
671
|
+
if not hasattr(self, '_initialized') or not self._initialized:
|
|
672
|
+
return []
|
|
673
|
+
|
|
674
|
+
try:
|
|
675
|
+
snapshots = []
|
|
676
|
+
for f in os.listdir(self.SNAPSHOT_DIR):
|
|
677
|
+
if f.endswith('.db'):
|
|
678
|
+
path = os.path.join(self.SNAPSHOT_DIR, f)
|
|
679
|
+
stat = os.stat(path)
|
|
680
|
+
snapshots.append((
|
|
681
|
+
f[:-3], # 去掉.db后缀
|
|
682
|
+
datetime.fromtimestamp(stat.st_ctime),
|
|
683
|
+
stat.st_size
|
|
684
|
+
))
|
|
685
|
+
return sorted(snapshots, key=lambda x: x[1], reverse=True)
|
|
686
|
+
except Exception as e:
|
|
687
|
+
from .logger import logger
|
|
688
|
+
logger.error(f"列出快照时发生错误: {e}")
|
|
689
|
+
return []
|
|
690
|
+
|
|
691
|
+
def delete_snapshot(self, snapshot_name: str) -> bool:
|
|
692
|
+
"""
|
|
693
|
+
删除指定的快照
|
|
694
|
+
|
|
695
|
+
:param snapshot_name: 快照名称
|
|
696
|
+
:return: 删除是否成功
|
|
697
|
+
|
|
698
|
+
:example:
|
|
699
|
+
>>> storage.delete_snapshot("old_backup")
|
|
700
|
+
"""
|
|
701
|
+
# 避免在初始化过程中调用此方法导致问题
|
|
702
|
+
if not hasattr(self, '_initialized') or not self._initialized:
|
|
703
|
+
return False
|
|
704
|
+
|
|
705
|
+
snapshot_path = os.path.join(self.SNAPSHOT_DIR, f"{snapshot_name}.db") \
|
|
706
|
+
if not snapshot_name.endswith('.db') else snapshot_name
|
|
707
|
+
|
|
708
|
+
if not os.path.exists(snapshot_path):
|
|
709
|
+
from .logger import logger
|
|
710
|
+
logger.error(f"快照文件不存在: {snapshot_path}")
|
|
711
|
+
return False
|
|
712
|
+
|
|
713
|
+
try:
|
|
714
|
+
os.remove(snapshot_path)
|
|
715
|
+
from .logger import logger
|
|
716
|
+
logger.info(f"快照已删除: {snapshot_path}")
|
|
717
|
+
return True
|
|
718
|
+
except Exception as e:
|
|
719
|
+
from .logger import logger
|
|
720
|
+
logger.error(f"删除快照失败: {e}")
|
|
721
|
+
return False
|
|
722
|
+
|
|
562
723
|
storage = StorageManager()
|
|
563
724
|
|
|
564
725
|
__all__ = [
|
|
565
726
|
"storage"
|
|
566
727
|
]
|
|
728
|
+
|
ErisPulse/Core/storage.pyi
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"""
|
|
8
8
|
ErisPulse 存储管理模块
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
提供键值存储、事务支持、快照和恢复功能,用于管理框架运行时数据。
|
|
11
11
|
基于SQLite实现持久化存储,支持复杂数据类型和原子操作。
|
|
12
12
|
|
|
13
13
|
支持两种数据库模式:
|
|
@@ -23,21 +23,23 @@ use_global_db = true
|
|
|
23
23
|
{!--< tips >!--}
|
|
24
24
|
1. 支持JSON序列化存储复杂数据类型
|
|
25
25
|
2. 提供事务支持确保数据一致性
|
|
26
|
+
3. 自动快照功能防止数据丢失
|
|
26
27
|
{!--< /tips >!--}
|
|
27
28
|
"""
|
|
28
29
|
|
|
29
30
|
import os
|
|
30
31
|
import json
|
|
31
32
|
import sqlite3
|
|
32
|
-
import
|
|
33
|
-
|
|
34
|
-
from
|
|
33
|
+
import shutil
|
|
34
|
+
import time
|
|
35
|
+
from datetime import datetime
|
|
36
|
+
from typing import List, Dict, Optional, Any, Tuple, Type
|
|
35
37
|
|
|
36
38
|
class StorageManager:
|
|
37
39
|
"""
|
|
38
40
|
存储管理器
|
|
39
41
|
|
|
40
|
-
|
|
42
|
+
单例模式实现,提供键值存储的增删改查、事务和快照管理
|
|
41
43
|
|
|
42
44
|
支持两种数据库模式:
|
|
43
45
|
1. 项目数据库(默认):位于项目目录下的 config/config.db
|
|
@@ -52,20 +54,13 @@ class StorageManager:
|
|
|
52
54
|
{!--< tips >!--}
|
|
53
55
|
1. 使用get/set方法操作存储项
|
|
54
56
|
2. 使用transaction上下文管理事务
|
|
57
|
+
3. 使用snapshot/restore管理数据快照
|
|
55
58
|
{!--< /tips >!--}
|
|
56
59
|
"""
|
|
57
60
|
def __new__(cls: object, *args: ..., **kwargs: ...) -> ...:
|
|
58
61
|
...
|
|
59
62
|
def __init__(self: None) -> ...:
|
|
60
63
|
...
|
|
61
|
-
def _get_connection(self: object) -> ...:
|
|
62
|
-
"""
|
|
63
|
-
获取数据库连接(支持事务)
|
|
64
|
-
|
|
65
|
-
如果在事务中,返回事务的连接
|
|
66
|
-
否则创建新连接
|
|
67
|
-
"""
|
|
68
|
-
...
|
|
69
64
|
def _ensure_directories(self: object) -> None:
|
|
70
65
|
"""
|
|
71
66
|
确保必要的目录存在
|
|
@@ -184,6 +179,17 @@ class StorageManager:
|
|
|
184
179
|
>>> storage.set("key2", "value2")
|
|
185
180
|
"""
|
|
186
181
|
...
|
|
182
|
+
def set_snapshot_interval(self: object, seconds: int) -> None:
|
|
183
|
+
"""
|
|
184
|
+
设置自动快照间隔
|
|
185
|
+
|
|
186
|
+
:param seconds: 间隔秒数
|
|
187
|
+
|
|
188
|
+
:example:
|
|
189
|
+
>>> # 每30分钟自动快照
|
|
190
|
+
>>> storage.set_snapshot_interval(1800)
|
|
191
|
+
"""
|
|
192
|
+
...
|
|
187
193
|
def clear(self: object) -> bool:
|
|
188
194
|
"""
|
|
189
195
|
清空所有存储项
|
|
@@ -218,3 +224,50 @@ class StorageManager:
|
|
|
218
224
|
>>> storage.app_name = "MyApp"
|
|
219
225
|
"""
|
|
220
226
|
...
|
|
227
|
+
def snapshot(self: object, name: Optional[str] = ...) -> str:
|
|
228
|
+
"""
|
|
229
|
+
创建数据库快照
|
|
230
|
+
|
|
231
|
+
:param name: 快照名称(可选)
|
|
232
|
+
:return: 快照文件路径
|
|
233
|
+
|
|
234
|
+
:example:
|
|
235
|
+
>>> # 创建命名快照
|
|
236
|
+
>>> snapshot_path = storage.snapshot("before_update")
|
|
237
|
+
>>> # 创建时间戳快照
|
|
238
|
+
>>> snapshot_path = storage.snapshot()
|
|
239
|
+
"""
|
|
240
|
+
...
|
|
241
|
+
def restore(self: object, snapshot_name: str) -> bool:
|
|
242
|
+
"""
|
|
243
|
+
从快照恢复数据库
|
|
244
|
+
|
|
245
|
+
:param snapshot_name: 快照名称或路径
|
|
246
|
+
:return: 恢复是否成功
|
|
247
|
+
|
|
248
|
+
:example:
|
|
249
|
+
>>> storage.restore("before_update")
|
|
250
|
+
"""
|
|
251
|
+
...
|
|
252
|
+
def list_snapshots(self: object) -> List[Tuple[(str, datetime, int)]]:
|
|
253
|
+
"""
|
|
254
|
+
列出所有可用的快照
|
|
255
|
+
|
|
256
|
+
:return: 快照信息列表(名称, 创建时间, 大小)
|
|
257
|
+
|
|
258
|
+
:example:
|
|
259
|
+
>>> for name, date, size in storage.list_snapshots():
|
|
260
|
+
>>> print(f"{name} - {date} ({size} bytes)")
|
|
261
|
+
"""
|
|
262
|
+
...
|
|
263
|
+
def delete_snapshot(self: object, snapshot_name: str) -> bool:
|
|
264
|
+
"""
|
|
265
|
+
删除指定的快照
|
|
266
|
+
|
|
267
|
+
:param snapshot_name: 快照名称
|
|
268
|
+
:return: 删除是否成功
|
|
269
|
+
|
|
270
|
+
:example:
|
|
271
|
+
>>> storage.delete_snapshot("old_backup")
|
|
272
|
+
"""
|
|
273
|
+
...
|