Coverage for Users/vladimirpavlov/PycharmProjects/parameterizable/tests/test_json_processor.py: 99%

1 import types

2 import builtins

3 import pytest

4

5 from enum import Enum

6

7 from mixinforge.json_processor import (

8 _to_serializable_dict,

9 _recreate_object,

10 _from_serializable_dict,

11 _Markers,

12)

13

14

15 class Color(Enum):

16 RED = 1

17 GREEN = 2

18 BLUE = 3

19

20

21 class BaseSlots:

22 __slots__ = ("base",)

23

24 def __init__(self):

25 self.base = "base"

26

27

28 class 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

37 class 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

46 class 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

57 class 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

67 class 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

79 class SelfRefer:

80 pass

81

82

83@pytest.mark.parametrize(

84 "value",

85 [None, True, False, 0, 123, -5, 3.14, "abc"],

86)

87 def test_to_serializable_primitives(value):

88 assert _to_serializable_dict(value) == value

89

90

91 def 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

113 def 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

120 def 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

138 def 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)

159 def 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)

177 def 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

183 def 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

191 def 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

199 def 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

208 def 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)