You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

异步SQLAlchemy中循环关联的Pydantic验证异常问题

问题:异步查询单条Test数据时Pydantic验证触发MissingGreenlet错误

我定义了SQLAlchemy模型实现Test与TestAttribute的一对多关联,TestAttribute还可关联其他Test对象(关联链数据层面保证终止),同时编写了对应Pydantic Schema和提供get(按ID查单条)、list(查全部)方法的TestRepository类。目前遇到的问题是:调用get方法时,若Test对象存在多层关联属性,Pydantic验证会抛出MissingGreenlet错误;但调用list方法时可正常返回嵌套结构数据。


SQLAlchemy模型代码

from typing import Optional

from sqlalchemy import ForeignKey, Integer
from sqlalchemy.orm import DeclarativeBase, mapped_column, relationship
from sqlalchemy.orm.base import Mapped


class Test(DeclarativeBase):
    __tablename__ = "test"
    id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    value: Mapped[int] = mapped_column(Integer, nullable=False)
    attributes: Mapped[list["TestAttribute"]] = relationship(
        "TestAttribute", back_populates="test", primaryjoin="Test.id == TestAttribute.test_id", lazy="selectin"
    )
    test_attributes: Mapped[list["TestAttribute"]] = relationship(
        "TestAttribute",
        back_populates="test_attribute",
        primaryjoin="Test.id == TestAttribute.test_attribute_id",
    )


class TestAttribute(DeclarativeBase):
    __tablename__ = "test_attribute"
    id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    test_value: Mapped[int] = mapped_column(Integer, nullable=False)
    test_id: Mapped[int] = mapped_column(ForeignKey("test.id"), nullable=False)
    test_attribute_id: Mapped[Optional[int]] = mapped_column(ForeignKey("test.id"), nullable=True)
    test: Mapped["Test"] = relationship(back_populates="attributes", foreign_keys=[test_id])
    test_attribute: Mapped[Optional["Test"]] = relationship(
        back_populates="test_attributes", foreign_keys=[test_attribute_id], lazy="joined"
    )

Pydantic Schema代码

from pydantic import BaseModel, ConfigDict
from typing import Optional


class TestAttributeSchema(BaseModel):
    test_attribute_id: Optional[int]
    test_value: int
    test_attribute: Optional["TestSchema"]
    model_config = ConfigDict(from_attributes=True)


class TestSchema(BaseModel):
    id: int
    value: int
    attributes: list[TestAttributeSchema]
    model_config = ConfigDict(from_attributes=True)

Repository类代码

class TestRepository:
    
    @staticmethod
    async def get(id: int) -> TestSchema:
        async with async_session_maker() as session:
            result = await session.execute(
                select(Test).where(Test.id == id)
            )
            data = result.scalars().one_or_none()
            if data:
                return TestSchema.model_validate(data)
            return None

    @staticmethod
    async def list() -> list[TestSchema]:
        async with async_session_maker() as session:
            result = await session.execute(select(Test))
            return [TestSchema.model_validate(i) for i in result.scalars().all()]

报错信息

pydantic_core._pydantic_core.ValidationError: 1 validation error for TestSchema
attributes.0.test_attribute.attributes
  Error extracting attribute: MissingGreenlet: greenlet_spawn has not been called; can't call await_only() here. Was IO attempted in an unexpected place? (Background on this error at: https://sqlalche.me/e/20/xd2s) [type=get_attribute_error, input_value=<src.db.test.Test object at 0x7fadc2b01b90>, input_type=Test]

list方法正常返回示例

[
  {
    "id": 1,
    "value": 1,
    "attributes": [
      {
        "test_attribute_id": 2,
        "test_value": 1,
        "test_attribute": {
          "id": 2,
          "value": 2,
          "attributes": [
            {
              "test_attribute_id": 3,
              "test_value": 1,
              "test_attribute": {
                "id": 3,
                "value": 3,
                "attributes": [
                  {
                    "test_attribute_id": 4,
                    "test_value": 1,
                    "test_attribute": {
                      "id": 4,
                      "value": 4,
                      "attributes": []
                    }
                  }
                ]
              }
            }
          ]
        }
      },
      {
        "test_attribute_id": 3,
        "test_value": 1,
        "test_attribute": {
          "id": 3,
          "value": 3,
          "attributes": [
            {
              "test_attribute_id": 4,
              "test_value": 1,
              "test_attribute": {
                "id": 4,
                "value": 4,
                "attributes": []
              }
            }
          ]
        }
      }
    ]
  },
  {
    "id": 2,
    "value": 2,
    "attributes": [
      {
        "test_attribute_id": 3,
        "test_value": 1,
        "test_attribute": {
          "id": 3,
          "value": 3,
          "attributes": [
            {
              "test_attribute_id": 4,
              "test_value": 1,
              "test_attribute": {
                "id": 4,
                "value": 4,
                "attributes": []
              }
            }
          ]
        }
      }
    ]
  },
  {
    "id": 3,
    "value": 3,
    "attributes": [
      {
        "test_attribute_id": 4,
        "test_value": 1,
        "test_attribute": {
          "id": 4,
          "value": 4,
          "attributes": []
        }
      }
    ]
  },
  {
    "id": 4,
    "value": 4,
    "attributes": []
  },
  {
    "id": 5,
    "value": 5,
    "attributes": []
  },
  {
    "id": 6,
    "value": 6,
    "attributes": []
  }
]

问题原因

核心差异在于异步会话内的数据加载时机:

  • list方法查询全部Test对象时,Test.attributes的lazy="selectin"会批量加载所有关联的TestAttribute,而TestAttribute.test_attribute的lazy="joined"会直接关联加载对应的Test对象,多层嵌套的关联数据在会话打开期间就被全部加载到内存,Pydantic验证时无需触发新查询。
  • get方法查询单条Test对象时,当Pydantic递归验证到test_attribute.attributes时,触发了Test.test_attributes的延迟加载(该关系未设置异步兼容的lazy策略,默认是同步的select),此时即使会话未关闭,异步环境下无greenlet上下文支持,直接抛出MissingGreenlet错误。

解决方法

方法1:修改模型的lazy加载策略

为Test.test_attributes设置异步兼容的lazy="selectin"策略,确保延迟加载在异步会话内可正常执行:

class Test(DeclarativeBase):
    # ... 其他字段不变
    test_attributes: Mapped[list["TestAttribute"]] = relationship(
        "TestAttribute",
        back_populates="test_attribute",
        primaryjoin="Test.id == TestAttribute.test_attribute_id",
        lazy="selectin"  # 添加这一行
    )

方法2:显式预加载所有嵌套关联

在get方法中使用SQLAlchemy的预加载API,一次性加载所有需要的嵌套关联,避免延迟加载:

from sqlalchemy.orm import selectinload, joinedload

class TestRepository:
    
    @staticmethod
    async def get(id: int) -> TestSchema | None:
        async with async_session_maker() as session:
            result = await session.execute(
                select(Test)
                .where(Test.id == id)
                .options(
                    # 预加载当前Test的attributes
                    selectinload(Test.attributes)
                    # 预加载每个attribute对应的test_attribute
                    .joinedload(TestAttribute.test_attribute)
                    # 预加载test_attribute的attributes
                    .selectinload(Test.attributes)
                    # 如需更深层关联,继续添加链式预加载
                )
            )
            data = result.scalars().one_or_none()
            return TestSchema.model_validate(data) if data else None

方法3:确保Pydantic验证在会话内完成(已满足,可配合其他方法使用)

当前get方法已经在async with会话块内执行model_validate,但需确保所有关联加载的触发都在会话关闭前,结合方法1或2即可解决问题。


内容的提问来源于stack exchange,提问作者Кирилл Сыроежкин

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 12:35:55