Skip to content

Commit 36f5ba8

Browse files
committed
update ch1
1 parent 8f1d4cc commit 36f5ba8

File tree

1 file changed

+28
-29
lines changed

1 file changed

+28
-29
lines changed

src/router/sample_1/readme-cn.md

Lines changed: 28 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
作为开始, 我们会一步步开始, 从返回单层 task 列表逐渐过渡到返回多层 Teams 列表.
1+
作为开始, 我们会一步步迭代, 从返回单层 task 列表逐渐过渡到返回多层 Teams 列表.
2+
3+
来满足我们构建视图数据时最核心的需求.
24

35
## 简单列表
46

@@ -27,13 +29,9 @@ async def get_step_1_tasks(session: AsyncSession = Depends(db.get_session)):
2729

2830
## 构建嵌套列表
2931

30-
接下来我们要将将 user 信息添加到 task 中, 在 sample_1 目录下创建 `schema.py`.
31-
32-
然后定义一个扩展了 user 信息的 `Sample1TaskDetail` 类型.
32+
接下来我们把 user 信息添加到 task 中, 在 sample_1 目录下创建 `schema.py`.
3333

34-
> 为了避免类型名字重复, 使用 router 名字作为前缀
35-
>
36-
> 因此 Sample1 开头的 schema 都是属于 sample_1 路由的 (这点在生成前端 sdk ts 类型的时候将会很有用.)
34+
定义一个扩展了 user 信息的 `Sample1TaskDetail` 类型.
3735

3836
```python
3937
class Sample1TaskDetail(ts.Task):
@@ -42,26 +40,29 @@ class Sample1TaskDetail(ts.Task):
4240
return loader.load(self.owner_id)
4341
```
4442

43+
> 为了避免类型名字重复, 使用 router 名字作为前缀
44+
>
45+
> 因此 Sample1 开头的 schema 都是属于 sample_1 路由的 (这点在生成前端 sdk ts 类型的时候将会很有用.)
46+
4547
几个注意点:
4648

4749
1. 继承`ts.Task`后, `Sample1TaskDetail` 就可以用 `tq.get_tasks(session)` 返回的 orm 对象赋值.
4850
2. 定义 user 需要添加默认值, 否则用 `Sample1TaskDetail.model_valiate` 会报缺少字段错误.
4951
3. `ul.user_batch_loader` 会根据 `list[task.owner_id]` 来关联 task 和 user 对象. 具体看 `src.services.user.loader`
52+
4. resolve 返回的数据需要是 pydantic 可以转化的类型.
53+
5. 如果是 orm 对象需要配置 `ConfigDict(from_attribute=True)`
5054

51-
loader 的作用是提前收集好所有task需要查询的 `task.owner_id`, 一次性查询完之后赋值给各自的 task
55+
loader 的作用是收集完所有task需要查询的 `task.owner_id`, 一次性查询完之后赋值给各自的 task
5256

53-
> resolve 返回的数据需要是 pydantic 可以转化的类型.
54-
>
55-
> 如果是 orm 对象需要配置 `ConfigDict(from_attribute=True)`
5657

57-
然后在 `router.py` 中, 依然是通过 `tq.get_tasks(session)` 来获取初始数据, 接着转换成 `Sample1TaskDetail`. 之后交给 `Resolver` 就能 resolve 出所有 user 信息.
58+
`router.py` 中, 依然是通过 `tq.get_tasks(session)` 来获取初始数据, 将数据转换成 `Sample1TaskDetail`. 之后交给 `Resolver` 就能 resolve 出所有 user 信息.
5859

5960
```python
6061
@route.get('/tasks-with-detail', response_model=List[Sample1TaskDetail])
6162
async def get_tasks_with_detail(session: AsyncSession = Depends(db.get_session)):
6263
""" 1.3 return list of tasks(user) """
6364
tasks = await tq.get_tasks(session)
64-
tasks = [Sample1TaskDetail.model_validate(t) for t in tasks]
65+
tasks = [Sample1TaskDetail.model_validate(t) for t in tasks] # 装载到目标schema
6566
tasks = await Resolver().resolve(tasks)
6667
return tasks
6768
```
@@ -100,7 +101,7 @@ async def get_tasks_with_detail(session: AsyncSession = Depends(db.get_session))
100101

101102
## 多层嵌套列表
102103

103-
使用相同的方式, 我们从 `tasks-with-details` 逐步构建到了 `teams-with-details`. 虽然是层层嵌套,但定义的方式非常简单。
104+
用相同的方式, 我们从 `tasks-with-details` 逐步构建到了 `teams-with-details`. 虽然是层层嵌套,但定义的方式非常简单。
104105

105106
```python
106107
# story
@@ -125,7 +126,9 @@ class Sample1TeamDetail(tms.Team):
125126
访问: `http://localhost:8000/sample_1/teams-with-detail`
126127

127128

128-
`get_teams_with_detail_2` 描述了另一种场景, 假如我们利用了一些 ORM 的外键查询, 提前获取到了 team + sprints 级别的数据, 拿我可以在这个数据的基础上继续 resolve.
129+
### 一种优化思路
130+
131+
`get_teams_with_detail_2` 描述了另一种场景, 假如我们利用了一些 ORM 的外键查询, 提前获取到了 team + sprints 级别的数据, 那我可以以这个数据为基础继续向下 resolve.
129132

130133
输入数据:
131134

@@ -156,7 +159,7 @@ teams = [{
156159
}]
157160
```
158161

159-
转换类型, 可以看到此处没有了 `resolve_sprints`. 但 sprints 数据转换成 `Simple1SprintDetail` 类型之后, 会自动继续扩展获取定义的关联类型.
162+
转换类型, 可以看到此处没有了 `resolve_sprints`(已经提供了). sprints 数据转换成 `Simple1SprintDetail` 类型之后, 会自动继续扩展获取定义的关联类型.
160163

161164
```python
162165
class Sample1TeamDetail2(tms.Team):
@@ -168,8 +171,10 @@ class Sample1TeamDetail2(tms.Team):
168171
```
169172

170173
> `resolve_method` 并不需要从顶层 class 就开始定义. `Resolver` 会递归遍历然后找到`resolver_method` 进行解析.
171-
>
172-
> pydantic-resolve 不会去处理 ORM model 和 schema 直接是否统一声明的问题, 因为 ORM 层面向的持久层和pydantic schema 面向的业务层并不能保证始终一致.
174+
>
175+
> 因此你可以根据输入的数据做定制, 找到最简洁的扩展方式
176+
>
177+
> pydantic-resolve 不会去考虑 ORM model 和 schema 直接是否统需要一声明的问题, 因为 ORM 层面向的持久层和pydantic schema 面向的业务层并不能保证一致.
173178
174179

175180
## Dataloader 的使用
@@ -194,7 +199,7 @@ async def team_to_sprint_loader(team_ids: list[int]):
194199
return build_list(sprints, team_ids, lambda u: u.team_id) # to list
195200
```
196201

197-
可以看到 1:1 的关系查询 id 是目标的主键, 查询非常简单, 因此可复用性最高
202+
可以看到 1:1 的关系查询 id 是目标的主键, 查询非常简单, 因此可复最方便
198203

199204
而 1:N 的查询需要有对应的关系表 (parent_id -> id) 来确定,所以复用情况取决于 parent_id。
200205

@@ -204,10 +209,6 @@ async def team_to_sprint_loader(team_ids: list[int]):
204209

205210
```python
206211
class Sample1StoryDetail(ss.Story):
207-
tasks: list[Sample1TaskDetail] = []
208-
def resolve_tasks(self, loader=LoaderDepend(tl.story_to_task_loader)):
209-
return loader.load(self.id)
210-
211212
owner: Optional[us.User] = None
212213
def resolve_owner(self, loader=LoaderDepend(ul.user_batch_loader)):
213214
return loader.load(self.owner_id)
@@ -244,16 +245,12 @@ async def team_to_user_loader(team_ids: list[int]):
244245
```python
245246

246247
class Sample1TeamDetail(tms.Team):
247-
sprints: list[Sample1SprintDetail] = []
248-
def resolve_sprints(self, loader=LoaderDepend(spl.team_to_sprint_loader)):
249-
return loader.load(self.id)
250-
251248
members: list[us.User] = []
252249
def resolve_members(self, loader=LoaderDepend(ul.team_to_user_loader)):
253250
return loader.load(self.id)
254251
```
255252

256-
至此, Dataloader 的使用就介绍玩了.
253+
至此, Dataloader 的使用介绍完毕.
257254

258255

259256
## 其他想法
@@ -271,4 +268,6 @@ class Sample1TeamDetail(tms.Team):
271268

272269
因此 `pydantic-resolve` 利用 Resolver 提供的单独入口, 实现了通过 `LoaderDepend` 就近申明 loader 的功能
273270

274-
这样一来 Resolver 就能按需来生成各个 loader 实例. 于是 loader 之间的替换修改就非常容易. 而且也不用把所有 loader 往一个 context 里面放了.
271+
这样一来 Resolver 就能按需来生成各个 loader 实例. 于是 loader 之间的替换修改就非常容易. 而且也不用把所有 loader 往一个 context 里面放了. 这间接约束了人们使用 loader 的自由.
272+
273+
而在 `pydantic-resolve` 中, loader 们自由了, 开发可以随心所欲定制各种loader 而不用担心任何全局管理问题.

0 commit comments

Comments
 (0)