File: test_class_methods.py

package info (click to toggle)
python-cattrs 25.3.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,812 kB
  • sloc: python: 12,236; makefile: 155
file content (122 lines) | stat: -rw-r--r-- 3,069 bytes parent folder | download | duplicates (2)
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))