attrs
attrs copied to clipboard
Typing fails when list of validators where one is custom and one is built in
Hi!
I am trying to add multiple validators to a field, which works fine as long as they are both from the attrs library or both written by me. When I mix them up mypy gives me an error. Am I doing something wrong here or is this an issue in the library? Here is a snippet to reproduce the issue:
from typing import Any
from attr import validators, Attribute
import attr
def is_not_empty(instance: Any, attribute: "Attribute[str]", value: str) -> Any:
if not value.strip():
raise ValueError("Is not empty")
@attr.s
class MyClass:
thing1: str = attr.ib(validator=[validators.max_len(10), is_not_empty]) # Mypy complains from this
thing2: str = attr.ib(validator=[validators.max_len(10)]) # This is fine
thing3: str = attr.ib(validator=[is_not_empty]) # Fine as well
thing4: str = attr.ib(validator=[is_not_empty, is_not_empty]) # Even this is fine
This is the error I get from mypy:
snippet.py:14: error: Argument "validator" has incompatible type "list[function]"; expected "Callable[[Any, Attribute[<nothing>], <nothing>], Any] | Sequence[Callable[[Any, Attribute[<nothing>], <nothing>], Any]] | None" [arg-type]
It looks like Mypy type inference isn't strong enough.
reveal_type([validators.max_len(10), is_not_empty]) # Revealed type is "builtins.list[builtins.function]"
Funnily enough, if you hold its hand enough, it works:
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from attr import _ValidatorType
test: _ValidatorType = is_not_empty
reveal_type([validators.max_len(10), test]) # Revealed type is "builtins.list[def (Any, attr.Attribute[Any], Any) -> Any]"
I couldn't figure out an elegant way of making it work. I'd suggest just sticking a #type: ignore on that line, to be honest.
I did consider importing the _ValidatorType and that it might fix the issue. But I did not want to do it since it is marked as private (The _ prefix). What do you think about making it public for these types of scenarios?
It wouldn't be the end of the world, but I wish we could come up with a better solution.
Hi, just wanted to say that I'm running into the same issue, so: +1
I just noticed that this is not restricted to combinations of built-in and custom validators but also holds for lists of built-in validators. Both the following classes do the same job, but class B is flagged by mypy while A passes as expected:
from attrs import define, field
from attrs.validators import and_, instance_of, min_len
@define
class A:
name: str = field(validator=and_(instance_of(str), min_len(1)))
@define
class B:
name: str = field(validator=[instance_of(str), min_len(1)])