ErisPulse 2.3.4.dev0__py3-none-any.whl → 2.3.4.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/{utils/cli → CLI}/__init__.py +1 -1
- ErisPulse/{utils/cli → CLI}/__init__.pyi +1 -1
- ErisPulse/{utils/cli/__main__.py → CLI/cli.py} +3 -4
- ErisPulse/{utils/cli/__main__.pyi → CLI/cli.pyi} +2 -3
- ErisPulse/{utils/cli → CLI}/commands/init.py +4 -9
- ErisPulse/{utils/cli → CLI}/commands/init.pyi +2 -14
- ErisPulse/{utils/cli → CLI}/commands/install.py +2 -7
- ErisPulse/{utils/cli → CLI}/commands/install.pyi +2 -14
- ErisPulse/{utils/cli → CLI}/commands/list.py +2 -7
- ErisPulse/{utils/cli → CLI}/commands/list.pyi +2 -14
- ErisPulse/{utils/cli → CLI}/commands/list_remote.py +2 -7
- ErisPulse/{utils/cli → CLI}/commands/list_remote.pyi +2 -14
- ErisPulse/{utils/cli → CLI}/commands/run.py +3 -7
- ErisPulse/{utils/cli → CLI}/commands/run.pyi +2 -11
- ErisPulse/{utils/cli → CLI}/commands/self_update.py +2 -7
- ErisPulse/{utils/cli → CLI}/commands/self_update.pyi +2 -14
- ErisPulse/{utils/cli → CLI}/commands/uninstall.py +1 -6
- ErisPulse/{utils/cli → CLI}/commands/uninstall.pyi +1 -14
- ErisPulse/{utils/cli → CLI}/commands/upgrade.py +1 -7
- ErisPulse/{utils/cli → CLI}/commands/upgrade.pyi +1 -14
- ErisPulse/{utils → CLI/utils}/__init__.py +1 -5
- ErisPulse/{utils → CLI/utils}/__init__.pyi +0 -2
- ErisPulse/{utils → CLI/utils}/package_manager.py +146 -20
- ErisPulse/{utils → CLI/utils}/package_manager.pyi +20 -3
- ErisPulse/{utils → CLI/utils}/reload_handler.py +7 -8
- ErisPulse/{utils → CLI/utils}/reload_handler.pyi +1 -1
- ErisPulse/Core/Bases/manager.py +136 -0
- ErisPulse/Core/Bases/manager.pyi +108 -0
- ErisPulse/Core/Bases/module.py +53 -1
- ErisPulse/Core/Bases/module.pyi +43 -0
- ErisPulse/Core/Event/command.py +6 -1
- ErisPulse/Core/_self_config.py +1 -1
- ErisPulse/Core/adapter.py +70 -10
- ErisPulse/Core/adapter.pyi +18 -1
- ErisPulse/Core/exceptions.py +4 -2
- ErisPulse/Core/lifecycle.py +9 -0
- ErisPulse/Core/logger.py +21 -15
- ErisPulse/Core/logger.pyi +2 -1
- ErisPulse/Core/module.py +57 -9
- ErisPulse/Core/module.pyi +12 -1
- ErisPulse/Core/router.py +13 -5
- ErisPulse/Core/storage.py +94 -256
- ErisPulse/Core/storage.pyi +13 -66
- ErisPulse/__init__.py +35 -1237
- ErisPulse/__init__.pyi +3 -290
- ErisPulse/__main__.py +1 -1
- ErisPulse/__main__.pyi +1 -1
- ErisPulse/loaders/__init__.py +22 -0
- ErisPulse/loaders/__init__.pyi +21 -0
- ErisPulse/loaders/adapter_loader.py +187 -0
- ErisPulse/loaders/adapter_loader.pyi +82 -0
- ErisPulse/loaders/base_loader.py +162 -0
- ErisPulse/loaders/base_loader.pyi +23 -0
- ErisPulse/loaders/initializer.py +150 -0
- ErisPulse/loaders/initializer.pyi +60 -0
- ErisPulse/loaders/module_loader.py +618 -0
- ErisPulse/loaders/module_loader.pyi +179 -0
- ErisPulse/loaders/strategy.py +129 -0
- ErisPulse/loaders/strategy.pyi +90 -0
- ErisPulse/sdk.py +435 -0
- ErisPulse/sdk.pyi +158 -0
- {erispulse-2.3.4.dev0.dist-info → erispulse-2.3.4.dev2.dist-info}/METADATA +1 -1
- erispulse-2.3.4.dev2.dist-info/RECORD +103 -0
- ErisPulse/sdk_protocol.py +0 -143
- ErisPulse/sdk_protocol.pyi +0 -97
- erispulse-2.3.4.dev0.dist-info/RECORD +0 -89
- /ErisPulse/{utils/cli → CLI}/base.py +0 -0
- /ErisPulse/{utils/cli → CLI}/base.pyi +0 -0
- /ErisPulse/{utils/cli → CLI}/commands/__init__.py +0 -0
- /ErisPulse/{utils/cli → CLI}/commands/__init__.pyi +0 -0
- /ErisPulse/{utils → CLI}/console.py +0 -0
- /ErisPulse/{utils → CLI}/console.pyi +0 -0
- /ErisPulse/{utils/cli → CLI}/registry.py +0 -0
- /ErisPulse/{utils/cli → CLI}/registry.pyi +0 -0
- {erispulse-2.3.4.dev0.dist-info → erispulse-2.3.4.dev2.dist-info}/WHEEL +0 -0
- {erispulse-2.3.4.dev0.dist-info → erispulse-2.3.4.dev2.dist-info}/entry_points.txt +0 -0
- {erispulse-2.3.4.dev0.dist-info → erispulse-2.3.4.dev2.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,23 +17,21 @@ use_global_db = true
|
|
|
17
17
|
{!--< tips >!--}
|
|
18
18
|
1. 支持JSON序列化存储复杂数据类型
|
|
19
19
|
2. 提供事务支持确保数据一致性
|
|
20
|
-
3. 自动快照功能防止数据丢失
|
|
21
20
|
{!--< /tips >!--}
|
|
22
21
|
"""
|
|
23
22
|
|
|
24
23
|
import os
|
|
25
24
|
import json
|
|
26
25
|
import sqlite3
|
|
27
|
-
import
|
|
28
|
-
import
|
|
29
|
-
from
|
|
30
|
-
from typing import List, Dict, Optional, Any, Tuple, Type
|
|
26
|
+
import threading
|
|
27
|
+
from typing import List, Dict, Optional, Any, Type
|
|
28
|
+
from contextlib import contextmanager
|
|
31
29
|
|
|
32
30
|
class StorageManager:
|
|
33
31
|
"""
|
|
34
32
|
存储管理器
|
|
35
33
|
|
|
36
|
-
|
|
34
|
+
单例模式实现,提供键值存储的增删改查和事务管理
|
|
37
35
|
|
|
38
36
|
支持两种数据库模式:
|
|
39
37
|
1. 项目数据库(默认):位于项目目录下的 config/config.db
|
|
@@ -48,7 +46,6 @@ class StorageManager:
|
|
|
48
46
|
{!--< tips >!--}
|
|
49
47
|
1. 使用get/set方法操作存储项
|
|
50
48
|
2. 使用transaction上下文管理事务
|
|
51
|
-
3. 使用snapshot/restore管理数据快照
|
|
52
49
|
{!--< /tips >!--}
|
|
53
50
|
"""
|
|
54
51
|
|
|
@@ -57,7 +54,8 @@ class StorageManager:
|
|
|
57
54
|
DEFAULT_PROJECT_DB_PATH = os.path.join(os.getcwd(), "config", "config.db")
|
|
58
55
|
# 包内全局数据库路径
|
|
59
56
|
GLOBAL_DB_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), "../data/config.db"))
|
|
60
|
-
|
|
57
|
+
# 线程本地存储,用于跟踪活动事务的连接
|
|
58
|
+
_local = threading.local()
|
|
61
59
|
|
|
62
60
|
def __new__(cls, *args, **kwargs):
|
|
63
61
|
if not cls._instance:
|
|
@@ -73,20 +71,41 @@ class StorageManager:
|
|
|
73
71
|
self._ensure_directories()
|
|
74
72
|
|
|
75
73
|
# 根据配置决定使用哪个数据库
|
|
76
|
-
from .
|
|
77
|
-
|
|
74
|
+
from ._self_config import get_storage_config
|
|
75
|
+
storage_config = get_storage_config()
|
|
76
|
+
|
|
77
|
+
use_global_db = storage_config.get("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
|
-
|
|
87
84
|
self._init_db()
|
|
88
85
|
self._initialized = True
|
|
89
86
|
|
|
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
|
+
|
|
90
109
|
def _ensure_directories(self) -> None:
|
|
91
110
|
"""
|
|
92
111
|
确保必要的目录存在
|
|
@@ -96,12 +115,6 @@ class StorageManager:
|
|
|
96
115
|
os.makedirs(os.path.dirname(self.DEFAULT_PROJECT_DB_PATH), exist_ok=True)
|
|
97
116
|
except Exception:
|
|
98
117
|
pass # 如果无法创建项目目录,则跳过
|
|
99
|
-
|
|
100
|
-
# 确保快照目录存在
|
|
101
|
-
try:
|
|
102
|
-
os.makedirs(self.SNAPSHOT_DIR, exist_ok=True)
|
|
103
|
-
except Exception:
|
|
104
|
-
pass # 如果无法创建快照目录,则跳过
|
|
105
118
|
|
|
106
119
|
def _init_db(self) -> None:
|
|
107
120
|
"""
|
|
@@ -140,10 +153,6 @@ class StorageManager:
|
|
|
140
153
|
except Exception as e:
|
|
141
154
|
logger.error(f"初始化数据库时发生未知错误: {e}")
|
|
142
155
|
raise
|
|
143
|
-
|
|
144
|
-
# 初始化自动快照调度器
|
|
145
|
-
self._last_snapshot_time = time.time() # 初始化为当前时间
|
|
146
|
-
self._snapshot_interval = 3600 # 默认每小时自动快照
|
|
147
156
|
|
|
148
157
|
def get(self, key: str, default: Any = None) -> Any:
|
|
149
158
|
"""
|
|
@@ -226,15 +235,14 @@ class StorageManager:
|
|
|
226
235
|
return False
|
|
227
236
|
|
|
228
237
|
try:
|
|
229
|
-
serialized_value = json.dumps(value)
|
|
230
|
-
with self.
|
|
231
|
-
conn = sqlite3.connect(self.db_path)
|
|
238
|
+
serialized_value = json.dumps(value)
|
|
239
|
+
with self._get_connection() as conn:
|
|
232
240
|
cursor = conn.cursor()
|
|
233
241
|
cursor.execute("INSERT OR REPLACE INTO config (key, value) VALUES (?, ?)", (key, serialized_value))
|
|
234
|
-
|
|
235
|
-
|
|
242
|
+
# 如果不在事务中,提交更改
|
|
243
|
+
if not (hasattr(self._local, 'transaction_conn') and self._local.transaction_conn is not None):
|
|
244
|
+
conn.commit()
|
|
236
245
|
|
|
237
|
-
self._check_auto_snapshot()
|
|
238
246
|
return True
|
|
239
247
|
except Exception as e:
|
|
240
248
|
from .logger import logger
|
|
@@ -260,17 +268,16 @@ class StorageManager:
|
|
|
260
268
|
return False
|
|
261
269
|
|
|
262
270
|
try:
|
|
263
|
-
with self.
|
|
264
|
-
conn = sqlite3.connect(self.db_path)
|
|
271
|
+
with self._get_connection() as conn:
|
|
265
272
|
cursor = conn.cursor()
|
|
266
273
|
for key, value in items.items():
|
|
267
|
-
serialized_value = json.dumps(value)
|
|
274
|
+
serialized_value = json.dumps(value)
|
|
268
275
|
cursor.execute("INSERT OR REPLACE INTO config (key, value) VALUES (?, ?)",
|
|
269
276
|
(key, serialized_value))
|
|
270
|
-
|
|
271
|
-
|
|
277
|
+
# 如果不在事务中,提交更改
|
|
278
|
+
if not (hasattr(self._local, 'transaction_conn') and self._local.transaction_conn is not None):
|
|
279
|
+
conn.commit()
|
|
272
280
|
|
|
273
|
-
self._check_auto_snapshot()
|
|
274
281
|
return True
|
|
275
282
|
except Exception:
|
|
276
283
|
return False
|
|
@@ -316,14 +323,13 @@ class StorageManager:
|
|
|
316
323
|
return False
|
|
317
324
|
|
|
318
325
|
try:
|
|
319
|
-
with self.
|
|
320
|
-
conn = sqlite3.connect(self.db_path)
|
|
326
|
+
with self._get_connection() as conn:
|
|
321
327
|
cursor = conn.cursor()
|
|
322
328
|
cursor.execute("DELETE FROM config WHERE key = ?", (key,))
|
|
323
|
-
|
|
324
|
-
|
|
329
|
+
# 如果不在事务中,提交更改
|
|
330
|
+
if not (hasattr(self._local, 'transaction_conn') and self._local.transaction_conn is not None):
|
|
331
|
+
conn.commit()
|
|
325
332
|
|
|
326
|
-
self._check_auto_snapshot()
|
|
327
333
|
return True
|
|
328
334
|
except Exception:
|
|
329
335
|
return False
|
|
@@ -343,14 +349,13 @@ class StorageManager:
|
|
|
343
349
|
return False
|
|
344
350
|
|
|
345
351
|
try:
|
|
346
|
-
with self.
|
|
347
|
-
conn = sqlite3.connect(self.db_path)
|
|
352
|
+
with self._get_connection() as conn:
|
|
348
353
|
cursor = conn.cursor()
|
|
349
354
|
cursor.executemany("DELETE FROM config WHERE key = ?", [(k,) for k in keys])
|
|
350
|
-
|
|
351
|
-
|
|
355
|
+
# 如果不在事务中,提交更改
|
|
356
|
+
if not (hasattr(self._local, 'transaction_conn') and self._local.transaction_conn is not None):
|
|
357
|
+
conn.commit()
|
|
352
358
|
|
|
353
|
-
self._check_auto_snapshot()
|
|
354
359
|
return True
|
|
355
360
|
except Exception:
|
|
356
361
|
return False
|
|
@@ -374,8 +379,12 @@ class StorageManager:
|
|
|
374
379
|
cursor = conn.cursor()
|
|
375
380
|
placeholders = ','.join(['?'] * len(keys))
|
|
376
381
|
cursor.execute(f"SELECT key, value FROM config WHERE key IN ({placeholders})", keys)
|
|
377
|
-
results = {
|
|
378
|
-
|
|
382
|
+
results = {}
|
|
383
|
+
for row in cursor.fetchall():
|
|
384
|
+
try:
|
|
385
|
+
results[row[0]] = json.loads(row[1])
|
|
386
|
+
except json.JSONDecodeError:
|
|
387
|
+
results[row[0]] = row[1]
|
|
379
388
|
conn.close()
|
|
380
389
|
return results
|
|
381
390
|
except Exception as e:
|
|
@@ -403,6 +412,15 @@ class StorageManager:
|
|
|
403
412
|
def __exit__(self, *args):
|
|
404
413
|
pass
|
|
405
414
|
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()
|
|
406
424
|
|
|
407
425
|
return self._Transaction(self)
|
|
408
426
|
|
|
@@ -426,12 +444,18 @@ class StorageManager:
|
|
|
426
444
|
self.conn = sqlite3.connect(self.storage_manager.db_path)
|
|
427
445
|
self.cursor = self.conn.cursor()
|
|
428
446
|
self.cursor.execute("BEGIN TRANSACTION")
|
|
447
|
+
# 将连接存储到线程本地存储,供其他方法复用
|
|
448
|
+
self.storage_manager._local.transaction_conn = self.conn
|
|
429
449
|
return self
|
|
430
450
|
|
|
431
451
|
def __exit__(self, exc_type: Type[Exception], exc_val: Exception, exc_tb: Any) -> None:
|
|
432
452
|
"""
|
|
433
453
|
退出事务上下文
|
|
434
454
|
"""
|
|
455
|
+
# 清除线程本地存储中的连接引用
|
|
456
|
+
if hasattr(self.storage_manager._local, 'transaction_conn'):
|
|
457
|
+
self.storage_manager._local.transaction_conn = None
|
|
458
|
+
|
|
435
459
|
if self.conn is not None:
|
|
436
460
|
try:
|
|
437
461
|
if exc_type is None:
|
|
@@ -446,58 +470,6 @@ class StorageManager:
|
|
|
446
470
|
if hasattr(self.conn, 'close'):
|
|
447
471
|
self.conn.close()
|
|
448
472
|
|
|
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
|
-
|
|
501
473
|
def clear(self) -> bool:
|
|
502
474
|
"""
|
|
503
475
|
清空所有存储项
|
|
@@ -512,11 +484,13 @@ class StorageManager:
|
|
|
512
484
|
return False
|
|
513
485
|
|
|
514
486
|
try:
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
487
|
+
with self._get_connection() as conn:
|
|
488
|
+
cursor = conn.cursor()
|
|
489
|
+
cursor.execute("DELETE FROM config")
|
|
490
|
+
# 如果不在事务中,提交更改
|
|
491
|
+
if not (hasattr(self._local, 'transaction_conn') and self._local.transaction_conn is not None):
|
|
492
|
+
conn.commit()
|
|
493
|
+
|
|
520
494
|
return True
|
|
521
495
|
except Exception:
|
|
522
496
|
return False
|
|
@@ -540,11 +514,24 @@ class StorageManager:
|
|
|
540
514
|
# 避免在初始化过程中调用此方法导致问题
|
|
541
515
|
if not hasattr(self, '_initialized') or not self._initialized:
|
|
542
516
|
raise AttributeError(f"存储尚未初始化完成: {key}")
|
|
543
|
-
|
|
517
|
+
|
|
518
|
+
# 检查键是否存在
|
|
544
519
|
try:
|
|
545
|
-
|
|
520
|
+
with sqlite3.connect(self.db_path) as conn:
|
|
521
|
+
cursor = conn.cursor()
|
|
522
|
+
cursor.execute("SELECT value FROM config WHERE key = ?", (key,))
|
|
523
|
+
result = cursor.fetchone()
|
|
546
524
|
except Exception:
|
|
547
525
|
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]
|
|
548
535
|
|
|
549
536
|
def __setattr__(self, key: str, value: Any) -> None:
|
|
550
537
|
"""
|
|
@@ -572,157 +559,8 @@ class StorageManager:
|
|
|
572
559
|
from .logger import logger
|
|
573
560
|
logger.error(f"设置存储项 {key} 失败: {e}")
|
|
574
561
|
|
|
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
|
-
|
|
723
562
|
storage = StorageManager()
|
|
724
563
|
|
|
725
564
|
__all__ = [
|
|
726
565
|
"storage"
|
|
727
566
|
]
|
|
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,23 +23,21 @@ use_global_db = true
|
|
|
23
23
|
{!--< tips >!--}
|
|
24
24
|
1. 支持JSON序列化存储复杂数据类型
|
|
25
25
|
2. 提供事务支持确保数据一致性
|
|
26
|
-
3. 自动快照功能防止数据丢失
|
|
27
26
|
{!--< /tips >!--}
|
|
28
27
|
"""
|
|
29
28
|
|
|
30
29
|
import os
|
|
31
30
|
import json
|
|
32
31
|
import sqlite3
|
|
33
|
-
import
|
|
34
|
-
import
|
|
35
|
-
from
|
|
36
|
-
from typing import List, Dict, Optional, Any, Tuple, Type
|
|
32
|
+
import threading
|
|
33
|
+
from typing import List, Dict, Optional, Any, Type
|
|
34
|
+
from contextlib import contextmanager
|
|
37
35
|
|
|
38
36
|
class StorageManager:
|
|
39
37
|
"""
|
|
40
38
|
存储管理器
|
|
41
39
|
|
|
42
|
-
|
|
40
|
+
单例模式实现,提供键值存储的增删改查和事务管理
|
|
43
41
|
|
|
44
42
|
支持两种数据库模式:
|
|
45
43
|
1. 项目数据库(默认):位于项目目录下的 config/config.db
|
|
@@ -54,13 +52,20 @@ class StorageManager:
|
|
|
54
52
|
{!--< tips >!--}
|
|
55
53
|
1. 使用get/set方法操作存储项
|
|
56
54
|
2. 使用transaction上下文管理事务
|
|
57
|
-
3. 使用snapshot/restore管理数据快照
|
|
58
55
|
{!--< /tips >!--}
|
|
59
56
|
"""
|
|
60
57
|
def __new__(cls: object, *args: ..., **kwargs: ...) -> ...:
|
|
61
58
|
...
|
|
62
59
|
def __init__(self: None) -> ...:
|
|
63
60
|
...
|
|
61
|
+
def _get_connection(self: object) -> ...:
|
|
62
|
+
"""
|
|
63
|
+
获取数据库连接(支持事务)
|
|
64
|
+
|
|
65
|
+
如果在事务中,返回事务的连接
|
|
66
|
+
否则创建新连接
|
|
67
|
+
"""
|
|
68
|
+
...
|
|
64
69
|
def _ensure_directories(self: object) -> None:
|
|
65
70
|
"""
|
|
66
71
|
确保必要的目录存在
|
|
@@ -179,17 +184,6 @@ class StorageManager:
|
|
|
179
184
|
>>> storage.set("key2", "value2")
|
|
180
185
|
"""
|
|
181
186
|
...
|
|
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
|
-
...
|
|
193
187
|
def clear(self: object) -> bool:
|
|
194
188
|
"""
|
|
195
189
|
清空所有存储项
|
|
@@ -224,50 +218,3 @@ class StorageManager:
|
|
|
224
218
|
>>> storage.app_name = "MyApp"
|
|
225
219
|
"""
|
|
226
220
|
...
|
|
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
|
-
...
|