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

1import types 

2import builtins 

3import pytest 

4 

5from enum import Enum 

6 

7from mixinforge.json_processor import ( 

8 _to_serializable_dict, 

9 _recreate_object, 

10 _from_serializable_dict, 

11 _Markers, 

12) 

13 

14 

15class Color(Enum): 

16 RED = 1 

17 GREEN = 2 

18 BLUE = 3 

19 

20 

21class BaseSlots: 

22 __slots__ = ("base",) 

23 

24 def __init__(self): 

25 self.base = "base" 

26 

27 

28class Hybrid(BaseSlots): 

29 __slots__ = ("__dict__", "s") 

30 

31 def __init__(self): 

32 super().__init__() 

33 self.s = 42 

34 self.d = "present in __dict__" 

35 

36 

37class GetParams: 

38 def __init__(self, a=3, b="z"): 

39 self.a = a 

40 self.b = b 

41 

42 def get_params(self): 

43 return {"a": self.a, "b": self.b} 

44 

45 

46class GetState: 

47 def __init__(self, v): 

48 self._v = v 

49 

50 def __getstate__(self): 

51 return {"v": self._v} 

52 

53 def __setstate__(self, state): 

54 self._v = state["v"] 

55 

56 

57class StateNoSetState: 

58 def __init__(self, a, b): 

59 # not used at reconstruction time 

60 self.a = a 

61 self.b = b 

62 

63 def __getstate__(self): 

64 return {"a": 111, "b": 222} 

65 

66 

67class GetParamsAndState: 

68 def __init__(self, a=3, b="z"): 

69 self.a = a 

70 self.b = b 

71 

72 def get_params(self): 

73 return {"a": self.a} 

74 

75 def __getstate__(self): 

76 return {"b": self.b} 

77 

78 

79class SelfRefer: 

80 pass 

81 

82 

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 

89 

90 

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) 

99 

100 assert out.keys() == {_Markers.DICT} 

101 out_dict = {k: v for k,v in out[_Markers.DICT].items()} 

102 

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"}} 

106 

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] 

111 

112 

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__ 

118 

119 

120def test_to_serializable_get_params_and_state_variants(): 

121 gp = GetParams(7, "k") 

122 st = GetState(99) 

123 hy = Hybrid() 

124 

125 gp_out = _to_serializable_dict(gp) 

126 st_out = _to_serializable_dict(st) 

127 hy_out = _to_serializable_dict(hy) 

128 

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) 

133 

134 # Ensure get_params converted recursively 

135 assert gp_out[_Markers.PARAMS][_Markers.DICT]["a"] == 7 

136 

137 

138def test_to_serializable_get_params_has_precedence_over_getstate(): 

139 obj = GetParamsAndState(a=10, b=20) 

140 ser = _to_serializable_dict(obj) 

141 

142 assert _Markers.PARAMS in ser 

143 assert _Markers.STATE not in ser 

144 assert ser[_Markers.PARAMS] == {_Markers.DICT: {"a": 10}} 

145 

146 reconstructed = _from_serializable_dict(ser) 

147 assert isinstance(reconstructed, GetParamsAndState) 

148 

149 

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) 

163 

164 

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) 

181 

182 

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" 

189 

190 

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 

197 

198 

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 

206 

207 

208def test_recreate_enum_and_errors(): 

209 enum_ser = _to_serializable_dict(Color.BLUE) 

210 assert _recreate_object(enum_ser) is Color.BLUE 

211 

212 # not a mapping 

213 with pytest.raises(TypeError): 

214 _recreate_object([1, 2]) 

215 

216 # missing MODULE/CLASS 

217 with pytest.raises(TypeError): 

218 _recreate_object({_Markers.STATE: {}}) 

219 

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) 

224 

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) 

233 

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)