tortoise / tortoise-orm

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

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

Open lokichaulagain opened 1 year ago

lokichaulagain commented 1 year 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 1 year ago

Which post endpoint causes this error?

lokichaulagain commented 1 year ago

Which post endpoint causes this error?

Sale post method

wudiwenOrg commented 11 months ago

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

wudiwenOrg commented 11 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