Как правильно возвращать в модели список из сущностей, принадлежащих связанной модели?

Делаю небольшой проект на Fastapi (мой первый на нем проект), python 3.10, с использованием sqlalchemy, sqlmodel. Есть 2 связанные между собой сущности (сделал их для наглядности). 1 - магазин, 2 - товары. Товар привязывается к магазину (это условно, для примера). При получении данных о магазине хочу возвращать список товаров, которые там содержатся.

Update: добавил тестовый проект на Гитхаб для большей наглядности.

models

product.py

from sqlmodel import SQLModel, Field, Relationship
from typing import TYPE_CHECKING, Optional

if TYPE_CHECKING:
    from app.models.shop import Shop


class ProductBase(SQLModel):
    """Сущность: продукт (товар)"""
    name: Optional[str] = None
    shop_id: int = Field(default=None, foreign_key="shop.id")


class Product(ProductBase, table=True):

    __tablename__ = "product"

    id: Optional[int] = Field(default=None, primary_key=True, nullable=False)
    shop: Optional["Shop"] = Relationship(back_populates="products")


class ProductRead(ProductBase):
    id: int


class ProductCreate(ProductBase):
    pass

shop.py

from sqlmodel import SQLModel, Field, Relationship
from typing import TYPE_CHECKING, List, Optional

if TYPE_CHECKING:
    from app.models.product import Product, ProductRead


class ShopBase(SQLModel):
    """Сущность: магазин, в котором содержатся продукты (товары)"""
    name: Optional[str] = None


class Shop(ShopBase, table=True):

    __tablename__ = "shop"

    id: Optional[int] = Field(default=None, primary_key=True, nullable=False)
    products: List["Product"] = Relationship(back_populates="shop")


class ShopRead(ShopBase):
    id: int


class ShopGet(ShopRead):
    products: List["ProductRead"] = []
    is_main: bool = True


class ShopCreate(ShopBase):
    pass

repositories

product.py

from typing import Optional

from sqlalchemy.future import select
from app.models.product import Product, ProductCreate
from app.repositories.base import BaseRepository


class ProductRepository(BaseRepository):

    async def create(self, product: ProductCreate) -> Product:
        db_product = Product.from_orm(product)
        self.session.add(db_product)
        await self.session.commit()
        await self.session.refresh(db_product)
        return db_product

    async def get_by_name(self, name: str) -> Optional[Product]:
        query = select(Product).where(Product.name == name)
        result = await self.session.execute(query)
        return result.scalar_one_or_none()

    async def get_by_id(self, product_id: int) -> Optional[Product]:
        result = await self.session.get(Product, int(product_id))
        return result

shop.py

from typing import Optional

from sqlalchemy.future import select
from app.models.shop import Shop, ShopCreate, ShopGet
from app.repositories.base import BaseRepository


class ShopRepository(BaseRepository):

    async def create(self, shop: ShopCreate) -> Shop:
        db_shop = Shop.from_orm(shop)
        self.session.add(db_shop)
        await self.session.commit()
        await self.session.refresh(db_shop)
        return db_shop

    async def get_by_name(self, name: str) -> Optional[Shop]:
        query = select(Shop).where(Shop.name == name)
        result = await self.session.execute(query)
        return result.scalar_one_or_none()

    async def get_by_id(self, shop_id: int) -> Optional[ShopGet]:
        result = await self.session.get(Shop, int(shop_id))
        return result

endpoints

shop.py

from fastapi import APIRouter, Depends, Query
from app.repositories.shop import ShopRepository
from app.models.shop import Shop, ShopCreate, ShopGet
from app.endpoints.depends import get_shop_repository


router = APIRouter()


@router.get("/get_by_name", response_model=ShopGet)
async def get_by_name(
        name: str = Query(description="Название магазина"),
        shop: ShopRepository = Depends(get_shop_repository)):
    return await shop.get_by_name(name=name)


@router.get("/get_by_id", response_model=ShopGet)
async def get_by_id(
        shop_id: int = Query(description="ID магазина"),
        shop: ShopRepository = Depends(get_shop_repository)):
    return await shop.get_by_id(shop_id=shop_id)


@router.post("/create", response_model=Shop)
async def create(
        name: str = Query(description="Название магазина"),
        shop: ShopRepository = Depends(get_shop_repository)):
    return await shop.create(shop=ShopCreate(name=name))

коннект к базе данных

from sqlalchemy.ext.asyncio import create_async_engine
from sqlalchemy.orm import sessionmaker
from sqlmodel.ext.asyncio.session import AsyncSession

from app import settings


async_engine = create_async_engine(settings.db_async_connection_str, echo=True, future=True)


async def get_async_session() -> AsyncSession:
    async_session = sessionmaker(
        bind=async_engine, class_=AsyncSession, expire_on_commit=False
    )
    async with async_session() as session:
        yield session

Использовал Relationship, информацию брал из документации

Первая проблема, с которой столкнулся, при разворачивании сваггера - ошибка

TypeError: issubclass() arg 1 must be a class

Выяснил, что это из-за такого импорта (использовал его по документации, чтобы не было конфликтов при взаимном импорте):

if TYPE_CHECKING:
    from app.models.shop import Shop

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

И вторая проблема (основная) - то что эндпоинт получение shop по id всегда возвращает пустой список products (хотя я их надобавлял несколько, привязанных именно к этому shop_id). Мне необходимо, чтобы в модели shop возвращался список из привязанных к нему products.

Аналогичный вопрос на английском


Ответы (0 шт):