1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
|
from typing import Any, NewType, Optional
import pytest
from attrs import define
from cattrs import BaseConverter, Converter
from ._compat import is_py310_plus
def test_newtype_optionals(genconverter):
"""Newtype optionals should work."""
Foo = NewType("Foo", str)
genconverter.register_unstructure_hook(Foo, lambda v: v.replace("foo", "bar"))
@define
class ModelWithFoo:
total_foo: Foo
maybe_foo: Optional[Foo]
assert genconverter.unstructure(ModelWithFoo(Foo("foo"), Foo("is it a foo?"))) == {
"total_foo": "bar",
"maybe_foo": "is it a bar?",
}
@pytest.mark.skipif(not is_py310_plus, reason="3.10+ union syntax")
def test_newtype_modern_optionals(genconverter):
"""Newtype optionals should work."""
Foo = NewType("Foo", str)
genconverter.register_unstructure_hook(Foo, lambda v: v.replace("foo", "bar"))
@define
class ModelWithFoo:
total_foo: Foo
maybe_foo: Foo | None
assert genconverter.unstructure(ModelWithFoo(Foo("foo"), Foo("is it a foo?"))) == {
"total_foo": "bar",
"maybe_foo": "is it a bar?",
}
def test_optional_any(converter: Converter):
"""Unstructuring Any|None is equivalent to unstructuring as v.__class__."""
@define
class A:
pass
assert converter.unstructure(A(), Optional[Any]) == {}
def test_override_optional(converter: BaseConverter):
"""Optionals can be overridden using singledispatch."""
@converter.register_structure_hook
def _(val, _) -> Optional[int]:
if val in ("", None):
return None
return int(val)
assert converter.structure("", Optional[int]) is None
assert converter.structure("1", Optional[int]) == 1
@converter.register_unstructure_hook
def _(val: Optional[int]) -> Any:
if val in (None, 0):
return None
return val
assert converter.unstructure(0, Optional[int]) is None
assert converter.unstructure(5, Optional[int]) == 5
|