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,311 @@
1
+ # !/usr/bin/env python
2
+ # -*-coding:utf-8 -*-
3
+
4
+ """
5
+ 健康检查路由(支持可选依赖)
6
+
7
+ 提供多层次的健康检查端点,支持 Kubernetes 探针
8
+ - /api/health - 基础健康检查
9
+ - /api/health/liveness - 存活探针
10
+ - /api/health/readiness - 就绪探针
11
+ - /api/health/startup - 启动探针
12
+ - /api/health/detailed - 详细健康状态
13
+
14
+ 注意:所有检查都会优雅处理可选依赖(Redis/Vector/NATS)
15
+ 未安装的依赖会标记为 "not_configured",不影响整体健康状态
16
+ """
17
+
18
+ import time
19
+ from typing import Dict, Any, List
20
+ from fastapi import Request, APIRouter, status
21
+ from loguru import logger
22
+
23
+ from infoman.service.core.response import success, failed
24
+
25
+ health_router = APIRouter(prefix="")
26
+
27
+ # 应用启动时间
28
+ _startup_time = time.time()
29
+
30
+
31
+ @health_router.get(
32
+ summary="基础健康检查",
33
+ path="",
34
+ description="返回应用的基本健康状态",
35
+ )
36
+ async def api_health(req: Request):
37
+ """基础健康检查 - 仅检查应用是否运行"""
38
+ uptime = time.time() - _startup_time
39
+ return success(data={
40
+ "status": "healthy",
41
+ "uptime_seconds": round(uptime, 2),
42
+ })
43
+
44
+
45
+ @health_router.get(
46
+ "/liveness",
47
+ summary="存活探针",
48
+ description="Kubernetes liveness probe - 检查应用是否存活",
49
+ status_code=status.HTTP_200_OK,
50
+ )
51
+ async def liveness_check(req: Request):
52
+ """
53
+ 存活探针 (Liveness Probe)
54
+
55
+ 用于判断应用是否还活着,如果失败,Kubernetes 会重启 Pod
56
+ 这个检查应该非常轻量,只检查应用进程是否响应
57
+ """
58
+ return {"status": "alive"}
59
+
60
+
61
+ @health_router.get(
62
+ "/readiness",
63
+ summary="就绪探针",
64
+ description="Kubernetes readiness probe - 检查应用是否就绪接受流量",
65
+ status_code=status.HTTP_200_OK,
66
+ responses={
67
+ 200: {"description": "Application is ready"},
68
+ 503: {"description": "Application is not ready"},
69
+ },
70
+ )
71
+ async def readiness_check(req: Request):
72
+ """
73
+ 就绪探针 (Readiness Probe)
74
+
75
+ 用于判断应用是否准备好接受流量
76
+ 如果失败,Kubernetes 会将 Pod 从 Service 负载均衡中移除
77
+
78
+ 检查项:
79
+ - 数据库连接(必需)
80
+ - Redis 连接(可选)
81
+ - 向量数据库连接(可选)
82
+ - 消息队列连接(可选)
83
+
84
+ 注意:只有必需的数据库会影响就绪状态,可选依赖不影响
85
+ """
86
+ checks: Dict[str, Any] = {}
87
+ all_ready = True
88
+
89
+ # 检查数据库(必需)
90
+ try:
91
+ if hasattr(req.app.state, "db_manager"):
92
+ db_health = await req.app.state.db_manager.health_check()
93
+ checks["database"] = db_health
94
+ if db_health.get("status") != "healthy":
95
+ all_ready = False
96
+ else:
97
+ checks["database"] = {"status": "not_configured"}
98
+ all_ready = False # 数据库是必需的
99
+ except Exception as e:
100
+ logger.error(f"Database readiness check failed: {e}")
101
+ checks["database"] = {"status": "unhealthy", "error": str(e)}
102
+ all_ready = False
103
+
104
+ # 检查 Redis(可选,不影响就绪状态)
105
+ try:
106
+ if hasattr(req.app.state, "redis_manager"):
107
+ redis_health = await req.app.state.redis_manager.health_check()
108
+ checks["redis"] = redis_health
109
+ # 可选依赖不影响就绪状态
110
+ else:
111
+ checks["redis"] = {"status": "not_configured"}
112
+ except Exception as e:
113
+ logger.warning(f"Redis readiness check failed: {e}")
114
+ checks["redis"] = {"status": "unhealthy", "error": str(e)}
115
+
116
+ # 检查向量数据库(可选,不影响就绪状态)
117
+ try:
118
+ if hasattr(req.app.state, "vector_manager"):
119
+ vector_health = await req.app.state.vector_manager.health_check()
120
+ checks["vector_db"] = vector_health
121
+ # 可选依赖不影响就绪状态
122
+ else:
123
+ checks["vector_db"] = {"status": "not_configured"}
124
+ except Exception as e:
125
+ logger.warning(f"Vector DB readiness check failed: {e}")
126
+ checks["vector_db"] = {"status": "unhealthy", "error": str(e)}
127
+
128
+ # 检查消息队列(可选,不影响就绪状态)
129
+ try:
130
+ if hasattr(req.app.state, "nats_manager"):
131
+ nats_health = await req.app.state.nats_manager.health_check()
132
+ checks["message_queue"] = nats_health
133
+ # 可选依赖不影响就绪状态
134
+ else:
135
+ checks["message_queue"] = {"status": "not_configured"}
136
+ except Exception as e:
137
+ logger.warning(f"NATS readiness check failed: {e}")
138
+ checks["message_queue"] = {"status": "unhealthy", "error": str(e)}
139
+
140
+ if all_ready:
141
+ return {
142
+ "status": "ready",
143
+ "checks": checks,
144
+ }
145
+ else:
146
+ return JSONResponse(
147
+ status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
148
+ content={
149
+ "status": "not_ready",
150
+ "checks": checks,
151
+ },
152
+ )
153
+
154
+
155
+ @health_router.get(
156
+ "/startup",
157
+ summary="启动探针",
158
+ description="Kubernetes startup probe - 检查应用是否已完成启动",
159
+ status_code=status.HTTP_200_OK,
160
+ responses={
161
+ 200: {"description": "Application has started"},
162
+ 503: {"description": "Application is still starting"},
163
+ },
164
+ )
165
+ async def startup_check(req: Request):
166
+ """
167
+ 启动探针 (Startup Probe)
168
+
169
+ 用于检查应用是否已经完成启动
170
+ 适用于启动缓慢的应用,避免 liveness probe 过早杀死容器
171
+
172
+ 启动完成标准:
173
+ - 应用运行超过最小启动时间
174
+ - 所有关键服务已初始化
175
+ """
176
+ uptime = time.time() - _startup_time
177
+ min_startup_time = 5 # 最小启动时间(秒)
178
+
179
+ if uptime < min_startup_time:
180
+ return JSONResponse(
181
+ status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
182
+ content={
183
+ "status": "starting",
184
+ "uptime_seconds": round(uptime, 2),
185
+ "message": f"Application is still starting (min: {min_startup_time}s)",
186
+ },
187
+ )
188
+
189
+ # 检查关键服务是否已初始化
190
+ checks = {}
191
+
192
+ if hasattr(req.app.state, "db_manager"):
193
+ checks["database"] = "initialized"
194
+ if hasattr(req.app.state, "redis_manager"):
195
+ checks["redis"] = "initialized"
196
+ if hasattr(req.app.state, "vector_manager"):
197
+ checks["vector_db"] = "initialized"
198
+ if hasattr(req.app.state, "nats_manager"):
199
+ checks["message_queue"] = "initialized"
200
+
201
+ return {
202
+ "status": "started",
203
+ "uptime_seconds": round(uptime, 2),
204
+ "initialized_services": checks,
205
+ }
206
+
207
+
208
+ @health_router.get(
209
+ "/detailed",
210
+ summary="详细健康状态",
211
+ description="返回所有组件的详细健康状态",
212
+ )
213
+ async def detailed_health(req: Request):
214
+ """
215
+ 详细健康检查
216
+
217
+ 返回应用和所有依赖服务的详细健康状态
218
+ 包括版本信息、连接状态、性能指标等
219
+ """
220
+ from infoman.config.settings import settings
221
+ import psutil
222
+
223
+ uptime = time.time() - _startup_time
224
+
225
+ health_data: Dict[str, Any] = {
226
+ "status": "healthy",
227
+ "timestamp": time.time(),
228
+ "uptime_seconds": round(uptime, 2),
229
+ "application": {
230
+ "name": settings.APP_NAME,
231
+ "version": settings.APP_VERSION,
232
+ "environment": settings.ENV,
233
+ },
234
+ "system": {
235
+ "cpu_percent": psutil.cpu_percent(interval=0.1),
236
+ "memory_percent": psutil.virtual_memory().percent,
237
+ "disk_percent": psutil.disk_usage('/').percent,
238
+ },
239
+ "dependencies": {},
240
+ }
241
+
242
+ # 收集所有依赖的健康状态
243
+ dependencies_health: List[Dict[str, Any]] = []
244
+
245
+ # 数据库
246
+ if hasattr(req.app.state, "db_manager"):
247
+ try:
248
+ db_health = await req.app.state.db_manager.health_check()
249
+ dependencies_health.append(db_health)
250
+ health_data["dependencies"]["database"] = db_health
251
+ except Exception as e:
252
+ logger.error(f"Database health check failed: {e}")
253
+ health_data["dependencies"]["database"] = {
254
+ "status": "error",
255
+ "error": str(e),
256
+ }
257
+
258
+ # Redis
259
+ if hasattr(req.app.state, "redis_manager"):
260
+ try:
261
+ redis_health = await req.app.state.redis_manager.health_check()
262
+ dependencies_health.append(redis_health)
263
+ health_data["dependencies"]["redis"] = redis_health
264
+ except Exception as e:
265
+ logger.error(f"Redis health check failed: {e}")
266
+ health_data["dependencies"]["redis"] = {
267
+ "status": "error",
268
+ "error": str(e),
269
+ }
270
+
271
+ # 向量数据库
272
+ if hasattr(req.app.state, "vector_manager"):
273
+ try:
274
+ vector_health = await req.app.state.vector_manager.health_check()
275
+ dependencies_health.append(vector_health)
276
+ health_data["dependencies"]["vector_db"] = vector_health
277
+ except Exception as e:
278
+ logger.error(f"Vector DB health check failed: {e}")
279
+ health_data["dependencies"]["vector_db"] = {
280
+ "status": "error",
281
+ "error": str(e),
282
+ }
283
+
284
+ # 消息队列
285
+ if hasattr(req.app.state, "nats_manager"):
286
+ try:
287
+ nats_health = await req.app.state.nats_manager.health_check()
288
+ dependencies_health.append(nats_health)
289
+ health_data["dependencies"]["message_queue"] = nats_health
290
+ except Exception as e:
291
+ logger.error(f"NATS health check failed: {e}")
292
+ health_data["dependencies"]["message_queue"] = {
293
+ "status": "error",
294
+ "error": str(e),
295
+ }
296
+
297
+ # 判断整体健康状态
298
+ unhealthy_count = sum(
299
+ 1 for dep in dependencies_health
300
+ if dep.get("status") not in ["healthy", "not_configured"]
301
+ )
302
+
303
+ if unhealthy_count > 0:
304
+ health_data["status"] = "degraded"
305
+ health_data["unhealthy_dependencies"] = unhealthy_count
306
+
307
+ return success(data=health_data)
308
+
309
+
310
+ # 导入 JSONResponse
311
+ from fastapi.responses import JSONResponse
@@ -0,0 +1,44 @@
1
+ # !/usr/bin/env python
2
+ # -*-coding:utf-8 -*-
3
+
4
+ """
5
+ # Time :2024/2/2 20:05
6
+ # Author :Maxwell
7
+ # Description:
8
+ """
9
+ import pytz
10
+ import psutil
11
+ from fastapi import APIRouter
12
+ from infoman.service.core.response import success
13
+ from infoman.config.settings import settings
14
+ from datetime import datetime
15
+
16
+
17
+ monitor_router = APIRouter(prefix="")
18
+
19
+
20
+ @monitor_router.get(path="/info")
21
+ async def monitor():
22
+ cpu_percent = psutil.cpu_percent(interval=0.1)
23
+ memory = psutil.virtual_memory()
24
+ process = psutil.Process()
25
+ process_memory = process.memory_info().rss / (1024 * 1024)
26
+ return success(
27
+ {
28
+ "app": {
29
+ "version": settings.APP_VERSION,
30
+ "name": settings.APP_NAME,
31
+ "env": settings.ENV,
32
+ },
33
+ "system": {
34
+ "cpu_percent": f"{cpu_percent:.3f}",
35
+ "memory_percent": f"{memory.percent:.3f}",
36
+ "memory_available_mb": int(memory.available / (1024 * 1024)),
37
+ },
38
+ "process": {
39
+ "memory_mb": int(process_memory),
40
+ "cpu_percent": f"'{process.cpu_percent(interval=0.1):.3f}",
41
+ },
42
+ "timestamp": datetime.now(pytz.UTC).isoformat(),
43
+ }
44
+ )
@@ -0,0 +1,8 @@
1
+ # !/usr/bin/env python
2
+ # -*-coding:utf-8 -*-
3
+
4
+ """
5
+ # Time :2025/6/26 16:29
6
+ # Author :Maxwell
7
+ # Description:
8
+ """
File without changes
@@ -0,0 +1,192 @@
1
+ from infoman.logger import logger
2
+ from functools import wraps
3
+ from typing import (
4
+ Any,
5
+ Callable,
6
+ Optional,
7
+ Type,
8
+ TypeVar,
9
+ get_type_hints,
10
+ get_origin,
11
+ get_args,
12
+ )
13
+ import json
14
+ from pydantic import BaseModel
15
+ import inspect
16
+
17
+
18
+ T = TypeVar("T")
19
+
20
+
21
+ def redis_cache(
22
+ prefix: str,
23
+ ttl: int = 3600,
24
+ model: Optional[Type[BaseModel]] = None, # ✅ 新增:指定返回模型
25
+ auto_detect: bool = True, # ✅ 新增:自动检测返回类型
26
+ ):
27
+ """
28
+ Redis 缓存装饰器(支持 Pydantic 模型反序列化)
29
+
30
+ Args:
31
+ prefix: 缓存键前缀
32
+ ttl: 过期时间(秒)
33
+ model: 返回的 Pydantic 模型类(可选,如果指定则强制使用)
34
+ auto_detect: 是否自动从函数签名检测返回类型
35
+
36
+ Examples:
37
+ # 方式 1:手动指定模型
38
+ @redis_cache(prefix="config", ttl=3600, model=ConfigKeyLogSchema)
39
+ async def get_config(pub_key: str):
40
+ ...
41
+
42
+ # 方式 2:自动检测(推荐)
43
+ @redis_cache(prefix="config", ttl=3600)
44
+ async def get_config(pub_key: str) -> ConfigKeyLogSchema:
45
+ ...
46
+
47
+ # 方式 3:列表类型
48
+ @redis_cache(prefix="configs", ttl=3600)
49
+ async def list_configs() -> list[ConfigKeyLogSchema]:
50
+ ...
51
+ """
52
+
53
+ def decorator(func: Callable) -> Callable:
54
+ # ✅ 自动检测返回类型
55
+ return_type = model
56
+ is_list = False
57
+
58
+ if auto_detect and not return_type:
59
+ type_hints = get_type_hints(func)
60
+ if "return" in type_hints:
61
+ hint = type_hints["return"]
62
+
63
+ # 处理 Optional[Model]
64
+ origin = get_origin(hint)
65
+ if origin is Optional or str(origin) == "typing.Union":
66
+ args = get_args(hint)
67
+ for arg in args:
68
+ if arg is not type(None) and (
69
+ isinstance(arg, type) and issubclass(arg, BaseModel)
70
+ ):
71
+ return_type = arg
72
+ break
73
+ elif origin is list:
74
+ args = get_args(hint)
75
+ if (
76
+ args
77
+ and isinstance(args[0], type)
78
+ and issubclass(args[0], BaseModel)
79
+ ):
80
+ return_type = args[0]
81
+ is_list = True
82
+
83
+ # 直接是 Model
84
+ elif isinstance(hint, type) and issubclass(hint, BaseModel):
85
+ return_type = hint
86
+
87
+ @wraps(func)
88
+ async def wrapper(*args, **kwargs) -> Any:
89
+ # 获取 Redis 客户端
90
+ redis_client = _get_redis_client(args, kwargs)
91
+
92
+ if not redis_client:
93
+ logger.warning("Redis client not found, executing without cache")
94
+ return await func(*args, **kwargs)
95
+
96
+ # 构建缓存键
97
+ cache_key = f"{prefix}:{_build_cache_key(func, args, kwargs)}"
98
+
99
+ # ==================== 读取缓存 ====================
100
+ try:
101
+ cached = await redis_client.get(cache_key)
102
+ if cached:
103
+ cached_data = json.loads(cached)
104
+ logger.debug(f"Cache HIT: {cache_key}")
105
+
106
+ if return_type:
107
+ if is_list:
108
+ return [return_type(**item) for item in cached_data]
109
+ else:
110
+ return return_type(**cached_data)
111
+ return cached_data
112
+
113
+ except Exception as e:
114
+ logger.error(f"Cache read error for {cache_key}: {e}")
115
+
116
+ # ==================== 执行函数 ====================
117
+ result = await func(*args, **kwargs)
118
+
119
+ if result is None:
120
+ return None
121
+
122
+ # ==================== 写入缓存 ====================
123
+ try:
124
+ cache_data = _serialize_result(result)
125
+ json_str = json.dumps(cache_data, default=str, ensure_ascii=False)
126
+ await redis_client.setex(cache_key, ttl, json_str)
127
+ logger.debug(f"Cache SET: {cache_key}")
128
+
129
+ except Exception as e:
130
+ logger.error(f"Cache write error for {cache_key}: {e}")
131
+
132
+ return result
133
+
134
+ return wrapper
135
+
136
+ return decorator
137
+
138
+
139
+ def _serialize_result(result: Any) -> Any:
140
+ """序列化结果"""
141
+ if isinstance(result, BaseModel):
142
+ return result.model_dump()
143
+ elif isinstance(result, list):
144
+ return [
145
+ item.model_dump() if isinstance(item, BaseModel) else item
146
+ for item in result
147
+ ]
148
+ elif isinstance(result, dict):
149
+ return {
150
+ k: v.model_dump() if isinstance(v, BaseModel) else v
151
+ for k, v in result.items()
152
+ }
153
+ return result
154
+
155
+
156
+ def _get_redis_client(args: tuple, kwargs: dict):
157
+
158
+ for arg in list(args) + list(kwargs.values()):
159
+ if hasattr(arg, "app") and hasattr(arg.app, "state"):
160
+ redis = getattr(arg.app.state, "redis_client", None)
161
+ if redis:
162
+ return redis
163
+
164
+ return None
165
+
166
+
167
+ def _build_cache_key(func: Callable, args: tuple, kwargs: dict) -> str:
168
+ """构建缓存键"""
169
+ key_parts = [func.__name__]
170
+
171
+ # 获取函数签名
172
+ sig = inspect.signature(func)
173
+ param_names = list(sig.parameters.keys())
174
+
175
+ # 处理位置参数(跳过 self 和 request)
176
+ for i, arg in enumerate(args):
177
+ if i == 0 and param_names and param_names[0] in ["self", "cls"]:
178
+ continue
179
+ if _is_cacheable_value(arg):
180
+ key_parts.append(str(arg))
181
+
182
+ # 处理关键字参数
183
+ for k, v in sorted(kwargs.items()):
184
+ if k not in ["req", "request"] and _is_cacheable_value(v):
185
+ key_parts.append(f"{k}={v}")
186
+
187
+ return ":".join(key_parts)
188
+
189
+
190
+ def _is_cacheable_value(value: Any) -> bool:
191
+ """判断值是否可用于缓存键"""
192
+ return isinstance(value, (str, int, float, bool))
@@ -0,0 +1,10 @@
1
+ import importlib
2
+ from pathlib import Path
3
+
4
+
5
+ def auto_import_modules_from_folder(package: str, folder: str):
6
+ for file in Path(folder).glob("*.py"):
7
+ if file.name.startswith("_"):
8
+ continue
9
+ module_name = f"{package}.{file.stem}"
10
+ importlib.import_module(module_name)
@@ -0,0 +1,10 @@
1
+ import json
2
+
3
+
4
+ def parse_message(msg):
5
+ try:
6
+ data = json.loads(msg.data.decode())
7
+ return data
8
+ except json.JSONDecodeError:
9
+ print("[NATS] Failed to decode message")
10
+ return {}
@@ -0,0 +1,8 @@
1
+ # !/usr/bin/env python
2
+ # -*-coding:utf-8 -*-
3
+
4
+ """
5
+ # Time :2025/6/25 21:51
6
+ # Author :Maxwell
7
+ # Description:
8
+ """
@@ -0,0 +1,47 @@
1
+ # !/usr/bin/env python
2
+ # -*-coding:utf-8 -*-
3
+
4
+ """
5
+ # Time :2025/7/9 16:10
6
+ # Author :Maxwell
7
+ # Description:
8
+ """
9
+ from pydantic import BaseModel
10
+ from datetime import datetime
11
+
12
+
13
+ class BaseResp(BaseModel):
14
+ class Config:
15
+ arbitrary_types_allowed = True
16
+
17
+ def _format_price(self, value: float) -> str:
18
+ """格式化价格显示(带两位小数)"""
19
+ if value:
20
+ return f"{value:.2f} 元"
21
+ return ""
22
+
23
+ def _format_percentage(self, value: float) -> str:
24
+ """格式化百分比显示(带百分号)"""
25
+ if value:
26
+ return f"{value:.2%}"
27
+ return ""
28
+
29
+ def _format_volume(self, value: int) -> str:
30
+ """格式化成交量显示(带千位分隔符)"""
31
+ if value:
32
+ return f"{value:,} 股"
33
+ return ""
34
+
35
+ def _format_amount(self, value: float) -> str:
36
+ """格式化成交额显示(带千位分隔符)"""
37
+ if value:
38
+ return f"{value:,} 元"
39
+ return ""
40
+
41
+ def _format_time(self, timestamp: int) -> str:
42
+ """将时间戳转换为可读时间"""
43
+ if timestamp:
44
+ return datetime.fromtimestamp(timestamp / 1000).strftime(
45
+ "%Y-%m-%d %H:%M:%S"
46
+ )
47
+ return ""