Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Typing fails when list of validators where one is custom and one is built in #1197

Open
superosku opened this issue Oct 31, 2023 · 5 comments
Labels
Typing Typing/stub/Mypy/PyRight related bugs.

Comments

@superosku
Copy link

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]
@Tinche
Copy link
Member

Tinche commented Nov 1, 2023

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.

@superosku
Copy link
Author

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?

@Tinche
Copy link
Member

Tinche commented Nov 6, 2023

It wouldn't be the end of the world, but I wish we could come up with a better solution.

@AdrianSosic
Copy link

Hi, just wanted to say that I'm running into the same issue, so: +1

@hynek hynek added the Typing Typing/stub/Mypy/PyRight related bugs. label Nov 17, 2023
@AdrianSosic
Copy link

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)])

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Typing Typing/stub/Mypy/PyRight related bugs.
Projects
None yet
Development

No branches or pull requests

4 participants