Fastapi/Tortoise 早期模型初始化

Fastapi/Tortoise early model init

我有以下使用 fastapi 的实现。

我目前的问题是,我一辈子都做不到 early init 乌龟模型来恢复模式中的关系。

我试过基本上在所有地方转储以下行,但它似乎不起作用。

Tortoise.init_models(["models.user", "models.group"], "models")

我也试过用prefetch_related这种方式,但也不管用

GetGroup.from_queryset(Group.get(id=3).prefetch_related('owner'))

我已经谷歌搜索了几个小时,但没有找到具体的 answer/way 来让它正常工作。

文件夹结构:

app
│   main.py
└───database
│   │   database.py
└───models
|    │   user.py
|    │   group.py
└───routers
|   │   user_router.py
|   │   group_router.py
└───services
|   │   auth.py
main.py
from fastapi import FastAPI
from database.database import init_db
from routers.user_router import router as UserRouter
from routers.group_router import router as GroupRouter

# Instantiate the Application
app = FastAPI(title="test", root_path="/api/")

# Include the Routers
app.include_router(UserRouter, tags=["User"], prefix="/user")
app.include_router(GroupRouter, tags=["Group"], prefix="/group")


# Start DB Connection on Startup
@app.on_event("startup")
async def startup_event():
    init_db(app)
database/database.py
from decouple import config
from fastapi import FastAPI
from tortoise import Tortoise
from tortoise.contrib.fastapi import register_tortoise


def init_db(app: FastAPI) -> None:
    Tortoise.init_models(["models.user", "models.group"], "models")
    register_tortoise(
        app,
        db_url=f"mysql://{config('MYSQL_USER')}:{config('MYSQL_PASSWORD')}@{config('MYSQL_HOST')}:{config('MYSQL_EXPOSE')}/{config('MYSQL_DB')}",
        modules={"models": ["models.user",
                            "models.group",
                            ]},
        generate_schemas=False,
        add_exception_handlers=True,
    )


TORTOISE_ORM = {
    "connections": {"default": f"mysql://{config('MYSQL_USER')}:{config('MYSQL_PASSWORD')}@{config('MYSQL_HOST')}:{config('MYSQL_EXPOSE')}/{config('MYSQL_DB')}"},
    "apps": {
        "models": {
            "models": ["models.user",
                       "models.group",
                       "aerich.models"],
            "default_connection": "default",
        },
    },
}
models/user.py
from tortoise import fields
from tortoise.models import Model
from tortoise.contrib.pydantic import pydantic_model_creator
from models.group import Group


class User(Model):
    # ##### Define Readonly Fields ##### #
    id = fields.BigIntField(pk=True)
    # ##### Define Normal Fields ##### #
    first_name = fields.CharField(max_length=50)
    last_name = fields.CharField(max_length=50)
    username = fields.CharField(max_length=50, unique=True)
    email = fields.CharField(max_length=50, unique=True)
    password = fields.CharField(max_length=128)
    # ##### Define O2M ##### #
    owned_groups: fields.ReverseRelation[Group]
    # ##### Define M2M ##### #
    groups: fields.ManyToManyRelation[Group]
    # ##### Define Time_Stamps ###### #
    created_at = fields.DatetimeField(auto_now_add=True)
    modified_at = fields.DatetimeField(auto_now=True)

    class Meta:
        table: str = 'users'


AuthData = pydantic_model_creator(User)
CreateUser = pydantic_model_creator(User, name="CreateUser", exclude_readonly=True)
UpdateUser = pydantic_model_creator(User, name="UpdateUser", exclude_readonly=True, exclude=['password'])
GetUser = pydantic_model_creator(User, name="GetUser", exclude=['password'])
ChangeUserPassword = pydantic_model_creator(User, name="ChangeUserPassword", exclude_readonly=True, include=['password'])
models/group.py
from tortoise import fields
from tortoise.models import Model
from tortoise.contrib.pydantic import pydantic_model_creator


class Group(Model):
    # ##### Define Readonly Fields ##### #
    id = fields.BigIntField(pk=True)
    # ##### Define Normal Fields ##### #
    name = fields.CharField(max_length=50, unique=True)
    # ##### Define O2M ##### #
    owner = fields.ForeignKeyField("models.User", related_name="owned_groups")
    # ##### Define M2M ##### #
    members = fields.ManyToManyField("models.User", related_name="groups")
    # ##### Define Time_Stamps ###### #
    created_at = fields.DatetimeField(auto_now_add=True)
    modified_at = fields.DatetimeField(auto_now=True)

    class Meta:
        table: str = 'groups'


CreateGroup = pydantic_model_creator(Group, name="CreateGroup", exclude_readonly=True, exclude=['members'])
UpdateGroup = pydantic_model_creator(Group, name="UpdateGroup", exclude_readonly=True, exclude=['members'])
GetGroup = pydantic_model_creator(Group, name="GetGroup")
routers/group_router.py
from typing import List
import json
from fastapi import HTTPException, APIRouter, Depends, status
from models.user import User
from models.group import Group, CreateGroup, UpdateGroup, GetGroup
from tortoise.contrib.fastapi import HTTPNotFoundError
from services.auth import current_user

# Intialize Router
router = APIRouter()


# ###################### Define Routes ###################### #

# Create A Group
@router.post("/", dependencies=[Depends(current_user)])
async def create_group(group: CreateGroup, user: User = Depends(current_user)):
    user = await user
    try:
        await Group.create(**group.dict(exclude_unset=True), owner_id=user.id)
    except Exception:
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
            detail="Group Name Already Exists"
        )
    return {"Group Created Successfully"}


# Update A Group
@router.put("/{group_id}", dependencies=[Depends(current_user)], responses={404: {"model": HTTPNotFoundError}})
async def update_group(group_id: int, group: UpdateGroup, user: User = Depends(current_user)):
    user = await user
    group = await GetGroup.from_queryset_single(Group.get(id=group_id))
    return group
    if user.id == group.id:
        await Group.filter(id=group_id).update(**group.dict(exclude_unset=True))
        return {"Group Successfully Updated"}
    else:
        raise HTTPException(
            status_code=status.HTTP_406_NOT_ACCEPTABLE,
            detail="You can't edit a group unless you're the owner"
        )


# Get All Groups
@router.get("/", dependencies=[Depends(current_user)])
async def get_groups():
    return GetGroup.schema()

如您所见,最后一行 GetGroup.schema(),从来没有 returns 关系。

我尝试在容器启动时捕获日志并得到以下信息

[2021-04-24 19:59:53 +0000] [1255] [INFO] Started server process [1255]
[2021-04-24 19:59:53 +0000] [1255] [INFO] Waiting for application startup.
[2021-04-24 19:59:53 +0000] [1255] [ERROR] Traceback (most recent call last):
File "/usr/local/lib/python3.8/site-packages/tortoise/__init__.py", line 358, in _discover_models
module = importlib.import_module(models_path)
File "/usr/local/lib/python3.8/importlib/__init__.py", line 127, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
File "<frozen importlib._bootstrap>", line 1014, in _gcd_import
File "<frozen importlib._bootstrap>", line 991, in _find_and_load
File "<frozen importlib._bootstrap>", line 973, in _find_and_load_unlocked
ModuleNotFoundError: No module named 'a'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/usr/local/lib/python3.8/site-packages/starlette/routing.py", line 526, in lifespan
async for item in self.lifespan_context(app):
File "/usr/local/lib/python3.8/site-packages/starlette/routing.py", line 467, in default_lifespan
await self.startup()
File "/usr/local/lib/python3.8/site-packages/starlette/routing.py", line 502, in startup
await handler()
File "/app/main.py", line 17, in startup_event
init_db(app)
File "/app/database/database.py", line 8, in init_db
Tortoise.init_models("app.models.user", "models")
File "/usr/local/lib/python3.8/site-packages/tortoise/__init__.py", line 415, in init_models
app_models += cls._discover_models(models_path, app_label)
File "/usr/local/lib/python3.8/site-packages/tortoise/__init__.py", line 360, in _discover_models
raise ConfigurationError(f'Module "{models_path}" not found')
tortoise.exceptions.ConfigurationError: Module "a" not found
[2021-04-24 19:59:53 +0000] [1255] [ERROR] Application startup failed. Exiting.

请注意,它会这样做几秒钟,然后应用程序会正确启动, 我也试过将 pydantic 模型创建分离到一个名为 'schema' 的单独文件夹中,但也没有做任何事情

所以我终于找到了答案,我会把它留在这里以防一些可怜的人偶然发现这个问题

诀窍是移动

from database.database import init_db

main.py 文件的顶部

并添加

Tortoise.init_models(["models.user", "models.group"], "models")

在 init_db() 函数下方,以便在 register_tortoise 函数执行和所有模型初始化之前调用它

信息来源: