8

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 ?

alex_noname
  • 26,459
  • 5
  • 69
  • 86

1 Answers1

7

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.

alex_noname
  • 26,459
  • 5
  • 69
  • 86
  • I see, I sort of followed the approach from the docs for Fast API. https://tortoise-orm.readthedocs.io/en/latest/examples/fastapi.html#example-fastapi Here, the code that creates the pydantic models are called first (because of the import), then later the `register_tortoise` function (which from looking at the sources initializes the model). But again, reordering the code, having `register_tortoise` first, then the creation of pydantic models did not work for me either. – Božidar Vulićević Jan 02 '21 at 20:48
  • I see the issue now, looking at `register_tortoise` `@app.on_event("startup")` is when the models are initialized. Ill give it a try – Božidar Vulićević Jan 02 '21 at 20:52
  • Invoking `register_tortoise` only registers startup and shutdown handlers, without actual db initialization at this moment. Try to use early init approach. – alex_noname Jan 02 '21 at 20:59
  • I cannot get it function, no matter my efforts. I tried the early init approach tortoise api approach, and as well init of tortoise before fast api, but that approach was not feasible because of fast api limitations in regards to fast api initilization.. – Božidar Vulićević Jan 02 '21 at 22:07
  • It makes sense to post a link to the gist of the latest version of the code that doesn't work – alex_noname Jan 03 '21 at 09:38
  • https://github.com/bozvul993/fast-api-tortoise-orm-experimentation Added instructions on how to run the app, the entry point, being main.py. The issue can be seen when trying to get a category by id: `/category/{id}`. The code that interacts with orm is in this file: https://github.com/bozvul993/fast-api-tortoise-orm-experimentation/blob/master/repositories/category_repository.py There is open api docs on `http://127.0.0.1:8000/docs` when running the app in dev mode, with the command given in readme. The `keywords` relation is not loaded.. @alex_noname – Božidar Vulićević Jan 03 '21 at 17:54
  • If you do get a chance to look or make a PR, I would be very grateful. – Božidar Vulićević Jan 03 '21 at 17:56
  • I'v made the smaill PR, 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 – alex_noname Jan 05 '21 at 09:21
  • Thank you very much, I will take a look later tonight. Very grateful. – Božidar Vulićević Jan 05 '21 at 10:51
  • Thanks again for your help. All the best. – Božidar Vulićević Jan 05 '21 at 20:16
  • what does `["__main__"]` represent? is that name of the main file like `main.py` or where does `__main__` come from? trying to know what to change this to for my code – uberrebu Mar 22 '21 at 23:24
  • `__main__` This is the name of the first module to run. More details [here](https://docs.python.org/3/library/__main__.html). In this example, the models are defined in one module, but here you can also pass the name of any other loaded module or the path as a string to the `py` file with the models. – alex_noname Mar 23 '21 at 06:54