Coverage for Users / vladimirpavlov / PycharmProjects / parameterizable / tests / test_json_processor.py: 99%
131 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-01 16:37 -0600
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-01 16:37 -0600
1import types
2import builtins
3import pytest
5from enum import Enum
7from mixinforge.json_processor import (
8 _to_serializable_dict,
9 _recreate_object,
10 _from_serializable_dict,
11 _Markers,
12)
15class Color(Enum):
16 RED = 1
17 GREEN = 2
18 BLUE = 3
21class BaseSlots:
22 __slots__ = ("base",)
24 def __init__(self):
25 self.base = "base"
28class Hybrid(BaseSlots):
29 __slots__ = ("__dict__", "s")
31 def __init__(self):
32 super().__init__()
33 self.s = 42
34 self.d = "present in __dict__"
37class GetParams:
38 def __init__(self, a=3, b="z"):
39 self.a = a
40 self.b = b
42 def get_params(self):
43 return {"a": self.a, "b": self.b}
46class GetState:
47 def __init__(self, v):
48 self._v = v
50 def __getstate__(self):
51 return {"v": self._v}
53 def __setstate__(self, state):
54 self._v = state["v"]
57class StateNoSetState:
58 def __init__(self, a, b):
59 # not used at reconstruction time
60 self.a = a
61 self.b = b
63 def __getstate__(self):
64 return {"a": 111, "b": 222}
67class GetParamsAndState:
68 def __init__(self, a=3, b="z"):
69 self.a = a
70 self.b = b
72 def get_params(self):
73 return {"a": self.a}
75 def __getstate__(self):
76 return {"b": self.b}
79class SelfRefer:
80 pass
83@pytest.mark.parametrize(
84 "value",
85 [None, True, False, 0, 123, -5, 3.14, "abc"],
86)
87def test_to_serializable_primitives(value):
88 assert _to_serializable_dict(value) == value
91def test_to_serializable_containers_and_markers():
92 data = {
93 "t": (1, 2, 3),
94 "s": {1, 2},
95 "d": {1: "a", 2: "b"},
96 "l": [1, 2, 3],
97 }
98 out = _to_serializable_dict(data)
100 assert out.keys() == {_Markers.DICT}
101 out_dict = {k: v for k,v in out[_Markers.DICT].items()}
103 assert out_dict["t"] == {_Markers.TUPLE: [1, 2, 3]}
104 assert out_dict["l"] == [1, 2, 3]
105 assert out_dict["d"] == {_Markers.DICT: {1: "a", 2: "b"}}
107 # For set, content matters, not order
108 assert out_dict["s"].keys() == {_Markers.SET}
109 assert isinstance(out_dict["s"][_Markers.SET], list)
110 assert sorted(out_dict["s"][_Markers.SET]) == [1,2]
113def test_to_serializable_enum():
114 out = _to_serializable_dict(Color.GREEN)
115 assert out[_Markers.ENUM] == "GREEN"
116 assert out[_Markers.CLASS] == "Color"
117 assert out[_Markers.MODULE] == __name__
120def test_to_serializable_get_params_and_state_variants():
121 gp = GetParams(7, "k")
122 st = GetState(99)
123 hy = Hybrid()
125 gp_out = _to_serializable_dict(gp)
126 st_out = _to_serializable_dict(st)
127 hy_out = _to_serializable_dict(hy)
129 for o in (gp_out, st_out, hy_out):
130 assert o[_Markers.CLASS]
131 assert o[_Markers.MODULE] == __name__
132 assert (_Markers.PARAMS in o) ^ (_Markers.STATE in o)
134 # Ensure get_params converted recursively
135 assert gp_out[_Markers.PARAMS][_Markers.DICT]["a"] == 7
138def test_to_serializable_get_params_has_precedence_over_getstate():
139 obj = GetParamsAndState(a=10, b=20)
140 ser = _to_serializable_dict(obj)
142 assert _Markers.PARAMS in ser
143 assert _Markers.STATE not in ser
144 assert ser[_Markers.PARAMS] == {_Markers.DICT: {"a": 10}}
146 reconstructed = _from_serializable_dict(ser)
147 assert isinstance(reconstructed, GetParamsAndState)
150@pytest.mark.parametrize(
151 "obj_creator, type_name",
152 [
153 (lambda: (l := [], l.append(l)), "list"),
154 (lambda: (d := {}, d.update({"d": d})), "dict"),
155 (lambda: (o := SelfRefer(), setattr(o, "me", o)), "SelfRefer"),
156 (lambda: (l := [{}], l[0].update({"l": l})), "list"),
157 ],
158)
159def test_to_serializable_cycle_detection(obj_creator, type_name):
160 obj = obj_creator()
161 with pytest.raises(RecursionError, match=type_name):
162 _to_serializable_dict(obj)
165@pytest.mark.parametrize(
166 "value",
167 [
168 builtins.open, # builtin function
169 len, # builtin function (another)
170 GetParams, # a class/type
171 GetParams(1, "x").__init__, # bound method (of instance)
172 (lambda x: x).__code__, # code object
173 str, # another class/type
174 types.ModuleType("m"), # a module
175 ],
176)
177def test_to_serializable_unsupported_types_message_includes_type(value):
178 with pytest.raises(TypeError) as ei:
179 _to_serializable_dict(value)
180 assert type(value).__name__ in str(ei.value)
183def test_recreate_object_via_params():
184 obj = GetParams(5, "y")
185 serialized = _to_serializable_dict(obj)
186 reconstructed = _recreate_object(serialized)
187 assert isinstance(reconstructed, GetParams)
188 assert reconstructed.a == 5 and reconstructed.b == "y"
191def test_recreate_object_via_state_with_setstate():
192 obj = GetState(123)
193 serialized = _to_serializable_dict(obj)
194 reconstructed = _recreate_object(serialized)
195 assert isinstance(reconstructed, GetState)
196 assert reconstructed._v == 123
199def test_recreate_object_via_state_fallback_without_setstate():
200 obj = StateNoSetState(1, 2)
201 serialized = _to_serializable_dict(obj)
202 reconstructed = _recreate_object(serialized)
203 assert isinstance(reconstructed, StateNoSetState)
204 # fallback assigns attributes from state
205 assert reconstructed.a == 111 and reconstructed.b == 222
208def test_recreate_enum_and_errors():
209 enum_ser = _to_serializable_dict(Color.BLUE)
210 assert _recreate_object(enum_ser) is Color.BLUE
212 # not a mapping
213 with pytest.raises(TypeError):
214 _recreate_object([1, 2])
216 # missing MODULE/CLASS
217 with pytest.raises(TypeError):
218 _recreate_object({_Markers.STATE: {}})
220 # wrong module/class
221 bad = {_Markers.MODULE: "does.not.exist", _Markers.CLASS: "X", _Markers.STATE: {}}
222 with pytest.raises(ImportError):
223 _recreate_object(bad)
225 # class not enum when ENUM provided
226 not_enum = {
227 _Markers.MODULE: __name__,
228 _Markers.CLASS: "GetParams",
229 _Markers.ENUM: "BLUE",
230 }
231 with pytest.raises(TypeError):
232 _recreate_object(not_enum)
234 # unknown payload
235 unk = {
236 _Markers.MODULE: __name__,
237 _Markers.CLASS: "GetParams",
238 "some": 1,
239 }
240 with pytest.raises(TypeError):
241 _recreate_object(unk)