ErisPulse 2.2.1.dev0__py3-none-any.whl → 2.3.0__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/__init__.py +14 -0
- ErisPulse/Core/Bases/adapter.py +196 -0
- ErisPulse/Core/Bases/module.py +54 -0
- ErisPulse/Core/Event/__init__.py +14 -0
- ErisPulse/Core/Event/base.py +15 -2
- ErisPulse/Core/Event/command.py +21 -2
- ErisPulse/Core/Event/message.py +15 -0
- ErisPulse/Core/Event/meta.py +15 -0
- ErisPulse/Core/Event/notice.py +15 -0
- ErisPulse/Core/Event/request.py +16 -1
- ErisPulse/Core/__init__.py +38 -19
- ErisPulse/Core/{erispulse_config.py → _self_config.py} +27 -2
- ErisPulse/Core/adapter.py +374 -377
- ErisPulse/Core/config.py +137 -38
- ErisPulse/Core/exceptions.py +6 -1
- ErisPulse/Core/lifecycle.py +167 -0
- ErisPulse/Core/logger.py +97 -49
- ErisPulse/Core/module.py +279 -56
- ErisPulse/Core/router.py +112 -23
- ErisPulse/Core/storage.py +258 -77
- ErisPulse/Core/ux.py +635 -0
- ErisPulse/__init__.py +722 -244
- ErisPulse/__main__.py +1 -1999
- ErisPulse/utils/__init__.py +17 -0
- ErisPulse/utils/cli.py +1092 -0
- ErisPulse/utils/console.py +53 -0
- ErisPulse/utils/package_manager.py +845 -0
- ErisPulse/utils/reload_handler.py +111 -0
- {erispulse-2.2.1.dev0.dist-info → erispulse-2.3.0.dist-info}/METADATA +24 -6
- erispulse-2.3.0.dist-info/RECORD +34 -0
- {erispulse-2.2.1.dev0.dist-info → erispulse-2.3.0.dist-info}/WHEEL +1 -1
- {erispulse-2.2.1.dev0.dist-info → erispulse-2.3.0.dist-info}/licenses/LICENSE +1 -1
- ErisPulse/Core/env.py +0 -15
- ErisPulse/Core/module_registry.py +0 -227
- erispulse-2.2.1.dev0.dist-info/RECORD +0 -26
- {erispulse-2.2.1.dev0.dist-info → erispulse-2.3.0.dist-info}/entry_points.txt +0 -0
ErisPulse/Core/storage.py
CHANGED
|
@@ -4,6 +4,16 @@ ErisPulse 存储管理模块
|
|
|
4
4
|
提供键值存储、事务支持、快照和恢复功能,用于管理框架运行时数据。
|
|
5
5
|
基于SQLite实现持久化存储,支持复杂数据类型和原子操作。
|
|
6
6
|
|
|
7
|
+
支持两种数据库模式:
|
|
8
|
+
1. 项目数据库(默认):位于项目目录下的 config/config.db
|
|
9
|
+
2. 全局数据库:位于包内的 ../data/config.db
|
|
10
|
+
|
|
11
|
+
用户可通过在 config.toml 中配置以下选项来选择使用全局数据库:
|
|
12
|
+
```toml
|
|
13
|
+
[ErisPulse.storage]
|
|
14
|
+
use_global_db = true
|
|
15
|
+
```
|
|
16
|
+
|
|
7
17
|
{!--< tips >!--}
|
|
8
18
|
1. 支持JSON序列化存储复杂数据类型
|
|
9
19
|
2. 提供事务支持确保数据一致性
|
|
@@ -25,6 +35,16 @@ class StorageManager:
|
|
|
25
35
|
|
|
26
36
|
单例模式实现,提供键值存储的增删改查、事务和快照管理
|
|
27
37
|
|
|
38
|
+
支持两种数据库模式:
|
|
39
|
+
1. 项目数据库(默认):位于项目目录下的 config/config.db
|
|
40
|
+
2. 全局数据库:位于包内的 ../data/config.db
|
|
41
|
+
|
|
42
|
+
用户可通过在 config.toml 中配置以下选项来选择使用全局数据库:
|
|
43
|
+
```toml
|
|
44
|
+
[ErisPulse.storage]
|
|
45
|
+
use_global_db = true
|
|
46
|
+
```
|
|
47
|
+
|
|
28
48
|
{!--< tips >!--}
|
|
29
49
|
1. 使用get/set方法操作存储项
|
|
30
50
|
2. 使用transaction上下文管理事务
|
|
@@ -33,44 +53,93 @@ class StorageManager:
|
|
|
33
53
|
"""
|
|
34
54
|
|
|
35
55
|
_instance = None
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
56
|
+
# 默认数据库放在项目下的 config/config.db
|
|
57
|
+
DEFAULT_PROJECT_DB_PATH = os.path.join(os.getcwd(), "config", "config.db")
|
|
58
|
+
# 包内全局数据库路径
|
|
59
|
+
GLOBAL_DB_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), "../data/config.db"))
|
|
60
|
+
SNAPSHOT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "../data/snapshots"))
|
|
61
|
+
|
|
39
62
|
def __new__(cls, *args, **kwargs):
|
|
40
63
|
if not cls._instance:
|
|
41
64
|
cls._instance = super().__new__(cls)
|
|
42
65
|
return cls._instance
|
|
43
66
|
|
|
44
67
|
def __init__(self):
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
68
|
+
# 避免重复初始化
|
|
69
|
+
if hasattr(self, '_initialized') and self._initialized:
|
|
70
|
+
return
|
|
71
|
+
|
|
72
|
+
# 确保目录存在
|
|
73
|
+
self._ensure_directories()
|
|
74
|
+
|
|
75
|
+
# 根据配置决定使用哪个数据库
|
|
76
|
+
from .config import config
|
|
77
|
+
use_global_db = config.getConfig("ErisPulse.storage.use_global_db", False)
|
|
78
|
+
|
|
79
|
+
if use_global_db and os.path.exists(self.GLOBAL_DB_PATH):
|
|
80
|
+
self.db_path = self.GLOBAL_DB_PATH
|
|
81
|
+
else:
|
|
82
|
+
self.db_path = self.DEFAULT_PROJECT_DB_PATH
|
|
83
|
+
|
|
84
|
+
self._last_snapshot_time = time.time()
|
|
85
|
+
self._snapshot_interval = 3600
|
|
86
|
+
|
|
87
|
+
self._init_db()
|
|
88
|
+
self._initialized = True
|
|
89
|
+
|
|
90
|
+
def _ensure_directories(self) -> None:
|
|
91
|
+
"""
|
|
92
|
+
确保必要的目录存在
|
|
93
|
+
"""
|
|
94
|
+
# 确保项目数据库目录存在
|
|
95
|
+
try:
|
|
96
|
+
os.makedirs(os.path.dirname(self.DEFAULT_PROJECT_DB_PATH), exist_ok=True)
|
|
97
|
+
except Exception:
|
|
98
|
+
pass # 如果无法创建项目目录,则跳过
|
|
99
|
+
|
|
100
|
+
# 确保快照目录存在
|
|
101
|
+
try:
|
|
102
|
+
os.makedirs(self.SNAPSHOT_DIR, exist_ok=True)
|
|
103
|
+
except Exception:
|
|
104
|
+
pass # 如果无法创建快照目录,则跳过
|
|
51
105
|
|
|
52
106
|
def _init_db(self) -> None:
|
|
53
107
|
"""
|
|
54
108
|
{!--< internal-use >!--}
|
|
55
109
|
初始化数据库
|
|
56
110
|
"""
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
# 启用WAL模式提高并发性能
|
|
62
|
-
conn.execute("PRAGMA journal_mode=WAL")
|
|
63
|
-
conn.execute("PRAGMA synchronous=NORMAL")
|
|
111
|
+
from .logger import logger
|
|
112
|
+
|
|
113
|
+
logger.debug(f"初始化数据库: {self.db_path}")
|
|
114
|
+
logger.debug(f"创建数据库目录: {os.path.dirname(self.db_path)}")
|
|
64
115
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
116
|
+
try:
|
|
117
|
+
os.makedirs(os.path.dirname(self.db_path), exist_ok=True)
|
|
118
|
+
except Exception:
|
|
119
|
+
pass # 如果无法创建目录,则继续尝试连接数据库
|
|
120
|
+
|
|
121
|
+
try:
|
|
122
|
+
conn = sqlite3.connect(self.db_path)
|
|
123
|
+
|
|
124
|
+
# 启用WAL模式提高并发性能
|
|
125
|
+
conn.execute("PRAGMA journal_mode=WAL")
|
|
126
|
+
conn.execute("PRAGMA synchronous=NORMAL")
|
|
127
|
+
|
|
128
|
+
cursor = conn.cursor()
|
|
129
|
+
cursor.execute("""
|
|
130
|
+
CREATE TABLE IF NOT EXISTS config (
|
|
131
|
+
key TEXT PRIMARY KEY,
|
|
132
|
+
value TEXT NOT NULL
|
|
133
|
+
)
|
|
134
|
+
""")
|
|
135
|
+
conn.commit()
|
|
136
|
+
conn.close()
|
|
137
|
+
except sqlite3.OperationalError as e:
|
|
138
|
+
logger.error(f"无法创建或打开数据库文件: {e}")
|
|
139
|
+
raise
|
|
140
|
+
except Exception as e:
|
|
141
|
+
logger.error(f"初始化数据库时发生未知错误: {e}")
|
|
142
|
+
raise
|
|
74
143
|
|
|
75
144
|
# 初始化自动快照调度器
|
|
76
145
|
self._last_snapshot_time = time.time() # 初始化为当前时间
|
|
@@ -88,6 +157,10 @@ class StorageManager:
|
|
|
88
157
|
>>> timeout = storage.get("network.timeout", 30)
|
|
89
158
|
>>> user_settings = storage.get("user.settings", {})
|
|
90
159
|
"""
|
|
160
|
+
# 避免在初始化过程中调用此方法导致问题
|
|
161
|
+
if not hasattr(self, '_initialized') or not self._initialized:
|
|
162
|
+
return default
|
|
163
|
+
|
|
91
164
|
try:
|
|
92
165
|
with sqlite3.connect(self.db_path) as conn:
|
|
93
166
|
cursor = conn.cursor()
|
|
@@ -104,8 +177,13 @@ class StorageManager:
|
|
|
104
177
|
self._init_db()
|
|
105
178
|
return self.get(key, default)
|
|
106
179
|
else:
|
|
107
|
-
from . import logger
|
|
180
|
+
from .logger import logger
|
|
108
181
|
logger.error(f"数据库操作错误: {e}")
|
|
182
|
+
return default
|
|
183
|
+
except Exception as e:
|
|
184
|
+
from .logger import logger
|
|
185
|
+
logger.error(f"获取存储项 {key} 时发生错误: {e}")
|
|
186
|
+
return default
|
|
109
187
|
|
|
110
188
|
def get_all_keys(self) -> List[str]:
|
|
111
189
|
"""
|
|
@@ -117,11 +195,20 @@ class StorageManager:
|
|
|
117
195
|
>>> all_keys = storage.get_all_keys()
|
|
118
196
|
>>> print(f"共有 {len(all_keys)} 个存储项")
|
|
119
197
|
"""
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
return [row[0] for row in cursor.fetchall()]
|
|
198
|
+
# 避免在初始化过程中调用此方法导致问题
|
|
199
|
+
if not hasattr(self, '_initialized') or not self._initialized:
|
|
200
|
+
return []
|
|
124
201
|
|
|
202
|
+
try:
|
|
203
|
+
with sqlite3.connect(self.db_path) as conn:
|
|
204
|
+
cursor = conn.cursor()
|
|
205
|
+
cursor.execute("SELECT key FROM config")
|
|
206
|
+
return [row[0] for row in cursor.fetchall()]
|
|
207
|
+
except Exception as e:
|
|
208
|
+
from .logger import logger
|
|
209
|
+
logger.error(f"获取所有键名时发生错误: {e}")
|
|
210
|
+
return []
|
|
211
|
+
|
|
125
212
|
def set(self, key: str, value: Any) -> bool:
|
|
126
213
|
"""
|
|
127
214
|
设置存储项的值
|
|
@@ -134,6 +221,10 @@ class StorageManager:
|
|
|
134
221
|
>>> storage.set("app.name", "MyApp")
|
|
135
222
|
>>> storage.set("user.settings", {"theme": "dark"})
|
|
136
223
|
"""
|
|
224
|
+
# 避免在初始化过程中调用此方法导致问题
|
|
225
|
+
if not hasattr(self, '_initialized') or not self._initialized:
|
|
226
|
+
return False
|
|
227
|
+
|
|
137
228
|
try:
|
|
138
229
|
serialized_value = json.dumps(value) if isinstance(value, (dict, list)) else str(value)
|
|
139
230
|
with self.transaction():
|
|
@@ -146,6 +237,8 @@ class StorageManager:
|
|
|
146
237
|
self._check_auto_snapshot()
|
|
147
238
|
return True
|
|
148
239
|
except Exception as e:
|
|
240
|
+
from .logger import logger
|
|
241
|
+
logger.error(f"设置存储项 {key} 失败: {e}")
|
|
149
242
|
return False
|
|
150
243
|
|
|
151
244
|
def set_multi(self, items: Dict[str, Any]) -> bool:
|
|
@@ -162,6 +255,10 @@ class StorageManager:
|
|
|
162
255
|
>>> "app.debug": True
|
|
163
256
|
>>> })
|
|
164
257
|
"""
|
|
258
|
+
# 避免在初始化过程中调用此方法导致问题
|
|
259
|
+
if not hasattr(self, '_initialized') or not self._initialized:
|
|
260
|
+
return False
|
|
261
|
+
|
|
165
262
|
try:
|
|
166
263
|
with self.transaction():
|
|
167
264
|
conn = sqlite3.connect(self.db_path)
|
|
@@ -175,7 +272,7 @@ class StorageManager:
|
|
|
175
272
|
|
|
176
273
|
self._check_auto_snapshot()
|
|
177
274
|
return True
|
|
178
|
-
except Exception
|
|
275
|
+
except Exception:
|
|
179
276
|
return False
|
|
180
277
|
|
|
181
278
|
def getConfig(self, key: str, default: Any = None) -> Any:
|
|
@@ -188,7 +285,7 @@ class StorageManager:
|
|
|
188
285
|
try:
|
|
189
286
|
from .config import config
|
|
190
287
|
return config.getConfig(key, default)
|
|
191
|
-
except Exception
|
|
288
|
+
except Exception:
|
|
192
289
|
return default
|
|
193
290
|
|
|
194
291
|
def setConfig(self, key: str, value: Any) -> bool:
|
|
@@ -201,7 +298,7 @@ class StorageManager:
|
|
|
201
298
|
try:
|
|
202
299
|
from .config import config
|
|
203
300
|
return config.setConfig(key, value)
|
|
204
|
-
except Exception
|
|
301
|
+
except Exception:
|
|
205
302
|
return False
|
|
206
303
|
|
|
207
304
|
def delete(self, key: str) -> bool:
|
|
@@ -214,6 +311,10 @@ class StorageManager:
|
|
|
214
311
|
:example:
|
|
215
312
|
>>> storage.delete("temp.session")
|
|
216
313
|
"""
|
|
314
|
+
# 避免在初始化过程中调用此方法导致问题
|
|
315
|
+
if not hasattr(self, '_initialized') or not self._initialized:
|
|
316
|
+
return False
|
|
317
|
+
|
|
217
318
|
try:
|
|
218
319
|
with self.transaction():
|
|
219
320
|
conn = sqlite3.connect(self.db_path)
|
|
@@ -224,7 +325,7 @@ class StorageManager:
|
|
|
224
325
|
|
|
225
326
|
self._check_auto_snapshot()
|
|
226
327
|
return True
|
|
227
|
-
except Exception
|
|
328
|
+
except Exception:
|
|
228
329
|
return False
|
|
229
330
|
|
|
230
331
|
def delete_multi(self, keys: List[str]) -> bool:
|
|
@@ -237,6 +338,10 @@ class StorageManager:
|
|
|
237
338
|
:example:
|
|
238
339
|
>>> storage.delete_multi(["temp.key1", "temp.key2"])
|
|
239
340
|
"""
|
|
341
|
+
# 避免在初始化过程中调用此方法导致问题
|
|
342
|
+
if not hasattr(self, '_initialized') or not self._initialized:
|
|
343
|
+
return False
|
|
344
|
+
|
|
240
345
|
try:
|
|
241
346
|
with self.transaction():
|
|
242
347
|
conn = sqlite3.connect(self.db_path)
|
|
@@ -247,7 +352,7 @@ class StorageManager:
|
|
|
247
352
|
|
|
248
353
|
self._check_auto_snapshot()
|
|
249
354
|
return True
|
|
250
|
-
except Exception
|
|
355
|
+
except Exception:
|
|
251
356
|
return False
|
|
252
357
|
|
|
253
358
|
def get_multi(self, keys: List[str]) -> Dict[str, Any]:
|
|
@@ -260,14 +365,23 @@ class StorageManager:
|
|
|
260
365
|
:example:
|
|
261
366
|
>>> settings = storage.get_multi(["app.name", "app.version"])
|
|
262
367
|
"""
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
368
|
+
# 避免在初始化过程中调用此方法导致问题
|
|
369
|
+
if not hasattr(self, '_initialized') or not self._initialized:
|
|
370
|
+
return {}
|
|
371
|
+
|
|
372
|
+
try:
|
|
373
|
+
conn = sqlite3.connect(self.db_path)
|
|
374
|
+
cursor = conn.cursor()
|
|
375
|
+
placeholders = ','.join(['?'] * len(keys))
|
|
376
|
+
cursor.execute(f"SELECT key, value FROM config WHERE key IN ({placeholders})", keys)
|
|
377
|
+
results = {row[0]: json.loads(row[1]) if row[1].startswith(('{', '[')) else row[1]
|
|
378
|
+
for row in cursor.fetchall()}
|
|
379
|
+
conn.close()
|
|
380
|
+
return results
|
|
381
|
+
except Exception as e:
|
|
382
|
+
from .logger import logger
|
|
383
|
+
logger.error(f"批量获取存储项失败: {e}")
|
|
384
|
+
return {}
|
|
271
385
|
|
|
272
386
|
def transaction(self) -> 'StorageManager._Transaction':
|
|
273
387
|
"""
|
|
@@ -280,6 +394,16 @@ class StorageManager:
|
|
|
280
394
|
>>> storage.set("key1", "value1")
|
|
281
395
|
>>> storage.set("key2", "value2")
|
|
282
396
|
"""
|
|
397
|
+
# 避免在初始化过程中调用此方法导致问题
|
|
398
|
+
if not hasattr(self, '_initialized') or not self._initialized:
|
|
399
|
+
# 返回一个空的事务对象
|
|
400
|
+
class EmptyTransaction:
|
|
401
|
+
def __enter__(self):
|
|
402
|
+
return self
|
|
403
|
+
def __exit__(self, *args):
|
|
404
|
+
pass
|
|
405
|
+
return EmptyTransaction()
|
|
406
|
+
|
|
283
407
|
return self._Transaction(self)
|
|
284
408
|
|
|
285
409
|
class _Transaction:
|
|
@@ -308,19 +432,29 @@ class StorageManager:
|
|
|
308
432
|
"""
|
|
309
433
|
退出事务上下文
|
|
310
434
|
"""
|
|
311
|
-
if
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
435
|
+
if self.conn is not None:
|
|
436
|
+
try:
|
|
437
|
+
if exc_type is None:
|
|
438
|
+
if hasattr(self.conn, 'commit'):
|
|
439
|
+
self.conn.commit()
|
|
440
|
+
else:
|
|
441
|
+
if hasattr(self.conn, 'rollback'):
|
|
442
|
+
self.conn.rollback()
|
|
443
|
+
from .logger import logger
|
|
444
|
+
logger.error(f"事务执行失败: {exc_val}")
|
|
445
|
+
finally:
|
|
446
|
+
if hasattr(self.conn, 'close'):
|
|
447
|
+
self.conn.close()
|
|
318
448
|
|
|
319
449
|
def _check_auto_snapshot(self) -> None:
|
|
320
450
|
"""
|
|
321
451
|
{!--< internal-use >!--}
|
|
322
452
|
检查并执行自动快照
|
|
323
453
|
"""
|
|
454
|
+
# 避免在初始化过程中调用此方法导致问题
|
|
455
|
+
if not hasattr(self, '_initialized') or not self._initialized:
|
|
456
|
+
return
|
|
457
|
+
|
|
324
458
|
from .logger import logger
|
|
325
459
|
|
|
326
460
|
if not hasattr(self, '_last_snapshot_time') or self._last_snapshot_time is None:
|
|
@@ -358,6 +492,10 @@ class StorageManager:
|
|
|
358
492
|
>>> # 每30分钟自动快照
|
|
359
493
|
>>> storage.set_snapshot_interval(1800)
|
|
360
494
|
"""
|
|
495
|
+
# 避免在初始化过程中调用此方法导致问题
|
|
496
|
+
if not hasattr(self, '_initialized') or not self._initialized:
|
|
497
|
+
return
|
|
498
|
+
|
|
361
499
|
self._snapshot_interval = seconds
|
|
362
500
|
|
|
363
501
|
def clear(self) -> bool:
|
|
@@ -369,6 +507,10 @@ class StorageManager:
|
|
|
369
507
|
:example:
|
|
370
508
|
>>> storage.clear() # 清空所有存储
|
|
371
509
|
"""
|
|
510
|
+
# 避免在初始化过程中调用此方法导致问题
|
|
511
|
+
if not hasattr(self, '_initialized') or not self._initialized:
|
|
512
|
+
return False
|
|
513
|
+
|
|
372
514
|
try:
|
|
373
515
|
conn = sqlite3.connect(self.db_path)
|
|
374
516
|
cursor = conn.cursor()
|
|
@@ -376,7 +518,7 @@ class StorageManager:
|
|
|
376
518
|
conn.commit()
|
|
377
519
|
conn.close()
|
|
378
520
|
return True
|
|
379
|
-
except Exception
|
|
521
|
+
except Exception:
|
|
380
522
|
return False
|
|
381
523
|
|
|
382
524
|
def __getattr__(self, key: str) -> Any:
|
|
@@ -386,16 +528,23 @@ class StorageManager:
|
|
|
386
528
|
:param key: 存储项键名
|
|
387
529
|
:return: 存储项的值
|
|
388
530
|
|
|
389
|
-
:raises
|
|
531
|
+
:raises AttributeError: 当存储项不存在时抛出
|
|
390
532
|
|
|
391
533
|
:example:
|
|
392
534
|
>>> app_name = storage.app_name
|
|
393
535
|
"""
|
|
536
|
+
# 避免访问内置属性时出现问题
|
|
537
|
+
if key.startswith('_'):
|
|
538
|
+
raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{key}'")
|
|
539
|
+
|
|
540
|
+
# 避免在初始化过程中调用此方法导致问题
|
|
541
|
+
if not hasattr(self, '_initialized') or not self._initialized:
|
|
542
|
+
raise AttributeError(f"存储尚未初始化完成: {key}")
|
|
543
|
+
|
|
394
544
|
try:
|
|
395
545
|
return self.get(key)
|
|
396
|
-
except
|
|
397
|
-
|
|
398
|
-
logger.error(f"存储项 {key} 不存在")
|
|
546
|
+
except Exception:
|
|
547
|
+
raise AttributeError(f"存储项 {key} 不存在或访问出错")
|
|
399
548
|
|
|
400
549
|
def __setattr__(self, key: str, value: Any) -> None:
|
|
401
550
|
"""
|
|
@@ -407,10 +556,20 @@ class StorageManager:
|
|
|
407
556
|
:example:
|
|
408
557
|
>>> storage.app_name = "MyApp"
|
|
409
558
|
"""
|
|
559
|
+
# 避免在初始化过程中出现问题
|
|
560
|
+
if key.startswith('_'):
|
|
561
|
+
super().__setattr__(key, value)
|
|
562
|
+
return
|
|
563
|
+
|
|
564
|
+
# 如果还未初始化完成,直接设置属性
|
|
565
|
+
if not hasattr(self, '_initialized') or not self._initialized:
|
|
566
|
+
super().__setattr__(key, value)
|
|
567
|
+
return
|
|
568
|
+
|
|
410
569
|
try:
|
|
411
570
|
self.set(key, value)
|
|
412
571
|
except Exception as e:
|
|
413
|
-
from . import logger
|
|
572
|
+
from .logger import logger
|
|
414
573
|
logger.error(f"设置存储项 {key} 失败: {e}")
|
|
415
574
|
|
|
416
575
|
def snapshot(self, name: Optional[str] = None) -> str:
|
|
@@ -426,6 +585,10 @@ class StorageManager:
|
|
|
426
585
|
>>> # 创建时间戳快照
|
|
427
586
|
>>> snapshot_path = storage.snapshot()
|
|
428
587
|
"""
|
|
588
|
+
# 避免在初始化过程中调用此方法导致问题
|
|
589
|
+
if not hasattr(self, '_initialized') or not self._initialized:
|
|
590
|
+
raise RuntimeError("存储尚未初始化完成")
|
|
591
|
+
|
|
429
592
|
if not name:
|
|
430
593
|
name = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
431
594
|
snapshot_path = os.path.join(self.SNAPSHOT_DIR, f"{name}.db")
|
|
@@ -439,16 +602,16 @@ class StorageManager:
|
|
|
439
602
|
try:
|
|
440
603
|
self._conn.close()
|
|
441
604
|
except Exception as e:
|
|
442
|
-
from . import logger
|
|
605
|
+
from .logger import logger
|
|
443
606
|
logger.warning(f"关闭数据库连接时出错: {e}")
|
|
444
607
|
|
|
445
608
|
# 创建快照
|
|
446
609
|
shutil.copy2(self.db_path, snapshot_path)
|
|
447
|
-
from . import logger
|
|
610
|
+
from .logger import logger
|
|
448
611
|
logger.info(f"数据库快照已创建: {snapshot_path}")
|
|
449
612
|
return snapshot_path
|
|
450
613
|
except Exception as e:
|
|
451
|
-
from . import logger
|
|
614
|
+
from .logger import logger
|
|
452
615
|
logger.error(f"创建快照失败: {e}")
|
|
453
616
|
raise
|
|
454
617
|
|
|
@@ -462,11 +625,15 @@ class StorageManager:
|
|
|
462
625
|
:example:
|
|
463
626
|
>>> storage.restore("before_update")
|
|
464
627
|
"""
|
|
628
|
+
# 避免在初始化过程中调用此方法导致问题
|
|
629
|
+
if not hasattr(self, '_initialized') or not self._initialized:
|
|
630
|
+
return False
|
|
631
|
+
|
|
465
632
|
snapshot_path = os.path.join(self.SNAPSHOT_DIR, f"{snapshot_name}.db") \
|
|
466
633
|
if not snapshot_name.endswith('.db') else snapshot_name
|
|
467
634
|
|
|
468
635
|
if not os.path.exists(snapshot_path):
|
|
469
|
-
from . import logger
|
|
636
|
+
from .logger import logger
|
|
470
637
|
logger.error(f"快照文件不存在: {snapshot_path}")
|
|
471
638
|
return False
|
|
472
639
|
|
|
@@ -476,17 +643,17 @@ class StorageManager:
|
|
|
476
643
|
try:
|
|
477
644
|
self._conn.close()
|
|
478
645
|
except Exception as e:
|
|
479
|
-
from . import logger
|
|
646
|
+
from .logger import logger
|
|
480
647
|
logger.warning(f"关闭数据库连接时出错: {e}")
|
|
481
648
|
|
|
482
649
|
# 执行恢复操作
|
|
483
650
|
shutil.copy2(snapshot_path, self.db_path)
|
|
484
651
|
self._init_db() # 恢复后重新初始化数据库连接
|
|
485
|
-
from . import logger
|
|
652
|
+
from .logger import logger
|
|
486
653
|
logger.info(f"数据库已从快照恢复: {snapshot_path}")
|
|
487
654
|
return True
|
|
488
655
|
except Exception as e:
|
|
489
|
-
from . import logger
|
|
656
|
+
from .logger import logger
|
|
490
657
|
logger.error(f"恢复快照失败: {e}")
|
|
491
658
|
return False
|
|
492
659
|
|
|
@@ -500,17 +667,26 @@ class StorageManager:
|
|
|
500
667
|
>>> for name, date, size in storage.list_snapshots():
|
|
501
668
|
>>> print(f"{name} - {date} ({size} bytes)")
|
|
502
669
|
"""
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
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 []
|
|
514
690
|
|
|
515
691
|
def delete_snapshot(self, snapshot_name: str) -> bool:
|
|
516
692
|
"""
|
|
@@ -522,21 +698,25 @@ class StorageManager:
|
|
|
522
698
|
:example:
|
|
523
699
|
>>> storage.delete_snapshot("old_backup")
|
|
524
700
|
"""
|
|
701
|
+
# 避免在初始化过程中调用此方法导致问题
|
|
702
|
+
if not hasattr(self, '_initialized') or not self._initialized:
|
|
703
|
+
return False
|
|
704
|
+
|
|
525
705
|
snapshot_path = os.path.join(self.SNAPSHOT_DIR, f"{snapshot_name}.db") \
|
|
526
706
|
if not snapshot_name.endswith('.db') else snapshot_name
|
|
527
707
|
|
|
528
708
|
if not os.path.exists(snapshot_path):
|
|
529
|
-
from . import logger
|
|
709
|
+
from .logger import logger
|
|
530
710
|
logger.error(f"快照文件不存在: {snapshot_path}")
|
|
531
711
|
return False
|
|
532
712
|
|
|
533
713
|
try:
|
|
534
714
|
os.remove(snapshot_path)
|
|
535
|
-
from . import logger
|
|
715
|
+
from .logger import logger
|
|
536
716
|
logger.info(f"快照已删除: {snapshot_path}")
|
|
537
717
|
return True
|
|
538
718
|
except Exception as e:
|
|
539
|
-
from . import logger
|
|
719
|
+
from .logger import logger
|
|
540
720
|
logger.error(f"删除快照失败: {e}")
|
|
541
721
|
return False
|
|
542
722
|
|
|
@@ -545,3 +725,4 @@ storage = StorageManager()
|
|
|
545
725
|
__all__ = [
|
|
546
726
|
"storage"
|
|
547
727
|
]
|
|
728
|
+
|