I have the following implementation with fastapi.
My current problem is that I can’t for the life of me do an early init
on the tortoise models to get the relationship back in the schema.
I’ve tried dumping the following line basically everywhere and it just doesn’t seem to work.
Tortoise.init_models(["models.user", "models.group"], "models")
i’ve also tried using prefetch_related
this way but that doesn’t work either
GetGroup.from_queryset(Group.get(id=3).prefetch_related('owner'))
I’ve been googling around for hours and haven’t found a concrete answer/way to get this to properly work.
Folder structure:
app │ main.py └───database │ │ database.py └───models | │ user.py | │ group.py └───routers | │ user_router.py | │ group_router.py └───services | │ auth.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)
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", }, }, }
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'])
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")
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()
As you can see that last line GetGroup.schema()
, never returns the relationship.
I tried capturing the logs while the container is starting and got the following
[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.
mind you it does that for a few seconds and then the application starts correctly, i’ve also tried separating the pydantic models creation in a separate folder called ‘schema’ and that didn’t do anything either
Advertisement
Answer
so i’ve finally found an answer and i’m gonna leave it here in case some poor soul stumbles upon this question
the trick was to move
from database.database import init_db
to the top of the main.py
file
and add
Tortoise.init_models(["models.user", "models.group"], "models")
below the init_db() function so that it’s called upon before the register_tortoise
function executes and all the models are initialized
Info source: https://stackoverflow.com/a/65881146/13637905