FastAPI w/ TortoiseORM: Error: Unprocessable Entity

965 views Asked by At

I'm new to fastAPI and trying to build a todo API. I use Tortoise-ORM. I believe the error was caused by entering null value to the todo section although i want it to be null or set to default value which is an empty list. This is the code related to the error. main.py

from typing import List
import os

from fastapi import FastAPI, Depends, HTTPException, status

from models import User, Todo, User_pydantic, UserIn_pydantic, Todo_pydantic, TodoIn_pydantic
from tortoise.contrib.fastapi import register_tortoise, HTTPNotFoundError

app = FastAPI()

@app.post("/users/", response_model=User_pydantic)
async def create_user(user: UserIn_pydantic):
    user_obj = await User.create(**user.dict(exclude_unset=True))
    return await User_pydantic.from_tortoise_orm(user_obj)


@app.get("/users/", response_model=List[User_pydantic])
async def read_all_users():
    return await User_pydantic.from_queryset(User.all())

models.py

from tortoise.models import Model
from tortoise import fields
from tortoise.contrib.pydantic import pydantic_model_creator


class Todo(Model):
    id = fields.IntField(pk=True)
    title = fields.CharField(500)
    description = fields.TextField(null=True)

Todo_pydantic = pydantic_model_creator(Todo, name="Todo")
TodoIn_pydantic = pydantic_model_creator(Todo, name="TodoIn", exclude_readonly=True)

class User(Model):
    id = fields.IntField(pk=True)
    username = fields.CharField(500, unique=True)
    email = fields.CharField(100, unique=True)
    password = fields.CharField(100)
    is_active = fields.BooleanField(default=True)
    todo: fields.ForeignKeyNullableRelation[Todo] = fields.ForeignKeyField("models.Todo", related_name="todo")

    class PydanticMeta:
        exclude = ["is_active", "todo"]

User_pydantic = pydantic_model_creator(User, name="User")
UserIn_pydantic = pydantic_model_creator(User, name="UserIn", exclude_readonly=True)

The error began when i tried to create a new user. Here is the error from swagger UI

{
  "detail": [
    {
      "loc": [],
      "msg": "null value in column \"todo_id\" of relation \"user\" violates not-null constraint\nDETAIL:  Failing row contains (6, bob, [email protected], bob123, t, null).",
      "type": "IntegrityError"
    }
  ]
}
2

There are 2 answers

8
lsabi On

The problem is stated in the error

"null value in column "todo_id" of relation "user" violates not-null constraint\nDETAIL: Failing row contains (6, bob, [email protected], bob123, t, null).

I don't see any todo_id column in your User table, but I guess that it's just due to an issue with copying and pasting on SO.

I haven't used TortoiseORM (but I would like to once it reaches a stable release), but have you tried changing the definition of todo into the following?

todo: fields.ForeignKeyNullableRelation[Todo, None] = fields.ForeignKeyField("models.Todo", related_name="todo")

This way you are telling that the field is of type ForeignKeyNullableRelation, and either Todo or None.

That's my guess, too long to fit in the comments, so I posted it as an answer.

Let me know if it works or not.

0
kamil.k On

I faced similar issue and found solution for that. Problem here is that your POST request is missing todo_id (not todo because database is automatically adding _id to relating fields). But if you try to add it you will get extra fields not permitted. The reason for that is because UserIn_pydantic don't have this field. To solve it, first try to initialize your models by calling init_models something like this:

Tortoise.init_models(['path_to_file_with_models'], 'models')

after pydantic_model_creator should do the rest and it will work for most cases. if not then just simply override it:

UserIn_pydantic = pydantic_model_creator(User, name="UserIn", exclude_readonly=True)

class UsersIn_PydanticWithRelation(UsersIn_Pydantic):
    todo_id: int

User_pydantic = pydantic_model_creator(User, name="User")
class Users_PydanticWithRelation(Users_Pydantic):
   client_id: int

and in your main.py replace your types annotations. This way you will be abble to POST todo_id, it will be stored in database and you will be abble to return it.

At least this worked for me. I hope it will help you too. Let me know if it helps.