python - 如何在异步属性上进行pydantic等待(tortoise-orm的反向ForeignKey)?

标签 python fastapi pydantic tortoise-orm

(MRE位于问题底部)

在 tortoise-orm 中,我们必须等待反向外键字段,如下所示:

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 (这听起来不太好)。

这是要重现的代码(尽量保持简单):

models.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',
]

ma​​in.py

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_lated('comments') 也没有帮助,它引发了 AttributeError: can't set attribute

最佳答案

对不起,我太笨了。

我想到的一个解决方案是使用tortoise.contrib.pydantic包:

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

但根据 this question ,在声明模型之前需要初始化Tortoise,否则不会包含Relation。所以我很想替换这一行:

asyncio.create_task(init_tortoise())

...与:

asyncio.get_event_loop().run_until_complete(init_tortoise())

但它出错了事件循环已在运行并删除了 uvloop 和 installing nest_asyncio helped with that.


我使用的解决方案

根据 documentation :

Fetching foreign keys can be done with both async and sync interfaces.

Async fetch:

events = await tournament.events.all()

Sync usage requires that you call fetch_related before the time, and then you can use common functions.

await tournament.fetch_related('events')

在使用 .fetch_lated) (或在查询集上使用 prefetch_lated)后,反向外键将变成一个可迭代对象,可以像列表一样使用。但 pydantic 仍然会提示这不是一个有效的列表,因此需要使用验证器:

class PPost(BaseModel):
    comments: List[PPostComment]

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

(请注意,据我所知,验证器不能是异步的)

由于我设置了 orm_mode,我必须使用 .from_orm 方法😅:

return PPost.from_orm(await Post.get_or_none(id=42))

Remember, a few hours of trial and error can save you several minutes of looking at the README.

关于python - 如何在异步属性上进行pydantic等待(tortoise-orm的反向ForeignKey)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67154219/

相关文章:

python - 从 python 作为子进程启动服务

python - 在 Pandas 中导入 excel 文件出现错误

python - 如何从终端运行 FastAPI?

python - python3 中的 pip 未安装 MySQL 相关库

fastapi - pydantic 模式 可选

python - 我可以让 FastAPI 端点接收 json 或文件吗

python - 如果 BaseModel 获得意外参数,如何使 pydantic 引发 ValidationError

python-3.6 - FastAPI 文件必须放在函数参数中的表单之前

python - 如何在编辑后验证 pydantic 对象

python - Pandas Dataframe 删除所有以双引号开头的行