0

我有以下装饰器函数(serialize_requestfrom的简化版本api-client-pydantic),它应该用于将任意数量的pydantic模型作为参数的函数。它使用传递给它的参数初始化这些模型,然后使用这些模型作为参数调用包装函数。

def serialize(extra_kwargs: Dict[str, Any] = None) -> Callable:
    extra_kw = extra_kwargs or {"by_alias": True, "exclude_none": True}

    def decorator(func: Callable) -> Callable:
        map_schemas = {}
        map_params = {}
        parameters = []

        for arg_name, arg_type in get_type_hints(func).items():
            if arg_name == "return":
                continue
            map_schemas[arg_name] = arg_type
            if inspect.isclass(arg_type) and issubclass(arg_type, BaseModel):
                # the model's signature contains only aliases
                arg_fields = list(arg_type.__fields__.keys())
                arg_params = inspect.signature(arg_type).parameters
                map_params[arg_name] = set(list(arg_params.keys()) + arg_fields)
                parameters.extend(list(arg_params.values()))

        @wraps(func)
        def wrap(*args, **kwargs):
            if map_schemas:
                data, origin_kwargs = {}, {}
                for arg_name, arg_type in map_schemas.items():
                    if inspect.isclass(arg_type) and issubclass(arg_type, BaseModel):
                        arg_kwargs = {
                            k: v for k, v in kwargs.items() if k in map_params[arg_name]
                        }
                        data[arg_name] = parse_obj_as(arg_type, arg_kwargs).dict(
                            **extra_kw
                        )
                    else:
                        val = kwargs.get(arg_name)
                        if val is not None:
                            origin_kwargs[arg_name] = val
                new_kwargs = {**origin_kwargs, **data} or kwargs
                return func(*args, **new_kwargs)
            return func(*args, **kwargs)

        # Override signature
        if parameters:
            sig = inspect.signature(func)
            _self_param = sig.parameters.get("self")
            self_param = [_self_param] if _self_param else []
            sig = sig.replace(parameters=tuple(self_param + parameters))
            wrap.__signature__ = sig  # type: ignore

        return wrap

    return decorator

通过覆盖签名,像 ipython 这样的工具可以识别新参数并在弹出帮助中显示它们。例如,具有以下模型和功能:

class ModelA(BaseModel):
    a: str
    b: int


class ModelB(BaseModel):
    one: float
    two: Optional[str] = None


@serialize()
def foo(model_a: ModelA, model_b: ModelB):
    print(model_a)
    print(model_b)

在 PyCharm 中: 在此处输入图像描述

在纯 ipython 中: 在此处输入图像描述

但 pyright 无法识别它们并显示错误:

在此处输入图像描述

我不知道 PyCharm 在内部使用什么,但它也无法识别新参数。但它没有显示错误,它只接受任何有效参数,也没有任何参数:

在此处输入图像描述

现在,我的问题是,是否有某种方法可以让 pyright/PyCharm 和类似工具识别装饰器设置的那些“新”参数,并使它们表现得好像参数是直接在函数上设置的一样。

4

0 回答 0