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.
- toms_fast-0.2.1.dist-info/METADATA +467 -0
- toms_fast-0.2.1.dist-info/RECORD +60 -0
- toms_fast-0.2.1.dist-info/WHEEL +4 -0
- toms_fast-0.2.1.dist-info/entry_points.txt +2 -0
- tomskit/__init__.py +0 -0
- tomskit/celery/README.md +693 -0
- tomskit/celery/__init__.py +4 -0
- tomskit/celery/celery.py +306 -0
- tomskit/celery/config.py +377 -0
- tomskit/cli/__init__.py +207 -0
- tomskit/cli/__main__.py +8 -0
- tomskit/cli/scaffold.py +123 -0
- tomskit/cli/templates/__init__.py +42 -0
- tomskit/cli/templates/base.py +348 -0
- tomskit/cli/templates/celery.py +101 -0
- tomskit/cli/templates/extensions.py +213 -0
- tomskit/cli/templates/fastapi.py +400 -0
- tomskit/cli/templates/migrations.py +281 -0
- tomskit/cli/templates_config.py +122 -0
- tomskit/logger/README.md +466 -0
- tomskit/logger/__init__.py +4 -0
- tomskit/logger/config.py +106 -0
- tomskit/logger/logger.py +290 -0
- tomskit/py.typed +0 -0
- tomskit/redis/README.md +462 -0
- tomskit/redis/__init__.py +6 -0
- tomskit/redis/config.py +85 -0
- tomskit/redis/redis_pool.py +87 -0
- tomskit/redis/redis_sync.py +66 -0
- tomskit/server/__init__.py +47 -0
- tomskit/server/config.py +117 -0
- tomskit/server/exceptions.py +412 -0
- tomskit/server/middleware.py +371 -0
- tomskit/server/parser.py +312 -0
- tomskit/server/resource.py +464 -0
- tomskit/server/server.py +276 -0
- tomskit/server/type.py +263 -0
- tomskit/sqlalchemy/README.md +590 -0
- tomskit/sqlalchemy/__init__.py +20 -0
- tomskit/sqlalchemy/config.py +125 -0
- tomskit/sqlalchemy/database.py +125 -0
- tomskit/sqlalchemy/pagination.py +359 -0
- tomskit/sqlalchemy/property.py +19 -0
- tomskit/sqlalchemy/sqlalchemy.py +131 -0
- tomskit/sqlalchemy/types.py +32 -0
- tomskit/task/README.md +67 -0
- tomskit/task/__init__.py +4 -0
- tomskit/task/task_manager.py +124 -0
- tomskit/tools/README.md +63 -0
- tomskit/tools/__init__.py +18 -0
- tomskit/tools/config.py +70 -0
- tomskit/tools/warnings.py +37 -0
- tomskit/tools/woker.py +81 -0
- tomskit/utils/README.md +666 -0
- tomskit/utils/README_SERIALIZER.md +644 -0
- tomskit/utils/__init__.py +35 -0
- tomskit/utils/fields.py +434 -0
- tomskit/utils/marshal_utils.py +137 -0
- tomskit/utils/response_utils.py +13 -0
- tomskit/utils/serializers.py +447 -0
|
@@ -0,0 +1,644 @@
|
|
|
1
|
+
# AsyncSerializer 使用指南
|
|
2
|
+
|
|
3
|
+
## 概述
|
|
4
|
+
|
|
5
|
+
`AsyncSerializer` 是一个通用的异步序列化器,专为 Pydantic V2 设计,能够将 ORM 对象、字典或混合数据源转换为 Pydantic 模型。它支持异步属性、函数调用、嵌套模型、并发处理等高级特性。
|
|
6
|
+
|
|
7
|
+
**Import Path:**
|
|
8
|
+
```python
|
|
9
|
+
from tomskit.utils.serializers import AsyncSerializer
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## 核心特性
|
|
13
|
+
|
|
14
|
+
### ✨ 主要功能
|
|
15
|
+
|
|
16
|
+
1. **多数据源支持**
|
|
17
|
+
- ORM 对象(如 SQLAlchemy 模型)
|
|
18
|
+
- 字典数据
|
|
19
|
+
- 混合数据源(对象 + 字典)
|
|
20
|
+
|
|
21
|
+
2. **异步属性自动处理**
|
|
22
|
+
- 自动识别并 `await` 异步属性(`@property async def`)
|
|
23
|
+
- 自动调用函数/异步函数
|
|
24
|
+
- 支持协程对象
|
|
25
|
+
|
|
26
|
+
3. **智能类型处理**
|
|
27
|
+
- 自动处理嵌套 Pydantic 模型
|
|
28
|
+
- 支持 `List`、`Dict`、`Set`、`Tuple` 等集合类型
|
|
29
|
+
- 智能处理 `Union` 和 `Optional` 类型
|
|
30
|
+
|
|
31
|
+
4. **并发处理**
|
|
32
|
+
- 列表序列化自动并发处理
|
|
33
|
+
- 大列表自动分批处理(每批 100 项)
|
|
34
|
+
- 嵌套列表/字典/集合也支持并发处理
|
|
35
|
+
|
|
36
|
+
5. **Pydantic V2 兼容**
|
|
37
|
+
- 正确处理 `validation_alias`(支持字符串)
|
|
38
|
+
- 支持字段默认值和 `default_factory`
|
|
39
|
+
- 智能处理字段别名
|
|
40
|
+
|
|
41
|
+
6. **性能优化**
|
|
42
|
+
- 字段元数据缓存,避免重复计算
|
|
43
|
+
- 大列表分批处理,避免资源耗尽
|
|
44
|
+
- 已实例化的模型直接返回,避免重复序列化
|
|
45
|
+
|
|
46
|
+
## 快速开始
|
|
47
|
+
|
|
48
|
+
### 基础用法
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from pydantic import BaseModel
|
|
52
|
+
from tomskit.utils.serializers import AsyncSerializer
|
|
53
|
+
|
|
54
|
+
# 定义 Pydantic 模型
|
|
55
|
+
class UserModel(BaseModel):
|
|
56
|
+
id: int
|
|
57
|
+
name: str
|
|
58
|
+
email: str
|
|
59
|
+
age: int = 0
|
|
60
|
+
|
|
61
|
+
# 从字典序列化
|
|
62
|
+
data = {"id": 1, "name": "张三", "email": "zhangsan@example.com", "age": 25}
|
|
63
|
+
user = await AsyncSerializer.serialize(UserModel, data)
|
|
64
|
+
print(user.name) # 输出: 张三
|
|
65
|
+
|
|
66
|
+
# 从 ORM 对象序列化
|
|
67
|
+
class UserORM:
|
|
68
|
+
def __init__(self):
|
|
69
|
+
self.id = 1
|
|
70
|
+
self.name = "李四"
|
|
71
|
+
self.email = "lisi@example.com"
|
|
72
|
+
self.age = 30
|
|
73
|
+
|
|
74
|
+
orm_user = UserORM()
|
|
75
|
+
user = await AsyncSerializer.serialize(UserModel, orm_user)
|
|
76
|
+
print(user.name) # 输出: 李四
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## API 文档
|
|
80
|
+
|
|
81
|
+
### serialize
|
|
82
|
+
|
|
83
|
+
单对象序列化入口。
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
@classmethod
|
|
87
|
+
async def serialize(
|
|
88
|
+
cls,
|
|
89
|
+
model_cls: Type[T],
|
|
90
|
+
source_data: Any
|
|
91
|
+
) -> T | None
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**参数:**
|
|
95
|
+
- `model_cls`: Pydantic 模型类
|
|
96
|
+
- `source_data`: 数据源(ORM 对象 / 字典 / 混合体)
|
|
97
|
+
|
|
98
|
+
**返回:**
|
|
99
|
+
- 序列化后的 Pydantic 模型实例
|
|
100
|
+
- 如果 `source_data` 为 `None`,返回 `None`
|
|
101
|
+
- 如果 `source_data` 已经是该模型的实例,直接返回(避免重复序列化)
|
|
102
|
+
|
|
103
|
+
**示例:**
|
|
104
|
+
```python
|
|
105
|
+
# 字典数据
|
|
106
|
+
data = {"id": 1, "name": "张三"}
|
|
107
|
+
user = await AsyncSerializer.serialize(UserModel, data)
|
|
108
|
+
|
|
109
|
+
# ORM 对象
|
|
110
|
+
orm_user = UserORM(id=1, name="张三")
|
|
111
|
+
user = await AsyncSerializer.serialize(UserModel, orm_user)
|
|
112
|
+
|
|
113
|
+
# None 值
|
|
114
|
+
result = await AsyncSerializer.serialize(UserModel, None)
|
|
115
|
+
assert result is None
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### serialize_list
|
|
119
|
+
|
|
120
|
+
列表序列化入口(并发处理)。
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
@classmethod
|
|
124
|
+
async def serialize_list(
|
|
125
|
+
cls,
|
|
126
|
+
model_cls: Type[T],
|
|
127
|
+
items: Sequence[Any] | Iterable[Any]
|
|
128
|
+
) -> list[T]
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**参数:**
|
|
132
|
+
- `model_cls`: Pydantic 模型类
|
|
133
|
+
- `items`: 待序列化的对象列表(支持任何可迭代对象)
|
|
134
|
+
|
|
135
|
+
**返回:**
|
|
136
|
+
- 序列化后的模型列表(自动过滤掉 `None` 值)
|
|
137
|
+
|
|
138
|
+
**特性:**
|
|
139
|
+
- 自动并发处理,提升性能
|
|
140
|
+
- 小列表(≤100 项)直接并发处理
|
|
141
|
+
- 大列表(>100 项)自动分批处理,每批 100 项
|
|
142
|
+
|
|
143
|
+
**示例:**
|
|
144
|
+
```python
|
|
145
|
+
items = [
|
|
146
|
+
{"id": 1, "name": "张三"},
|
|
147
|
+
{"id": 2, "name": "李四"},
|
|
148
|
+
None, # None 值会被自动过滤
|
|
149
|
+
{"id": 3, "name": "王五"}
|
|
150
|
+
]
|
|
151
|
+
|
|
152
|
+
users = await AsyncSerializer.serialize_list(UserModel, items)
|
|
153
|
+
# 返回: [UserModel(id=1, name="张三"), UserModel(id=2, name="李四"), UserModel(id=3, name="王五")]
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### serialize_pagination
|
|
157
|
+
|
|
158
|
+
分页对象序列化入口。
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
@classmethod
|
|
162
|
+
async def serialize_pagination(
|
|
163
|
+
cls,
|
|
164
|
+
pagination_obj: Any,
|
|
165
|
+
model_cls: Type[T]
|
|
166
|
+
) -> dict[str, Any]
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**参数:**
|
|
170
|
+
- `pagination_obj`: 分页对象,需要具有以下属性之一:
|
|
171
|
+
- `items`: 数据项列表
|
|
172
|
+
- `page`: 当前页码
|
|
173
|
+
- `per_page` 或 `limit`: 每页数量
|
|
174
|
+
- `total`: 总记录数
|
|
175
|
+
- `model_cls`: 数据项的 Pydantic 模型类
|
|
176
|
+
|
|
177
|
+
**返回:**
|
|
178
|
+
包含以下字段的字典:
|
|
179
|
+
- `page`: 当前页码
|
|
180
|
+
- `limit`: 每页数量
|
|
181
|
+
- `total`: 总记录数
|
|
182
|
+
- `has_more`: 是否还有更多数据
|
|
183
|
+
- `data`: 序列化后的数据列表
|
|
184
|
+
|
|
185
|
+
**示例:**
|
|
186
|
+
```python
|
|
187
|
+
class MockPagination:
|
|
188
|
+
def __init__(self):
|
|
189
|
+
self.page = 1
|
|
190
|
+
self.per_page = 10
|
|
191
|
+
self.total = 25
|
|
192
|
+
self.items = [
|
|
193
|
+
{"id": i, "name": f"用户{i}"}
|
|
194
|
+
for i in range(1, 11)
|
|
195
|
+
]
|
|
196
|
+
|
|
197
|
+
pagination = MockPagination()
|
|
198
|
+
result = await AsyncSerializer.serialize_pagination(pagination, UserModel)
|
|
199
|
+
|
|
200
|
+
# 返回:
|
|
201
|
+
# {
|
|
202
|
+
# "page": 1,
|
|
203
|
+
# "limit": 10,
|
|
204
|
+
# "total": 25,
|
|
205
|
+
# "has_more": True,
|
|
206
|
+
# "data": [UserModel(...), ...]
|
|
207
|
+
# }
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## 高级功能
|
|
211
|
+
|
|
212
|
+
### 1. 异步属性支持
|
|
213
|
+
|
|
214
|
+
自动识别并 `await` 异步属性。
|
|
215
|
+
|
|
216
|
+
```python
|
|
217
|
+
class UserORM:
|
|
218
|
+
def __init__(self, name: str):
|
|
219
|
+
self.name = name
|
|
220
|
+
|
|
221
|
+
@property
|
|
222
|
+
async def full_name(self):
|
|
223
|
+
await asyncio.sleep(0.01) # 模拟异步操作
|
|
224
|
+
return f"{self.name} (Full)"
|
|
225
|
+
|
|
226
|
+
class UserModel(BaseModel):
|
|
227
|
+
name: str
|
|
228
|
+
full_name: str | None = None
|
|
229
|
+
|
|
230
|
+
orm_user = UserORM("张三")
|
|
231
|
+
user = await AsyncSerializer.serialize(UserModel, orm_user)
|
|
232
|
+
print(user.full_name) # 输出: 张三 (Full)
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### 2. 函数自动调用
|
|
236
|
+
|
|
237
|
+
自动调用字典中的函数或对象属性中的函数。
|
|
238
|
+
|
|
239
|
+
```python
|
|
240
|
+
# 字典中的函数
|
|
241
|
+
data = {
|
|
242
|
+
"name": "张三",
|
|
243
|
+
"display_name": lambda: "员工-张三" # 自动调用
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
# 字典中的异步函数(直接传递函数对象)
|
|
247
|
+
async def get_async_name():
|
|
248
|
+
await asyncio.sleep(0.01)
|
|
249
|
+
return "异步名称"
|
|
250
|
+
|
|
251
|
+
data = {
|
|
252
|
+
"name": "张三",
|
|
253
|
+
"display_name": get_async_name # 传递函数对象,会自动调用并 await
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
user = await AsyncSerializer.serialize(UserModel, data)
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### 3. 嵌套模型序列化
|
|
260
|
+
|
|
261
|
+
支持多层嵌套的 Pydantic 模型。
|
|
262
|
+
|
|
263
|
+
```python
|
|
264
|
+
class AddressModel(BaseModel):
|
|
265
|
+
city: str
|
|
266
|
+
street: str
|
|
267
|
+
|
|
268
|
+
class UserModel(BaseModel):
|
|
269
|
+
name: str
|
|
270
|
+
address: AddressModel # 嵌套模型
|
|
271
|
+
|
|
272
|
+
data = {
|
|
273
|
+
"name": "张三",
|
|
274
|
+
"address": {
|
|
275
|
+
"city": "北京",
|
|
276
|
+
"street": "中关村大街"
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
user = await AsyncSerializer.serialize(UserModel, data)
|
|
281
|
+
print(user.address.city) # 输出: 北京
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### 4. 列表嵌套序列化
|
|
285
|
+
|
|
286
|
+
支持列表中的嵌套模型,自动并发处理。
|
|
287
|
+
|
|
288
|
+
```python
|
|
289
|
+
class UserModel(BaseModel):
|
|
290
|
+
name: str
|
|
291
|
+
|
|
292
|
+
class DepartmentModel(BaseModel):
|
|
293
|
+
name: str
|
|
294
|
+
users: list[UserModel] # 列表嵌套
|
|
295
|
+
|
|
296
|
+
data = {
|
|
297
|
+
"name": "技术部",
|
|
298
|
+
"users": [
|
|
299
|
+
{"name": "张三"},
|
|
300
|
+
{"name": "李四"}
|
|
301
|
+
]
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
dept = await AsyncSerializer.serialize(DepartmentModel, data)
|
|
305
|
+
# users 列表会自动并发序列化
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### 5. validation_alias 支持
|
|
309
|
+
|
|
310
|
+
正确处理 Pydantic V2 的字段别名。
|
|
311
|
+
|
|
312
|
+
```python
|
|
313
|
+
from pydantic import Field, BaseModel
|
|
314
|
+
|
|
315
|
+
class UserModel(BaseModel):
|
|
316
|
+
user_id: int = Field(validation_alias="userId")
|
|
317
|
+
user_name: str = Field(validation_alias="userName")
|
|
318
|
+
|
|
319
|
+
# 使用别名
|
|
320
|
+
data = {
|
|
321
|
+
"userId": 1,
|
|
322
|
+
"userName": "张三"
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
user = await AsyncSerializer.serialize(UserModel, data)
|
|
326
|
+
print(user.user_id) # 输出: 1
|
|
327
|
+
print(user.user_name) # 输出: 张三
|
|
328
|
+
|
|
329
|
+
# 也支持回退到字段名
|
|
330
|
+
data = {
|
|
331
|
+
"user_id": 1, # 使用字段名也可以
|
|
332
|
+
"userName": "张三"
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
user = await AsyncSerializer.serialize(UserModel, data)
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### 6. 默认值处理
|
|
339
|
+
|
|
340
|
+
智能处理字段默认值。
|
|
341
|
+
|
|
342
|
+
```python
|
|
343
|
+
class UserModel(BaseModel):
|
|
344
|
+
name: str
|
|
345
|
+
profile: dict = Field(default_factory=dict) # 有默认值
|
|
346
|
+
age: int | None = None # 默认值是 None
|
|
347
|
+
|
|
348
|
+
# 如果字段有默认值且值为 None,会跳过让 Pydantic 使用默认值
|
|
349
|
+
data = {"name": "张三"} # profile 会使用默认值 {}
|
|
350
|
+
user = await AsyncSerializer.serialize(UserModel, data)
|
|
351
|
+
print(user.profile) # 输出: {}
|
|
352
|
+
|
|
353
|
+
# 如果默认值本身就是 None,会传递 None
|
|
354
|
+
data = {"name": "张三", "age": None}
|
|
355
|
+
user = await AsyncSerializer.serialize(UserModel, data)
|
|
356
|
+
print(user.age) # 输出: None
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### 7. 混合数据源
|
|
360
|
+
|
|
361
|
+
支持在同一数据源中混合使用对象和字典。
|
|
362
|
+
|
|
363
|
+
```python
|
|
364
|
+
class UserORM:
|
|
365
|
+
def __init__(self):
|
|
366
|
+
self.id = 1
|
|
367
|
+
self.name = "张三"
|
|
368
|
+
|
|
369
|
+
# 字典中包含 ORM 对象
|
|
370
|
+
data = {
|
|
371
|
+
"id": 1,
|
|
372
|
+
"name": "张三",
|
|
373
|
+
"profile": {"age": 25} # 字典数据
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
# 或者对象中包含字典
|
|
377
|
+
orm_user = UserORM()
|
|
378
|
+
orm_user.profile = {"age": 25}
|
|
379
|
+
|
|
380
|
+
user = await AsyncSerializer.serialize(UserModel, data)
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
## 完整示例
|
|
384
|
+
|
|
385
|
+
### 3层嵌套示例
|
|
386
|
+
|
|
387
|
+
```python
|
|
388
|
+
from typing import List, Optional, Dict, Any
|
|
389
|
+
from pydantic import BaseModel, Field
|
|
390
|
+
from tomskit.utils.serializers import AsyncSerializer
|
|
391
|
+
|
|
392
|
+
# 第3层:员工
|
|
393
|
+
class EmployeeModel(BaseModel):
|
|
394
|
+
id: int
|
|
395
|
+
name: str
|
|
396
|
+
email: str
|
|
397
|
+
salary: float
|
|
398
|
+
profile: Dict[str, Any] = Field(default_factory=dict)
|
|
399
|
+
full_name: Optional[str] = None
|
|
400
|
+
|
|
401
|
+
# 第2层:部门
|
|
402
|
+
class DepartmentModel(BaseModel):
|
|
403
|
+
id: int
|
|
404
|
+
name: str
|
|
405
|
+
budget: Optional[float] = None
|
|
406
|
+
config: Dict[str, Any] = Field(default_factory=dict)
|
|
407
|
+
employees: List[EmployeeModel] = Field(default_factory=list)
|
|
408
|
+
|
|
409
|
+
# 第1层:公司
|
|
410
|
+
class CompanyModel(BaseModel):
|
|
411
|
+
id: int
|
|
412
|
+
name: str
|
|
413
|
+
total_employees: Optional[int] = None
|
|
414
|
+
metadata: Dict[str, Any] = Field(default_factory=dict)
|
|
415
|
+
departments: List[DepartmentModel] = Field(default_factory=list)
|
|
416
|
+
|
|
417
|
+
# ORM 对象(带异步属性)
|
|
418
|
+
class EmployeeORM:
|
|
419
|
+
def __init__(self, id: int, name: str, email: str, salary: float):
|
|
420
|
+
self.id = id
|
|
421
|
+
self.name = name
|
|
422
|
+
self.email = email
|
|
423
|
+
self.salary = salary
|
|
424
|
+
|
|
425
|
+
@property
|
|
426
|
+
async def full_name(self):
|
|
427
|
+
await asyncio.sleep(0.01)
|
|
428
|
+
return f"{self.name} (ID: {self.id})"
|
|
429
|
+
|
|
430
|
+
class DepartmentORM:
|
|
431
|
+
def __init__(self, id: int, name: str, employees: list):
|
|
432
|
+
self.id = id
|
|
433
|
+
self.name = name
|
|
434
|
+
self._employees = employees
|
|
435
|
+
|
|
436
|
+
@property
|
|
437
|
+
async def budget(self):
|
|
438
|
+
await asyncio.sleep(0.01)
|
|
439
|
+
return 100000.0 + (self.id * 10000)
|
|
440
|
+
|
|
441
|
+
@property
|
|
442
|
+
def employees(self):
|
|
443
|
+
return self._employees
|
|
444
|
+
|
|
445
|
+
class CompanyORM:
|
|
446
|
+
def __init__(self, id: int, name: str, departments: list):
|
|
447
|
+
self.id = id
|
|
448
|
+
self.name = name
|
|
449
|
+
self._departments = departments
|
|
450
|
+
|
|
451
|
+
@property
|
|
452
|
+
async def total_employees(self):
|
|
453
|
+
await asyncio.sleep(0.01)
|
|
454
|
+
count = 0
|
|
455
|
+
for dept in self._departments:
|
|
456
|
+
count += len(getattr(dept, 'employees', []))
|
|
457
|
+
return count
|
|
458
|
+
|
|
459
|
+
@property
|
|
460
|
+
def departments(self):
|
|
461
|
+
return self._departments
|
|
462
|
+
|
|
463
|
+
# 使用
|
|
464
|
+
employees = [
|
|
465
|
+
EmployeeORM(id=1, name="张三", email="zhangsan@example.com", salary=5000.0),
|
|
466
|
+
EmployeeORM(id=2, name="李四", email="lisi@example.com", salary=6000.0)
|
|
467
|
+
]
|
|
468
|
+
|
|
469
|
+
dept = DepartmentORM(
|
|
470
|
+
id=1,
|
|
471
|
+
name="技术部",
|
|
472
|
+
employees=employees
|
|
473
|
+
)
|
|
474
|
+
|
|
475
|
+
company = CompanyORM(
|
|
476
|
+
id=1,
|
|
477
|
+
name="测试公司",
|
|
478
|
+
departments=[dept]
|
|
479
|
+
)
|
|
480
|
+
|
|
481
|
+
# 序列化(自动处理所有异步属性)
|
|
482
|
+
result = await AsyncSerializer.serialize(CompanyModel, company)
|
|
483
|
+
|
|
484
|
+
# 验证结果
|
|
485
|
+
assert result.id == 1
|
|
486
|
+
assert result.name == "测试公司"
|
|
487
|
+
assert result.total_employees == 2 # 异步属性已 await
|
|
488
|
+
assert len(result.departments) == 1
|
|
489
|
+
assert result.departments[0].budget == 110000.0 # 异步属性已 await
|
|
490
|
+
assert len(result.departments[0].employees) == 2
|
|
491
|
+
assert result.departments[0].employees[0].full_name == "张三 (ID: 1)" # 异步属性已 await
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
## 性能特性
|
|
495
|
+
|
|
496
|
+
### 并发处理
|
|
497
|
+
|
|
498
|
+
- **列表序列化**:自动使用 `asyncio.gather` 并发处理
|
|
499
|
+
- **嵌套列表**:嵌套列表中的模型也会并发序列化
|
|
500
|
+
- **大列表优化**:超过 100 项的列表自动分批处理,避免资源耗尽
|
|
501
|
+
|
|
502
|
+
### 缓存机制
|
|
503
|
+
|
|
504
|
+
- **字段键名缓存**:字段的 `validation_alias` 解析结果会被缓存
|
|
505
|
+
- **Pydantic 键名缓存**:传递给 Pydantic 的键名会被缓存
|
|
506
|
+
- **避免重复序列化**:如果源数据已经是目标模型的实例,直接返回
|
|
507
|
+
|
|
508
|
+
### 分批处理
|
|
509
|
+
|
|
510
|
+
对于大列表、大字典、大集合,自动分批处理:
|
|
511
|
+
- 列表:每批 100 项
|
|
512
|
+
- 字典:每批 100 个键
|
|
513
|
+
- 集合:每批 100 项
|
|
514
|
+
|
|
515
|
+
## 注意事项
|
|
516
|
+
|
|
517
|
+
### 1. 异步函数处理
|
|
518
|
+
|
|
519
|
+
序列化器**会自动调用**异步函数对象。应该直接传递函数对象,而不是协程:
|
|
520
|
+
|
|
521
|
+
```python
|
|
522
|
+
# ✅ 正确:传递函数对象(推荐)
|
|
523
|
+
data = {"name": async_func} # 会自动调用并 await
|
|
524
|
+
|
|
525
|
+
# ❌ 不推荐:传递协程
|
|
526
|
+
data = {"name": async_func()} # 虽然也能工作,但不推荐
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
**注意**:虽然传递协程也能工作(会被自动 await),但推荐传递函数对象,这样更一致且更清晰。
|
|
530
|
+
|
|
531
|
+
### 2. None 值处理
|
|
532
|
+
|
|
533
|
+
- 对于字典:如果键存在但值为 `None`,会返回 `None`(`None` 可能是有效值)
|
|
534
|
+
- 对于对象:如果属性存在但值为 `None`,会返回 `None`
|
|
535
|
+
- 对于字段默认值:如果字段有默认值且值为 `None`,会跳过让 Pydantic 使用默认值(除非默认值本身就是 `None`)
|
|
536
|
+
|
|
537
|
+
### 3. 大列表性能
|
|
538
|
+
|
|
539
|
+
对于非常大的列表(>10000 项),建议:
|
|
540
|
+
- 使用分批处理(已自动实现)
|
|
541
|
+
- 考虑使用生成器或流式处理
|
|
542
|
+
- 监控内存使用情况
|
|
543
|
+
|
|
544
|
+
### 4. 类型注解
|
|
545
|
+
|
|
546
|
+
虽然类型注解是可选的,但提供类型注解可以获得更好的类型推断和性能:
|
|
547
|
+
- 嵌套模型需要类型注解才能正确序列化
|
|
548
|
+
- Union 类型需要类型注解才能智能匹配
|
|
549
|
+
|
|
550
|
+
## 最佳实践
|
|
551
|
+
|
|
552
|
+
### 1. 定义清晰的 Pydantic 模型
|
|
553
|
+
|
|
554
|
+
```python
|
|
555
|
+
class UserModel(BaseModel):
|
|
556
|
+
id: int
|
|
557
|
+
name: str
|
|
558
|
+
email: str
|
|
559
|
+
profile: dict = Field(default_factory=dict) # 使用 default_factory
|
|
560
|
+
created_at: datetime | None = None # Optional 类型
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
### 2. 使用类型注解
|
|
564
|
+
|
|
565
|
+
```python
|
|
566
|
+
class DepartmentModel(BaseModel):
|
|
567
|
+
name: str
|
|
568
|
+
users: list[UserModel] # 明确指定嵌套类型
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
### 3. 处理异步属性
|
|
572
|
+
|
|
573
|
+
```python
|
|
574
|
+
class UserORM:
|
|
575
|
+
@property
|
|
576
|
+
async def full_name(self):
|
|
577
|
+
# 异步属性会自动 await
|
|
578
|
+
return await get_full_name(self.id)
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
### 4. 利用并发处理
|
|
582
|
+
|
|
583
|
+
```python
|
|
584
|
+
# 列表会自动并发处理,无需手动优化
|
|
585
|
+
users = await AsyncSerializer.serialize_list(UserModel, user_list)
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
## 常见问题
|
|
589
|
+
|
|
590
|
+
### Q: 如何处理字段别名?
|
|
591
|
+
|
|
592
|
+
A: 使用 Pydantic 的 `Field(validation_alias="...")`,序列化器会自动处理。
|
|
593
|
+
|
|
594
|
+
### Q: 大列表会内存溢出吗?
|
|
595
|
+
|
|
596
|
+
A: 不会。序列化器会自动分批处理大列表(每批 100 项),避免资源耗尽。
|
|
597
|
+
|
|
598
|
+
### Q: 支持哪些数据源?
|
|
599
|
+
|
|
600
|
+
A: 支持字典、ORM 对象、以及它们的混合体。只要数据可以通过字典键或对象属性访问即可。
|
|
601
|
+
|
|
602
|
+
### Q: 如何处理嵌套的异步属性?
|
|
603
|
+
|
|
604
|
+
A: 序列化器会自动递归处理嵌套模型,并自动 await 所有异步属性。
|
|
605
|
+
|
|
606
|
+
### Q: 性能如何?
|
|
607
|
+
|
|
608
|
+
A:
|
|
609
|
+
- 小列表(≤100 项):并发处理,性能优秀
|
|
610
|
+
- 大列表(>100 项):分批并发处理,避免资源问题
|
|
611
|
+
- 字段元数据缓存:避免重复计算
|
|
612
|
+
- 已实例化模型:直接返回,零开销
|
|
613
|
+
|
|
614
|
+
## 技术细节
|
|
615
|
+
|
|
616
|
+
### 支持的集合类型
|
|
617
|
+
|
|
618
|
+
- `List[T]` / `list[T]`
|
|
619
|
+
- `Tuple[T, ...]` / `tuple[T, ...]`
|
|
620
|
+
- `Dict[str, T]` / `dict[str, T]`
|
|
621
|
+
- `Set[T]` / `set[T]`
|
|
622
|
+
- `Sequence[T]` / `Iterable[T]`
|
|
623
|
+
|
|
624
|
+
### 支持的 Union 类型
|
|
625
|
+
|
|
626
|
+
- `Union[A, B]`
|
|
627
|
+
- `Optional[T]` (等价于 `Union[T, None]`)
|
|
628
|
+
- 智能类型匹配:根据实际值类型选择最匹配的 Union 成员
|
|
629
|
+
|
|
630
|
+
### validation_alias 支持
|
|
631
|
+
|
|
632
|
+
- 字符串别名:`Field(validation_alias="userId")`
|
|
633
|
+
- AliasChoices:`Field(validation_alias=AliasChoices("userId", "user_id"))`
|
|
634
|
+
- AliasPath:`Field(validation_alias=AliasPath("user", "id"))`
|
|
635
|
+
|
|
636
|
+
## 版本要求
|
|
637
|
+
|
|
638
|
+
- Python >= 3.11
|
|
639
|
+
- Pydantic >= 2.0
|
|
640
|
+
|
|
641
|
+
## 相关文档
|
|
642
|
+
|
|
643
|
+
- [Pydantic V2 文档](https://docs.pydantic.dev/)
|
|
644
|
+
- [测试用例](../tests/test_serializers/test_async_serializer.py)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from tomskit.utils.marshal_utils import marshal, marshal_with, marshal_with_field
|
|
2
|
+
from tomskit.utils.fields import (
|
|
3
|
+
String,
|
|
4
|
+
DateTime,
|
|
5
|
+
Float,
|
|
6
|
+
Integer,
|
|
7
|
+
Nested,
|
|
8
|
+
List,
|
|
9
|
+
Raw,
|
|
10
|
+
Boolean,
|
|
11
|
+
FormattedString,
|
|
12
|
+
Arbitrary,
|
|
13
|
+
Fixed,
|
|
14
|
+
Price,
|
|
15
|
+
MarshallingException,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
'marshal',
|
|
20
|
+
'marshal_with',
|
|
21
|
+
'marshal_with_field',
|
|
22
|
+
'String',
|
|
23
|
+
'DateTime',
|
|
24
|
+
'Float',
|
|
25
|
+
'Integer',
|
|
26
|
+
'Nested',
|
|
27
|
+
'List',
|
|
28
|
+
'Raw',
|
|
29
|
+
'Boolean',
|
|
30
|
+
'FormattedString',
|
|
31
|
+
'Arbitrary',
|
|
32
|
+
'Fixed',
|
|
33
|
+
'Price',
|
|
34
|
+
'MarshallingException',
|
|
35
|
+
]
|