pythonpostgresqlfastapipydantictortoise-orm

Tortoise ORM for Python no returns relations of entities (Pyndantic, FastAPI)


I was making a sample Fast Api server with Tortoise ORM as an asynchronous orm library, but I just cannot seem to return the relations I have defined. These are my relations:

# Category
from tortoise.fields.data import DatetimeField
from tortoise.models import Model
from tortoise.fields import UUIDField, CharField
from tortoise.fields.relational import ManyToManyField
from tortoise.contrib.pydantic import pydantic_model_creator


class Category(Model):
    id = UUIDField(pk=True)
    name = CharField(max_length=255)
    description = CharField(max_length=255)
    keywords = ManyToManyField(
        "models.Keyword", related_name="categories", through="category_keywords"
    )
    created_on = DatetimeField(auto_now_add=True)
    updated_on = DatetimeField(auto_now=True)



Category_dto = pydantic_model_creator(Category, name="Category", allow_cycles = True)
# Keyword
from models.expense import Expense
from models.category import Category
from tortoise.fields.data import DatetimeField
from tortoise.fields.relational import ManyToManyRelation
from tortoise.models import Model
from tortoise.fields import UUIDField, CharField
from tortoise.contrib.pydantic import pydantic_model_creator


class Keyword(Model):
    id = UUIDField(pk=True)
    name = CharField(max_length=255)
    description = CharField(max_length=255)
    categories: ManyToManyRelation[Category]
    expenses: ManyToManyRelation[Expense]
    created_on = DatetimeField(auto_now_add=True)
    updated_on = DatetimeField(auto_now=True)

    class Meta:
        table="keyword"


Keyword_dto = pydantic_model_creator(Keyword)

The tables have been created correctly. When adding keywords to categories the db state is all good. The problem is when i want to query the categories and include the keywords. I have this code for that:

class CategoryRepository():

    @staticmethod
    async def get_one(id: str) -> Category:
        category_orm = await Category.get_or_none(id=id).prefetch_related('keywords')
        if (category_orm is None):
            raise NotFoundHTTP('Category')
        return category_orm

Debugging the category_orm here I have the following:

category_orm debug at run-time

Which kind of tells me that they are loaded. Then when i cant a Pydantic model I have this code

class CategoryUseCases():

    @staticmethod
    async def get_one(id: str) -> Category_dto:
        category_orm = await CategoryRepository.get_one(id)
        category = await Category_dto.from_tortoise_orm(category_orm)
        return category

and debugging this, there is no keywords field

category (pydantic) debug at run-time

Looking at the source code of tortoise orm for the function from_tortoise_orm

@classmethod
    async def from_tortoise_orm(cls, obj: "Model") -> "PydanticModel":
        """
        Returns a serializable pydantic model instance built from the provided model instance.

        .. note::

            This will prefetch all the relations automatically. It is probably what you want.

But my relation is just not returned. Anyone have a similar experience ?


Solution

  • The issue occurs when one try to generate pydantic models before Tortoise ORM is initialised. If you look at basic pydantic example you will see that all pydantic_model_creator are called after Tortoise.init.

    The obvious solution is to create pydantic models after Tortoise initialisation, like so:

    
    await Tortoise.init(db_url="sqlite://:memory:", modules={"models": ["__main__"]})
    await Tortoise.generate_schemas()
    
    Event_Pydantic = pydantic_model_creator(Event)
    
    

    Or a more convenient way, use early model init by means of Tortoise.init_models(). Like so:

    
    from tortoise import Tortoise
    
    Tortoise.init_models(["__main__"], "models")
    Tournament_Pydantic = pydantic_model_creator(Tournament)
    

    In the case, the main idea is to split pydantic and db models into different modules, so that importing the first does not lead to the creation of the second ahead of time. And ensure calling Tortoise.init_models() before creating pydantic models.

    A more detailed description with examples can be found here.