I was playing around with FastAPI using Tortoise-ORM for it's orm and encountered a problem. Specifically, I cannot return a relationship in the model.
Here is my application structure. Structure is inspired by Django's app structure.
.
├── Dockerfile
├── LICENSE
├── Pipfile
├── Pipfile.lock
├── README.md
├── app
│ ├── contacts
│ │ ├── __init__.py
│ │ ├── main.py
│ │ ├── models.py
│ │ └── routers.py
│ ├── main.py
│ ├── resources
│ │ ├── __init__.py
│ │ ├── constants.py
│ │ ├── core_model.py
│ │ ├── database.py
│ │ └── middlewares.py
│ └── users
│ ├── __init__.py
│ ├── main.py
│ ├── models.py
│ └── routers.py
└── docker-compose.yml
database connection is setup in app/resources/database.py
like so;
from fastapi import FastAPI
from tortoise.contrib.fastapi import register_tortoise
def get_db_uri(*, user, password, host, db):
return f'postgres://{user}:{password}@{host}:5432/{db}'
def setup_database(app: FastAPI):
register_tortoise(
app,
db_url=get_db_uri(
user='postgres',
password='postgres',
host='db', # docker-compose service name
db='postgres',
),
modules={
'models': [
'app.users.models',
'app.contacts.models',
],
},
generate_schemas=True,
add_exception_handlers=True,
)
from the models
argument you can see that there are 2 models setup. Here are the two.
app/users/models.py
from tortoise import Tortoise, fields
from tortoise.contrib.pydantic import pydantic_model_creator
from passlib.hash import bcrypt
from app.resources.core_model import CoreModel
class User(CoreModel):
username = fields.CharField(50, unique=True)
email = fields.CharField(60, unique=True)
password_hash = fields.CharField(128)
def __str__(self):
return self.email
def verify_password(self, password):
return bcrypt.verify(password, self.password_hash)
class PydanticMeta:
exclude = ["password_hash"]
# Tortoise.init_models(['app.users.models'], 'models')
User_Pydantic = pydantic_model_creator(User, name='User')
UserIn_Pydantic = pydantic_model_creator(
User, name='UserIn', exclude_readonly=True)
app/contacts/models.py
from tortoise import Tortoise, fields
from tortoise.contrib.pydantic import pydantic_model_creator
from app.resources.core_model import CoreModel
class Contact(CoreModel):
user = fields.ForeignKeyField(
'models.User', related_name='contacts')
name = fields.CharField(50)
def __str__(self):
return self.name
# Tortoise.init_models(['app.users'], 'models')
Contact_Pydantic = pydantic_model_creator(Contact, name='Contact')
ContactIn_Pydantic = pydantic_model_creator(
Contact, name='ContactIn', exclude_readonly=True)
Here is what happens when the user tries to save a contact.
@router.post('/', status_code=status.HTTP_201_CREATED)
async def create_contact(contact_name: str = Form(...), user: User_Pydantic = Depends(get_current_user)):
try:
contact = Contact(user_id=user.id, name=contact_name)
await contact.save()
contact_obj = await Contact_Pydantic.from_tortoise_orm(contact)
print(contact_obj.schema_json(indent=4))
except Exception as err:
print(err)
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, detail='failed to save data')
return {'status_code': status.HTTP_201_CREATED, 'contact': contact_obj.dict()}
users can retrieve their saved contacts from the following route.
@router.get('/me')
async def get_all_contacts(user: User_Pydantic = Depends(get_current_user)):
try:
contacts = await Contact.filter(user_id=user.id)
contacts_list = [await Contact_Pydantic.from_tortoise_orm(contact) for contact in contacts]
print(user.schema_json())
except:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, detail='failed to fetch related data')
return {'contacts': contacts_list}
when the user retrieves their contacts it does not show the relationship with the users. In this example it is not necessary but I would like to figure out how to get the relationships in the response for future reference.
I went through the docs and found that early-init is a thing and tried to use init_models
but this does not seem to work. Or maybe I just don't know how it works. If I need to use init_models
, confusing part is the 1. when to call it and 2. how to call it. init_models
two arguments made me very confused.
For summary, I have 2 questions.
- How can I get the relationship from a model and return that to users.
- If I need to use
init_models
, where do I call it and with this application structure what will be the correct way for the 2 required arguments.
Thank you in advance.
Note
- I have tried the following method to save the contact but the result was the same.
@router.post('/', status_code=status.HTTP_201_CREATED)
async def create_contact(contact_name: str = Form(...), user: User_Pydantic = Depends(get_current_user)):
try:
user = await User.get(id=user.id)
contact = await Contact.create(user=user, name=contact_name)
contact_obj = await Contact_Pydantic.from_tortoise_orm(contact)
print(contact_obj.schema_json(indent=4))
except Exception as err:
print(err)
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, detail='failed to save data')
return {'status_code': status.HTTP_201_CREATED, 'contact': contact_obj.dict()}
question from:
https://stackoverflow.com/questions/65879512/model-relationships-not-showing-in-tortoise-orm-fastapi