使pyright/PyCharm识别由装饰器设置的参数

6ss1mwsb  于 2022-11-08  发布在  PyCharm
关注(0)|答案(1)|浏览(146)

我有下面的装饰器函数(serialize_request的简化版本,来自api-client-pydantic),它应该被用在一个函数上,这个函数接受任意数量的pydantic模型作为参数,它用传递给它的参数初始化这些模型,然后用这些模型作为参数调用 Package 函数。

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中:x1c 0d1x
在纯ipython中:

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

我不知道PyCharm在内部使用了什么,但它也不识别新的参数。虽然它没有显示错误,但它只接受任何有效的参数,甚至一个都不接受:

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

kupeojn6

kupeojn61#

不,在Python当前的类型系统中没有这样的方法。
以下是您需要使用的成分:

  • https://mypy.readthedocs.io/en/stable/cheat_sheet_py3.html#decorators
  • https://peps.python.org/pep-0612/#parameter-specification-variables

但是你所描述的远远超出了他们的能力范围。

相关问题