tortoise / tortoise-orm

Familiar asyncio ORM for python, built with relations in mind
https://tortoise.github.io
Apache License 2.0
4.39k stars 359 forks source link

AttributeError: 'int' object has no attribute '_saved_in_db' , Fastapi , Tortoise-orm , pydantic #1485

Open lokichaulagain opened 9 months ago

lokichaulagain commented 9 months ago

I m using fastapi , tortoise-orm and pydantic , everything works fine except the Post method which contain foreign key field. I get the error saying AttributeError: 'int' object has no attribute '_saved_in_db' for database I am using elephantSql(postgres) Repo : https://github.com/lokendra-chaulagain/object-has-no-attribute-in-db-issue

Table model

from tortoise import fields
from tortoise.models import Model

class Table(Model):
    id = fields.IntField(pk=True)
    name = fields.CharField(max_length=20)

Sale Model

from tortoise import fields
from tortoise.models import Model
from models.Table import Table

class Sale(Model): 
    id = fields.IntField(pk=True)
    discount_amount = fields.FloatField(default=0)
    is_draft = fields.BooleanField(default=True)
    table = fields.ForeignKeyField(
        "models.Table", related_name='sales', on_delete=fields.NO_ACTION)
    table: fields.ForeignKeyRelation[Table] = fields.ForeignKeyField(
        "models.Table", related_name="sales")

Table Schema

from typing import Optional
from pydantic import BaseModel, ValidationError

class TableIn(BaseModel):
    name: str

class TableOut(BaseModel):
    id: int
    name: str

class TableQueryParams(BaseModel):
    page: Optional[int] = 1
    limit: Optional[int] = 5
    name: Optional[str] = None

Sale Schema

from typing import Optional
from pydantic import BaseModel
from models.Table import Table

class SaleIn(BaseModel):
    discount_amount: float
    is_draft: bool
    table: int

class SaleOut(BaseModel):
    id: int
    discount_amount: float
    is_draft: bool
    table: int

class SaleQueryParams(BaseModel):
    page: Optional[int] = 1
    limit: Optional[int] = 5
    table: Optional[int] = None

Sale Route (Controller)

from fastapi import APIRouter, Depends
from typing import List
from models.Sale import Sale
from schemas.saleSchema import SaleIn, SaleOut, SaleQueryParams
saleRouter = APIRouter()

@saleRouter.post("", response_model=SaleOut)
async def create(sale: SaleIn) -> SaleOut:
    # new_sale = await Sale.create(**sale.model_dump())
    # return new_sale

    new_sale = await Sale.create(discount_amount=sale.discount_amount, is_draft=sale.is_draft, table=sale.table)
    saved_sale=new_sale.save()
    return saved_sale

@saleRouter.get("", response_model=List[SaleOut])
async def get_all(query_params: SaleQueryParams = Depends()) -> List[SaleOut]:
    offset = (query_params.page - 1) * query_params.limit
    query = Sale.all()

    if query_params.table:
        query = query.filter(table__icontains=query_params.name)

    sales = await query.offset(offset).limit(query_params.limit)
    return list(sales)

Error:

INFO:     127.0.0.1:58776 - "POST /sales HTTP/1.1" 500 Internal Server Error
ERROR:    Exception in ASGI application
Traceback (most recent call last):
  File "C:\Users\loken\AppData\Local\Programs\Python\Python311\Lib\site-packages\uvicorn\protocols\http\httptools_impl.py", line 426, in run_asgi
    result = await app(  # type: ignore[func-returns-value]
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\loken\AppData\Local\Programs\Python\Python311\Lib\site-packages\uvicorn\middleware\proxy_headers.py", line 84, in __call__
    return await self.app(scope, receive, send)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\loken\AppData\Local\Programs\Python\Python311\Lib\site-packages\fastapi\applications.py", line 292, in __call__
    await super().__call__(scope, receive, send)
  File "C:\Users\loken\AppData\Local\Programs\Python\Python311\Lib\site-packages\starlette\applications.py", line 122, in __call__
    await self.middleware_stack(scope, receive, send)
  File "C:\Users\loken\AppData\Local\Programs\Python\Python311\Lib\site-packages\starlette\middleware\errors.py", line 184, in __call__
    raise exc
  File "C:\Users\loken\AppData\Local\Programs\Python\Python311\Lib\site-packages\starlette\middleware\errors.py", line 162, in __call__
    await self.app(scope, receive, _send)
  File "C:\Users\loken\AppData\Local\Programs\Python\Python311\Lib\site-packages\starlette\middleware\exceptions.py", line 79, in __call__
    raise exc
  File "C:\Users\loken\AppData\Local\Programs\Python\Python311\Lib\site-packages\starlette\middleware\exceptions.py", line 68, in __call__
    await self.app(scope, receive, sender)
  File "C:\Users\loken\AppData\Local\Programs\Python\Python311\Lib\site-packages\fastapi\middleware\asyncexitstack.py", line 20, in __call__
    raise e
  File "C:\Users\loken\AppData\Local\Programs\Python\Python311\Lib\site-packages\fastapi\middleware\asyncexitstack.py", line 17, in __call__
    await self.app(scope, receive, send)
  File "C:\Users\loken\AppData\Local\Programs\Python\Python311\Lib\site-packages\starlette\routing.py", line 718, in __call__
    await route.handle(scope, receive, send)
  File "C:\Users\loken\AppData\Local\Programs\Python\Python311\Lib\site-packages\starlette\routing.py", line 276, in handle
    await self.app(scope, receive, send)
  File "C:\Users\loken\AppData\Local\Programs\Python\Python311\Lib\site-packages\starlette\routing.py", line 66, in app
    response = await func(request)
               ^^^^^^^^^^^^^^^^^^^
  File "C:\Users\loken\AppData\Local\Programs\Python\Python311\Lib\site-packages\fastapi\routing.py", line 273, in app
    raw_response = await run_endpoint_function(
i701 commented 9 months ago

Which post endpoint causes this error?

lokichaulagain commented 9 months ago

Which post endpoint causes this error?

Sale post method

wudiwenOrg commented 6 months ago

Did you solved this problem? I'm running into this issue as well

wudiwenOrg commented 6 months ago

https://tortoise.github.io/models.html#foreignkeyfield I got it

from typing import Optional
from pydantic import BaseModel
from models.Table import Table

class SaleIn(BaseModel):
    discount_amount: float
    is_draft: bool
    table_id: int   # foreignkeyfield will append `_id` in database