Как правильно настроить двунаправленную связь один-ко-многим с помощью fastAPI, Pydantic и SQLAlchemy

Я использую full-stack-fastapi-postgresql, fastapi версии 0.54.1 и pydantic версия 1.4.

Я понятия не имею, как настроить pydantic, поэтому он правильно работает с двунаправленными отношениями «многие к одному» в SQLAlchemy. По какой-то причине моя текущая реализация взрывает стек с максимальной ошибкой рекурсии.

Мне известно об обсуждении на github. from_orm () должен определять циклы при загрузке двунаправленных отношений SQLAlchemy, а не взорвать стек. Я считаю, что это тесно связано, но пока я не смог сделать из этого ничего полезного.

Любая помощь будет принята с благодарностью.

Сообщение об ошибке от fastAPI:

Файл /usr/local/lib/python3.7/site-packages/fastapi/encoders.py, строка 113, в jsonable_encoder sqlalchemy_safe = sqlalchemy_safe, Файл /usr/local/lib/python3.7/site-packages/fastapi/encoders .py, строка 166, в jsonable_encoder sqlalchemy_safe = sqlalchemy_safe, File /usr/local/lib/python3.7/site-packages/fastapi/encoders.py, строка 52, в jsonable_encoder, если isinstance (obj, BaseModel): File / usr /local/lib/python3.7/abc.py, строка 139, в instancecheck return _abc_instancecheck (cls, instance) RecursionError: максимальная глубина рекурсии превышена при сравнении

Модели:

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")

Схемы:

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

person Jabb    schedule 14.03.2021    source источник
comment
Не стесняйтесь сообщить мне, если вам понадобится дополнительная помощь в работе с интеграцией FastAPI / Pydantic / SQLAlchemy, я имел свою долю взаимодействия с этим стеком. Надеюсь, мой ответ решит проблему.   -  person Jason Rebelo Neves    schedule 14.03.2021


Ответы (1)


Вы пытаетесь, чтобы схема user содержала company, но также хотите, чтобы схема company содержала user, это означает, что, например, когда вы извлекаете конкретную компанию, ее пользователи будут содержать ту же компанию, и эта компания снова будет содержать все пользователей, отсюда и проблема рекурсии.

Вам нужно решить, какие данные вы хотите отображать при извлечении user или company, вероятно, нет смысла извлекать полные данные компании, когда все, что вы пытаетесь сделать, это получить конкретный user.

Решение

Этого должно быть достаточно, чтобы удалить company из схемы UserBase.

person Jason Rebelo Neves    schedule 14.03.2021