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,15 @@
1
+ # !/usr/bin/env python
2
+ # -*-coding:utf-8 -*-
3
+
4
+ """
5
+ 消息队列基础设施
6
+
7
+ 支持:
8
+ - NATS: 高性能消息队列
9
+ """
10
+
11
+ from .manager import NATSManager
12
+
13
+ __all__ = [
14
+ "NATSManager",
15
+ ]
@@ -0,0 +1,178 @@
1
+ # !/usr/bin/env python
2
+ # -*-coding:utf-8 -*-
3
+
4
+ """
5
+ 消息队列管理器(支持延迟导入)
6
+
7
+ 支持:
8
+ - NATS
9
+ - 其他消息队列(待实现)
10
+ """
11
+
12
+ from typing import Optional, Dict, Any, TYPE_CHECKING
13
+ from fastapi import FastAPI
14
+ from loguru import logger
15
+
16
+ from infoman.config import settings
17
+
18
+ if TYPE_CHECKING:
19
+ from infoman.service.infrastructure.mq.nats.nats_client import NATSClient
20
+
21
+
22
+ class NATSManager:
23
+ """NATS 消息队列管理器"""
24
+
25
+ def __init__(self):
26
+ self.nats_client: Optional[Any] = None
27
+ self.initialized = False
28
+
29
+ @property
30
+ def is_available(self) -> bool:
31
+ """是否可用"""
32
+ return self.nats_client is not None and self.nats_client.connected
33
+
34
+ @property
35
+ def client(self):
36
+ """获取 NATS 客户端"""
37
+ return self.nats_client
38
+
39
+ async def startup(self, app: Optional[FastAPI] = None) -> bool:
40
+ """
41
+ 启动 NATS 连接
42
+
43
+ Args:
44
+ app: FastAPI 应用实例(可选)
45
+
46
+ Returns:
47
+ 是否成功启动
48
+ """
49
+ if self.initialized:
50
+ logger.warning("⚠️ NATSManager 已初始化,跳过重复初始化")
51
+ return True
52
+
53
+ if not settings.NATS_SERVERS:
54
+ logger.info("⏭️ NATS 未配置,跳过初始化")
55
+ return False
56
+
57
+ # 延迟导入 NATSClient
58
+ try:
59
+ from infoman.service.infrastructure.mq.nats.nats_client import NATSClient
60
+ except ImportError as e:
61
+ logger.error(f"❌ NATS 依赖未安装: {e}")
62
+ logger.error("请运行: pip install infomankit[messaging]")
63
+ return False
64
+
65
+ logger.info("🚀 初始化 NATS...")
66
+
67
+ try:
68
+ self.nats_client = NATSClient(
69
+ servers=settings.NATS_SERVERS,
70
+ name=settings.APP_NAME
71
+ )
72
+
73
+ # 连接到 NATS
74
+ await self.nats_client.connect()
75
+
76
+ # 挂载到 app.state(如果提供了 app)
77
+ if app:
78
+ app.state.nats_client = self.nats_client
79
+ logger.debug("✅ NATS 客户端已挂载到 app.state")
80
+
81
+ self.initialized = True
82
+ logger.success(f"✅ NATS 连接成功: {settings.NATS_SERVERS}")
83
+ return True
84
+
85
+ except Exception as e:
86
+ logger.error(f"❌ NATS 连接失败: {e}")
87
+ return False
88
+
89
+ async def shutdown(self):
90
+ """关闭 NATS 连接"""
91
+ if not self.initialized:
92
+ return
93
+
94
+ logger.info("⏹️ 关闭 NATS 连接...")
95
+
96
+ try:
97
+ if self.nats_client:
98
+ await self.nats_client.close()
99
+ logger.success("✅ NATS 连接已关闭")
100
+
101
+ except Exception as e:
102
+ logger.error(f"❌ NATS 关闭失败: {e}")
103
+
104
+ finally:
105
+ self.initialized = False
106
+
107
+ async def health_check(self) -> Dict[str, Any]:
108
+ """
109
+ 健康检查
110
+
111
+ Returns:
112
+ {
113
+ "status": "healthy" | "unhealthy" | "not_configured",
114
+ "name": "nats",
115
+ "details": {...}
116
+ }
117
+ """
118
+ if not settings.NATS_SERVER:
119
+ return {
120
+ "status": "not_configured",
121
+ "name": "nats",
122
+ "details": {"enabled": False}
123
+ }
124
+
125
+ if not self.initialized or not self.nats_client:
126
+ return {
127
+ "status": "unhealthy",
128
+ "name": "nats",
129
+ "details": {"error": "未初始化"}
130
+ }
131
+
132
+ try:
133
+ # 检查连接状态
134
+ is_connected = self.nats_client.connected
135
+
136
+ if is_connected:
137
+ return {
138
+ "status": "healthy",
139
+ "name": "nats",
140
+ "details": {
141
+ "connected": True,
142
+ "servers": self.nats_client.servers
143
+ }
144
+ }
145
+ else:
146
+ return {
147
+ "status": "unhealthy",
148
+ "name": "nats",
149
+ "details": {
150
+ "connected": False,
151
+ "error": "连接已断开"
152
+ }
153
+ }
154
+
155
+ except Exception as e:
156
+ return {
157
+ "status": "unhealthy",
158
+ "name": "nats",
159
+ "details": {"error": str(e)}
160
+ }
161
+
162
+ async def get_stats(self) -> Dict[str, Any]:
163
+ """
164
+ 获取统计信息
165
+
166
+ Returns:
167
+ {
168
+ "connected": bool,
169
+ "servers": list,
170
+ }
171
+ """
172
+ if not self.is_available:
173
+ return {}
174
+
175
+ return {
176
+ "connected": self.nats_client.connected,
177
+ "servers": self.nats_client.servers,
178
+ }
File without changes
@@ -0,0 +1,57 @@
1
+ import asyncio
2
+ import json
3
+ from nats.aio.client import Client as NATS
4
+ from nats.aio.errors import ErrConnectionClosed, ErrTimeout, ErrNoServers
5
+ from infoman.logger import logger
6
+
7
+
8
+ class NATSClient:
9
+
10
+ def __init__(self, servers=None, name="nats-client"):
11
+ if servers is None:
12
+ servers = ["nats://127.0.0.1:4222"]
13
+ self.nc = NATS()
14
+ self.servers = servers
15
+ self.name = name
16
+ self.connected = False
17
+
18
+ async def connect(self):
19
+ if not self.connected:
20
+ await self.nc.connect(servers=self.servers, name=self.name)
21
+ self.connected = True
22
+ logger.info(f"[NATS] Connected to {self.servers}")
23
+
24
+ async def publish(self, subject: str, message: dict):
25
+ if not self.connected:
26
+ await self.connect()
27
+ try:
28
+ payload = json.dumps(message).encode()
29
+ await self.nc.publish(subject, payload)
30
+ logger.info(f"[NATS] Published to {subject}: {message}")
31
+ except Exception as e:
32
+ logger.info(f"[NATS] Publish error: {e}")
33
+
34
+ async def request(self, subject: str, message: dict, timeout=1.0):
35
+ if not self.connected:
36
+ await self.connect()
37
+ try:
38
+ payload = json.dumps(message).encode()
39
+ msg = await self.nc.request(subject, payload, timeout=timeout)
40
+ return json.loads(msg.data.decode())
41
+ except ErrTimeout:
42
+ logger.info("[NATS] Request timeout")
43
+ return None
44
+ except Exception as e:
45
+ logger.info(f"[NATS] Request error: {e}")
46
+ return None
47
+
48
+ async def subscribe(self, subject: str, callback, queue: str):
49
+ if not self.connected:
50
+ await self.connect()
51
+ await self.nc.subscribe(subject, cb=callback, queue=queue)
52
+
53
+ async def close(self):
54
+ if self.connected:
55
+ await self.nc.drain()
56
+ self.connected = False
57
+ logger.info("[NATS] Connection closed")
@@ -0,0 +1,25 @@
1
+ from infoman.logger import logger
2
+
3
+
4
+ class EventRouter:
5
+ def __init__(self):
6
+ self.routes = {}
7
+
8
+ def on(self, subject, queue=None):
9
+ def decorator(func):
10
+ self.routes[subject] = {"handler": func, "queue": queue}
11
+ return func
12
+
13
+ return decorator
14
+
15
+ async def register(self, nats_client):
16
+ for subject, meta in self.routes.items():
17
+
18
+ async def handler(msg, func=meta["handler"]):
19
+ await func(msg, nats_cli=nats_client)
20
+
21
+ await nats_client.subscribe(subject, handler, meta["queue"])
22
+ logger.info(f"[Router] Bound {meta['handler'].__name__} to '{subject}'")
23
+
24
+
25
+ event_router = EventRouter()
@@ -0,0 +1,284 @@
1
+ #!/usr/bin/env python
2
+ # -*-coding:utf-8 -*-
3
+
4
+ """
5
+ 应用启动入口(库模式)
6
+
7
+ 作为基础库使用时,支持:
8
+ 1. 直接启动内置应用:python -m infoman.service.launch
9
+ 2. 启动用户应用:python -m infoman.service.launch --app your_module:app
10
+ 3. 作为库函数调用:from infoman.service.launch import serve
11
+
12
+ 支持多种 ASGI 服务器:
13
+ - granian (推荐生产环境,Rust 实现,性能最佳)
14
+ - uvicorn (开发环境友好,热重载)
15
+ - gunicorn (传统部署)
16
+ """
17
+
18
+ import os
19
+ from typing import Optional, Dict, Any
20
+
21
+
22
+ def serve(
23
+ app_target: str = "infoman.service.app:application",
24
+ server: str = "granian",
25
+ host: Optional[str] = None,
26
+ port: Optional[int] = None,
27
+ workers: Optional[int] = None,
28
+ log_level: Optional[str] = None,
29
+ **kwargs
30
+ ):
31
+ """
32
+ 启动 ASGI 应用服务器(库函数)
33
+
34
+ Args:
35
+ app_target: 应用目标 (格式: "module.path:app_instance")
36
+ server: 服务器类型 (granian/uvicorn/gunicorn)
37
+ host: 监听地址
38
+ port: 监听端口
39
+ workers: 工作进程数
40
+ threads: 线程数(仅 Granian)
41
+ reload: 是否启用热重载
42
+ log_level: 日志级别
43
+ **kwargs: 其他服务器特定参数
44
+
45
+ Examples:
46
+ >>> # 启动默认应用
47
+ >>> from infoman.service.launch import serve
48
+ >>> serve()
49
+
50
+ >>> # 启动自定义应用
51
+ >>> serve(app_target="myapp.main:app", port=8080)
52
+
53
+ >>> # 生产环境配置
54
+ >>> serve(
55
+ ... app_target="myapp.main:app",
56
+ ... server="granian",
57
+ ... workers=4,
58
+ ... reload=False,
59
+ ... log_level="info"
60
+ ... )
61
+ """
62
+ # 导入配置(优先使用参数,其次使用配置文件)
63
+ try:
64
+ from infoman.config import settings
65
+ except ImportError:
66
+ settings = None
67
+
68
+ # 参数优先级:函数参数 > 配置文件 > 默认值
69
+ # 注意:在 macOS 上使用 Granian 时,0.0.0.0 可能导致 "Can't assign requested address" 错误
70
+ import platform
71
+
72
+ # 确定主机地址
73
+ if host:
74
+ # 用户明确指定,直接使用
75
+ resolved_host = host
76
+ elif settings and settings.APP_HOST != "0.0.0.0":
77
+ # 配置文件中有非默认值,使用配置
78
+ resolved_host = settings.APP_HOST
79
+ else:
80
+ # 使用平台相关的默认值
81
+ if server == "granian" and platform.system() == "Darwin":
82
+ # macOS + Granian: 使用 127.0.0.1
83
+ resolved_host = "127.0.0.1"
84
+ else:
85
+ # 其他情况:使用 0.0.0.0
86
+ resolved_host = "0.0.0.0"
87
+
88
+ config = {
89
+ "host": resolved_host,
90
+ "port": port or (settings.APP_PORT if settings else 8000),
91
+ "workers": workers or (settings.APP_WORKERS if settings and hasattr(settings, "APP_WORKERS") else 2),
92
+ "log_level": log_level or (settings.LOG_LEVEL.lower() if settings and hasattr(settings, "LOG_LEVEL") else "info"),
93
+ "app_name": settings.APP_NAME if settings else "Application",
94
+ "env": settings.ENV if settings else "unknown",
95
+ "docs_url": settings.DOCS_URL if settings and hasattr(settings, "DOCS_URL") else "/docs",
96
+ }
97
+
98
+ # 合并 kwargs
99
+ config.update(kwargs)
100
+
101
+ # 根据服务器类型启动
102
+ if server == "granian":
103
+ _run_granian(app_target, config)
104
+ elif server == "uvicorn":
105
+ _run_uvicorn(app_target, config)
106
+ elif server == "gunicorn":
107
+ _run_gunicorn(app_target, config)
108
+ else:
109
+ raise ValueError(f"不支持的服务器类型: {server}")
110
+
111
+
112
+ def _run_granian(app_target: str, config: Dict[str, Any]):
113
+ """使用 Granian 启动(内部函数)"""
114
+ try:
115
+ from granian import Granian
116
+ from granian.constants import Interfaces, Loops
117
+ except ImportError:
118
+ raise ImportError(
119
+ "Granian 未安装。请运行: pip install granian\n"
120
+ "或安装完整 web 依赖: pip install infomankit[web]"
121
+ )
122
+
123
+ print(f"🚀 使用 Granian 启动 [{config['app_name']}]")
124
+ print(f" 应用: {app_target}")
125
+ print(f" 环境: {config['env']}")
126
+ print(f" 地址: http://{config['host']}:{config['port']}")
127
+ print(f" 文档: http://{config['host']}:{config['port']}{config['docs_url']}")
128
+ print(f" 进程: {config['workers']} workers")
129
+ # 创建 Granian 实例(仅使用核心兼容参数)
130
+ # Granian 2.6.0+ 的核心参数
131
+ app = Granian(
132
+ target=app_target,
133
+ address=config["host"],
134
+ port=int(config["port"]),
135
+ interface=Interfaces.ASGI,
136
+ workers=config["workers"],
137
+ loop=Loops.auto,
138
+ log_level=config["log_level"],
139
+ reload=config["reload"],
140
+ )
141
+ app.serve()
142
+
143
+
144
+ def _run_uvicorn(app_target: str, config: Dict[str, Any]):
145
+ """使用 Uvicorn 启动(内部函数)"""
146
+ try:
147
+ import uvicorn
148
+ except ImportError:
149
+ raise ImportError(
150
+ "Uvicorn 未安装。请运行: pip install uvicorn\n"
151
+ "或安装完整 web 依赖: pip install infomankit[web]"
152
+ )
153
+
154
+ print(f"🚀 使用 Uvicorn 启动 [{config['app_name']}]")
155
+ print(f" 应用: {app_target}")
156
+ print(f" 环境: {config['env']}")
157
+ print(f" 地址: http://{config['host']}:{config['port']}")
158
+ uvicorn.run(
159
+ app_target,
160
+ host=config["host"],
161
+ port=int(config["port"]),
162
+ reload=config["reload"],
163
+ log_level=config["log_level"],
164
+ access_log=config.get("access_log", config["reload"]),
165
+ workers=config["workers"] if not config["reload"] else 1, # reload 模式只能单进程
166
+ )
167
+
168
+
169
+ def _run_gunicorn(app_target: str, config: Dict[str, Any]):
170
+ """使用 Gunicorn 启动(内部函数)"""
171
+ try:
172
+ import gunicorn
173
+ except ImportError:
174
+ raise ImportError(
175
+ "Gunicorn 未安装。请运行: pip install gunicorn\n"
176
+ "注意: Gunicorn 仅支持 Linux/macOS"
177
+ )
178
+
179
+ print(f"🚀 使用 Gunicorn 启动 [{config['app_name']}]")
180
+ print(f" 应用: {app_target}")
181
+ print(f" 环境: {config['env']}")
182
+ print(f" 地址: http://{config['host']}:{config['port']}")
183
+
184
+ # Gunicorn 配置
185
+ bind_address = f"{config['host']}:{config['port']}"
186
+ worker_class = "uvicorn.workers.UvicornWorker"
187
+ workers = config["workers"]
188
+ os.system(
189
+ f'gunicorn {app_target} '
190
+ f'-b {bind_address} '
191
+ f'-w {workers} '
192
+ f'-k {worker_class} '
193
+ f'--log-level {config["log_level"]} '
194
+ f'--access-logfile - '
195
+ f'--error-logfile -'
196
+ )
197
+
198
+
199
+ def main():
200
+ """命令行入口"""
201
+ import argparse
202
+
203
+ parser = argparse.ArgumentParser(
204
+ description="Infoman Service Launcher - 启动 ASGI 应用服务器",
205
+ formatter_class=argparse.RawDescriptionHelpFormatter,
206
+ epilog="""
207
+ 示例:
208
+ # 启动内置应用
209
+ python -m infoman.service.launch
210
+
211
+ # 启动自定义应用
212
+ python -m infoman.service.launch --app myapp.main:app
213
+
214
+ # 生产环境配置
215
+ python -m infoman.service.launch --server granian --workers 4 --port 8080
216
+
217
+ # 开发环境热重载
218
+ python -m infoman.service.launch --server uvicorn --reload
219
+ """
220
+ )
221
+
222
+ parser.add_argument(
223
+ "--app",
224
+ default="infoman.service.app:application",
225
+ help="应用目标 (格式: module.path:app_instance, 默认: infoman.service.app:application)",
226
+ )
227
+ parser.add_argument(
228
+ "--server",
229
+ choices=["granian", "uvicorn", "gunicorn"],
230
+ default="granian",
231
+ help="选择 ASGI 服务器 (默认: granian)",
232
+ )
233
+ parser.add_argument(
234
+ "--host",
235
+ default=None,
236
+ help="监听地址 (默认: 从配置文件读取或 0.0.0.0)",
237
+ )
238
+ parser.add_argument(
239
+ "--port",
240
+ type=int,
241
+ default=None,
242
+ help="监听端口 (默认: 从配置文件读取或 8000)",
243
+ )
244
+ parser.add_argument(
245
+ "--workers",
246
+ type=int,
247
+ default=None,
248
+ help="工作进程数 (默认: 从配置文件读取或 2)",
249
+ )
250
+ parser.add_argument(
251
+ "--threads",
252
+ type=int,
253
+ default=None,
254
+ help="线程数 (仅 Granian, 默认: 1)",
255
+ )
256
+ parser.add_argument(
257
+ "--reload",
258
+ action="store_true",
259
+ help="启用热重载 (开发环境)",
260
+ )
261
+ parser.add_argument(
262
+ "--log-level",
263
+ choices=["debug", "info", "warning", "error", "critical"],
264
+ default=None,
265
+ help="日志级别 (默认: info)",
266
+ )
267
+
268
+ args = parser.parse_args()
269
+
270
+ # 调用 serve 函数
271
+ serve(
272
+ app_target=args.app,
273
+ server=args.server,
274
+ host=args.host,
275
+ port=args.port,
276
+ workers=args.workers,
277
+ threads=args.threads,
278
+ reload=args.reload,
279
+ log_level=args.log_level,
280
+ )
281
+
282
+
283
+ if __name__ == "__main__":
284
+ main()
@@ -0,0 +1,7 @@
1
+ # !/usr/bin/env python
2
+ # -*-coding:utf-8 -*-
3
+ """
4
+ # Time :2024/2/2 10:36
5
+ # Author :Maxwell
6
+ # Description:
7
+ """
@@ -0,0 +1,41 @@
1
+ # -*- coding:utf-8 -*-
2
+ """
3
+ # Time :2023/12/8 18:23
4
+ # Author :Maxwell
5
+ # version :python 3.9
6
+ # Description:
7
+ """
8
+
9
+ import time
10
+ from fastapi import Request
11
+ from starlette.datastructures import MutableHeaders
12
+ from starlette.types import ASGIApp, Receive, Scope, Send, Message
13
+ from infoman.utils.hash.hash import HashManager
14
+
15
+
16
+ class BaseMiddleware:
17
+
18
+ def __init__(
19
+ self,
20
+ app: ASGIApp,
21
+ ) -> None:
22
+ self.app = app
23
+
24
+ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
25
+ if scope["type"] != "http":
26
+ await self.app(scope, receive, send)
27
+ return
28
+
29
+ start_time = time.time()
30
+ req = Request(scope, receive, send)
31
+ if not req.session.get("session"):
32
+ req.session.setdefault("session", HashManager.uuid())
33
+
34
+ async def send_wrapper(message: Message) -> None:
35
+ process_time = time.time() - start_time
36
+ if message["type"] == "http.response.start":
37
+ headers = MutableHeaders(scope=message)
38
+ headers.append("X-Process-Time", str(process_time))
39
+ await send(message)
40
+
41
+ await self.app(scope, receive, send_wrapper)
@@ -0,0 +1,51 @@
1
+ # !/usr/bin/env python
2
+ # -*-coding:utf-8 -*-
3
+
4
+ """
5
+ # Time :2024/1/12 10:27
6
+ # Author :Maxwell
7
+ # Description:
8
+ """
9
+ import time
10
+ import traceback
11
+ from fastapi import Request, Response
12
+ from starlette.middleware.base import BaseHTTPMiddleware
13
+ from infoman.logger import logger
14
+ from infoman.utils.http.info import ClientInfoExtractor
15
+ from starlette.responses import StreamingResponse, FileResponse
16
+
17
+
18
+ class LoggingMiddleware(BaseHTTPMiddleware):
19
+
20
+ @staticmethod
21
+ def format_size(size_bytes: int) -> str:
22
+ if size_bytes < 1024:
23
+ return f"{size_bytes}B"
24
+ elif size_bytes < 1024 * 1024:
25
+ return f"{size_bytes / 1024:.2f}KB"
26
+ else:
27
+ return f"{size_bytes / (1024 * 1024):.2f}MB"
28
+
29
+ async def dispatch(self, request: Request, call_next) -> Response:
30
+ start_time = time.monotonic()
31
+ client_ip = ClientInfoExtractor.ip_address(request=request)
32
+ path = request.url.path[:200]
33
+
34
+ response = await call_next(request)
35
+ elapsed_ms = int((time.monotonic() - start_time) * 1000)
36
+
37
+ content_length = response.headers.get("content-length")
38
+ if content_length:
39
+ response_size = int(content_length)
40
+ size_str = self.format_size(response_size)
41
+ else:
42
+ size_str = "unknown"
43
+
44
+ logger.info(
45
+ f"Req: ip={client_ip}, elapsed_ms={elapsed_ms}, "
46
+ f"path={path}, status={response.status_code}, "
47
+ f"size={size_str}"
48
+ )
49
+ return response
50
+
51
+