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 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
|
"""Tests for tuples of all kinds."""
from typing import List, NamedTuple, Tuple
from attrs import Factory, define
from pytest import raises
from cattrs.cols import (
is_namedtuple,
namedtuple_dict_structure_factory,
namedtuple_dict_unstructure_factory,
)
from cattrs.converters import Converter
from cattrs.errors import ForbiddenExtraKeysError
def test_simple_hetero_tuples(genconverter: Converter):
"""Simple heterogenous tuples work.
Only supported for the Converter (not the BaseConverter).
"""
genconverter.register_unstructure_hook(int, lambda v: v + 1)
assert genconverter.unstructure((1, "2"), unstructure_as=Tuple[int, str]) == (
2,
"2",
)
genconverter.register_structure_hook(int, lambda v, _: v - 1)
assert genconverter.structure([2, "2"], Tuple[int, str]) == (1, "2")
def test_named_tuple_predicate():
"""The NamedTuple predicate works."""
assert not is_namedtuple(tuple)
assert not is_namedtuple(Tuple[int, ...])
assert not is_namedtuple(Tuple[int])
class Test(NamedTuple):
a: int
assert is_namedtuple(Test)
class Test2(Tuple[int, int]):
pass
assert not is_namedtuple(Test2)
def test_simple_typed_namedtuples(genconverter: Converter):
"""Simple typed namedtuples work."""
class Test(NamedTuple):
a: int
assert genconverter.unstructure(Test(1)) == Test(1)
assert genconverter.structure([1], Test) == Test(1)
genconverter.register_unstructure_hook(int, lambda v: v + 1)
genconverter.register_structure_hook(int, lambda v, _: v - 1)
assert genconverter.unstructure(Test(1)) == (2,)
assert genconverter.structure([2], Test) == Test(1)
def test_simple_dict_nametuples(genconverter: Converter):
"""Namedtuples can be un/structured to/from dicts."""
class TestInner(NamedTuple):
a: int
class Test(NamedTuple):
a: int
b: str = "test"
c: TestInner = TestInner(1)
genconverter.register_unstructure_hook_factory(
lambda t: t in (Test, TestInner), namedtuple_dict_unstructure_factory
)
genconverter.register_structure_hook_factory(
lambda t: t in (Test, TestInner), namedtuple_dict_structure_factory
)
assert genconverter.unstructure(Test(1)) == {"a": 1, "b": "test", "c": {"a": 1}}
assert genconverter.structure({"a": 1, "b": "2"}, Test) == Test(
1, "2", TestInner(1)
)
# Defaults work.
assert genconverter.structure({"a": 1}, Test) == Test(1, "test")
@define
class RecursiveAttrs:
b: "List[RecursiveNamedtuple]" = Factory(list)
class RecursiveNamedtuple(NamedTuple):
a: RecursiveAttrs
def test_recursive_dict_nametuples(genconverter: Converter):
"""Recursive namedtuples can be un/structured to/from dicts."""
genconverter.register_unstructure_hook_factory(
lambda t: t is RecursiveNamedtuple, namedtuple_dict_unstructure_factory
)
genconverter.register_structure_hook_factory(
lambda t: t is RecursiveNamedtuple, namedtuple_dict_structure_factory
)
assert genconverter.unstructure(RecursiveNamedtuple(RecursiveAttrs())) == {
"a": {"b": []}
}
assert genconverter.structure(
{"a": {}}, RecursiveNamedtuple
) == RecursiveNamedtuple(RecursiveAttrs())
def test_dict_nametuples_forbid_extra_keys(genconverter: Converter):
"""Forbidding extra keys works for structuring namedtuples from dicts."""
class Test(NamedTuple):
a: int
genconverter.register_structure_hook_factory(
lambda t: t is Test,
lambda t, c: namedtuple_dict_structure_factory(t, c, "from_converter", True),
)
with raises(Exception) as exc_info:
genconverter.structure({"a": 1, "b": "2"}, Test)
if genconverter.detailed_validation:
exc = exc_info.value.exceptions[0]
else:
exc = exc_info.value
assert isinstance(exc, ForbiddenExtraKeysError)
assert exc.extra_fields == {"b"}
|