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,447 @@
1
+ import asyncio
2
+ import inspect
3
+ from collections.abc import Sequence, Iterable
4
+ from typing import Any, Type, TypeVar, get_origin, get_args
5
+ from pydantic import BaseModel
6
+ from pydantic.fields import FieldInfo
7
+
8
+ T = TypeVar("T", bound=BaseModel)
9
+
10
+
11
+ class AsyncSerializer:
12
+ """通用异步序列化器 (Pydantic V2 版本)
13
+
14
+ 功能:
15
+ 1. 将 ORM 对象 (SQLAlchemy) 或 字典 转换为 Pydantic 模型
16
+ 2. 自动识别并 await 异步属性 (Async Properties)
17
+ 3. 自动调用函数/异步函数:如果字典值或对象属性是 callable,会自动调用并 await(如果是异步)
18
+ 4. 并发处理 List 类型的嵌套字段
19
+ 5. 支持混合数据源 (Object + Dict)
20
+ 6. 正确处理 Pydantic V2 的 validation_alias (支持 AliasChoices, AliasPath)
21
+
22
+ 使用示例:
23
+ # 字典中的函数
24
+ data = {"name": lambda: "John", "age": async_func}
25
+ result = await AsyncSerializer.serialize(UserModel, data)
26
+
27
+ # 对象属性是异步函数
28
+ class User:
29
+ @property
30
+ async def full_name(self):
31
+ return f"{self.first} {self.last}"
32
+ """
33
+
34
+ # 字段元数据缓存
35
+ _field_keys_cache: dict[tuple[Type, str], list[str]] = {}
36
+ _pydantic_keys_cache: dict[tuple[Type, str], str] = {}
37
+
38
+ # 批处理大小常量
39
+ BATCH_SIZE = 100
40
+
41
+ @classmethod
42
+ async def serialize(cls, model_cls: Type[T], source_data: Any) -> T | None:
43
+ """
44
+ 单对象序列化入口
45
+
46
+ Args:
47
+ model_cls: Pydantic 模型类
48
+ source_data: 数据源 (ORM 对象 / 字典 / 混合体)
49
+
50
+ Returns:
51
+ 序列化后的 Pydantic 模型实例,如果 source_data 为 None 则返回 None
52
+ """
53
+ if source_data is None:
54
+ return None
55
+
56
+ # 优化:如果源数据本身就是该模型的实例,直接返回 (避免重复序列化)
57
+ if isinstance(source_data, model_cls):
58
+ return source_data
59
+
60
+ data: dict[str, Any] = {}
61
+
62
+ for field_name, field_info in model_cls.model_fields.items():
63
+ keys = cls._get_cached_keys(model_cls, field_name, field_info)
64
+ raw_value = await cls._get_value(source_data, keys)
65
+ resolved_value = await cls._resolve_value(raw_value, field_info.annotation)
66
+
67
+ # 处理默认值:如果字段有默认值且值为 None,跳过让 Pydantic 使用默认值
68
+ # 但如果默认值本身就是 None,则传递 None
69
+ if resolved_value is None:
70
+ has_default = field_info.default is not ...
71
+ has_default_factory = field_info.default_factory is not None
72
+ if (has_default or has_default_factory) and not (has_default and field_info.default is None):
73
+ continue
74
+
75
+ pydantic_key = cls._get_cached_pydantic_key(model_cls, field_name, field_info)
76
+ data[pydantic_key] = resolved_value
77
+
78
+ return model_cls(**data)
79
+
80
+ @classmethod
81
+ async def serialize_list(cls, model_cls: Type[T], items: Sequence[Any] | Iterable[Any]) -> list[T]:
82
+ """列表序列化入口(并发处理)
83
+
84
+ Args:
85
+ model_cls: Pydantic 模型类
86
+ items: 待序列化的对象列表
87
+
88
+ Returns:
89
+ 序列化后的模型列表(过滤掉 None 值)
90
+ """
91
+ if not items:
92
+ return []
93
+
94
+ # 优化:避免不必要的类型检查和转换
95
+ if isinstance(items, list):
96
+ items_list = items
97
+ else:
98
+ items_list = list(items)
99
+
100
+ if not items_list:
101
+ return []
102
+
103
+ if len(items_list) <= cls.BATCH_SIZE:
104
+ results = await asyncio.gather(*(
105
+ cls.serialize(model_cls, item) for item in items_list
106
+ ))
107
+ return [item for item in results if item is not None]
108
+ else:
109
+ results = []
110
+ for i in range(0, len(items_list), cls.BATCH_SIZE):
111
+ batch = items_list[i:i + cls.BATCH_SIZE]
112
+ batch_results = await asyncio.gather(*(
113
+ cls.serialize(model_cls, item) for item in batch
114
+ ))
115
+ results.extend(batch_results)
116
+ return [item for item in results if item is not None]
117
+
118
+ @classmethod
119
+ async def serialize_pagination(
120
+ cls,
121
+ pagination_obj: Any,
122
+ model_cls: Type[T]
123
+ ) -> dict[str, Any]:
124
+ """分页对象序列化入口
125
+
126
+ 假设 pagination_obj 具有 items, page, per_page(或 limit), total 属性
127
+
128
+ Args:
129
+ pagination_obj: 分页对象,支持不同分页库的属性命名
130
+ model_cls: 数据项的 Pydantic 模型类
131
+
132
+ Returns:
133
+ 包含 page, limit, total, has_more, data 的字典
134
+ """
135
+ limit = getattr(pagination_obj, 'per_page', None) or getattr(pagination_obj, 'limit', 10)
136
+ limit = int(limit) if limit else 10
137
+ page = getattr(pagination_obj, 'page', 1)
138
+ total = getattr(pagination_obj, 'total', 0)
139
+ items = getattr(pagination_obj, 'items', [])
140
+ serialized_items = await cls.serialize_list(model_cls, items)
141
+
142
+ return {
143
+ "page": page,
144
+ "limit": limit,
145
+ "total": total,
146
+ "has_more": (page * limit) < total,
147
+ "data": serialized_items
148
+ }
149
+
150
+ @classmethod
151
+ def _get_cached_keys(cls, model_cls: Type, field_name: str, field_info: FieldInfo) -> list[str]:
152
+ """获取缓存的字段键名列表(用于从源数据中取值)"""
153
+ cache_key = (model_cls, field_name)
154
+ if cache_key not in cls._field_keys_cache:
155
+ cls._field_keys_cache[cache_key] = cls._extract_keys_from_alias(field_info, field_name)
156
+ return cls._field_keys_cache[cache_key]
157
+
158
+ @classmethod
159
+ def _get_cached_pydantic_key(cls, model_cls: Type, field_name: str, field_info: FieldInfo) -> str:
160
+ """获取缓存的 Pydantic 键名(用于传递给 Pydantic)"""
161
+ cache_key = (model_cls, field_name)
162
+ if cache_key not in cls._pydantic_keys_cache:
163
+ cls._pydantic_keys_cache[cache_key] = cls._get_pydantic_key(field_info, field_name)
164
+ return cls._pydantic_keys_cache[cache_key]
165
+
166
+ @staticmethod
167
+ def _extract_keys_from_alias(field_info: FieldInfo, field_name: str) -> list[str]:
168
+ """从 FieldInfo 中提取可能的键名列表(用于从源数据中取值)"""
169
+ validation_alias = field_info.validation_alias
170
+
171
+ if validation_alias is None:
172
+ return [field_name]
173
+
174
+ if isinstance(validation_alias, str):
175
+ return [validation_alias, field_name]
176
+
177
+ if isinstance(validation_alias, tuple):
178
+ keys = [str(k) for k in validation_alias if isinstance(k, str)]
179
+ if keys:
180
+ keys.append(field_name)
181
+ return keys
182
+ return [field_name]
183
+
184
+ try:
185
+ return [str(validation_alias), field_name]
186
+ except Exception:
187
+ return [field_name]
188
+
189
+ @staticmethod
190
+ def _get_pydantic_key(field_info: FieldInfo, field_name: str) -> str:
191
+ """获取传递给 Pydantic 的键名"""
192
+ validation_alias = field_info.validation_alias
193
+
194
+ if validation_alias is None:
195
+ return field_name
196
+
197
+ if isinstance(validation_alias, str):
198
+ return validation_alias
199
+
200
+ if isinstance(validation_alias, tuple):
201
+ keys = [str(k) for k in validation_alias if isinstance(k, str)]
202
+ if keys:
203
+ return keys[0]
204
+ return field_name
205
+
206
+ try:
207
+ return str(validation_alias)
208
+ except Exception:
209
+ return field_name
210
+
211
+ @staticmethod
212
+ async def _get_value(source: Any, keys: list[str]) -> Any:
213
+ """智能取值:支持字典 Key 和对象 Attribute
214
+
215
+ 按 keys 列表的顺序尝试取值。对于字典,区分"键不存在"和"键存在但值为 None"。
216
+ 如果获取到异步属性(协程),会立即 await。
217
+
218
+ Args:
219
+ source: 数据源(字典或对象)
220
+ keys: 可能的键名列表,按优先级排序
221
+
222
+ Returns:
223
+ 找到的值,如果所有键都不存在则返回 None
224
+ """
225
+ if source is None:
226
+ return None
227
+
228
+ is_dict = isinstance(source, dict)
229
+
230
+ for key in keys:
231
+ if is_dict:
232
+ if key in source:
233
+ value = source[key]
234
+ if inspect.isawaitable(value):
235
+ value = await value
236
+ return value
237
+ continue
238
+
239
+ try:
240
+ value = getattr(source, key)
241
+ if inspect.isawaitable(value):
242
+ value = await value
243
+ # 对于对象属性,如果找到了属性(即使值为 None),也应该返回
244
+ # 因为 None 可能是有效值(特别是对于 Optional 类型)
245
+ return value
246
+ except (AttributeError, TypeError):
247
+ continue
248
+
249
+ return None
250
+
251
+ @classmethod
252
+ async def _resolve_value(cls, value: Any, type_annotation: Any = None) -> Any:
253
+ """递归解析值:处理协程、函数、嵌套模型、列表、字典、集合等
254
+
255
+ 支持的 value 类型:
256
+ 1. Awaitable: 自动 await
257
+ 2. Callable: 自动调用,如果返回 awaitable 则 await
258
+ 3. List/Sequence: 如果元素是 Pydantic 模型,并发序列化
259
+ 4. Dict: 如果值是 Pydantic 模型,并发序列化值
260
+ 5. Set: 如果元素是 Pydantic 模型,并发序列化
261
+ 6. Pydantic Model: 递归序列化
262
+
263
+ Args:
264
+ value: 待解析的值(可能是协程、函数、嵌套模型等)
265
+ type_annotation: 类型注解,用于指导解析过程
266
+
267
+ Returns:
268
+ 解析后的值
269
+ """
270
+ # 如果值是协程,直接 await(用于处理异步属性返回的协程)
271
+ # 注意:不支持直接传递协程对象(如 get_async_name()),应该传递函数对象
272
+ if inspect.isawaitable(value):
273
+ value = await value
274
+
275
+ if value is None:
276
+ return None
277
+
278
+ # 处理可调用对象(函数):支持同步函数和异步函数对象
279
+ # 不支持传递协程对象(inspect.iscoroutine),应该传递函数对象
280
+ if (callable(value) and
281
+ not isinstance(value, type) and
282
+ not inspect.iscoroutine(value)):
283
+ try:
284
+ # 调用函数(同步或异步)
285
+ result = value()
286
+ # 如果返回 awaitable,则 await
287
+ if inspect.isawaitable(result):
288
+ result = await result
289
+ value = result
290
+ if value is None:
291
+ return None
292
+ except (TypeError, ValueError, Exception):
293
+ pass
294
+
295
+ if type_annotation is None:
296
+ return value
297
+
298
+ origin = get_origin(type_annotation)
299
+ args = get_args(type_annotation)
300
+
301
+ # 处理 Union 类型(包括 Optional[T])
302
+ if origin is not None:
303
+ origin_name = getattr(origin, '__name__', None) or str(origin)
304
+ is_union = (
305
+ 'Union' in origin_name or
306
+ (args and type(None) in args) or
307
+ (hasattr(origin, '__origin__') and
308
+ getattr(origin, '__origin__', None) is not None)
309
+ )
310
+
311
+ if is_union and args:
312
+ # 根据实际值类型选择最匹配的 Union 成员类型
313
+ non_none_args = [arg for arg in args if arg is not type(None)]
314
+ if non_none_args:
315
+ # 尝试根据值的类型选择匹配的 Union 成员
316
+ matched_type = cls._match_union_type(value, non_none_args)
317
+ if matched_type:
318
+ type_annotation = matched_type
319
+ else:
320
+ type_annotation = non_none_args[0]
321
+ origin = get_origin(type_annotation)
322
+ args = get_args(type_annotation)
323
+
324
+ # 处理列表类型
325
+ is_annotation_list = origin is None and type_annotation is list
326
+ is_annotation_tuple = origin is None and type_annotation is tuple
327
+ is_list_type = origin is list or is_annotation_list
328
+ is_tuple_type = origin is tuple or is_annotation_tuple
329
+ is_sequence_type = origin is not None and origin not in (list, tuple) and isinstance(value, (list, tuple, Sequence))
330
+
331
+ if is_list_type or is_tuple_type or is_sequence_type:
332
+ is_original_tuple = isinstance(value, tuple)
333
+
334
+ if not isinstance(value, (list, tuple)):
335
+ value = list(value) if value else []
336
+
337
+ if not value:
338
+ return () if is_original_tuple else []
339
+
340
+ inner_type = args[0] if args else None
341
+
342
+ if inner_type and isinstance(inner_type, type) and issubclass(inner_type, BaseModel):
343
+ # 对于大列表,分批处理以避免资源耗尽
344
+ results = await cls._batch_serialize(inner_type, value, cls.BATCH_SIZE)
345
+ return tuple(results) if is_original_tuple or is_tuple_type else list(results)
346
+ else:
347
+ return tuple(value) if is_original_tuple or is_tuple_type else value
348
+
349
+ # 处理字典类型
350
+ if origin is dict or isinstance(value, dict):
351
+ if not value:
352
+ return {}
353
+
354
+ value_type = args[1] if len(args) > 1 else None
355
+
356
+ if (value_type and isinstance(value_type, type) and
357
+ issubclass(value_type, BaseModel)):
358
+ keys = list(value.keys())
359
+ # 对于大字典,分批处理以避免资源耗尽
360
+ values_list = await cls._batch_serialize(
361
+ value_type,
362
+ [value[k] for k in keys],
363
+ cls.BATCH_SIZE
364
+ )
365
+ return dict(zip(keys, values_list))
366
+ else:
367
+ return value
368
+
369
+ # 处理集合类型
370
+ if origin is set or isinstance(value, set):
371
+ if not value:
372
+ return set()
373
+
374
+ inner_type = args[0] if args else None
375
+
376
+ if inner_type and isinstance(inner_type, type) and issubclass(inner_type, BaseModel):
377
+ # 对于大集合,分批处理以避免资源耗尽
378
+ value_list = list(value)
379
+ serialized_items = await cls._batch_serialize(inner_type, value_list, cls.BATCH_SIZE)
380
+ return set(serialized_items)
381
+ else:
382
+ return value
383
+
384
+ # 处理嵌套模型
385
+ if isinstance(type_annotation, type) and issubclass(type_annotation, BaseModel):
386
+ return await cls.serialize(type_annotation, value)
387
+
388
+ return value
389
+
390
+ @classmethod
391
+ async def _batch_serialize(cls, model_cls: Type[T], items: list[Any], batch_size: int) -> list[T | None]:
392
+ """批量序列化辅助方法,统一处理大列表的分批逻辑
393
+
394
+ Args:
395
+ model_cls: Pydantic 模型类
396
+ items: 待序列化的项目列表
397
+ batch_size: 每批的大小
398
+
399
+ Returns:
400
+ 序列化后的模型列表(可能包含 None)
401
+ """
402
+ if len(items) <= batch_size:
403
+ return list(await asyncio.gather(*(
404
+ cls.serialize(model_cls, item) for item in items
405
+ )))
406
+
407
+ results = []
408
+ for i in range(0, len(items), batch_size):
409
+ batch = items[i:i + batch_size]
410
+ batch_results = await asyncio.gather(*(
411
+ cls.serialize(model_cls, item) for item in batch
412
+ ))
413
+ results.extend(batch_results)
414
+ return results
415
+
416
+ @staticmethod
417
+ def _match_union_type(value: Any, union_args: list[Any]) -> Any | None:
418
+ """根据实际值类型匹配 Union 成员类型
419
+
420
+ Args:
421
+ value: 实际值
422
+ union_args: Union 类型的所有非 None 成员类型列表
423
+
424
+ Returns:
425
+ 匹配的类型,如果没有匹配则返回 None
426
+ """
427
+ value_type = type(value)
428
+
429
+ # 精确匹配(最快)
430
+ if value_type in union_args:
431
+ return value_type
432
+
433
+ # 检查是否是子类(使用 isinstance,比 issubclass 更快)
434
+ for arg in union_args:
435
+ if isinstance(arg, type) and isinstance(value, arg):
436
+ return arg
437
+
438
+ # 检查是否是 BaseModel 子类(优化:先检查是否是 BaseModel 实例)
439
+ if isinstance(value, BaseModel):
440
+ value_class = value.__class__
441
+ for arg in union_args:
442
+ if isinstance(arg, type) and issubclass(arg, BaseModel):
443
+ # 优化:先检查类是否相同,再检查是否是子类
444
+ if value_class is arg or issubclass(value_class, arg):
445
+ return arg
446
+
447
+ return None