ErisPulse 2.3.3.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.
Files changed (97) hide show
  1. ErisPulse/CLI/__init__.py +11 -0
  2. ErisPulse/CLI/__init__.pyi +13 -0
  3. ErisPulse/CLI/base.py +52 -0
  4. ErisPulse/CLI/base.pyi +50 -0
  5. ErisPulse/CLI/cli.py +224 -0
  6. ErisPulse/CLI/cli.pyi +80 -0
  7. ErisPulse/CLI/commands/__init__.py +6 -0
  8. ErisPulse/CLI/commands/__init__.pyi +12 -0
  9. ErisPulse/CLI/commands/init.py +395 -0
  10. ErisPulse/CLI/commands/init.pyi +70 -0
  11. ErisPulse/CLI/commands/install.py +302 -0
  12. ErisPulse/CLI/commands/install.pyi +58 -0
  13. ErisPulse/CLI/commands/list.py +160 -0
  14. ErisPulse/CLI/commands/list.pyi +44 -0
  15. ErisPulse/CLI/commands/list_remote.py +123 -0
  16. ErisPulse/CLI/commands/list_remote.pyi +35 -0
  17. ErisPulse/CLI/commands/run.py +108 -0
  18. ErisPulse/CLI/commands/run.pyi +39 -0
  19. ErisPulse/CLI/commands/self_update.py +232 -0
  20. ErisPulse/CLI/commands/self_update.pyi +47 -0
  21. ErisPulse/CLI/commands/uninstall.py +32 -0
  22. ErisPulse/CLI/commands/uninstall.pyi +24 -0
  23. ErisPulse/CLI/commands/upgrade.py +56 -0
  24. ErisPulse/CLI/commands/upgrade.pyi +25 -0
  25. ErisPulse/CLI/console.pyi +20 -0
  26. ErisPulse/CLI/registry.py +112 -0
  27. ErisPulse/CLI/registry.pyi +99 -0
  28. ErisPulse/{utils → CLI/utils}/__init__.py +2 -6
  29. ErisPulse/CLI/utils/__init__.pyi +14 -0
  30. ErisPulse/{utils → CLI/utils}/package_manager.py +146 -20
  31. ErisPulse/CLI/utils/package_manager.pyi +241 -0
  32. ErisPulse/{utils → CLI/utils}/reload_handler.py +7 -8
  33. ErisPulse/CLI/utils/reload_handler.pyi +64 -0
  34. ErisPulse/Core/Bases/__init__.pyi +14 -0
  35. ErisPulse/Core/Bases/adapter.py +13 -1
  36. ErisPulse/Core/Bases/adapter.pyi +140 -0
  37. ErisPulse/Core/Bases/manager.py +136 -0
  38. ErisPulse/Core/Bases/manager.pyi +108 -0
  39. ErisPulse/Core/Bases/module.py +53 -1
  40. ErisPulse/Core/Bases/module.pyi +95 -0
  41. ErisPulse/Core/Event/__init__.pyi +26 -0
  42. ErisPulse/Core/Event/base.pyi +62 -0
  43. ErisPulse/Core/Event/command.py +6 -1
  44. ErisPulse/Core/Event/command.pyi +113 -0
  45. ErisPulse/Core/Event/exceptions.pyi +43 -0
  46. ErisPulse/Core/Event/message.pyi +93 -0
  47. ErisPulse/Core/Event/meta.pyi +92 -0
  48. ErisPulse/Core/Event/notice.pyi +108 -0
  49. ErisPulse/Core/Event/request.pyi +76 -0
  50. ErisPulse/Core/Event/wrapper.py +2 -3
  51. ErisPulse/Core/Event/wrapper.pyi +403 -0
  52. ErisPulse/Core/__init__.py +16 -13
  53. ErisPulse/Core/__init__.pyi +16 -0
  54. ErisPulse/Core/_self_config.py +1 -1
  55. ErisPulse/Core/_self_config.pyi +72 -0
  56. ErisPulse/Core/adapter.py +70 -10
  57. ErisPulse/Core/adapter.pyi +246 -0
  58. ErisPulse/Core/config.pyi +70 -0
  59. ErisPulse/Core/exceptions.py +4 -2
  60. ErisPulse/Core/exceptions.pyi +60 -0
  61. ErisPulse/Core/lifecycle.py +15 -1
  62. ErisPulse/Core/lifecycle.pyi +92 -0
  63. ErisPulse/Core/logger.py +21 -15
  64. ErisPulse/Core/logger.pyi +169 -0
  65. ErisPulse/Core/module.py +57 -9
  66. ErisPulse/Core/module.pyi +189 -0
  67. ErisPulse/Core/router.py +13 -5
  68. ErisPulse/Core/router.pyi +120 -0
  69. ErisPulse/Core/storage.py +94 -256
  70. ErisPulse/Core/storage.pyi +220 -0
  71. ErisPulse/__init__.py +35 -1236
  72. ErisPulse/__init__.pyi +22 -0
  73. ErisPulse/__main__.py +1 -1
  74. ErisPulse/__main__.pyi +24 -0
  75. ErisPulse/loaders/__init__.py +22 -0
  76. ErisPulse/loaders/__init__.pyi +21 -0
  77. ErisPulse/loaders/adapter_loader.py +187 -0
  78. ErisPulse/loaders/adapter_loader.pyi +82 -0
  79. ErisPulse/loaders/base_loader.py +162 -0
  80. ErisPulse/loaders/base_loader.pyi +23 -0
  81. ErisPulse/loaders/initializer.py +150 -0
  82. ErisPulse/loaders/initializer.pyi +60 -0
  83. ErisPulse/loaders/module_loader.py +618 -0
  84. ErisPulse/loaders/module_loader.pyi +179 -0
  85. ErisPulse/loaders/strategy.py +129 -0
  86. ErisPulse/loaders/strategy.pyi +90 -0
  87. ErisPulse/sdk.py +435 -0
  88. ErisPulse/sdk.pyi +158 -0
  89. {erispulse-2.3.3.dev0.dist-info → erispulse-2.3.4.dev2.dist-info}/METADATA +6 -20
  90. erispulse-2.3.4.dev2.dist-info/RECORD +103 -0
  91. {erispulse-2.3.3.dev0.dist-info → erispulse-2.3.4.dev2.dist-info}/licenses/LICENSE +3 -3
  92. ErisPulse/Core/ux.py +0 -635
  93. ErisPulse/utils/cli.py +0 -1097
  94. erispulse-2.3.3.dev0.dist-info/RECORD +0 -35
  95. /ErisPulse/{utils → CLI}/console.py +0 -0
  96. {erispulse-2.3.3.dev0.dist-info → erispulse-2.3.4.dev2.dist-info}/WHEEL +0 -0
  97. {erispulse-2.3.3.dev0.dist-info → erispulse-2.3.4.dev2.dist-info}/entry_points.txt +0 -0
ErisPulse/Core/router.py CHANGED
@@ -235,13 +235,20 @@ class RouterManager:
235
235
  try:
236
236
  full_path = f"/{module_name}{path}"
237
237
 
238
- # 使用类型忽略注释
239
- if full_path in self.app.websocket_routes: # type: ignore || 原因:实际上,FastAPI的API提供了websocket_routes属性
240
- self.app.remove_api_websocket_route(full_path) # type: ignore || 原因:实际上,FastAPI的API提供了remove_api_websocket_route方法
238
+ # 检查 WebSocket 路由是否存在于我们的内部记录中
239
+ if full_path in self._websocket_routes.get(module_name, {}):
241
240
  display_url = self._format_display_url(f"{self.base_url}{full_path}")
242
241
  logger.info(f"注销WebSocket: {display_url}")
243
242
  del self._websocket_routes[module_name][full_path]
243
+
244
+ # 从 FastAPI 路由列表中移除对应的 WebSocket 路由
245
+ # FastAPI 的 WebSocket 路由有 websocket_endpoint 属性
246
+ self.app.router.routes = [
247
+ route for route in self.app.router.routes
248
+ if not (hasattr(route, 'path') and route.path == full_path)
249
+ ]
244
250
  return True
251
+
245
252
  display_url = self._format_display_url(f"{self.base_url}{full_path}")
246
253
  logger.error(f"注销WebSocket失败: 路径 {display_url} 不存在")
247
254
  return False
@@ -339,8 +346,9 @@ class RouterManager:
339
346
  if "0.0.0.0" in url:
340
347
  display_url = url.replace("0.0.0.0", "127.0.0.1")
341
348
  return f"{url} (可访问: {display_url})"
342
- elif "::" in url:
343
- display_url = url.replace("::", "localhost")
349
+ elif "[::]" in url:
350
+ # IPv6 回环地址需要替换括号
351
+ display_url = url.replace("[::]", "localhost")
344
352
  return f"{url} (可访问: {display_url})"
345
353
  return url
346
354
 
@@ -0,0 +1,120 @@
1
+ # type: ignore
2
+ #
3
+ # Auto-generated type stub for router.py
4
+ # DO NOT EDIT MANUALLY - Generated by generate-type-stubs.py
5
+ #
6
+
7
+ """
8
+ ErisPulse 路由系统
9
+
10
+ 提供统一的HTTP和WebSocket路由管理,支持多适配器路由注册和生命周期管理。
11
+
12
+ {!--< tips >!--}
13
+ 1. 适配器只需注册路由,无需自行管理服务器
14
+ 2. WebSocket支持自定义认证逻辑
15
+ {!--< /tips >!--}
16
+ """
17
+
18
+ from fastapi import FastAPI, WebSocket, WebSocketDisconnect
19
+ from fastapi.routing import APIRoute
20
+ from typing import Dict, List, Optional, Callable, Any, Awaitable, Tuple
21
+ from collections import defaultdict
22
+ from .logger import logger
23
+ from .lifecycle import lifecycle
24
+ import asyncio
25
+ from hypercorn.config import Config
26
+ from hypercorn.asyncio import serve
27
+
28
+ class RouterManager:
29
+ """
30
+ 路由管理器
31
+
32
+ {!--< tips >!--}
33
+ 核心功能:
34
+ - HTTP/WebSocket路由注册
35
+ - 生命周期管理
36
+ - 统一错误处理
37
+ {!--< /tips >!--}
38
+ """
39
+ def __init__(self: None) -> ...:
40
+ """
41
+ 初始化路由管理器
42
+
43
+ {!--< tips >!--}
44
+ 会自动创建FastAPI实例并设置核心路由
45
+ {!--< /tips >!--}
46
+ """
47
+ ...
48
+ def register_http_route(self: object, module_name: str, path: str, handler: Callable, methods: List[str] = ...) -> None:
49
+ """
50
+ 注册HTTP路由
51
+
52
+ :param module_name: str 模块名称
53
+ :param path: str 路由路径
54
+ :param handler: Callable 处理函数
55
+ :param methods: List[str] HTTP方法列表(默认["POST"])
56
+
57
+ :raises ValueError: 当路径已注册时抛出
58
+ """
59
+ ...
60
+ def register_webhook(self: object, *args: ..., **kwargs: ...) -> None:
61
+ """
62
+ 兼容性方法:注册HTTP路由(适配器旧接口)
63
+ """
64
+ ...
65
+ def unregister_http_route(self: object, module_name: str, path: str) -> bool:
66
+ """
67
+ 取消注册HTTP路由
68
+
69
+ :param module_name: 模块名称
70
+ :param path: 路由路径
71
+
72
+ :return: Bool
73
+ """
74
+ ...
75
+ def register_websocket(self: object, module_name: str, path: str, handler: Callable[([WebSocket], Awaitable[Any])], auth_handler: Optional[Callable[([WebSocket], Awaitable[bool])]] = ...) -> None:
76
+ """
77
+ 注册WebSocket路由
78
+
79
+ :param module_name: str 模块名称
80
+ :param path: str WebSocket路径
81
+ :param handler: Callable[[WebSocket], Awaitable[Any]] 主处理函数
82
+ :param auth_handler: Optional[Callable[[WebSocket], Awaitable[bool]]] 认证函数
83
+
84
+ :raises ValueError: 当路径已注册时抛出
85
+ """
86
+ ...
87
+ def unregister_websocket(self: object, module_name: str, path: str) -> bool:
88
+ ...
89
+ def get_app(self: object) -> FastAPI:
90
+ """
91
+ 获取FastAPI应用实例
92
+
93
+ :return: FastAPI应用实例
94
+ """
95
+ ...
96
+ async def start(self: object, host: str = ..., port: int = ..., ssl_certfile: Optional[str] = ..., ssl_keyfile: Optional[str] = ...) -> None:
97
+ """
98
+ 启动路由服务器
99
+
100
+ :param host: str 监听地址(默认"0.0.0.0")
101
+ :param port: int 监听端口(默认8000)
102
+ :param ssl_certfile: Optional[str] SSL证书路径
103
+ :param ssl_keyfile: Optional[str] SSL密钥路径
104
+
105
+ :raises RuntimeError: 当服务器已在运行时抛出
106
+ """
107
+ ...
108
+ async def stop(self: object) -> None:
109
+ """
110
+ 停止服务器
111
+ """
112
+ ...
113
+ def _format_display_url(self: object, url: str) -> str:
114
+ """
115
+ 格式化URL显示,将回环地址转换为更友好的格式
116
+
117
+ :param url: 原始URL
118
+ :return: 格式化后的URL
119
+ """
120
+ ...
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 shutil
28
- import time
29
- from datetime import datetime
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
- SNAPSHOT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "../data/snapshots"))
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 .config import config
77
- use_global_db = config.getConfig("ErisPulse.storage.use_global_db", False)
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) if isinstance(value, (dict, list)) else str(value)
230
- with self.transaction():
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
- conn.commit()
235
- conn.close()
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.transaction():
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) if isinstance(value, (dict, list)) else str(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
- conn.commit()
271
- conn.close()
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.transaction():
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
- conn.commit()
324
- conn.close()
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.transaction():
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
- conn.commit()
351
- conn.close()
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 = {row[0]: json.loads(row[1]) if row[1].startswith(('{', '[')) else row[1]
378
- for row in cursor.fetchall()}
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
- conn = sqlite3.connect(self.db_path)
516
- cursor = conn.cursor()
517
- cursor.execute("DELETE FROM config")
518
- conn.commit()
519
- conn.close()
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
- return self.get(key)
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
-