Skip to content

Commit aa55b36

Browse files
committed
add case
1 parent 0d9bab6 commit aa55b36

File tree

3 files changed

+139
-16
lines changed

3 files changed

+139
-16
lines changed

src/router/sample_1/readme-cn.md

Lines changed: 93 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,14 @@
1-
作为开始, 我们会一步步开始, 返回单层 task 列表的 API 逐渐过渡到返回多层 Teams 列表 API.
1+
作为开始, 我们会一步步开始, 从返回单层 task 列表逐渐过渡到返回多层 Teams 列表.
22

3-
To begin, we will start step by step, transitioning from an API that returns a single-layer task list to an API that returns a multi-layer Teams list.
3+
## 简单列表
44

5-
## 简单列表 Simple list
6-
7-
对应的路由:
8-
9-
routers:
5+
路由:
106

117
- `sample_1.router:get_users`
128
- `sample_1.router:get_tasks`
139

1410
`src.router.sample_1` 中,我们依次创建 users, tasks 的 API, 以 list[T] 的形式返回。
1511

16-
In `src.router.sample_1`, we will sequentially create APIs for users and tasks, returning them in the form of list[T].
17-
1812
```python
1913
import src.services.task.query as tq
2014

@@ -26,15 +20,20 @@ async def get_step_1_tasks(session: AsyncSession = Depends(db.get_session)):
2620

2721
通过引入 `src.services.user.query``src.services.task.query` 中的查询,返回了 `list[orm]` 对象, 然后 FastAPI 会自动将对象转成 response_model 中对应的类型.
2822

29-
by importing queries from `src.services.user.query` and `src.services.task.query`, we can get `list[orm]`, and then FastAPI will automatically convert the objects into the corresponding types defined in response_model
23+
访问
24+
- `http://localhost:8000/sample_1/users`
25+
- `http://localhost:8000/sample_1/tasks`
3026

31-
## 嵌套列表
3227

33-
接下来我们要将将 user 信息添加到 task 中, 在 sample_1 目录下创建 `schema.py`, 定义一个扩展了 user 信息的 `Sample1TaskDetail` 类型.
28+
## 构建嵌套列表
3429

35-
> 为了避免类型名字重复,使用 router 名字作为前缀
30+
接下来我们要将将 user 信息添加到 task 中, 在 sample_1 目录下创建 `schema.py`.
31+
32+
然后定义一个扩展了 user 信息的 `Sample1TaskDetail` 类型.
33+
34+
> 为了避免类型名字重复, 使用 router 名字作为前缀
3635
>
37-
> 因此 Sample1 开头的 schema 都是属于 sample_1 路由的 (这点在生成前端 sdk ts 类型的时候会很有用.)
36+
> 因此 Sample1 开头的 schema 都是属于 sample_1 路由的 (这点在生成前端 sdk ts 类型的时候将会很有用.)
3837
3938
```python
4039
class Sample1TaskDetail(ts.Task):
@@ -49,11 +48,13 @@ class Sample1TaskDetail(ts.Task):
4948
2. 定义 user 需要添加默认值, 否则用 `Sample1TaskDetail.model_valiate` 会报缺少字段错误.
5049
3. `ul.user_batch_loader` 会根据 `list[task.owner_id]` 来关联 task 和 user 对象. 具体看 `src.services.user.loader`
5150

51+
loader 的作用是提前收集好所有task需要查询的 `task.owner_id`, 一次性查询完之后赋值给各自的 task
52+
5253
> resolve 返回的数据需要是 pydantic 可以转化的类型.
5354
>
5455
> 如果是 orm 对象需要配置 `ConfigDict(from_attribute=True)`
5556
56-
`router.py` 中, 依然是通过 `tq.get_tasks(session)` 来获取初始数据, 接着转换成 `Sample1TaskDetail`. 之后交给 `Resolver` 就能 resolve 出所有 user 信息.
57+
然后在 `router.py` 中, 依然是通过 `tq.get_tasks(session)` 来获取初始数据, 接着转换成 `Sample1TaskDetail`. 之后交给 `Resolver` 就能 resolve 出所有 user 信息.
5758

5859
```python
5960
@route.get('/tasks-with-detail', response_model=List[Sample1TaskDetail])
@@ -65,6 +66,38 @@ async def get_tasks_with_detail(session: AsyncSession = Depends(db.get_session))
6566
return tasks
6667
```
6768

69+
访问:
70+
- `http://localhost:8000/sample_1/tasks-with-detail`
71+
72+
可以看到 user 信息被添加了进来.
73+
```json
74+
[
75+
{
76+
"id": 1,
77+
"name": "mvp tech design",
78+
"owner_id": 2,
79+
"story_id": 1,
80+
"user": {
81+
"id": 2,
82+
"name": "Eric",
83+
"level": "junior"
84+
}
85+
},
86+
{
87+
"id": 2,
88+
"name": "implementation",
89+
"owner_id": 2,
90+
"story_id": 1,
91+
"user": {
92+
"id": 2,
93+
"name": "Eric",
94+
"level": "junior"
95+
}
96+
}
97+
]
98+
```
99+
100+
68101
## 多层嵌套列表
69102

70103
使用相同的方式, 我们从 `tasks-with-details` 逐步构建到了 `teams-with-details`. 虽然是层层嵌套,但定义的方式非常简单。
@@ -89,6 +122,51 @@ class Sample1TeamDetail(tms.Team):
89122
return loader.load(self.id)
90123
```
91124

125+
访问: `http://localhost:8000/sample_1/teams-with-detail`
126+
127+
128+
`get_teams_with_detail_2` 描述了另一种场景, 假如我们利用了一些 ORM 的外键查询, 提前获取到了 team + sprints 级别的数据, 拿我可以在这个数据的基础上继续 resolve.
129+
130+
输入数据:
131+
132+
```python
133+
teams = [{
134+
"id": 1,
135+
"name": "team-A",
136+
"sprints": [
137+
{
138+
"id": 1,
139+
"name": "Sprint A W1",
140+
"status": "close",
141+
"team_id": 1
142+
},
143+
{
144+
"id": 2,
145+
"name": "Sprint A W3",
146+
"status": "active",
147+
"team_id": 1
148+
},
149+
{
150+
"id": 3,
151+
"name": "Sprint A W5",
152+
"status": "plan",
153+
"team_id": 1
154+
}
155+
]
156+
}]
157+
```
158+
159+
转换类型, 可以看到此处没有了 `resolve_sprints`. 但 sprints 数据转换成 `Simple1SprintDetail` 类型之后, 会自动继续扩展获取定义的关联类型.
160+
161+
```python
162+
class Sample1TeamDetail2(tms.Team):
163+
sprints: list[Sample1SprintDetail] = []
164+
165+
members: list[us.User] = []
166+
def resolve_members(self, loader=LoaderDepend(ul.team_to_user_loader)):
167+
return loader.load(self.id)
168+
```
169+
92170
## Dataloader 的使用
93171

94172
Dataloader 的作用收集完所有要查询的 parent_ids 之后,一次性查询到所有的 childrent 对象,接着根据 child 的 parent_id 聚合起来。

src/router/sample_1/router.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,12 @@
1414
import src.services.sprint.query as spq
1515
import src.services.team.query as tmq
1616

17-
from .schema import Sample1TaskDetail, Sample1StoryDetail, Sample1SprintDetail, Sample1TeamDetail
17+
from .schema import (
18+
Sample1TaskDetail,
19+
Sample1StoryDetail,
20+
Sample1SprintDetail,
21+
Sample1TeamDetail,
22+
Sample1TeamDetail2)
1823

1924
route = APIRouter(tags=['sample_1'], prefix="/sample_1")
2025

@@ -63,4 +68,36 @@ async def get_teams_with_detail(session: AsyncSession = Depends(db.get_session))
6368
teams = await tmq.get_teams(session)
6469
teams = [Sample1TeamDetail.model_validate(t) for t in teams]
6570
teams = await Resolver().resolve(teams)
71+
return teams
72+
73+
74+
@route.get('/teams-with-detail2', response_model=List[Sample1TeamDetail2])
75+
async def get_teams_with_detail_2(session: AsyncSession = Depends(db.get_session)):
76+
""" 1.7 return list of team(sprint(story(task(user)))) """
77+
teams = [{
78+
"id": 1,
79+
"name": "team-A",
80+
"sprints": [
81+
{
82+
"id": 1,
83+
"name": "Sprint A W1",
84+
"status": "close",
85+
"team_id": 1
86+
},
87+
{
88+
"id": 2,
89+
"name": "Sprint A W3",
90+
"status": "active",
91+
"team_id": 1
92+
},
93+
{
94+
"id": 3,
95+
"name": "Sprint A W5",
96+
"status": "plan",
97+
"team_id": 1
98+
}
99+
]
100+
}]
101+
teams = [Sample1TeamDetail2.model_validate(t) for t in teams]
102+
teams = await Resolver().resolve(teams)
66103
return teams

src/router/sample_1/schema.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from typing import Optional
22
from pydantic2_resolve import LoaderDepend
3+
from pydantic import ConfigDict
34

45
import src.services.task.loader as tl
56
import src.services.user.loader as ul
@@ -37,6 +38,13 @@ class Sample1TeamDetail(tms.Team):
3738
def resolve_sprints(self, loader=LoaderDepend(spl.team_to_sprint_loader)):
3839
return loader.load(self.id)
3940

41+
members: list[us.User] = []
42+
def resolve_members(self, loader=LoaderDepend(ul.team_to_user_loader)):
43+
return loader.load(self.id)
44+
45+
class Sample1TeamDetail2(tms.Team):
46+
sprints: list[Sample1SprintDetail] = []
47+
4048
members: list[us.User] = []
4149
def resolve_members(self, loader=LoaderDepend(ul.team_to_user_loader)):
4250
return loader.load(self.id)

0 commit comments

Comments
 (0)