0

I use full-stack-fastapi-postgresql, fastapi version 0.54.1 and pydantic version 1.4.

I have no idea how to setup pydantic, so it properly works with a many to one bidirectional relationship in SQLAlchemy. For some reason, my current implementation blows the stack with a maximum recursion error.

I am aware of a github discussion from_orm() should detect cycles when loading SQLAlchemy bi-directional relationships rather than blow stack . I believe this is closely related, but I could not make anything useful out of it so far.

Any help would be greatly appreciated.

Error message from fastAPI:

File "/usr/local/lib/python3.7/site-packages/fastapi/encoders.py", line 113, in jsonable_encoder sqlalchemy_safe=sqlalchemy_safe, File "/usr/local/lib/python3.7/site-packages/fastapi/encoders.py", line 166, in jsonable_encoder sqlalchemy_safe=sqlalchemy_safe, File "/usr/local/lib/python3.7/site-packages/fastapi/encoders.py", line 52, in jsonable_encoder if isinstance(obj, BaseModel): File "/usr/local/lib/python3.7/abc.py", line 139, in instancecheck return _abc_instancecheck(cls, instance) RecursionError: maximum recursion depth exceeded in comparison

The models:

app/models/company.py

from typing import TYPE_CHECKING
from sqlalchemy import Boolean, Column, Integer, String
from sqlalchemy.orm import relationship
from app.db.base_class import Base

if TYPE_CHECKING:
    from .company import Company  # noqa: F401

class Company(Base):
    id = Column(Integer, primary_key=True, index=True)
    enabled = Column(Boolean(), default=True)
    logourl = Column(String, index=True)
    name = Column(String, index=True)
    users = relationship("User", back_populates="company")

app/models/user.py

from typing import TYPE_CHECKING

from sqlalchemy import Column, ForeignKey, Integer, String, Boolean, PrimaryKeyConstraint,UniqueConstraint, DateTime    
from sqlalchemy.orm import relationship

from app.db.base_class import Base

if TYPE_CHECKING:
    from .job import Job  # noqa: F401
    from .company import Company  # noqa: F401


class User(Base):
    id = Column(Integer, primary_key=True, index=True)
    full_name = Column(String, index=True)
    email = Column(String, unique=True, index=True, nullable=False)
    hashed_password = Column(String, nullable=False)
    is_active = Column(Boolean(), default=True)
    is_superuser = Column(Boolean(), default=False)
    company_id = Column(Integer, ForeignKey("company.id"))
    company = relationship("Company", back_populates="users")

The schemas:

app/schemas/user.py

from typing import Optional, Any
from pydantic import BaseModel, EmailStr


# Shared properties
class UserBase(BaseModel):
    email: Optional[EmailStr] = None
    is_active: Optional[bool] = True
    is_superuser: bool = False
    full_name: Optional[str] = None
    company_id: Optional[int] = None
    company: Optional[Any] = None


# Properties to receive via API on creation
class UserCreate(UserBase):
    email: EmailStr
    password: str
    company_id: int

# Properties to receive via API on update
class UserUpdate(UserBase):
    password: Optional[str] = None
    company_id: Optional[int] = None


class UserInDBBase(UserBase):
    id: Optional[int] = None

    class Config:
        orm_mode = True


# Additional properties to return via API
class User(UserInDBBase):
    pass


# Additional properties stored in DB
class UserInDB(UserInDBBase):
    hashed_password: str

app/schemas/company.py

from typing import Optional, List
from pydantic import BaseModel
from .user import User


# Shared properties
class CompanyBase(BaseModel):
    enabled: Optional[bool] = None
    logourl: Optional[str] = None
    name: Optional[str] = None
    users: List[User] = None


# Properties to receive on Company creation
class CompanyCreate(CompanyBase):
    name: str


# Properties to receive on Company update
class CompanyUpdate(CompanyBase):
    pass


# Properties shared by models stored in DB
class CompanyInDBBase(CompanyBase):
    id: int
    name: str

    class Config:
        orm_mode = True


# Properties to return to client
class Company(CompanyInDBBase):
    pass


# Properties properties stored in DB
class CompanyInDB(CompanyInDBBase):
    pass
Jabb
  • 3,414
  • 8
  • 35
  • 58
  • Feel free to let me know if you need more help dealing with FastAPI/Pydantic/SQLAlchemy integration, I've had my fair share of interaction with that stack. Hope my answer solves the problem. – Jason Rebelo Neves Mar 14 '21 at 17:00
  • https://stackoverflow.com/questions/63420889/fastapi-pydantic-circular-references-in-separate-files – Val Apr 14 '22 at 18:33
  • I think this is the answer you are looking for : https://stackoverflow.com/questions/63420889/fastapi-pydantic-circular-references-in-separate-files – Val Apr 14 '22 at 18:34

1 Answers1

1

You are trying to have the user schema contain the company, but also want the company schema to contain the user, this means when you retrieve a specific company for example, its users will contain the same company, and that company will again contain all the users, hence the recursion issue.

You need to decide what data you want to show when a user or a company is retrieved, there is probably no use for a company's full data to be retrieved when all you're trying to do is retrieve a specific user.

Solution

It should be enough to remove company from the UserBase schema.

Jason Rebelo Neves
  • 1,131
  • 10
  • 20