Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Declaring specific class as response_model in FastAPI produces RecursionError #580

Closed
mawoka-myblock opened this issue Feb 25, 2022 · 10 comments
Labels
bug Something isn't working

Comments

@mawoka-myblock
Copy link

Describe the bug
I get a RecursionError if the Quiz from below is set as the response_model in a FasAPI route-decorator.

from uuid import uuid4 as uuid
from datetime import datetime
from typing import Optional
from pydantic import BaseModel, Json
from . import metadata, database
import ormar

class User(ormar.Model):
    """
    The user model
    """
    id: uuid = ormar.UUID(primary_key=True, default=uuid)
    email: str = ormar.String(unique=True, max_length=100)
    username: str = ormar.String(unique=True, max_length=100)
    password: str = ormar.String(unique=True, max_length=100)
    verified: bool = ormar.Boolean(default=False)
    verify_key: str = ormar.String(unique=True, max_length=100, nullable=True)
    created_at: datetime = ormar.DateTime(default=datetime.now())

    class Meta:
        tablename = 'users'
        metadata = metadata
        database = database


class UserSession(ormar.Model):
    """
    The user session model
    """
    id: uuid = ormar.UUID(primary_key=True, default=uuid)
    user: uuid = ormar.ForeignKey(User)
    session_key: str = ormar.String(unique=True, max_length=64)
    created_at: datetime = ormar.DateTime(default=datetime.now())

    class Meta:
        tablename = 'user_sessions'
        metadata = metadata
        database = database
class QuizAnswer(BaseModel):
    right: bool
    answer: str


class QuizQuestion(BaseModel):
    question: str
    answers: list[QuizAnswer]


class QuizInput(BaseModel):
    title: str
    description: str
    questions: list[QuizQuestion]


class Quiz(ormar.Model):
    id: uuid = ormar.UUID(primary_key=True, default=uuid)
    title: str = ormar.String(max_length=100)
    description: str = ormar.String(max_length=300, nullable=True)
    created_at: datetime = ormar.DateTime(default=datetime.now())
    updated_at: datetime = ormar.DateTime(default=datetime.now())
    user_id: uuid = ormar.UUID(foreign_key=User.id)
    questions: Json[list[QuizQuestion]] = ormar.JSON(nullable=False)

    class Meta:
        tablename = 'quiz'
        metadata = metadata
        database = database

Like this:

from fastapi import APIRouter
router = APIRouter()

The example **doesn't** run "as is"

@router.post("/create", response_model=Quiz)
async def create_quiz_lol(quiz_input: QuizInput):
    quiz = Quiz(**quiz_input.dict(), user_id=user.id)
    return await quiz.save()

Versions (please complete the following information):

  • Database backend used (mysql/sqlite/postgress)
  • Python 3.10.2
  • ormar 0.10.25
  • pydantic 1.9.0
  • FastAPI 0.74.1
@mawoka-myblock mawoka-myblock added the bug Something isn't working label Feb 25, 2022
@collerek
Copy link
Owner

Can you check with an older release i.e. 0.10.24? Want to know if this is something new

@mawoka-myblock
Copy link
Author

Can you check with an older release i.e. 0.10.24? Want to know if this is something new

It also occurs on 0.10.24. Thank you for the fast response!

@collerek
Copy link
Owner

Thanks for checking, can you also post a sample payload you are using to call this API endpoint, will be easier to test. See that you are using python 3.10 which is not yet fully supported, which database and driver are you using?

@mawoka-myblock
Copy link
Author

mawoka-myblock commented Feb 26, 2022

I am using sqlite (aiosqlite 0.17.0)

Example payload

{
   "title":"Some test question",
   "description":"A description",
   "questions":[
      {
         "question":"Is ClassQuiz cool?",
         "answers":[
            {
               "right":true,
               "answer":"Yes"
            },
            {
               "right":false,
               "answer":"No"
            }
         ]
      },
      {
         "question":"Do you like open source?",
         "answers":[
            {
               "right":true,
               "answer":"Yes"
            },
            {
               "right":false,
               "answer":"No"
            },
            {
               "right":false,
               "answer":"Maybe"
            }
         ]
      }
   ]
}

My route

@router.post("/create")
async def create_quiz_lol(quiz_input: QuizInput, user: User = Depends(get_current_user)):
    quiz = Quiz(**quiz_input.dict(), user_id=user.id)
    return await quiz.save()

@mciszczon
Copy link

mciszczon commented Mar 2, 2022

Hmmm, from what I've seen, if you set response_model, then you should not return this model instance from the view? When I set a response_model to be a model inheriting from pydantic.BaseModel, and then returned this model from the view, the payload would be transformed twice: once by me in the view, and then again by the model provided in response_model?

@mawoka-myblock
Copy link
Author

Hmmm, from what I've seen, if you set response_model, then you should not return this model instance from the view? When I set a response_model to be a model inheriting from pydantic.BaseModel, and then returned this model from the view, the payload would be transformed twice: once by me in the view, and then again by the model provided in response_model?

I don't know, but it displays the return model in the openapi schema, so that's enough for me.

collerek added a commit that referenced this issue Mar 28, 2022
@collerek collerek mentioned this issue Mar 28, 2022
collerek added a commit that referenced this issue Mar 28, 2022
* fix for #584

* fix for #580

* fix typing

* connect to db in test

* refactor test

* remove async mark

* connect client

* fix mypy

* fix mypy

* update deps

* check py3.10?

* remove py3.6, bump version
@collerek
Copy link
Owner

Fixed in 0.11.0 - please update and check.

@mawoka-myblock
Copy link
Author

Fixed in 0.11.0 - please update and check.

Yes, the RecursionError is gone, but now, I get the following ValueError:

ValueError: [TypeError("'ModelField' object is not iterable"), TypeError('vars() argument must have __dict__ attribute')]

@collerek
Copy link
Owner

Can you provide some code behind this? Cause i used your code for a test case that passes so need something more to reproduce.

@mawoka-myblock
Copy link
Author

mawoka-myblock commented Mar 28, 2022

Can you provide some code behind this? Cause i used your code for a test case that passes so need something more to reproduce.

Oh, I should have added that this error only occurs if you try to access the /openapi.json-route. The code itself is still the same.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants