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

functools.wraps新增session参数时适配Strawberry报错求解

问题描述

我希望实现数据库会话自动注入的装饰器逻辑,最初的实现代码如下:

def need_session(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        session = SessionLocal()
        try:
            func(session, *args, **kwargs)
        finally:
            session.close()

我使用wraps函数是因为项目采用Strawberry框架开发GraphQL服务,该框架强依赖函数的参数类型注解。
按照上述逻辑,被装饰的原函数会被额外传入一个session参数,但实际运行时出现异常:使用该装饰器修饰Strawberry的Mutation字段函数启动服务时,抛出如下错误:

TypeError: Mutation fields cannot be resolved. Unexpected type '<class 'sqlalchemy.orm.session.Session'>'

之后我调整装饰器实现,改为将session通过关键字参数传入:

def need_session(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        session = SessionLocal()
        kwargs['session'] = session
        try:
            func(*args, **kwargs)
        finally:
            session.close()

同时将被装饰函数的参数列表修改为如下形式:

def some_func(some_arg: SomeClass, **kwargs):
    ...

此时运行服务又抛出新的错误:

strawberry.exceptions.MissingArgumentsAnnotationsError: Missing annotation for argument "kwargs" in field "login_user", did you forget to add it?

问题原因

出现这两个错误的核心原因是Strawberry在解析字段解析函数时,会完整读取函数签名和所有参数的类型注解,将所有公开参数识别为GraphQL字段的入参,两种写法分别触发了框架的校验规则:

  • 第一种写法中,session参数直接暴露在函数签名里,类型为SQLAlchemy的Session,Strawberry会尝试将其映射为GraphQL支持的输入类型,但Session不属于合法的GraphQL输入类型,因此抛出类型不识别错误。
  • 第二种写法中,使用了无类型注解的**kwargs参数,Strawberry要求解析函数的所有公开参数都必须有明确的类型注解,因此抛出缺少注解的错误。
正确实现方案

核心思路是在装饰器中主动修改包装函数的对外签名,将内部注入的session参数从公开签名中移除,让Strawberry无法感知到这个内部参数,就不会将其作为GraphQL入参做校验。可以借助Python标准库inspect模块实现:

from functools import wraps
from inspect import signature
# 从项目自身的数据库配置模块导入SessionLocal
from db import SessionLocal

def need_session(func):
    # 获取原函数的签名
    original_sig = signature(func)
    # 过滤掉名为session的参数,生成对外暴露的新签名
    public_params = [
        param for param_name, param in original_sig.parameters.items()
        if param_name != "session"
    ]
    public_sig = original_sig.replace(parameters=public_params)

    @wraps(func)
    def wrapper(*args, **kwargs):
        session = SessionLocal()
        try:
            # 执行原函数时注入session,必须return返回值否则GraphQL响应会为空
            return func(session=session, *args, **kwargs)
        finally:
            session.close()
    
    # 将包装函数的签名替换为过滤后的公开签名
    wrapper.__signature__ = public_sig
    return wrapper

使用时不需要写**kwargs,直接在被装饰的解析函数里正常声明session: Session参数即可:

import strawberry
from sqlalchemy.orm import Session
# 导入自定义的响应类型、数据模型
from schema.types import LoginResponse
from models import User

@strawberry.type
class Mutation:
    @strawberry.mutation
    @need_session
    def login_user(self, username: str, password: str, session: Session) -> LoginResponse:
        # 直接使用注入的session操作数据库即可
        user = session.query(User).filter(User.username == username).first()
        # 业务逻辑处理...
        return LoginResponse(success=True)

这种实现下Strawberry读取到的解析函数签名只包含username、password两个公开参数,不会感知到session的存在,既不会报类型映射错误,也不会报参数注解缺失错误。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 07:42:18