Обработка циклического импорта в моделях Pydantic с помощью FastAPIPython

Программы на Python
Anonymous
Обработка циклического импорта в моделях Pydantic с помощью FastAPI

Сообщение Anonymous »

Я разрабатываю приложение FastAPI, имеющее следующую структуру модулей.

Код: Выделить всё

...
│   ├── modules
│   │   ├── box
│   │   │   ├── routes.py
│   │   │   ├── services.py
│   │   │   ├── models.py # the sqlalchemy classes
│   │   │   ├── schemas.py # the pydantic schemas
│   │   ├── toy
│   │   │   ├── routes.py
│   │   │   ├── services.py
│   │   │   ├── models.py
│   │   │   ├── schemas.py
Каждый модуль содержит модели SQLAlchemy, модели Pydantic (также называемые схемами), маршруты FastAPI и службы, обрабатывающие бизнес-логику.
В этом примере я использую два модуля, которые представляют коробки и игрушки. Каждая игрушка хранится в одной коробке, а каждая коробка содержит несколько игрушек в соответствии с классическим соотношением 1 x N.
С SQLAlchemy все работает хорошо , определить отношения очень просто, используя TYPE_CHECKING для обработки циклических зависимостей:

Код: Выделить всё

# my_app.modules.box.models.py

from sqlalchemy.orm import Mapped, mapped_column, relationship
if TYPE_CHECKING:
from my_app.modules.toy.models import Toy

class Box(Base):
__tablename__ = "box"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)

toys: Mapped[list["Toy"]] = relationship(back_populates="box")

Код: Выделить всё

# my_app.modules.toy.models.py

from sqlalchemy.orm import Mapped, mapped_column, relationship
if TYPE_CHECKING:
from my_app.modules.box.models import Box

class Toy(Base):
__tablename__ = "toy"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
box: Mapped["Box"] = relationship(back_populates="toys")

Эта настройка работает идеально, не вызывая ошибок циклического импорта. Однако я сталкиваюсь с проблемами при определении одинаковых отношений между схемами Pydantic. Если я напрямую импортирую модули в свой файл Schemas.py,

Код: Выделить всё

# my_app.modules.box.schemas.py
from my_app.modules.toy.schemas import ToyBase

class BoxBase(BaseModel):
id: int

class BoxResponse(BoxBase):
toys: list[ToyBase]

Код: Выделить всё

# my_app.modules.toy.schemas.py
from my_app.modules.box.schemas import BoxBase

class ToyBase(BaseModel):
id: int

class ToyResponse(ToyBase):
box: BoxBase
Я получаю сообщение об ошибке циклического импорта:

Код: Выделить всё

ImportError: cannot import name 'ToyBase' from partially initialized module 'my_app.modules.toy.schemas' (most likely due to a circular import)...
Я также пробую подход SQLAlchemy TYPE_CHECKING и объявление строки:

Код: Выделить всё

# my_app.modules.box.schemas.py
if TYPE_CHECKING:
from my_app.modules.toy.schemas import ToyBase

class BoxBase(BaseModel):
id: int

class BoxResponse(BoxBase):
toys: list["ToyBase"]

Код: Выделить всё

# my_app.modules.toy.schemas.py
if TYPE_CHECKING:
from my_app.modules.box.schemas import BoxBase

class ToyBase(BaseModel):
id: int

class ToyResponse(ToyBase):
box: "BoxBase"
Но, видимо, pydantic это не поддерживает:

Код: Выделить всё

raise PydanticUndefinedAnnotation.from_name_error(e) from e
pydantic.errors.PydanticUndefinedAnnotation: name 'ToyBase' is not defined
(Некоторые ответы) предполагают, что проблема связана с плохой организацией модуля. (Другие) предлагают слишком сложные и трудные для понимания решения.
Возможно, я ошибаюсь, но я считаю связь между Box и Toy чем-то тривиальным и фундаментальным. это должно быть осуществимо в любом умеренно сложном проекте. Например, простым вариантом использования может быть запрос игрушки вместе с коробкой, в которой она находится, и наоборот, коробкой со всеми игрушками. Разве это не законные запросы?
Итак, мой вопрос
Как я могу определить взаимосвязанные схемы Pydantic (

Код: Выделить всё

BoxResponse
и ToyResponse), которые ссылаются друг на друга, не встречая циклических ошибок импорта? Я ищу четкое и удобное в сопровождении решение, сохраняющее независимость коробочных и игрушечных модулей, аналогично тому, как отношения обрабатываются в моделях SQLAlchemy. Есть какие-нибудь предложения или хотя бы объяснение, почему этого так сложно достичь?

Подробнее здесь: https://stackoverflow.com/questions/790 ... th-fastapi

Вернуться в «Python»