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

使用SQLAlchemy 2.0调用.values().returning()时触发NotImplementedError

SQLAlchemy 2.0 + FastAPI 更新操作抛出NotImplementedError问题

问题背景

刚接触Web开发几个月,当前基于FastAPI开发后端,使用SQLAlchemy 2.0操作数据库时,调用update_member函数更新数据时,在赋值result的代码行抛出NotImplementedError。

相关代码与信息

数据库表字段定义

models.Member表中first_name字段定义:

first_name = Column(String, nullable=False)

请求体patch_data

已通过print确认键值对合法,其他字段值为None:

patch_data = {"first_name": "test" ...}

出错的update_member函数

async def update_member(db: AsyncSession,
                        short_uuid: str,
                        patch_data: MemberSelfPatch | MemberMasterPatch,
                        response_schema: Type[S]) -> S:
    async with db.begin():
        result = (await db.execute(
            update(models.Member)
            .where(models.Member.short_uuid == short_uuid)
            .values(patch_data.dict())
            .returning('*')
        )).fetchone()

可正常运行的对比函数

create_member函数

async def create_member(db: AsyncSession, data: MemberMasterCreate) -> MemberMasterView:
    uuid = uuid4()
    async with db.begin():
        db.add_all([
            Member(uuid=str(uuid), **data.dict()),
        ])
    return await get_member(db=db, uuid=uuid, response_schema=MemberMasterView)

get_member_by_short_uuid函数

async def get_member_by_short_uuid(db: AsyncSession, short_uuid: str, response_schema: Type[S]) -> S:    
    result = (await db.execute(
        select(models.Member)
        .where(models.Member.short_uuid == short_uuid)
    )).fetchone()

错误堆栈信息

INFO:     127.0.0.1:46434 - "PATCH /members/BqUoAGdN HTTP/1.1" 500 Internal Server Error
ERROR:    Exception in ASGI application
Traceback (most recent call last):
File "/code/./tmeit_backend/deps.py", line 86, in __call__
  yield member
File "/code/.venv/lib/python3.10/site-packages/fastapi/middleware/asyncexitstack.py", line 21, in __call__
  raise e
File "/code/.venv/lib/python3.10/site-packages/fastapi/middleware/asyncexitstack.py", line 18, in __call__
  await self.app(scope, receive, send)
File "/code/.venv/lib/python3.10/site-packages/starlette/routing.py", line 680, in __call__
  await route.handle(scope, receive, send)
File "/code/.venv/lib/python3.10/site-packages/starlette/routing.py", line 275, in handle
  await self.app(scope, receive, send)
File "/code/.venv/lib/python3.10/site-packages/starlette/routing.py", line 65, in app
  response = await func(request)
File "/code/.venv/lib/python3.10/site-packages/fastapi/routing.py", line 231, in app
  raw_response = await run_endpoint_function(
File "/code/.venv/lib/python3.10/site-packages/fastapi/routing.py", line 160, in run_endpoint_function
  return await dependant.call(**values)
File "/code/./tmeit_backend/routers/members.py", line 123, in patch_member
  member = await update_member(db=db, short_uuid=short_uuid, patch_data=validated_data, response_schema=response_schema)
File "/code/./tmeit_backend/crud/members.py", line 104, in update_member
  result = (await db.execute(
File "/code/.venv/lib/python3.10/site-packages/sqlalchemy/ext/asyncio/session.py", line 312, in execute
  result = await greenlet_spawn(
File "/code/.venv/lib/python3.10/site-packages/sqlalchemy/util/_concurrency_py3k.py", line 154, in greenlet_spawn
  result = context.switch(*args, **kwargs)
File "/code/.venv/lib/python3.10/site-packages/sqlalchemy/orm/session.py", line 2081, in execute
  return self._execute_internal(
File "/code/.venv/lib/python3.10/site-packages/sqlalchemy/orm/session.py", line 1976, in _execute_internal
  result: Result[Any] = compile_state_cls.orm_execute_statement(
File "/code/.venv/lib/python3.10/site-packages/sqlalchemy/orm/bulk_persistence.py", line 1522, in orm_execute_statement
  return super().orm_execute_statement(
File "/code/.venv/lib/python3.10/site-packages/sqlalchemy/orm/context.py", line 250, in orm_execute_statement
  result = conn.execute(
File "/code/.venv/lib/python3.10/site-packages/sqlalchemy/engine/base.py", line 1392, in execute
  return meth(
File "/code/.venv/lib/python3.10/site-packages/sqlalchemy/sql/elements.py", line 487, in _execute_on_connection
  return connection._execute_clauseelement(
File "/code/.venv/lib/python3.10/site-packages/sqlalchemy/engine/base.py", line 1608, in _execute_clauseelement
  compiled_sql, extracted_params, cache_hit = elem._compile_w_cache(
File "/code/.venv/lib/python3.10/site-packages/sqlalchemy/sql/elements.py", line 652, in _compile_w_cache
  compiled_sql = self._compiler(
File "/code/.venv/lib/python3.10/site-packages/sqlalchemy/sql/elements.py", line 288, in _compiler
  return dialect.statement_compiler(dialect, self, **kw)
File "/code/.venv/lib/python3.10/site-packages/sqlalchemy/sql/compiler.py", line 1075, in __init__
  Compiled.__init__(self, dialect, statement, **kwargs)
File "/code/.venv/lib/python3.10/site-packages/sqlalchemy/sql/compiler.py", line 639, in __init__
  self.string = self.process(self.statement, **compile_kwargs)
File "/code/.venv/lib/python3.10/site-packages/sqlalchemy/sql/compiler.py", line 671, in process
  return obj._compiler_dispatch(self, **kwargs)
File "/code/.venv/lib/python3.10/site-packages/sqlalchemy/sql/annotation.py", line 359, in _compiler_dispatch
  return self.__element.__class__._compiler_dispatch(
File "/code/.venv/lib/python3.10/site-packages/sqlalchemy/sql/visitors.py", line 143, in _compiler_dispatch
  return meth(self, **kw)  # type: ignore  # noqa: E501
File "/code/.venv/lib/python3.10/site-packages/sqlalchemy/sql/compiler.py", line 5009, in visit_update
  compile_state = update_stmt._compile_state_factory(
File "/code/.venv/lib/python3.10/site-packages/sqlalchemy/sql/base.py", line 638, in create_for_statement
  return klass.create_for_statement(statement, compiler, **kw)
File "/code/.venv/lib/python3.10/site-packages/sqlalchemy/orm/bulk_persistence.py", line 1336, in create_for_statement
  self._setup_for_orm_update(statement, compiler)
File "/code/.venv/lib/python3.10/site-packages/sqlalchemy/orm/bulk_persistence.py", line 1423, in _setup_for_orm_update
  new_stmt = self._setup_orm_returning(
File "/code/.venv/lib/python3.10/site-packages/sqlalchemy/orm/bulk_persistence.py", line 493, in _setup_orm_returning
  fsc.setup_dml_returning_compile_state(dml_mapper)
File "/code/.venv/lib/python3.10/site-packages/sqlalchemy/orm/context.py", line 749, in setup_dml_returning_compile_state
  entity.setup_dml_returning_compile_state(self, adapter)
File "/code/.venv/lib/python3.10/site-packages/sqlalchemy/orm/context.py", line 2394, in setup_dml_returning_compile_state
  raise NotImplementedError()
     NotImplementedError

问题原因及解决方法

这个错误源于SQLAlchemy ORM模式下,批量更新构造器配合.returning('*')的用法存在限制:要么是数据库方言(如SQLite)对DML RETURNING语法的ORM映射支持不足,要么是ORM本身不支持直接返回完整实例。

方案1:改用先查询再更新的ORM常规流程(推荐)

先查询到目标实例,修改属性后提交并刷新,确保获取最新数据:

async def update_member(db: AsyncSession,
                        short_uuid: str,
                        patch_data: MemberSelfPatch | MemberMasterPatch,
                        response_schema: Type[S]) -> S:
    async with db.begin():
        # 查询目标实例
        member = (await db.execute(
            select(models.Member).where(models.Member.short_uuid == short_uuid)
        )).scalar_one()
        # 仅更新请求中提供的非空字段
        update_data = patch_data.dict(exclude_unset=True)
        for key, value in update_data.items():
            if value is not None:
                setattr(member, key, value)
        # 提交并刷新实例
        await db.commit()
        await db.refresh(member)
    return response_schema.from_orm(member)

方案2:调整.returning用法,指定具体返回字段

如果坚持用批量更新构造器,不要用returning('*'),明确指定需要返回的字段后手动映射到响应模型:

async def update_member(db: AsyncSession,
                        short_uuid: str,
                        patch_data: MemberSelfPatch | MemberMasterPatch,
                        response_schema: Type[S]) -> S:
    async with db.begin():
        result = (await db.execute(
            update(models.Member)
            .where(models.Member.short_uuid == short_uuid)
            .values(patch_data.dict(exclude_unset=True))
            # 明确指定返回字段
            .returning(models.Member.id, models.Member.first_name, models.Member.short_uuid)
        )).fetchone()
        if not result:
            raise HTTPException(status_code=404, detail="Member not found")
        return response_schema(**result._asdict())

方案3:检查数据库方言支持

如果使用SQLite,需确保版本在3.35.0及以上(该版本才支持RETURNING语法),且SQLAlchemy对其RETURNING的ORM映射支持有限;建议切换到PostgreSQL等对RETURNING语法支持更完善的数据库。


内容的提问来源于stack exchange,提问作者acke

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 00:06:59