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
|
import itertools
from typing import Union
import pytest
from attrs import define
from hypothesis import given
from hypothesis.strategies import integers
from cattrs import BaseConverter
from cattrs.strategies import use_class_methods
@define
class Base:
a: int
class Structure(Base):
@classmethod
def _structure(cls, data: dict):
return cls(data["b"]) # expecting "b", not "a"
class Unstructure(Base):
def _unstructure(self):
return {"c": self.a} # unstructuring as "c", not "a"
class Both(Structure, Unstructure):
pass
@pytest.fixture
def get_converter(converter: BaseConverter):
def aux(structure: str, unstructure: str) -> BaseConverter:
use_class_methods(converter, structure, unstructure)
return converter
return aux
@pytest.mark.parametrize(
"cls,structure_method,unstructure_method",
itertools.product(
[Structure, Unstructure, Both],
["_structure", "_undefined", None],
["_unstructure", "_undefined", None],
),
)
def test_not_nested(get_converter, structure_method, unstructure_method, cls) -> None:
converter = get_converter(structure_method, unstructure_method)
assert converter.structure(
{
(
"b"
if structure_method == "_structure" and hasattr(cls, "_structure")
else "a"
): 42
},
cls,
) == cls(42)
assert converter.unstructure(cls(42)) == {
(
"c"
if unstructure_method == "_unstructure" and hasattr(cls, "_unstructure")
else "a"
): 42
}
@given(integers(1, 5))
def test_nested_roundtrip(depth):
@define
class Nested:
a: Union["Nested", None]
c: int
@classmethod
def _structure(cls, data, conv):
b = data["b"]
return cls(None if b is None else conv.structure(b, cls), data["c"])
def _unstructure(self, conv):
return {"b": conv.unstructure(self.a), "c": self.c}
@staticmethod
def create(depth: int) -> Union["Nested", None]:
return None if depth == 0 else Nested(Nested.create(depth - 1), 42)
structured = Nested.create(depth)
converter = BaseConverter()
use_class_methods(converter, "_structure", "_unstructure")
assert structured == converter.structure(converter.unstructure(structured), Nested)
def test_edge_cases():
"""Test some edge cases, for coverage."""
@define
class Bad:
a: int
@classmethod
def _structure(cls):
"""This has zero args, so can't work."""
@classmethod
def _unstructure(cls):
"""This has zero args, so can't work."""
converter = BaseConverter()
use_class_methods(converter, "_structure", "_unstructure")
# The methods take the wrong number of args, so this should fail.
with pytest.raises(TypeError):
converter.structure({"a": 1}, Bad)
with pytest.raises(TypeError):
converter.unstructure(Bad(1))
|