I am having trouble annotating attrs class attribute.
I am using NewType for defining new UserId type and attrs frozen classes.
This is code where mypy doesn't complain and everything is alright:
from typing import NewType
from attr import frozen, field
UserId = NewType("UserId", str)
@frozen
class Order:
id: UserId = field()
mypy does not have any issues when checking this code. The problem appears after using validators from 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 now complains about incorrect types:
project/test_annotation.py:10: error: Incompatible types in assignment (expression has type "str", variable has type "UserId") [assignment]
Found 1 error in 1 file (checked 1 source file)
I don't understand how field() returns string type right now.
Can somebody explain that? Also, how can we work around this problem?
env:
Python 3.10.6
attrs==22.1.0
cattrs==22.2.0
To make it happy,
cast.fieldis considerably complicated, yourfieldreturns astrthanks to this overload:It basically says "when
validatorsis a [sequence of or one of] validators working on some typeT, thenfieldreturnsT".So, you pass a validator that works on
str, and thusfieldtype isstras well.NewType("UserID", str)is not a subtype ofstr, so this assignment fails. You have two major options:cast to desired type:
create your own validator. You don't need to replicate the logic, only change the signature and call original implementation with
type: ignoreor casts.... and use it in your class instead of
validators.matches_re. The signature above is stolen from stubs withAnyStrreplaced withstr, because you don't allowbytesanyway.I'd recommend the first variant, because another solution is just more boilerplate with the same
castas a result, it gives you no more safety. However, it may be viable if you have many fields with this validator.