blob: e42a9de8a3401548530cb69169a6b5b999af61e6 [file] [edit]
# SPDX-License-Identifier: MIT
"""
Typing examples that rely on Pyrefly's advanced attrs integration.
"""
from __future__ import annotations
import re
from typing import Any
import attrs
@attrs.define
class C:
a: int = attrs.field()
cc = C(1)
C(a=1)
@attrs.define
class D:
x: list[int] = attrs.field()
li: list[int] = D([1]).x
@attrs.define
class E:
y: "list[int]" = attrs.field()
li = E([1]).y
@attrs.define
class F:
z: Any = attrs.field()
# Inheritance --
@attrs.define
class GG(D):
y: str = attrs.field()
GG(x=[1], y="foo")
@attrs.define
class HH(D, E):
z: float = attrs.field()
HH(x=[1], y=[], z=1.1)
# Exceptions
@attrs.define
class Error(Exception):
x: int = attrs.field()
try:
raise Error(1)
except Error as e:
e.x
e.args
str(e)
@attrs.define(auto_exc=False)
class Error2(Exception):
x: int
try:
raise Error2(1)
except Error as e:
e.x
e.args
str(e)
# Field aliases
@attrs.define
class AliasExample:
without_alias: int
_with_alias: int = attrs.field(alias="_with_alias")
attrs.fields(AliasExample).without_alias.alias
attrs.fields(AliasExample)._with_alias.alias
# Converters
@attrs.define
class ConvCOptional:
x: int | None = attrs.field(converter=attrs.converters.optional(int))
ConvCOptional(1)
ConvCOptional(None)
@attrs.define
class ConvCPipe:
x: str = attrs.field(converter=attrs.converters.pipe(int, str))
ConvCPipe(3.4)
ConvCPipe("09")
ConvCPipe({}) # XXX pipe makes field Any
@attrs.define
class ConvCDefaultIfNone:
x: int = attrs.field(converter=attrs.converters.default_if_none(42))
ConvCDefaultIfNone(1)
ConvCDefaultIfNone(None)
ConvCDefaultIfNone({}) # XXX: default_if_none makes field any
@attrs.define
class ConvCToBool:
x: int = attrs.field(converter=attrs.converters.to_bool)
ConvCToBool(1)
ConvCToBool(True)
ConvCToBool("on")
ConvCToBool("yes")
ConvCToBool(0)
ConvCToBool(False)
ConvCToBool("n")
@attrs.define
class DecoratorConverter:
x: int = attrs.field()
@x.converter
def _to_int(self, val: str | float) -> int:
return int(val)
# XXX: fails
# DecoratorConverter("foo")
# Validators
@attrs.define
class Validated:
a: list[C] = attrs.field(
validator=attrs.validators.deep_iterable(
attrs.validators.instance_of(C), attrs.validators.instance_of(list)
),
)
a2: tuple[C] = attrs.field(
validator=attrs.validators.deep_iterable(
attrs.validators.instance_of(C),
attrs.validators.instance_of(tuple),
),
)
a3: tuple[C] = attrs.field(
validator=attrs.validators.deep_iterable(
[attrs.validators.instance_of(C)],
[attrs.validators.instance_of(tuple)],
),
)
b: list[C] = attrs.field(
validator=attrs.validators.deep_iterable(
attrs.validators.instance_of(C)
),
)
c: dict[C, D] = attrs.field(
validator=attrs.validators.deep_mapping(
attrs.validators.instance_of(C),
attrs.validators.instance_of(D),
attrs.validators.instance_of(dict),
),
)
d: dict[C, D] = attrs.field(
validator=attrs.validators.deep_mapping(
attrs.validators.instance_of(C), attrs.validators.instance_of(D)
),
)
d2: dict[C, D] = attrs.field(
validator=attrs.validators.deep_mapping(
attrs.validators.instance_of(C)
),
)
d3: dict[C, D] = attrs.field(
validator=attrs.validators.deep_mapping(
value_validator=attrs.validators.instance_of(C)
),
)
d4: dict[C, D] = attrs.field(
validator=attrs.validators.deep_mapping(
key_validator=[attrs.validators.instance_of(C)],
value_validator=[attrs.validators.instance_of(C)],
mapping_validator=[attrs.validators.instance_of(dict)],
),
)
e: str = attrs.field(
validator=attrs.validators.matches_re(re.compile(r"foo"))
)
f: str = attrs.field(
validator=attrs.validators.matches_re(r"foo", flags=42, func=re.search)
)
# Test different forms of instance_of
g: int = attrs.field(validator=attrs.validators.instance_of(int))
h: int = attrs.field(validator=attrs.validators.instance_of((int,)))
j: int | str = attrs.field(
validator=attrs.validators.instance_of((int, str))
)
k: int | str | C = attrs.field(
validator=attrs.validators.instance_of((int, C, str))
)
kk: int | str | C = attrs.field(
validator=attrs.validators.instance_of(int | C | str)
)
l: Any = attrs.field(
validator=attrs.validators.not_(attrs.validators.in_("abc"))
)
m: Any = attrs.field(
validator=attrs.validators.not_(
attrs.validators.in_("abc"), exc_types=ValueError
)
)
n: Any = attrs.field(
validator=attrs.validators.not_(
attrs.validators.in_("abc"), exc_types=(ValueError,)
)
)
o: Any = attrs.field(
validator=attrs.validators.not_(
attrs.validators.in_("abc"), msg="spam"
)
)
p: Any = attrs.field(
validator=attrs.validators.not_(attrs.validators.in_("abc"), msg=None)
)
q: Any = attrs.field(
validator=attrs.validators.optional(attrs.validators.instance_of(C))
)
r: Any = attrs.field(
validator=attrs.validators.optional([attrs.validators.instance_of(C)])
)
s: Any = attrs.field(
validator=attrs.validators.optional((attrs.validators.instance_of(C),))
)
@attrs.define
class Validated2:
num: int = attrs.field(validator=attrs.validators.ge(0))
with attrs.validators.disabled():
Validated2(num=-1)
try:
attrs.validators.set_disabled(True)
Validated2(num=-1)
finally:
attrs.validators.set_disabled(False)
# Custom repr()
@attrs.define
class WithCustomRepr:
a: int = attrs.field(repr=True)
b: str = attrs.field(repr=False)
c: str = attrs.field(repr=lambda value: "c is for cookie")
d: bool = attrs.field(repr=str)
# Check some of our own types
@attrs.define(eq=True, order=False)
class OrderFlags:
a: int = attrs.field(eq=False, order=False)
b: int = attrs.field(eq=True, order=True)
# on_setattr hooks
@attrs.define(on_setattr=attrs.setters.validate)
class ValidatedSetter:
a: int
b: str = attrs.field(on_setattr=attrs.setters.NO_OP)
c: bool = attrs.field(on_setattr=attrs.setters.frozen)
d: int = attrs.field(
converter=int,
on_setattr=[attrs.setters.convert, attrs.setters.validate],
)
e: bool = attrs.field(
converter=attrs.converters.to_bool,
on_setattr=attrs.setters.pipe(
attrs.setters.convert, attrs.setters.validate
),
)
vs = ValidatedSetter(1, "2", True, 4, False)
vs.d = "2"
vs.e = "yes"
vs.e = "foo" # XXX: should only allow the literals we know
# field_transformer
def ft_hook(
cls: type, attribs: list[attrs.Attribute]
) -> list[attrs.Attribute]:
return attribs
@attrs.define(field_transformer=ft_hook)
class TransformedAttrs:
x: int
# Auto-detect
@attrs.define(auto_detect=True)
class AutoDetect:
x: int
def __init__(self, x: int):
self.x = x
@attrs.define(order=True)
class NGClass:
x: int = attrs.field(default=42)
ngc = NGClass(1)
@attrs.frozen(str=True)
class NGFrozen:
x: int
attrs.fields(NGFrozen).x.evolve(eq=False)
a = attrs.fields(NGFrozen).x
a.evolve(repr=False)
@attrs.define
class FactoryTest:
a: list[int] = attrs.field(default=attrs.Factory(list))
b: list[Any] = attrs.field(default=attrs.Factory(list, False))
c: list[int] = attrs.field(default=attrs.Factory((lambda s: s.a), True))
attrs.asdict(FactoryTest(), tuple_keys=True)
# Check match_args stub
@attrs.define(match_args=False)
class MatchArgs:
a: int = attrs.field()
b: int = attrs.field()
attrs.asdict(FactoryTest())
attrs.asdict(FactoryTest(), retain_collection_types=False)
foo = object
if attrs.has(foo) or attrs.has(foo):
foo.__attrs_attrs__
@attrs.define(unsafe_hash=True)
class Hashable:
pass
def test(cls: type) -> None:
if attrs.has(cls):
attrs.resolve_types(cls)