How to annotate attrs field with validator? - python-3.x

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. field is considerably complicated, your field returns a str thanks to this overload:
... # 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: ...
It basically says "when validators is a [sequence of or one of] validators working on some type T, then field returns T".
So, you pass a validator that works on str, and thus field type is str as well. NewType("UserID", str) is not a subtype of str, so this assignment fails. You have two major options:
cast to desired type:
from typing import cast
...
#frozen
class Order:
id: UserId = cast(str, field(validator=validators.matches_re("^\d+$")))
create your own validator. You don't need to replicate the logic, only change the signature and call original implementation with type: ignore or casts.
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))
... and use it in your class instead of validators.matches_re. The signature above is stolen from stubs with AnyStr replaced with str, because you don't allow bytes anyway.
I'd recommend the first variant, because another solution is just more boilerplate with the same cast as a result, it gives you no more safety. However, it may be viable if you have many fields with this validator.

Related

How to receive multiple return values with type hint syntax in one line in python3.8

I have a method returning multiple values in python3.8:
I read here : How to annotate types of multiple return values? that we can mention multiple return types as Tuple[A,B,C] .
def _calculate_lending_share(user_id: str, squad_id: str) -> Tuple[List[int], Dict[str, Any]]:
...
But how do i accept the multiple values with type hint on the same line:
tokens: List[int], token_borrowed_map: Dict[str, Any] = _calculate_lending_share(user_id, squad_id)
The error is :
SyntaxError: invalid syntax
As of now i have identified that we have to do something like this.
tokens: List[int] = []
token_borrowed_map: Dict[str, Any] = {}
tokens, token_borrowed_map = _calculate_lending_share(user_id, squad_id)
but providing types on the same line is not supported as of now.

Python3 - function signature indicates a return value, but without specifying the type

I would like to define a function such that the return value has a different type in different occasions.
Still, I would like to include in the function signature an indication that it returns something.
I know one way to do this is to use Union, for example:
from typing import Union
def f(x: int) -> Union[str, int]:
return x if x > 0 else "this is zero"
But in my case, I don't have the list of possible output types at hand.
I tried to use:
def f(x: int) -> object:
return some_other_func(x)
The problem is, that now when I try to use this function the IDE tells me I have a typing error:
y: SomeClass = f(42)
Error: Expected type 'SomeClass', got 'object' instead
So - how can I indicate in the function signature that f is returning some value, without indicating the type of the value?
From the typing documentation, it is possible to use Any, for example:
from typing import Any
def f(x: int) -> Any:
return some_other_func(x)

python 3.7.0 mypy 0.641 extending pythons' list with UserList?

I am trying to extend pythons’ list with some custom methods, for that I
am creating an class inheriting from UserList.
I am not sure what is the right way and I would like to get mypy play
nicely with UserList.
I consulted cpython UserList docs and searched inside mypy for
UserList but couldn’t find anything.
Using:
mypy 0.641
python 3.7.0
This is a minimal example of what I am trying to achive
from collections import UserList
from typing import List, Optional, Union
class A:
...
class B:
...
class C:
...
TMessage = Union[A, B, C]
class MyList(UserList):
"""Minimal example"""
def __init__(self, data: Optional[List[TMessage]] = None) -> None:
self.data: List[TMessage] = []
if data:
self.data = data[:]
def get_last(self) -> TMessage:
return self.data[-1]
# other methods to be added ...
some_data = [A(), B(), C(), C(), B(), A()]
my_list_a = MyList(some_data)
my_list_b = MyList(some_data)
my_list_b = my_list_a[3:]
Mypy complains as follows
~/tmp ❯❯❯ mypy mypy_userlist.py
mypy_userlist.py:34: error: Argument 1 to "MyList" has incompatible type "List[object]"; expected "Optional[List[Union[A, B, C]]]"
mypy_userlist.py:35: error: Argument 1 to "MyList" has incompatible type "List[object]"; expected "Optional[List[Union[A, B, C]]]"
mypy_userlist.py:37: error: Incompatible types in assignment (expression has type "MutableSequence[Any]", variable has type "MyList")
I could add # type: ignore to the conflicting lines but I would like
to avoid that.
What is the right way to extend the python’s list with custom methods
and get mypy happy?
I'm fairly new at MyPy but I think you have two issues. The first is that lists are mutable, so although your list object some_data satisfies the required structure in your code, there's no reason that an object, not of type A, B or C couldn't be added to it later, meaning at compile tile, Mypy can't ensure that
my_list_a = MyList(some_data)
is a valid assignment. (have a look at the common issues section of the Mypy docs here for more discussion)
You can fix this by explicitly annotating some_data:
some_data : List[TMessage] = [A(), B(), C(), C(), B(), A()]
The second problem will pop up when you fix this, when you try and assign your two lists using slicing. MyPy won't know what your slice function will return and will complain about incompatible types.
To fix this, you can explicitly implement the slice functionality into your class.
def __getitem__(self, slice_indices) -> 'MyList':
return self.data[slice_indices]

How to specify the return type based on function's argument?

Currently I've got this code:
class Value:
def __init__(self, data: Any):
self.data = data
# ...and much more than this
def convert_value(self, value_type: Type['Value']) -> 'Value':
return value_type(self.data)
class BooleanValue(Value):
pass
The convert_value method converts an instance of Value into an instance of value_type passed as argument. For example:
value = Value(123)
new_value = value.convert_value(BooleanValue)
In this case new_value is of type BooleanValue. I don't think it's necessary to do like this (there should be a better way):
new_value: BooleanValue = value.convert_value(BooleanValue)
Currently PyCharm understands that I return a Value instance, but I'd like it to understand from typing that a BooleanValue is returned.
I tried to do it this way:
T = TypeVar('T', 'Value')
class Value:
# Other methods
def convert_value(self, value_type: Type[T]) -> T:
return value_type(self.data)
but PyCharm claims that value_type is not callable.
Question: how to make PyCharm understand that the object returned from this method has type value_type?
Your type hints are correct, it's a bug in PyCharm

String literal not interpreted as type String

I'm trying to implement a simple subclass of Iterable[String], and I'm encountering something that seems a bit strange to me. My code is as follows:
class ItString[String] extends Iterable[String]{
override def iterator: Iterator[String] = new Iterator[String] {
def hasNext = true
def next = "/"
}
}
My IDE complains that it's expecting a String but it's actually of type String, and when trying to compile, it further reveals that the problem is that it requires String but that the literal is actually of type java.lang.String. Now, I can fix this by changing it to "/".asInstanceOf[String], but I'd like to know why Scala isn't recognising the type correctly.
Since I can get the following to compile:
class ItString extends Iterable[String] {
override def iterator: Iterator[String] = new Iterator[String] {
def hasNext = true
def next = "/"
}
}
I can deduce that your problem is with writing:
class ItString[String] extends ...
What's going on is that this occurrence of String doesn't refer to the type java.lang.String, but it is instead an unknown parameterised type (like T or A). So, when you write [String], this means some unknown type that will be determined by the implementing class rather than the String type that you intended.

Resources