How do I annotate an `attrs` validator function without breaking it?

Viewed 53

Using the attrs library, I can define a validator for attribute values:

from attrs import define, field

def is_odd(inst, attr, value):
    if value % 2 == 1:
        return None

    raise ValueError("Only odd values allowed")

@define
class Foo:
  bar = field(validator=is_odd)

Foo(1)
Foo(2)

The Foo(1) instantiation succeeds and the Foo(2) instantiation fails with a ValueError, as intended.

Unsurprisingly, this code does not type-check (according to mypy --strict):

foo.py:3:1: error: Function is missing a type annotation  [no-untyped-def]
    def is_odd(inst, attr, value):
    ^
foo.py:11:3: error: Need type annotation for "bar"  [var-annotated]
      bar = field(validator=is_odd)
      ^
Found 2 errors in 1 file (checked 1 source file)

However, I don't see how to annotate it so that it does. A naive first attempt might look like:

from attrs import Attribute, define, field

def is_odd(inst: object, attr: Attribute, value: int) -> None:
    if value % 2 == 1:
        return None

    raise ValueError("Only odd values allowed")

@define
class Foo:
  bar: int = field(validator=is_odd)

Foo(1)
Foo(2)

However mypy --strict reports this error:

foo.py:3:32: error: Missing type parameters for generic type "Attribute"  [type-arg]

This can be fixed by supplying the missing type parameter:

from attrs import Attribute, define, field

def is_odd(inst: object, attr: Attribute[int], value: int) -> None:
    if value % 2 == 1:
        return None

    raise ValueError("Only odd values allowed")

@define
class Foo:
  bar: int = field(validator=is_odd)

Foo(1)
Foo(2)

This satisfies mypy:

Success: no issues found in 1 source file

However, the program is now broken at runtime:

Traceback (most recent call last):
  File "foo.py", line 3, in <module>
    def is_odd(inst: object, attr: Attribute[int], value: int) -> None:
TypeError: 'type' object is not subscriptable

How can the program be made correct at runtime and also type-check under mypy --strict (without just adding "ignore" comments to silence the error)?

1 Answers

As mentioned in this Github issue, quote the type annotation:

from attrs import Attribute, define, field


def is_odd(inst: object, attr: "Attribute[int]", value: int) -> None:
    if value % 2 == 1:
        return None

    raise ValueError("Only odd values allowed")


@define
class Foo:
    bar: int = field(validator=is_odd)


Foo(1)
Foo(2)

or use from __future__ import annotations:

from __future__ import annotations

from attrs import Attribute, define, field


def is_odd(inst: object, attr: Attribute[int], value: int) -> None:
    if value % 2 == 1:
        return None

    raise ValueError("Only odd values allowed")


@define
class Foo:
    bar: int = field(validator=is_odd)


Foo(1)
Foo(2)
Related