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

发布于 2025-01-11 05:58:29 字数 3557 浏览 0 评论 0原文

我有以下装饰器函数(来自 serialize_request 的简化版本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 和类似工具识别这些“新的”由装饰器设置的参数并使它们的行为就像直接在函数上设置参数一样。

I have the following decorator function (simplified version of serialize_request from api-client-pydantic) that is supposed to be used on a function that takes any number of pydantic models as parameters. It initializes those models with the arguments passed to it and then calls the wrapped function with these models as arguments.

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

By overwriting the signature, tools like ipython recognize the new arguments and show them in the pop up help. For example, with the below models and function:

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)

In PyCharm:
enter image description here

In pure ipython:
enter image description here

But pyright doesn't recognize them and shows an error:

enter image description here

I don't know what PyCharm uses internally, but it also doesn't recognize the new arguments. It doesn't show an error though, it just accepts anything as valid arguments, also none at all:

enter image description here

Now, my question is if there is some way to make pyright/PyCharm and similar tools recognize those "new" arguments set by the decorator and make them behave as though the parameters were set as such on the function directly.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

飘落散花 2025-01-18 05:58:29

不,Python 当前的类型系统没有这样的方法来做到这一点。

这些是您需要使用的成分:

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

No, there is no such way to do this with Python's current typing system.

These are the ingredients that you would need to use:

But what you describe is far beyond their capabilities.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文