Python 如何使pydantic在异步属性(tortoise orm';的反向外键)上等待?

Python 如何使pydantic在异步属性(tortoise orm';的反向外键)上等待?,python,fastapi,pydantic,tortoise-orm,Python,Fastapi,Pydantic,Tortoise Orm,(问题底部的MRE) 在乌龟orm中,我们必须等待反向外键字段: comments=wait Post.get(id=id).comments 但在fastapi中,当返回Post实例时,pydantic抱怨: pydantic.error_wrappers.ValidationError: 1 validation error for PPost response -> comments value is not a valid list (type=type_error.list

(问题底部的MRE)

在乌龟orm中,我们必须等待反向外键字段:

comments=wait Post.get(id=id).comments
但在fastapi中,当返回Post实例时,pydantic抱怨:

pydantic.error_wrappers.ValidationError: 1 validation error for PPost
response -> comments
  value is not a valid list (type=type_error.list)
这很有意义,因为
comments
属性返回协同路由。我不得不用这个小技巧来解决这个问题:

post=post.get(id=id)
返回{**post.\uuuu dict\uuuuuu,'comments':wait post.comments}
然而,真正的问题是当我有多个关系时:返回一个用户的帖子和评论。在这种情况下,我不得不以一种非常丑陋的方式(听起来不太好)将我的实体模型转换成dict

下面是要复制的代码(尽量保持简单):

型号.py

从tortoise.fields导入*
从乌龟。模型导入模型
从tortoise导入tortoise,运行异步
异步def init_tortoise():
等待乌龟(
db_url=sqlite://db.sqlite3',
模块={'models':['models']},
)
等待乌龟。生成_模式()
类别用户(型号):
name=CharField(80)
班级职务(模范):
title=CharField(80)
content=TextField()
owner=ForeignKeyField('models.User',related_name='posts')
类别后通知(型号):
text=CharField(80)
post=ForeignKeyField('models.post',related_name='comments')
如果uuuu name uuuuuu='\uuuuuuu main\uuuuuuu':
运行异步(init_tortoise())
__全部[
“用户”,
"岗位",,
"后承诺",,
“一只乌龟”,
]
main.py

导入异步IO
从输入导入列表开始
从fastapi导入fastapi,HTTPException
从pydantic导入BaseModel
从模型导入*
app=FastAPI()
asyncio.create_任务(init_tortoise())
#pydantic模型的前缀是P
类别建议(基本模型):
文本:str
类PPOT(基本模型):
id:int
标题:str
内容:str
注释:列表[PPOTCOMMENT]
类配置:
orm_模式=真
类PUser(基本模型):
id:int
姓名:str
帖子:列表[PPost]
类配置:
orm_模式=真
@app.get('/posts/{id}',response_model=PPost)
异步定义索引(id:int):
post=等待post。获取\或\无(id=id)
返回{**post.\uuuu dict\uuuuuu,'comments':wait post.comments}
@app.get('/users/{id}',response\u model=PUser)
异步定义索引(id:int):
用户=等待用户。获取或无(id=id)
返回{**user.\u dict\u'posts':wait user.posts}
/users/1
出现以下错误:

pydantic.error_wrappers.ValidationError: 1 validation error for PUser
response -> posts -> 0 -> comments
  value is not a valid list (type=type_error.list)
此外,您可能希望将其放入init.py并运行:

导入异步IO
从模型导入*
异步def main():
等待初始化乌龟()
u=wait User.create(name='drdilyor')
p=wait Post.create(title='foo',content='lorem ipsum',owner=u)
c=等待后通知。创建(text='spam egg',post=p)
asyncio.run(main())
我想要的是让pydantic自动等待这些异步字段(这样我就可以返回Post实例)。用pydantic怎么可能


更改
/posts/{id}
以返回post及其所有者而不添加注释,在使用这种方式时实际起作用(感谢@papple23j):

return wait Post.get\u或\u none(id=id)。预取\u相关('owner'))

但不适用于反向外键。另外,
select_related('comments')
没有帮助,它正在引发
AttributeError:can't set attribute

您可以尝试使用
prefetch_related()

例如:

@app.get('/posts/{id}',response_model=PPost)
异步定义索引(id:int):
post=等待post.获取\或\无(id=id).预取\相关('注释')
返回{**post.\u dict\u}
(以下文本使用DeepL翻译)

有一种方法可以做到这一点,但它有点棘手

首先将pydantic模型片段拆分为schemas.py

来自pydantic import BaseModel的

从输入导入列表开始
#pydantic模型的前缀是P
类别建议(基本模型):
文本:str
类配置:
orm_mode=True#添加此行
类PPOT(基本模型):
id:int
标题:str
内容:str
注释:列表[PPOTCOMMENT]
类配置:
orm_模式=真
类PUser(基本模型):
id:int
姓名:str
帖子:列表[PPost]
类配置:
orm_模式=真
接下来,重写models.py

从tortoise.fields导入*
从乌龟。模型导入模型
从tortoise导入tortoise,运行异步
从模式导入*
异步def init_tortoise():
等待乌龟(
db_url=sqlite://db.sqlite3',
模块={'models':['models']},
)
等待乌龟。生成_模式()
类别用户(型号):
name=CharField(80)
_posts=反向关联[“Post”]#1
@财产
def职位(自我):#3
返回[post.from_orm(post)for post in self.\u posts]
班级职务(模范):
title=CharField(80)
content=TextField()
owner=ForeignKeyField('models.User',related_name='u posts')2
_注释=反向关联[“后注释”]#1
@财产
def评论(自我):#3
返回[posttcomment.from_orm(comment)for comment in self.\u comments]
类别后通知(型号):
text=CharField(80)
post=ForeignKeyField('models.post',相关名称='u注释')2
如果uuuu name uuuuuu='\uuuuuuu main\uuuuuuu':
运行异步(init_tortoise())
__全部[
“用户”,
"岗位",,
"后承诺",,
“一只乌龟”,
]
在哪里

#1:使用
ReverseRelation
声明反向字段,这里使用底线的前缀来区分

#2:修改
相关的\u名称

#3:编写一个属性函数并返回相应的pydantic模型列表,在这里您不需要使用
await
,因为默认情况下是使用
prefetch\u related()访问它。

PPost = pydantic_model_creator(Post) # used as return await PPost.from_tortoise_orm(await Post.get_or_none(id=1))

asyncio.create_task(init_tortoise())
asyncio.get_event_loop().run_until_complete(init_tortoise())
events = await tournament.events.all()
await tournament.fetch_related('events')
class PPost(BaseModel):
    comments: List[PPostComment]

    @validator('comments', pre=True)
    def _iter_to_list(cls, v):
        return list(v)