(问题底部的 MRE)
在 tortoise-orm 中,我们必须等待反向 ForeignKey 字段,如下所示:
comments = await 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
属性返回协程。我不得不使用这个小技巧来获得 aronud:
post = Post.get(id=id)
return {**post.__dict__, 'comments': await post.comments}
然而,真正的问题是当我有多个关系时:返回一个用户的帖子及其评论。在那种情况下,我不得不以一种非常丑陋的方式将我的整个模型转换为 dict(这听起来不太好)。
这是要重现的代码(试图使其尽可能简单):
模型.py
from tortoise.fields import *
from tortoise.models import Model
from tortoise import Tortoise, run_async
async def init_tortoise():
await Tortoise.init(
db_url='sqlite://db.sqlite3',
modules={'models': ['models']},
)
await Tortoise.generate_schemas()
class User(Model):
name = CharField(80)
class Post(Model):
title = CharField(80)
content = TextField()
owner = ForeignKeyField('models.User', related_name='posts')
class PostComment(Model):
text = CharField(80)
post = ForeignKeyField('models.Post', related_name='comments')
if __name__ == '__main__':
run_async(init_tortoise())
__all__ = [
'User',
'Post',
'PostComment',
'init_tortoise',
]
主文件
import asyncio
from typing import List
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from models import *
app = FastAPI()
asyncio.create_task(init_tortoise())
# pydantic models are prefixed with P
class PPostComment(BaseModel):
text: str
class PPost(BaseModel):
id: int
title: str
content: str
comments: List[PPostComment]
class Config:
orm_mode = True
class PUser(BaseModel):
id: int
name: str
posts: List[PPost]
class Config:
orm_mode = True
@app.get('/posts/{id}', response_model=PPost)
async def index(id: int):
post = await Post.get_or_none(id=id)
return {**post.__dict__, 'comments': await post.comments}
@app.get('/users/{id}', response_model=PUser)
async def index(id: int):
user = await User.get_or_none(id=id)
return {**user.__dict__, 'posts': await 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并运行:
import asyncio
from models import *
async def main():
await init_tortoise()
u = await User.create(name='drdilyor')
p = await Post.create(title='foo', content='lorem ipsum', owner=u)
c = await PostComment.create(text='spam egg', post=p)
asyncio.run(main())
我想要的是让 pydantic 在这些异步字段上自动等待(所以我可以只返回 Post 实例)。pydantic怎么可能呢?
在使用这种方式时,更改/posts/{id}
为返回帖子及其所有者而不带评论实际上是有效的(感谢@papple23j):
return await Post.get_or_none(id=id).prefetch_related('owner')
但不适用于反向外键。也select_related('comments')
没有帮助,它正在提高AttributeError: can't set attribute
。