476 lines
8.8 KiB
Python
476 lines
8.8 KiB
Python
# SPDX-License-Identifier: MIT
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
|
|
from typing import Any, Dict, List, Tuple
|
|
|
|
import attr
|
|
import attrs
|
|
|
|
|
|
# Typing via "type" Argument ---
|
|
|
|
|
|
@attr.s
|
|
class C:
|
|
a = attr.ib(type=int)
|
|
|
|
|
|
c = C(1)
|
|
C(a=1)
|
|
|
|
|
|
@attr.s
|
|
class D:
|
|
x = attr.ib(type=List[int])
|
|
|
|
|
|
@attr.s
|
|
class E:
|
|
y = attr.ib(type="List[int]")
|
|
|
|
|
|
@attr.s
|
|
class F:
|
|
z = attr.ib(type=Any)
|
|
|
|
|
|
# Typing via Annotations ---
|
|
|
|
|
|
@attr.s
|
|
class CC:
|
|
a: int = attr.ib()
|
|
|
|
|
|
cc = CC(1)
|
|
CC(a=1)
|
|
|
|
|
|
@attr.s
|
|
class DD:
|
|
x: list[int] = attr.ib()
|
|
|
|
|
|
@attr.s
|
|
class EE:
|
|
y: "list[int]" = attr.ib()
|
|
|
|
|
|
@attr.s
|
|
class FF:
|
|
z: Any = attr.ib()
|
|
|
|
|
|
@attrs.define
|
|
class FFF:
|
|
z: int
|
|
|
|
|
|
FFF(1)
|
|
|
|
|
|
# Inheritance --
|
|
|
|
|
|
@attr.s
|
|
class GG(DD):
|
|
y: str = attr.ib()
|
|
|
|
|
|
GG(x=[1], y="foo")
|
|
|
|
|
|
@attr.s
|
|
class HH(DD, EE):
|
|
z: float = attr.ib()
|
|
|
|
|
|
HH(x=[1], y=[], z=1.1)
|
|
|
|
|
|
# same class
|
|
c == cc
|
|
|
|
|
|
# Exceptions
|
|
@attr.s(auto_exc=True)
|
|
class Error(Exception):
|
|
x: int = attr.ib()
|
|
|
|
|
|
try:
|
|
raise Error(1)
|
|
except Error as e:
|
|
e.x
|
|
e.args
|
|
str(e)
|
|
|
|
|
|
@attrs.define
|
|
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 = attr.ib(alias="_with_alias")
|
|
|
|
|
|
attr.fields(AliasExample).without_alias.alias
|
|
attr.fields(AliasExample)._with_alias.alias
|
|
|
|
# Converters
|
|
# XXX: Currently converters can only be functions so none of this works
|
|
# although the stubs should be correct.
|
|
|
|
# @attr.s
|
|
# class ConvCOptional:
|
|
# x: Optional[int] = attr.ib(converter=attr.converters.optional(int))
|
|
|
|
|
|
# ConvCOptional(1)
|
|
# ConvCOptional(None)
|
|
|
|
|
|
# @attr.s
|
|
# class ConvCDefaultIfNone:
|
|
# x: int = attr.ib(converter=attr.converters.default_if_none(42))
|
|
|
|
|
|
# ConvCDefaultIfNone(1)
|
|
# ConvCDefaultIfNone(None)
|
|
|
|
|
|
# @attr.s
|
|
# class ConvCToBool:
|
|
# x: int = attr.ib(converter=attr.converters.to_bool)
|
|
|
|
|
|
# ConvCToBool(1)
|
|
# ConvCToBool(True)
|
|
# ConvCToBool("on")
|
|
# ConvCToBool("yes")
|
|
# ConvCToBool(0)
|
|
# ConvCToBool(False)
|
|
# ConvCToBool("n")
|
|
|
|
|
|
# Validators
|
|
@attr.s
|
|
class Validated:
|
|
a = attr.ib(
|
|
type=List[C],
|
|
validator=attr.validators.deep_iterable(
|
|
attr.validators.instance_of(C), attr.validators.instance_of(list)
|
|
),
|
|
)
|
|
aa = attr.ib(
|
|
type=Tuple[C],
|
|
validator=attr.validators.deep_iterable(
|
|
attr.validators.instance_of(C), attr.validators.instance_of(tuple)
|
|
),
|
|
)
|
|
b = attr.ib(
|
|
type=List[C],
|
|
validator=attr.validators.deep_iterable(
|
|
attr.validators.instance_of(C)
|
|
),
|
|
)
|
|
c = attr.ib(
|
|
type=Dict[C, D],
|
|
validator=attr.validators.deep_mapping(
|
|
attr.validators.instance_of(C),
|
|
attr.validators.instance_of(D),
|
|
attr.validators.instance_of(dict),
|
|
),
|
|
)
|
|
d = attr.ib(
|
|
type=Dict[C, D],
|
|
validator=attr.validators.deep_mapping(
|
|
attr.validators.instance_of(C), attr.validators.instance_of(D)
|
|
),
|
|
)
|
|
e: str = attr.ib(validator=attr.validators.matches_re(re.compile(r"foo")))
|
|
f: str = attr.ib(
|
|
validator=attr.validators.matches_re(r"foo", flags=42, func=re.search)
|
|
)
|
|
|
|
# Test different forms of instance_of
|
|
g: int = attr.ib(validator=attr.validators.instance_of(int))
|
|
h: int = attr.ib(validator=attr.validators.instance_of((int,)))
|
|
j: int | str = attr.ib(validator=attr.validators.instance_of((int, str)))
|
|
k: int | str | C = attr.ib(
|
|
validator=attrs.validators.instance_of((int, C, str))
|
|
)
|
|
|
|
l: Any = attr.ib(
|
|
validator=attr.validators.not_(attr.validators.in_("abc"))
|
|
)
|
|
m: Any = attr.ib(
|
|
validator=attr.validators.not_(
|
|
attr.validators.in_("abc"), exc_types=ValueError
|
|
)
|
|
)
|
|
n: Any = attr.ib(
|
|
validator=attr.validators.not_(
|
|
attr.validators.in_("abc"), exc_types=(ValueError,)
|
|
)
|
|
)
|
|
o: Any = attr.ib(
|
|
validator=attr.validators.not_(attr.validators.in_("abc"), msg="spam")
|
|
)
|
|
p: Any = attr.ib(
|
|
validator=attr.validators.not_(attr.validators.in_("abc"), msg=None)
|
|
)
|
|
q: Any = attr.ib(
|
|
validator=attrs.validators.optional(attrs.validators.instance_of(C))
|
|
)
|
|
r: Any = attr.ib(
|
|
validator=attrs.validators.optional([attrs.validators.instance_of(C)])
|
|
)
|
|
s: Any = attr.ib(
|
|
validator=attrs.validators.optional((attrs.validators.instance_of(C),))
|
|
)
|
|
|
|
|
|
@attr.define
|
|
class Validated2:
|
|
num: int = attr.field(validator=attr.validators.ge(0))
|
|
|
|
|
|
@attrs.define
|
|
class Validated3:
|
|
num: int = attr.field(validator=attr.validators.ge(0))
|
|
|
|
|
|
with attr.validators.disabled():
|
|
Validated2(num=-1)
|
|
|
|
with attrs.validators.disabled():
|
|
Validated3(num=-1)
|
|
|
|
try:
|
|
attr.validators.set_disabled(True)
|
|
Validated2(num=-1)
|
|
finally:
|
|
attr.validators.set_disabled(False)
|
|
|
|
|
|
# Custom repr()
|
|
@attr.s
|
|
class WithCustomRepr:
|
|
a: int = attr.ib(repr=True)
|
|
b: str = attr.ib(repr=False)
|
|
c: str = attr.ib(repr=lambda value: "c is for cookie")
|
|
d: bool = attr.ib(repr=str)
|
|
|
|
|
|
@attrs.define
|
|
class WithCustomRepr2:
|
|
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
|
|
@attr.s(eq=True, order=False)
|
|
class OrderFlags:
|
|
a: int = attr.ib(eq=False, order=False)
|
|
b: int = attr.ib(eq=True, order=True)
|
|
|
|
|
|
# on_setattr hooks
|
|
@attr.s(on_setattr=attr.setters.validate)
|
|
class ValidatedSetter:
|
|
a: int
|
|
b: str = attr.ib(on_setattr=attr.setters.NO_OP)
|
|
c: bool = attr.ib(on_setattr=attr.setters.frozen)
|
|
d: int = attr.ib(on_setattr=[attr.setters.convert, attr.setters.validate])
|
|
e: bool = attr.ib(
|
|
on_setattr=attr.setters.pipe(
|
|
attr.setters.convert, attr.setters.validate
|
|
)
|
|
)
|
|
|
|
|
|
@attrs.define(on_setattr=attr.setters.validate)
|
|
class ValidatedSetter2:
|
|
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(
|
|
on_setattr=[attrs.setters.convert, attrs.setters.validate]
|
|
)
|
|
e: bool = attrs.field(
|
|
on_setattr=attrs.setters.pipe(
|
|
attrs.setters.convert, attrs.setters.validate
|
|
)
|
|
)
|
|
|
|
|
|
# field_transformer
|
|
def ft_hook(cls: type, attribs: list[attr.Attribute]) -> list[attr.Attribute]:
|
|
return attribs
|
|
|
|
|
|
# field_transformer
|
|
def ft_hook2(
|
|
cls: type, attribs: list[attrs.Attribute]
|
|
) -> list[attrs.Attribute]:
|
|
return attribs
|
|
|
|
|
|
@attr.s(field_transformer=ft_hook)
|
|
class TransformedAttrs:
|
|
x: int
|
|
|
|
|
|
@attrs.define(field_transformer=ft_hook2)
|
|
class TransformedAttrs2:
|
|
x: int
|
|
|
|
|
|
# Auto-detect
|
|
@attr.s(auto_detect=True)
|
|
class AutoDetect:
|
|
x: int
|
|
|
|
def __init__(self, x: int):
|
|
self.x = x
|
|
|
|
|
|
# Provisional APIs
|
|
@attr.define(order=True)
|
|
class NGClass:
|
|
x: int = attr.field(default=42)
|
|
|
|
|
|
ngc = NGClass(1)
|
|
|
|
|
|
@attr.mutable(slots=False)
|
|
class NGClass2:
|
|
x: int
|
|
|
|
|
|
ngc2 = NGClass2(1)
|
|
|
|
|
|
@attr.frozen(str=True)
|
|
class NGFrozen:
|
|
x: int
|
|
|
|
|
|
ngf = NGFrozen(1)
|
|
|
|
attr.fields(NGFrozen).x.evolve(eq=False)
|
|
a = attr.fields(NGFrozen).x
|
|
a.evolve(repr=False)
|
|
|
|
|
|
attrs.fields(NGFrozen).x.evolve(eq=False)
|
|
a = attrs.fields(NGFrozen).x
|
|
a.evolve(repr=False)
|
|
|
|
|
|
@attr.s(collect_by_mro=True)
|
|
class MRO:
|
|
pass
|
|
|
|
|
|
@attr.s
|
|
class FactoryTest:
|
|
a: list[int] = attr.ib(default=attr.Factory(list))
|
|
b: list[Any] = attr.ib(default=attr.Factory(list, False))
|
|
c: list[int] = attr.ib(default=attr.Factory((lambda s: s.a), True))
|
|
|
|
|
|
@attrs.define
|
|
class FactoryTest2:
|
|
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(FactoryTest2())
|
|
attr.asdict(FactoryTest(), tuple_keys=True)
|
|
|
|
|
|
# Check match_args stub
|
|
@attr.s(match_args=False)
|
|
class MatchArgs:
|
|
a: int = attr.ib()
|
|
b: int = attr.ib()
|
|
|
|
|
|
attr.asdict(FactoryTest())
|
|
attr.asdict(FactoryTest(), retain_collection_types=False)
|
|
|
|
|
|
# Check match_args stub
|
|
@attrs.define(match_args=False)
|
|
class MatchArgs2:
|
|
a: int
|
|
b: int
|
|
|
|
|
|
# NG versions of asdict/astuple
|
|
attrs.asdict(MatchArgs2(1, 2))
|
|
attrs.astuple(MatchArgs2(1, 2))
|
|
|
|
|
|
def accessing_from_attr() -> None:
|
|
"""
|
|
Use a function to keep the ns clean.
|
|
"""
|
|
attr.converters.optional
|
|
attr.exceptions.FrozenError
|
|
attr.filters.include
|
|
attr.filters.exclude
|
|
attr.setters.frozen
|
|
attr.validators.and_
|
|
attr.cmp_using
|
|
|
|
|
|
def accessing_from_attrs() -> None:
|
|
"""
|
|
Use a function to keep the ns clean.
|
|
"""
|
|
attrs.converters.optional
|
|
attrs.exceptions.FrozenError
|
|
attrs.filters.include
|
|
attrs.filters.exclude
|
|
attrs.setters.frozen
|
|
attrs.validators.and_
|
|
attrs.cmp_using
|
|
|
|
|
|
foo = object
|
|
if attrs.has(foo) or attr.has(foo):
|
|
foo.__attrs_attrs__
|
|
|
|
|
|
@attrs.define(unsafe_hash=True)
|
|
class Hashable:
|
|
pass
|
|
|
|
|
|
def test(cls: type) -> None:
|
|
if attr.has(cls):
|
|
attr.resolve_types(cls)
|