infomankit 0.3.23__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 (143) hide show
  1. infoman/__init__.py +1 -0
  2. infoman/cli/README.md +378 -0
  3. infoman/cli/__init__.py +7 -0
  4. infoman/cli/commands/__init__.py +3 -0
  5. infoman/cli/commands/init.py +312 -0
  6. infoman/cli/scaffold.py +634 -0
  7. infoman/cli/templates/Makefile.template +132 -0
  8. infoman/cli/templates/app/__init__.py.template +3 -0
  9. infoman/cli/templates/app/app.py.template +4 -0
  10. infoman/cli/templates/app/models_base.py.template +18 -0
  11. infoman/cli/templates/app/models_entity_init.py.template +11 -0
  12. infoman/cli/templates/app/models_schemas_init.py.template +11 -0
  13. infoman/cli/templates/app/repository_init.py.template +11 -0
  14. infoman/cli/templates/app/routers_init.py.template +15 -0
  15. infoman/cli/templates/app/services_init.py.template +11 -0
  16. infoman/cli/templates/app/static_index.html.template +39 -0
  17. infoman/cli/templates/app/static_main.js.template +31 -0
  18. infoman/cli/templates/app/static_style.css.template +111 -0
  19. infoman/cli/templates/app/utils_init.py.template +11 -0
  20. infoman/cli/templates/config/.env.dev.template +43 -0
  21. infoman/cli/templates/config/.env.prod.template +43 -0
  22. infoman/cli/templates/config/README.md.template +28 -0
  23. infoman/cli/templates/docker/.dockerignore.template +60 -0
  24. infoman/cli/templates/docker/Dockerfile.template +47 -0
  25. infoman/cli/templates/docker/README.md.template +240 -0
  26. infoman/cli/templates/docker/docker-compose.yml.template +81 -0
  27. infoman/cli/templates/docker/mysql_custom.cnf.template +42 -0
  28. infoman/cli/templates/docker/mysql_init.sql.template +15 -0
  29. infoman/cli/templates/project/.env.example.template +1 -0
  30. infoman/cli/templates/project/.gitignore.template +60 -0
  31. infoman/cli/templates/project/Makefile.template +38 -0
  32. infoman/cli/templates/project/README.md.template +137 -0
  33. infoman/cli/templates/project/deploy.sh.template +97 -0
  34. infoman/cli/templates/project/main.py.template +10 -0
  35. infoman/cli/templates/project/manage.sh.template +97 -0
  36. infoman/cli/templates/project/pyproject.toml.template +47 -0
  37. infoman/cli/templates/project/service.sh.template +203 -0
  38. infoman/config/__init__.py +25 -0
  39. infoman/config/base.py +67 -0
  40. infoman/config/db_cache.py +237 -0
  41. infoman/config/db_relation.py +181 -0
  42. infoman/config/db_vector.py +39 -0
  43. infoman/config/jwt.py +16 -0
  44. infoman/config/llm.py +16 -0
  45. infoman/config/log.py +627 -0
  46. infoman/config/mq.py +26 -0
  47. infoman/config/settings.py +65 -0
  48. infoman/llm/__init__.py +0 -0
  49. infoman/llm/llm.py +297 -0
  50. infoman/logger/__init__.py +57 -0
  51. infoman/logger/context.py +191 -0
  52. infoman/logger/core.py +358 -0
  53. infoman/logger/filters.py +157 -0
  54. infoman/logger/formatters.py +138 -0
  55. infoman/logger/handlers.py +276 -0
  56. infoman/logger/metrics.py +160 -0
  57. infoman/performance/README.md +583 -0
  58. infoman/performance/__init__.py +19 -0
  59. infoman/performance/cli.py +215 -0
  60. infoman/performance/config.py +166 -0
  61. infoman/performance/reporter.py +519 -0
  62. infoman/performance/runner.py +303 -0
  63. infoman/performance/standards.py +222 -0
  64. infoman/service/__init__.py +8 -0
  65. infoman/service/app.py +67 -0
  66. infoman/service/core/__init__.py +0 -0
  67. infoman/service/core/auth.py +105 -0
  68. infoman/service/core/lifespan.py +132 -0
  69. infoman/service/core/monitor.py +57 -0
  70. infoman/service/core/response.py +37 -0
  71. infoman/service/exception/__init__.py +7 -0
  72. infoman/service/exception/error.py +274 -0
  73. infoman/service/exception/exception.py +25 -0
  74. infoman/service/exception/handler.py +238 -0
  75. infoman/service/infrastructure/__init__.py +8 -0
  76. infoman/service/infrastructure/base.py +212 -0
  77. infoman/service/infrastructure/db_cache/__init__.py +8 -0
  78. infoman/service/infrastructure/db_cache/manager.py +194 -0
  79. infoman/service/infrastructure/db_relation/__init__.py +41 -0
  80. infoman/service/infrastructure/db_relation/manager.py +300 -0
  81. infoman/service/infrastructure/db_relation/manager_pro.py +408 -0
  82. infoman/service/infrastructure/db_relation/mysql.py +52 -0
  83. infoman/service/infrastructure/db_relation/pgsql.py +54 -0
  84. infoman/service/infrastructure/db_relation/sqllite.py +25 -0
  85. infoman/service/infrastructure/db_vector/__init__.py +40 -0
  86. infoman/service/infrastructure/db_vector/manager.py +201 -0
  87. infoman/service/infrastructure/db_vector/qdrant.py +322 -0
  88. infoman/service/infrastructure/mq/__init__.py +15 -0
  89. infoman/service/infrastructure/mq/manager.py +178 -0
  90. infoman/service/infrastructure/mq/nats/__init__.py +0 -0
  91. infoman/service/infrastructure/mq/nats/nats_client.py +57 -0
  92. infoman/service/infrastructure/mq/nats/nats_event_router.py +25 -0
  93. infoman/service/launch.py +284 -0
  94. infoman/service/middleware/__init__.py +7 -0
  95. infoman/service/middleware/base.py +41 -0
  96. infoman/service/middleware/logging.py +51 -0
  97. infoman/service/middleware/rate_limit.py +301 -0
  98. infoman/service/middleware/request_id.py +21 -0
  99. infoman/service/middleware/white_list.py +24 -0
  100. infoman/service/models/__init__.py +8 -0
  101. infoman/service/models/base.py +441 -0
  102. infoman/service/models/type/embed.py +70 -0
  103. infoman/service/routers/__init__.py +18 -0
  104. infoman/service/routers/health_router.py +311 -0
  105. infoman/service/routers/monitor_router.py +44 -0
  106. infoman/service/utils/__init__.py +8 -0
  107. infoman/service/utils/cache/__init__.py +0 -0
  108. infoman/service/utils/cache/cache.py +192 -0
  109. infoman/service/utils/module_loader.py +10 -0
  110. infoman/service/utils/parse.py +10 -0
  111. infoman/service/utils/resolver/__init__.py +8 -0
  112. infoman/service/utils/resolver/base.py +47 -0
  113. infoman/service/utils/resolver/resp.py +102 -0
  114. infoman/service/vector/__init__.py +20 -0
  115. infoman/service/vector/base.py +56 -0
  116. infoman/service/vector/qdrant.py +125 -0
  117. infoman/service/vector/service.py +67 -0
  118. infoman/utils/__init__.py +2 -0
  119. infoman/utils/decorators/__init__.py +8 -0
  120. infoman/utils/decorators/cache.py +137 -0
  121. infoman/utils/decorators/retry.py +99 -0
  122. infoman/utils/decorators/safe_execute.py +99 -0
  123. infoman/utils/decorators/timing.py +99 -0
  124. infoman/utils/encryption/__init__.py +8 -0
  125. infoman/utils/encryption/aes.py +66 -0
  126. infoman/utils/encryption/ecc.py +108 -0
  127. infoman/utils/encryption/rsa.py +112 -0
  128. infoman/utils/file/__init__.py +0 -0
  129. infoman/utils/file/handler.py +22 -0
  130. infoman/utils/hash/__init__.py +0 -0
  131. infoman/utils/hash/hash.py +61 -0
  132. infoman/utils/http/__init__.py +8 -0
  133. infoman/utils/http/client.py +62 -0
  134. infoman/utils/http/info.py +94 -0
  135. infoman/utils/http/result.py +19 -0
  136. infoman/utils/notification/__init__.py +8 -0
  137. infoman/utils/notification/feishu.py +35 -0
  138. infoman/utils/text/__init__.py +8 -0
  139. infoman/utils/text/extractor.py +111 -0
  140. infomankit-0.3.23.dist-info/METADATA +632 -0
  141. infomankit-0.3.23.dist-info/RECORD +143 -0
  142. infomankit-0.3.23.dist-info/WHEEL +4 -0
  143. infomankit-0.3.23.dist-info/entry_points.txt +5 -0
@@ -0,0 +1,238 @@
1
+ from fastapi import HTTPException, Request, Response
2
+ from fastapi.responses import JSONResponse
3
+ from fastapi.exceptions import RequestValidationError
4
+ from pydantic import ValidationError
5
+ from tortoise.exceptions import (
6
+ OperationalError,
7
+ DoesNotExist,
8
+ IntegrityError,
9
+ ValidationError as MysqlValidationError,
10
+ )
11
+ from typing import List, Dict, Any, Type
12
+ import traceback
13
+
14
+ from infoman.service.exception.error import DatabaseError, RequestError, AppSystemError
15
+ from infoman.service.exception.exception import AppException
16
+ from infoman.logger import logger
17
+
18
+
19
+ def format_error_response(
20
+ code: int, message: str, data: Any = None, details: Any = None
21
+ ) -> Dict[str, Any]:
22
+ response = {"code": code, "message": message, "data": data or {}}
23
+ if details:
24
+ response["details"] = details
25
+ return response
26
+
27
+
28
+ def format_validation_errors(errors: List[Dict]) -> List[Dict[str, str]]:
29
+ formatted_errors = []
30
+ for error in errors:
31
+ loc_path = " -> ".join([str(loc) for loc in error.get("loc", [])])
32
+ formatted_errors.append(
33
+ {
34
+ "field": loc_path,
35
+ "message": error.get("msg", "Unknown error"),
36
+ "type": error.get("type", "unknown"),
37
+ "input": str(error.get("input", "")) if "input" in error else None,
38
+ }
39
+ )
40
+ return formatted_errors
41
+
42
+
43
+ async def http_exception_handler(request: Request, exc: HTTPException) -> Response:
44
+ """处理 HTTP 异常"""
45
+ logger.warning(
46
+ f"HTTP Exception: status={exc.status_code}, "
47
+ f"detail={exc.detail}, path={request.url.path}"
48
+ )
49
+ return JSONResponse(
50
+ status_code=exc.status_code,
51
+ content=format_error_response(code=exc.status_code, message=exc.detail),
52
+ headers=exc.headers,
53
+ )
54
+
55
+
56
+ async def handle_app_exception(_: Request, exc: AppException) -> Response:
57
+ """处理应用自定义异常"""
58
+ logger.error(f"App Exception: code={exc.error_code}, message={exc.message}")
59
+ return JSONResponse(
60
+ format_error_response(code=exc.error_code, message=exc.message), status_code=200
61
+ )
62
+
63
+
64
+ async def mysql_validation_error_handler(
65
+ request: Request, exc: MysqlValidationError
66
+ ) -> Response:
67
+ """处理 MySQL 验证错误"""
68
+ logger.error(
69
+ f"MySQL Validation Error at {request.url.path}\n"
70
+ f"Error: {exc}\n"
71
+ f"Traceback: {traceback.format_exc()}"
72
+ )
73
+ err = DatabaseError.VALIDATION_ERROR
74
+ return JSONResponse(
75
+ format_error_response(code=err.code, message=err.message, details=str(exc)),
76
+ status_code=200,
77
+ )
78
+
79
+
80
+ async def mysql_integrity_error_handler(
81
+ request: Request, exc: IntegrityError
82
+ ) -> Response:
83
+ """处理 MySQL 完整性约束错误"""
84
+ error_msg = str(exc)
85
+ logger.error(
86
+ f"MySQL Integrity Error at {request.url.path}\n"
87
+ f"Error: {error_msg}\n"
88
+ f"Traceback: {traceback.format_exc()}"
89
+ )
90
+
91
+ # 提取更友好的错误信息
92
+ friendly_message = DatabaseError.INTEGRITY_ERROR.message
93
+ if "Duplicate entry" in error_msg:
94
+ friendly_message = "数据已存在,请勿重复添加"
95
+ elif "foreign key constraint" in error_msg.lower():
96
+ friendly_message = "关联数据不存在或已被引用"
97
+
98
+ err = DatabaseError.INTEGRITY_ERROR
99
+ return JSONResponse(
100
+ format_error_response(
101
+ code=err.code, message=friendly_message, details=error_msg
102
+ ),
103
+ status_code=200,
104
+ )
105
+
106
+
107
+ async def mysql_does_not_exist_handler(request: Request, exc: DoesNotExist) -> Response:
108
+ """处理 MySQL 记录不存在错误"""
109
+ logger.warning(f"MySQL Record Not Found at {request.url.path}: {exc}")
110
+ err = DatabaseError.RECORD_NOT_FOUND
111
+ return JSONResponse(
112
+ format_error_response(code=err.code, message=err.message, details=str(exc)),
113
+ status_code=200,
114
+ )
115
+
116
+
117
+ async def mysql_operational_error_handler(
118
+ request: Request, exc: OperationalError
119
+ ) -> Response:
120
+ """处理 MySQL 操作错误"""
121
+ error_msg = str(exc)
122
+ logger.error(
123
+ f"MySQL Operational Error at {request.url.path}\n"
124
+ f"Error: {error_msg}\n"
125
+ f"Traceback: {traceback.format_exc()}"
126
+ )
127
+
128
+ # 提取更友好的错误信息
129
+ friendly_message = DatabaseError.OPERATIONAL_ERROR.message
130
+ if "Lost connection" in error_msg:
131
+ friendly_message = "数据库连接已断开"
132
+ elif "Deadlock" in error_msg:
133
+ friendly_message = "数据库死锁,请稍后重试"
134
+
135
+ err = DatabaseError.OPERATIONAL_ERROR
136
+ return JSONResponse(
137
+ format_error_response(
138
+ code=err.code, message=friendly_message, details=error_msg
139
+ ),
140
+ status_code=200,
141
+ )
142
+
143
+
144
+ async def validation_exception_handler(
145
+ request: Request, exc: RequestValidationError
146
+ ) -> Response:
147
+ """处理请求验证错误"""
148
+ error_details = format_validation_errors(exc.errors())
149
+
150
+ logger.warning(
151
+ f"Request Validation Error at {request.url.path}\n"
152
+ f"Method: {request.method}\n"
153
+ f"Errors: {error_details}"
154
+ )
155
+
156
+ err = RequestError.VALIDATION_ERROR
157
+ if len(error_details) == 1:
158
+ main_message = f"{error_details[0]['field']}: {error_details[0]['message']}"
159
+ else:
160
+ main_message = f"发现 {len(error_details)} 个验证错误"
161
+
162
+ return JSONResponse(
163
+ format_error_response(
164
+ code=err.code, message=main_message, details=error_details
165
+ ),
166
+ status_code=200,
167
+ )
168
+
169
+
170
+ async def pydantic_validation_handler(
171
+ request: Request, exc: ValidationError
172
+ ) -> Response:
173
+ """处理 Pydantic 验证错误"""
174
+ error_details = format_validation_errors(exc.errors())
175
+
176
+ logger.warning(
177
+ f"Pydantic Validation Error at {request.url.path}\n"
178
+ f"Method: {request.method}\n"
179
+ f"Errors: {error_details}"
180
+ )
181
+
182
+ err = RequestError.VALIDATION_ERROR
183
+ # 生成更友好的错误消息
184
+ if len(error_details) == 1:
185
+ main_message = f"{error_details[0]['field']}: {error_details[0]['message']}"
186
+ else:
187
+ main_message = f"数据验证失败,发现 {len(error_details)} 个错误"
188
+
189
+ return JSONResponse(
190
+ format_error_response(
191
+ code=err.code, message=main_message, details=error_details
192
+ ),
193
+ status_code=200,
194
+ )
195
+
196
+
197
+ async def unhandled_exception_handler(request: Request, exc: Exception) -> Response:
198
+ """处理未捕获的异常"""
199
+ logger.error(
200
+ f"Unhandled Exception at {request.url.path}\n"
201
+ f"Method: {request.method}\n"
202
+ f"Exception Type: {type(exc).__name__}\n"
203
+ f"Error: {str(exc)}\n"
204
+ f"Traceback: {traceback.format_exc()}"
205
+ )
206
+ err = AppSystemError.UNKNOWN_ERROR
207
+ return JSONResponse(
208
+ format_error_response(
209
+ code=err.code,
210
+ message="服务器内部错误,请稍后重试",
211
+ details=str(exc) if logger.level == "DEBUG" else None,
212
+ ),
213
+ status_code=500,
214
+ )
215
+
216
+
217
+ EXCEPTION_HANDLERS: Dict[Type[Exception], Any] = {
218
+ # 应用异常
219
+ AppException: handle_app_exception,
220
+ # HTTP 异常
221
+ HTTPException: http_exception_handler,
222
+ # 请求验证异常
223
+ RequestValidationError: validation_exception_handler,
224
+ ValidationError: pydantic_validation_handler,
225
+ # MySQL 异常
226
+ DoesNotExist: mysql_does_not_exist_handler,
227
+ IntegrityError: mysql_integrity_error_handler,
228
+ MysqlValidationError: mysql_validation_error_handler,
229
+ OperationalError: mysql_operational_error_handler,
230
+ # 兜底异常
231
+ Exception: unhandled_exception_handler,
232
+ }
233
+
234
+
235
+ def register_exception_handlers(app) -> None:
236
+ for exception_class, handler_func in EXCEPTION_HANDLERS.items():
237
+ app.add_exception_handler(exception_class, handler_func)
238
+ # logger.info(f"Registered handler for {exception_class.__name__}")
@@ -0,0 +1,8 @@
1
+ # !/usr/bin/env python
2
+ # -*-coding:utf-8 -*-
3
+
4
+ """
5
+ # Time :2025/12/22 22:38
6
+ # Author :Maxwell
7
+ # Description:
8
+ """
@@ -0,0 +1,212 @@
1
+ # !/usr/bin/env python
2
+ # -*-coding:utf-8 -*-
3
+
4
+ """
5
+ # Time :2025/12/22 22:38
6
+ # Author :Maxwell
7
+ # Description:基础设施组件抽象基类
8
+ 统一管理:
9
+ - 数据库(MySQL, PostgreSQL)
10
+ - 缓存(Redis, DragonflyDB)
11
+ - 消息队列(RabbitMQ, Kafka)
12
+ - 搜索引擎(Elasticsearch)
13
+ - 对象存储(MinIO, S3)
14
+ - 等等...
15
+
16
+ """
17
+
18
+ from abc import ABC, abstractmethod
19
+ from typing import Optional, Dict, Any, Literal
20
+ from enum import Enum
21
+ from loguru import logger
22
+
23
+
24
+ class ComponentType(str, Enum):
25
+ """组件类型枚举"""
26
+ DATABASE = "database" # 数据库
27
+ CACHE = "cache" # 缓存
28
+ MESSAGE_QUEUE = "message_queue" # 消息队列
29
+ SEARCH = "search" # 搜索引擎
30
+ STORAGE = "storage" # 对象存储
31
+ MONITORING = "monitoring" # 监控
32
+ TRACING = "tracing" # 链路追踪
33
+ LOGGING = "logging" # 日志
34
+ OTHER = "other" # 其他
35
+
36
+
37
+ class ComponentStatus(str, Enum):
38
+ """组件状态枚举"""
39
+ NOT_CONFIGURED = "not_configured" # 未配置
40
+ INITIALIZING = "initializing" # 初始化中
41
+ HEALTHY = "healthy" # 健康
42
+ UNHEALTHY = "unhealthy" # 不健康
43
+ DEGRADED = "degraded" # 降级
44
+ STOPPED = "stopped" # 已停止
45
+
46
+
47
+ class BaseInfrastructureComponent(ABC):
48
+ """
49
+ 基础设施组件抽象基类
50
+
51
+ 所有基础设施组件(数据库、缓存、MQ 等)都应该继承此类。
52
+
53
+ 生命周期:
54
+ 1. __init__() - 创建实例
55
+ 2. startup() - 启动组件
56
+ 3. health_check() - 健康检查
57
+ 4. shutdown() - 关闭组件
58
+ """
59
+
60
+ def __init__(
61
+ self,
62
+ component_type: ComponentType,
63
+ name: str,
64
+ enabled: bool = True,
65
+ ):
66
+ """
67
+ 初始化组件
68
+
69
+ Args:
70
+ component_type: 组件类型
71
+ name: 组件名称(如 "mysql", "redis")
72
+ enabled: 是否启用
73
+ """
74
+ self.component_type = component_type
75
+ self.name = name
76
+ self.enabled = enabled
77
+ self._status = ComponentStatus.NOT_CONFIGURED
78
+ self._client: Optional[Any] = None
79
+ self._metadata: Dict[str, Any] = {}
80
+
81
+ @property
82
+ def status(self) -> ComponentStatus:
83
+ """获取组件状态"""
84
+ return self._status
85
+
86
+ @property
87
+ def client(self) -> Optional[Any]:
88
+ """获取客户端实例"""
89
+ return self._client
90
+
91
+ @property
92
+ def is_available(self) -> bool:
93
+ """组件是否可用"""
94
+ return (
95
+ self.enabled and
96
+ self._status in [ComponentStatus.HEALTHY, ComponentStatus.DEGRADED]
97
+ )
98
+
99
+ @abstractmethod
100
+ async def startup(self) -> bool:
101
+ """
102
+ 启动组件
103
+
104
+ 职责:
105
+ 1. 检查配置
106
+ 2. 创建连接/客户端
107
+ 3. 测试连接
108
+ 4. 初始化资源
109
+
110
+ Returns:
111
+ True: 启动成功
112
+ False: 启动失败
113
+ """
114
+ pass
115
+
116
+ @abstractmethod
117
+ async def shutdown(self):
118
+ """
119
+ 关闭组件
120
+
121
+ 职责:
122
+ 1. 关闭连接
123
+ 2. 释放资源
124
+ 3. 清理状态
125
+ """
126
+ pass
127
+
128
+ @abstractmethod
129
+ async def health_check(self) -> Dict[str, Any]:
130
+ """
131
+ 健康检查
132
+
133
+ Returns:
134
+ 健康状态字典:
135
+ {
136
+ "status": "healthy" | "unhealthy" | "not_configured",
137
+ "component_type": "database",
138
+ "name": "mysql",
139
+ "details": {...}
140
+ }
141
+ """
142
+ pass
143
+
144
+ async def get_info(self) -> Dict[str, Any]:
145
+ """
146
+ 获取组件信息
147
+
148
+ Returns:
149
+ 组件信息字典
150
+ """
151
+ return {
152
+ "component_type": self.component_type.value,
153
+ "name": self.name,
154
+ "enabled": self.enabled,
155
+ "status": self._status.value,
156
+ "available": self.is_available,
157
+ "metadata": self._metadata,
158
+ }
159
+
160
+ async def get_stats(self) -> Dict[str, Any]:
161
+ """
162
+ 获取统计信息(可选实现)
163
+
164
+ Returns:
165
+ 统计信息字典
166
+ """
167
+ return {}
168
+
169
+ def _set_status(self, status: ComponentStatus):
170
+ """设置组件状态"""
171
+ old_status = self._status
172
+ self._status = status
173
+
174
+ if old_status != status:
175
+ logger.debug(
176
+ f"[{self.name}] 状态变更: {old_status.value} -> {status.value}"
177
+ )
178
+
179
+ def _log_startup(self, success: bool, message: str = ""):
180
+ """记录启动日志"""
181
+ if success:
182
+ logger.success(
183
+ f"✅ [{self.component_type.value}] {self.name} 启动成功"
184
+ + (f": {message}" if message else "")
185
+ )
186
+ else:
187
+ logger.error(
188
+ f"❌ [{self.component_type.value}] {self.name} 启动失败"
189
+ + (f": {message}" if message else "")
190
+ )
191
+
192
+ def _log_shutdown(self, success: bool, message: str = ""):
193
+ """记录关闭日志"""
194
+ if success:
195
+ logger.info(
196
+ f"✅ [{self.component_type.value}] {self.name} 已关闭"
197
+ + (f": {message}" if message else "")
198
+ )
199
+ else:
200
+ logger.error(
201
+ f"❌ [{self.component_type.value}] {self.name} 关闭失败"
202
+ + (f": {message}" if message else "")
203
+ )
204
+
205
+ def __repr__(self) -> str:
206
+ return (
207
+ f"{self.__class__.__name__}("
208
+ f"type={self.component_type.value}, "
209
+ f"name={self.name}, "
210
+ f"status={self._status.value}"
211
+ f")"
212
+ )
@@ -0,0 +1,8 @@
1
+ # !/usr/bin/env python
2
+ # -*-coding:utf-8 -*-
3
+
4
+ """
5
+ # Time :2025/12/22 21:39
6
+ # Author :Maxwell
7
+ # Description:
8
+ """
@@ -0,0 +1,194 @@
1
+ # !/usr/bin/env python
2
+ # -*-coding:utf-8 -*-
3
+
4
+ """
5
+ Redis 缓存管理器(支持延迟导入)
6
+ """
7
+ from typing import Optional, Dict, Any, TYPE_CHECKING
8
+ from fastapi import FastAPI
9
+ from loguru import logger
10
+ from infoman.config import settings
11
+
12
+ if TYPE_CHECKING:
13
+ import redis.asyncio as redis
14
+
15
+
16
+ class RedisManager:
17
+ """Redis 管理器"""
18
+
19
+ def __init__(self):
20
+ self.client: Optional[Any] = None
21
+ self.initialized = False
22
+
23
+ @property
24
+ def is_available(self) -> bool:
25
+ """是否可用"""
26
+ return self.client is not None and self.initialized
27
+
28
+ async def startup(self, app: Optional[FastAPI] = None) -> bool:
29
+ """
30
+ 启动 Redis 连接
31
+
32
+ Args:
33
+ app: FastAPI 应用实例(可选)
34
+
35
+ Returns:
36
+ 是否成功启动
37
+ """
38
+ if self.initialized:
39
+ logger.warning("⚠️ RedisManager 已初始化,跳过重复初始化")
40
+ return True
41
+
42
+ if not settings.redis_configured:
43
+ logger.info("⏭️ Redis 未配置,跳过初始化")
44
+ return False
45
+
46
+ # 延迟导入(只在真正需要时导入)
47
+ try:
48
+ import redis.asyncio as redis
49
+ from fastapi_cache import FastAPICache
50
+ from fastapi_cache.backends.redis import RedisBackend
51
+ except ImportError as e:
52
+ logger.error(f"❌ Redis 依赖未安装: {e}")
53
+ logger.error("请运行: pip install infomankit[cache]")
54
+ return False
55
+
56
+ logger.info("🚀 初始化 Redis...")
57
+
58
+ try:
59
+ # 创建连接池
60
+ pool = redis.ConnectionPool(
61
+ host=settings.REDIS_HOST,
62
+ port=settings.REDIS_PORT,
63
+ db=settings.REDIS_DB,
64
+ password=settings.REDIS_PASSWORD,
65
+ encoding="utf-8",
66
+ decode_responses=False,
67
+ max_connections=settings.REDIS_MAX_CONNECTIONS,
68
+ socket_timeout=settings.REDIS_SOCKET_TIMEOUT,
69
+ socket_connect_timeout=settings.REDIS_SOCKET_CONNECT_TIMEOUT,
70
+ health_check_interval=settings.REDIS_HEALTH_CHECK_INTERVAL,
71
+ )
72
+
73
+ # 创建客户端
74
+ self.client = redis.Redis(connection_pool=pool)
75
+
76
+ # 测试连接
77
+ await self.client.ping()
78
+
79
+ # 挂载到 app.state(如果提供了 app)
80
+ if app:
81
+ app.state.redis_client = self.client
82
+ logger.debug("✅ Redis 客户端已挂载到 app.state")
83
+
84
+ # 初始化缓存
85
+ FastAPICache.init(
86
+ RedisBackend(self.client),
87
+ prefix=f"{settings.REDIS_CACHE_PREFIX}:v{settings.APP_VERSION}:",
88
+ )
89
+
90
+ self.initialized = True
91
+ logger.success(f"✅ Redis 连接成功: {settings.REDIS_HOST}:{settings.REDIS_PORT}")
92
+ return True
93
+
94
+ except Exception as e:
95
+ logger.error(f"❌ Redis 连接失败: {e}")
96
+ self.client = None
97
+ return False
98
+
99
+ async def shutdown(self):
100
+ """关闭 Redis 连接"""
101
+ if not self.initialized:
102
+ return
103
+
104
+ logger.info("⏹️ 关闭 Redis 连接...")
105
+
106
+ try:
107
+ if self.client:
108
+ await self.client.close()
109
+ await self.client.connection_pool.disconnect()
110
+ logger.success("✅ Redis 连接已关闭")
111
+ except Exception as e:
112
+ logger.error(f"❌ Redis 关闭失败: {e}")
113
+ finally:
114
+ self.initialized = False
115
+
116
+ async def health_check(self) -> Dict[str, Any]:
117
+ """
118
+ 健康检查
119
+
120
+ Returns:
121
+ {
122
+ "status": "healthy" | "unhealthy" | "not_configured",
123
+ "name": "redis",
124
+ "details": {...}
125
+ }
126
+ """
127
+ if not settings.redis_configured:
128
+ return {
129
+ "status": "not_configured",
130
+ "name": "redis",
131
+ "details": {"enabled": False}
132
+ }
133
+
134
+ if not self.initialized or not self.client:
135
+ return {
136
+ "status": "unhealthy",
137
+ "name": "redis",
138
+ "details": {"error": "未初始化"}
139
+ }
140
+
141
+ try:
142
+ await self.client.ping()
143
+
144
+ # 获取 Redis 信息
145
+ info = await self.client.info()
146
+
147
+ return {
148
+ "status": "healthy",
149
+ "name": "redis",
150
+ "details": {
151
+ "host": settings.REDIS_HOST,
152
+ "port": settings.REDIS_PORT,
153
+ "db": settings.REDIS_DB,
154
+ "connected_clients": info.get("connected_clients", 0),
155
+ "used_memory_human": info.get("used_memory_human", "N/A"),
156
+ }
157
+ }
158
+ except Exception as e:
159
+ return {
160
+ "status": "unhealthy",
161
+ "name": "redis",
162
+ "details": {"error": str(e)}
163
+ }
164
+
165
+ async def get_stats(self) -> Dict[str, Any]:
166
+ """
167
+ 获取统计信息
168
+
169
+ Returns:
170
+ {
171
+ "host": str,
172
+ "port": int,
173
+ "db": int,
174
+ "info": {...}
175
+ }
176
+ """
177
+ if not self.is_available:
178
+ return {}
179
+
180
+ try:
181
+ info = await self.client.info()
182
+
183
+ return {
184
+ "host": settings.REDIS_HOST,
185
+ "port": settings.REDIS_PORT,
186
+ "db": settings.REDIS_DB,
187
+ "connected_clients": info.get("connected_clients", 0),
188
+ "used_memory": info.get("used_memory_human", "N/A"),
189
+ "uptime_in_seconds": info.get("uptime_in_seconds", 0),
190
+ "total_commands_processed": info.get("total_commands_processed", 0),
191
+ }
192
+ except Exception as e:
193
+ logger.error(f"获取 Redis 统计信息失败: {e}")
194
+ return {}
@@ -0,0 +1,41 @@
1
+ """
2
+ 数据库关系层 - 统一导出
3
+
4
+ Version: 0.3.0
5
+
6
+ 向前兼容说明:
7
+ - 默认导出保持不变(使用原有的 Tortoise 单一后端管理器)
8
+ - 新的混合管理器通过 hybrid_db_manager 导出
9
+ """
10
+
11
+ # ==================== 向前兼容(默认使用 Tortoise)====================
12
+
13
+ from infoman.service.infrastructure.db_relation.manager import (
14
+ DatabaseManager,
15
+ db_manager,
16
+ register_databases as register_databases_tortoise,
17
+ check_databases_health as check_databases_health_tortoise,
18
+ close_databases as close_databases_tortoise,
19
+ get_connection_names as get_connection_names_tortoise,
20
+ )
21
+
22
+
23
+ # ==================== 默认导出(向前兼容)====================
24
+
25
+ # 为了100%向前兼容,默认导出仍然是 Tortoise 单一后端
26
+ __all__ = [
27
+ # Tortoise 管理器(默认,向前兼容)
28
+ "DatabaseManager",
29
+ "db_manager",
30
+ "register_databases",
31
+ "check_databases_health",
32
+ "close_databases",
33
+ "get_connection_names",
34
+
35
+ ]
36
+
37
+ # 默认导出指向 Tortoise 版本(向前兼容)
38
+ register_databases = register_databases_tortoise
39
+ check_databases_health = check_databases_health_tortoise
40
+ close_databases = close_databases_tortoise
41
+ get_connection_names = get_connection_names_tortoise