toms-fast 0.2.1__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 (60) hide show
  1. toms_fast-0.2.1.dist-info/METADATA +467 -0
  2. toms_fast-0.2.1.dist-info/RECORD +60 -0
  3. toms_fast-0.2.1.dist-info/WHEEL +4 -0
  4. toms_fast-0.2.1.dist-info/entry_points.txt +2 -0
  5. tomskit/__init__.py +0 -0
  6. tomskit/celery/README.md +693 -0
  7. tomskit/celery/__init__.py +4 -0
  8. tomskit/celery/celery.py +306 -0
  9. tomskit/celery/config.py +377 -0
  10. tomskit/cli/__init__.py +207 -0
  11. tomskit/cli/__main__.py +8 -0
  12. tomskit/cli/scaffold.py +123 -0
  13. tomskit/cli/templates/__init__.py +42 -0
  14. tomskit/cli/templates/base.py +348 -0
  15. tomskit/cli/templates/celery.py +101 -0
  16. tomskit/cli/templates/extensions.py +213 -0
  17. tomskit/cli/templates/fastapi.py +400 -0
  18. tomskit/cli/templates/migrations.py +281 -0
  19. tomskit/cli/templates_config.py +122 -0
  20. tomskit/logger/README.md +466 -0
  21. tomskit/logger/__init__.py +4 -0
  22. tomskit/logger/config.py +106 -0
  23. tomskit/logger/logger.py +290 -0
  24. tomskit/py.typed +0 -0
  25. tomskit/redis/README.md +462 -0
  26. tomskit/redis/__init__.py +6 -0
  27. tomskit/redis/config.py +85 -0
  28. tomskit/redis/redis_pool.py +87 -0
  29. tomskit/redis/redis_sync.py +66 -0
  30. tomskit/server/__init__.py +47 -0
  31. tomskit/server/config.py +117 -0
  32. tomskit/server/exceptions.py +412 -0
  33. tomskit/server/middleware.py +371 -0
  34. tomskit/server/parser.py +312 -0
  35. tomskit/server/resource.py +464 -0
  36. tomskit/server/server.py +276 -0
  37. tomskit/server/type.py +263 -0
  38. tomskit/sqlalchemy/README.md +590 -0
  39. tomskit/sqlalchemy/__init__.py +20 -0
  40. tomskit/sqlalchemy/config.py +125 -0
  41. tomskit/sqlalchemy/database.py +125 -0
  42. tomskit/sqlalchemy/pagination.py +359 -0
  43. tomskit/sqlalchemy/property.py +19 -0
  44. tomskit/sqlalchemy/sqlalchemy.py +131 -0
  45. tomskit/sqlalchemy/types.py +32 -0
  46. tomskit/task/README.md +67 -0
  47. tomskit/task/__init__.py +4 -0
  48. tomskit/task/task_manager.py +124 -0
  49. tomskit/tools/README.md +63 -0
  50. tomskit/tools/__init__.py +18 -0
  51. tomskit/tools/config.py +70 -0
  52. tomskit/tools/warnings.py +37 -0
  53. tomskit/tools/woker.py +81 -0
  54. tomskit/utils/README.md +666 -0
  55. tomskit/utils/README_SERIALIZER.md +644 -0
  56. tomskit/utils/__init__.py +35 -0
  57. tomskit/utils/fields.py +434 -0
  58. tomskit/utils/marshal_utils.py +137 -0
  59. tomskit/utils/response_utils.py +13 -0
  60. tomskit/utils/serializers.py +447 -0
@@ -0,0 +1,290 @@
1
+ """
2
+ 日志模块
3
+
4
+ 提供结构化的日志配置和追踪 ID 支持,支持应用日志、访问日志和 SQL 日志的分离。
5
+ 支持集中管理框架日志(uvicorn、fastapi)和第三方库日志。
6
+ """
7
+
8
+ import os
9
+ import logging
10
+ from contextvars import ContextVar
11
+ from logging.handlers import TimedRotatingFileHandler
12
+ from typing import Optional
13
+ from tomskit.logger.config import LoggerConfig
14
+
15
+ # === Trace ID 上下文变量 ===
16
+ # 使用 ContextVar 实现线程/协程安全的上下文变量,支持异步环境
17
+ _trace_id_context_var: ContextVar[str] = ContextVar("app_trace_id", default="-")
18
+
19
+
20
+ def set_app_trace_id(trace_id: str) -> None:
21
+ """
22
+ 设置当前请求的追踪 ID
23
+
24
+ 该 ID 会自动添加到所有日志记录中,用于分布式追踪和问题定位。
25
+ 通常在 FastAPI 中间件中调用,为每个请求生成唯一的追踪 ID。
26
+
27
+ Args:
28
+ trace_id: 追踪 ID 字符串,通常是 UUID
29
+ """
30
+ _trace_id_context_var.set(trace_id)
31
+
32
+
33
+ def get_app_trace_id() -> str:
34
+ """
35
+ 获取当前请求的追踪 ID
36
+
37
+ Returns:
38
+ 追踪 ID 字符串,如果未设置则返回默认值 "-"
39
+ """
40
+ return _trace_id_context_var.get()
41
+
42
+
43
+ class TraceIdFormatter(logging.Formatter):
44
+ """
45
+ 追踪 ID 格式化器
46
+
47
+ 继承自 logging.Formatter,自动为每条日志记录注入 trace_id 和 prefix。
48
+ 确保所有日志都包含追踪信息,便于问题定位和日志关联。
49
+ """
50
+
51
+ def __init__(self, prefix: str = "", *args, **kwargs):
52
+ """
53
+ 初始化格式化器
54
+
55
+ Args:
56
+ prefix: 日志前缀,会添加到每条日志记录中
57
+ *args, **kwargs: 传递给父类的其他参数
58
+ """
59
+ self.prefix = prefix
60
+ super().__init__(*args, **kwargs)
61
+
62
+ def format(self, record: logging.LogRecord) -> str:
63
+ """
64
+ 格式化日志记录
65
+
66
+ 自动注入 trace_id 和 prefix 到日志记录中。
67
+ 注意:LOG_FORMAT 中需要包含 %(trace_id)s 和 %(prefix)s 才能显示。
68
+
69
+ Args:
70
+ record: 日志记录对象
71
+
72
+ Returns:
73
+ 格式化后的日志字符串
74
+ """
75
+ record.trace_id = get_app_trace_id()
76
+ record.prefix = self.prefix
77
+ return super().format(record)
78
+
79
+
80
+ def setup_logging(settings: LoggerConfig) -> None:
81
+ """
82
+ 配置日志系统
83
+
84
+ 设置应用日志、访问日志、SQL 日志和 Celery 日志的处理器和格式化器。
85
+ 集中管理框架日志(uvicorn、fastapi、celery)和第三方库日志。
86
+
87
+ Args:
88
+ settings: 日志配置对象
89
+
90
+ Raises:
91
+ OSError: 当日志目录创建失败时抛出
92
+ PermissionError: 当没有权限创建日志文件时抛出
93
+ """
94
+ # === 基础配置 ===
95
+ log_level = settings.LOG_LEVEL.upper()
96
+ application_log_path = os.path.join(settings.LOG_DIR, f"{settings.LOG_NAME}.log")
97
+ access_log_path = os.path.join(settings.LOG_DIR, f"{settings.LOG_ACCESS_NAME}.log")
98
+ sql_log_path = os.path.join(settings.LOG_DIR, f"{settings.LOG_SQL_NAME}.log")
99
+ celery_log_path = os.path.join(settings.LOG_DIR, f"{settings.LOG_CELERY_NAME}.log")
100
+
101
+ # 创建日志目录(如果不存在)
102
+ try:
103
+ os.makedirs(settings.LOG_DIR, exist_ok=True)
104
+ except (OSError, PermissionError) as e:
105
+ raise OSError(f"无法创建日志目录 {settings.LOG_DIR}: {e}")
106
+
107
+ # === 格式化器 ===
108
+ application_log_formatter = TraceIdFormatter(
109
+ prefix=settings.LOG_PREFIX,
110
+ fmt=settings.LOG_FORMAT,
111
+ datefmt=settings.LOG_DATE_FORMAT
112
+ )
113
+
114
+ access_log_formatter = TraceIdFormatter(
115
+ prefix=settings.LOG_PREFIX,
116
+ fmt=settings.LOG_ACCESS_FORMAT,
117
+ datefmt=settings.LOG_DATE_FORMAT
118
+ )
119
+
120
+ # === 处理器 ===
121
+ # 控制台处理器:输出到标准输出
122
+ console_handler = logging.StreamHandler()
123
+ console_handler.setFormatter(application_log_formatter)
124
+
125
+ # 应用日志文件处理器:按天轮转
126
+ try:
127
+ application_file_handler = TimedRotatingFileHandler(
128
+ filename=application_log_path,
129
+ when=settings.LOG_ROTATE_WHEN,
130
+ backupCount=settings.LOG_BACKUP_COUNT,
131
+ encoding="utf-8",
132
+ utc=settings.LOG_USE_UTC,
133
+ )
134
+ application_file_handler.suffix = "%Y-%m-%d"
135
+ application_file_handler.setFormatter(application_log_formatter)
136
+ except (OSError, PermissionError) as e:
137
+ raise OSError(f"无法创建应用日志文件 {application_log_path}: {e}")
138
+
139
+ # 访问日志文件处理器:按天轮转
140
+ try:
141
+ access_file_handler = TimedRotatingFileHandler(
142
+ filename=access_log_path,
143
+ when=settings.LOG_ROTATE_WHEN,
144
+ backupCount=settings.LOG_BACKUP_COUNT,
145
+ encoding="utf-8",
146
+ utc=settings.LOG_USE_UTC,
147
+ )
148
+ access_file_handler.suffix = "%Y-%m-%d"
149
+ access_file_handler.setFormatter(access_log_formatter)
150
+ except (OSError, PermissionError) as e:
151
+ raise OSError(f"无法创建访问日志文件 {access_log_path}: {e}")
152
+
153
+ # SQL 日志文件处理器(可选):按天轮转
154
+ sql_file_handler: Optional[TimedRotatingFileHandler] = None
155
+ if settings.LOG_SQL_ENABLED:
156
+ try:
157
+ sql_file_handler = TimedRotatingFileHandler(
158
+ filename=sql_log_path,
159
+ when=settings.LOG_ROTATE_WHEN,
160
+ backupCount=settings.LOG_BACKUP_COUNT,
161
+ encoding="utf-8",
162
+ utc=settings.LOG_USE_UTC,
163
+ )
164
+ sql_file_handler.suffix = "%Y-%m-%d"
165
+ sql_file_handler.setFormatter(application_log_formatter)
166
+ except (OSError, PermissionError) as e:
167
+ raise OSError(f"无法创建 SQL 日志文件 {sql_log_path}: {e}")
168
+
169
+ # Celery 日志文件处理器(可选):按天轮转
170
+ celery_file_handler: Optional[TimedRotatingFileHandler] = None
171
+ if settings.LOG_CELERY_ENABLED:
172
+ try:
173
+ celery_file_handler = TimedRotatingFileHandler(
174
+ filename=celery_log_path,
175
+ when=settings.LOG_ROTATE_WHEN,
176
+ backupCount=settings.LOG_BACKUP_COUNT,
177
+ encoding="utf-8",
178
+ utc=settings.LOG_USE_UTC,
179
+ )
180
+ celery_file_handler.suffix = "%Y-%m-%d"
181
+ celery_file_handler.setFormatter(application_log_formatter)
182
+ except (OSError, PermissionError) as e:
183
+ raise OSError(f"无法创建 Celery 日志文件 {celery_log_path}: {e}")
184
+
185
+ # === Root Logger:所有 getLogger(__name__) 会冒泡到这里 ===
186
+ root_logger = logging.getLogger()
187
+ root_logger.setLevel(log_level)
188
+ root_logger.handlers.clear()
189
+ root_logger.addHandler(console_handler)
190
+ root_logger.addHandler(application_file_handler)
191
+
192
+ # === 访问日志(gunicorn/uvicorn)===
193
+ # 访问日志单独处理,不冒泡到 root,避免重复
194
+ for access_logger_name in ["uvicorn.access", "gunicorn.access"]:
195
+ access_logger = logging.getLogger(access_logger_name)
196
+ access_logger.setLevel("INFO")
197
+ access_logger.handlers.clear()
198
+ access_logger.addHandler(console_handler)
199
+ access_logger.addHandler(access_file_handler)
200
+ access_logger.propagate = False # 不冒泡到 root,避免重复
201
+
202
+ # === 框架日志(uvicorn、fastapi)===
203
+ # uvicorn 错误日志和主日志
204
+ for uvicorn_logger_name in ["uvicorn.error", "uvicorn"]:
205
+ uvicorn_logger = logging.getLogger(uvicorn_logger_name)
206
+ uvicorn_logger.setLevel(log_level)
207
+ uvicorn_logger.handlers.clear()
208
+ uvicorn_logger.addHandler(console_handler)
209
+ uvicorn_logger.addHandler(application_file_handler)
210
+ uvicorn_logger.propagate = False # 不冒泡到 root,避免重复
211
+
212
+ # FastAPI 日志
213
+ fastapi_logger = logging.getLogger("fastapi")
214
+ fastapi_logger.setLevel(log_level)
215
+ fastapi_logger.handlers.clear()
216
+ fastapi_logger.addHandler(console_handler)
217
+ fastapi_logger.addHandler(application_file_handler)
218
+ fastapi_logger.propagate = False # 不冒泡到 root,避免重复
219
+
220
+ # === SQLAlchemy 日志 ===
221
+ if settings.LOG_SQL_ENABLED and sql_file_handler:
222
+ # SQL 日志单独文件处理
223
+ for sql_logger_name in ["sqlalchemy.engine", "sqlalchemy.pool"]:
224
+ sql_logger = logging.getLogger(sql_logger_name)
225
+ sql_logger.setLevel(settings.LOG_SQL_LEVEL.upper())
226
+ sql_logger.handlers.clear()
227
+ sql_logger.addHandler(sql_file_handler) # 只写入 SQL 日志文件
228
+ sql_logger.propagate = False # 不冒泡,避免重复
229
+ else:
230
+ # SQL 日志合并到应用日志
231
+ for sql_logger_name in ["sqlalchemy.engine", "sqlalchemy.pool"]:
232
+ sql_logger = logging.getLogger(sql_logger_name)
233
+ sql_logger.setLevel(settings.LOG_SQL_LEVEL.upper())
234
+ sql_logger.handlers.clear()
235
+ sql_logger.addHandler(console_handler)
236
+ sql_logger.addHandler(application_file_handler)
237
+ sql_logger.propagate = False # 不冒泡,避免重复
238
+
239
+ # === Celery 日志 ===
240
+ celery_logger_names = ["celery", "celery.task", "celery.worker", "celery.beat"]
241
+ if settings.LOG_CELERY_ENABLED and celery_file_handler:
242
+ # Celery 日志单独文件处理
243
+ for celery_logger_name in celery_logger_names:
244
+ celery_logger = logging.getLogger(celery_logger_name)
245
+ celery_logger.setLevel(settings.LOG_CELERY_LEVEL.upper())
246
+ celery_logger.handlers.clear()
247
+ celery_logger.addHandler(celery_file_handler) # 只写入 Celery 日志文件
248
+ celery_logger.propagate = False # 不冒泡,避免重复
249
+
250
+ # Kombu 日志(消息队列)也写入 Celery 日志文件
251
+ kombu_logger = logging.getLogger("kombu")
252
+ kombu_logger.setLevel(settings.LOG_CELERY_LEVEL.upper())
253
+ kombu_logger.handlers.clear()
254
+ kombu_logger.addHandler(celery_file_handler)
255
+ kombu_logger.propagate = False # 不冒泡,避免重复
256
+ else:
257
+ # Celery 日志合并到应用日志
258
+ for celery_logger_name in celery_logger_names:
259
+ celery_logger = logging.getLogger(celery_logger_name)
260
+ celery_logger.setLevel(settings.LOG_CELERY_LEVEL.upper())
261
+ celery_logger.handlers.clear()
262
+ celery_logger.addHandler(console_handler)
263
+ celery_logger.addHandler(application_file_handler)
264
+ celery_logger.propagate = False # 不冒泡,避免重复
265
+
266
+ # Kombu 日志合并到应用日志
267
+ kombu_logger = logging.getLogger("kombu")
268
+ kombu_logger.setLevel(settings.LOG_CELERY_LEVEL.upper())
269
+ kombu_logger.handlers.clear()
270
+ kombu_logger.addHandler(console_handler)
271
+ kombu_logger.addHandler(application_file_handler)
272
+ kombu_logger.propagate = False # 不冒泡,避免重复
273
+
274
+ # === 系统日志降噪 ===
275
+ # asyncio 日志:只显示 WARNING 及以上级别
276
+ logging.getLogger("asyncio").setLevel(logging.WARNING)
277
+
278
+ # === 第三方库日志降噪 ===
279
+ # HTTP 客户端库
280
+ third_party_log_level = getattr(logging, settings.LOG_THIRD_PARTY_LEVEL.upper(), logging.WARNING)
281
+ for http_lib_name in ["httpx", "httpcore", "urllib3"]:
282
+ logging.getLogger(http_lib_name).setLevel(third_party_log_level)
283
+
284
+ # AWS SDK(如果使用)
285
+ for aws_lib_name in ["boto3", "botocore"]:
286
+ logging.getLogger(aws_lib_name).setLevel(third_party_log_level)
287
+
288
+ # 其他可能产生大量日志的库
289
+ logging.getLogger("PIL").setLevel(third_party_log_level) # Pillow
290
+ logging.getLogger("matplotlib").setLevel(third_party_log_level)
tomskit/py.typed ADDED
File without changes