如何使用验证器注释attrs字段?

问题描述 投票:0回答:1

我在注释 attrs 类属性时遇到问题。

我正在使用 NewType 来定义新的 UserId 类型和 attrs 冻结类。

这是 mypy 不会抱怨并且一切正常的代码:

from typing import NewType
from attr import frozen, field


UserId = NewType("UserId", str)


@frozen
class Order:
    id: UserId = field()

mypy 检查此代码时没有任何问题。 使用 attrs 中的验证器后出现问题。

from typing import NewType
from attr import frozen, field, validators


UserId = NewType("UserId", str)


@frozen
class Order:
    id: UserId = field(validator=validators.matches_re("^\d+$"))

mypy 现在抱怨类型不正确:

project/test_annotation.py:10:错误:赋值中的类型不兼容 (表达式的类型为“str”,变量的类型为“UserId”)[赋值]

在 1 个文件中发现 1 个错误(检查了 1 个源文件)

我现在不明白 field() 如何返回字符串类型。

有人可以解释一下吗?另外,我们如何解决这个问题?

环境:

Python 3.10.6

属性==22.1.0

cattrs==22.2.0

python mypy python-typing python-attrs
1个回答
1
投票

让它快乐,

cast
field
相当复杂,由于
这个重载
,你的
field
返回了str

... # other overloads
# This form catches an explicit None or no default and infers the type from the
# other arguments.
@overload
def field(
    *,
    default: None = ...,
    validator: Optional[_ValidatorArgType[_T]] = ...,
    repr: _ReprArgType = ...,
    hash: Optional[bool] = ...,
    init: bool = ...,
    metadata: Optional[Mapping[Any, Any]] = ...,
    converter: Optional[_ConverterType] = ...,
    factory: Optional[Callable[[], _T]] = ...,
    kw_only: bool = ...,
    eq: Optional[_EqOrderType] = ...,
    order: Optional[_EqOrderType] = ...,
    on_setattr: Optional[_OnSetAttrArgType] = ...,
    alias: Optional[str] = ...,
) -> _T: ...

它基本上是说“当

validators
是处理某种类型
T
的验证器[序列或其中之一]时,那么
field
返回
T
”。

因此,您传递了一个适用于

str
的验证器,因此
field
类型也是
str
NewType("UserID", str)
不是
str
的子类型,因此此分配失败。您有两个主要选择:

  • 转换为所需类型:

    from typing import cast
    
    ...
    @frozen
    class Order:
         id: UserId = cast(str, field(validator=validators.matches_re("^\d+$")))
    
  • 创建您自己的验证器。您不需要复制逻辑,只需更改签名并使用

    type: ignore
    或强制转换调用原始实现。

    from typing import TYPE_CHECKING, Callable, Match, Optional, Pattern, Union, cast
    if TYPE_CHECKING:
         from attr import _ValidatorType
    
    def userid_matches_re(
         regex: Union[Pattern[str], str],
         flags: int = ...,
         func: Optional[
             Callable[[str, str, int], Optional[Match[str]]]
         ] = ...,
    ) -> '_ValidatorType[UserID]':
         return cast('_ValidatorType[UserID]', validators.matches_re(regex, flags, func))
    

    ...并在课堂上使用它而不是

    validators.matches_re
    。上面的签名是从 stubs 盗来的,其中
    AnyStr
    替换为
    str
    ,因为无论如何你都不允许
    bytes

我推荐第一个变体,因为另一种解决方案只是更多具有相同

cast
的样板,因此,它不会给你带来更多的安全性。但是,如果您有很多字段使用此验证器,那么它可能是可行的。

© www.soinside.com 2019 - 2024. All rights reserved.