Coverage for tests / test_dictField.py: 17%

120 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-26 08:53 +0000

1# This file is part of pex_config. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (http://www.lsst.org). 

6# See the COPYRIGHT file at the top-level directory of this distribution 

7# for details of code ownership. 

8# 

9# This software is dual licensed under the GNU General Public License and also 

10# under a 3-clause BSD license. Recipients may choose which of these licenses 

11# to use; please see the files gpl-3.0.txt and/or bsd_license.txt, 

12# respectively. If you choose the GPL option then the following text applies 

13# (but note that there is still no warranty even if you opt for BSD instead): 

14# 

15# This program is free software: you can redistribute it and/or modify 

16# it under the terms of the GNU General Public License as published by 

17# the Free Software Foundation, either version 3 of the License, or 

18# (at your option) any later version. 

19# 

20# This program is distributed in the hope that it will be useful, 

21# but WITHOUT ANY WARRANTY; without even the implied warranty of 

22# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

23# GNU General Public License for more details. 

24# 

25# You should have received a copy of the GNU General Public License 

26# along with this program. If not, see <http://www.gnu.org/licenses/>. 

27 

28import pickle 

29import unittest 

30 

31import lsst.pex.config as pexConfig 

32 

33 

34class Config1(pexConfig.Config): 

35 """First test config.""" 

36 

37 d1 = pexConfig.DictField("d1", keytype=str, itemtype=int, default={"hi": 4}, itemCheck=lambda x: x > 0) 

38 d2 = pexConfig.DictField("d2", keytype=str, itemtype=str, default=None) 

39 d3 = pexConfig.DictField("d3", keytype=float, itemtype=float, optional=True, itemCheck=lambda x: x > 0) 

40 d4 = pexConfig.DictField("d4", keytype=str, itemtype=None, default={}) 

41 d5 = pexConfig.DictField[str, float]("d5", default={}, keyCheck=lambda k: k not in ["k1", "k2"]) 

42 d6 = pexConfig.DictField[int, str]("d6", default={-2: "v1", 4: "v2"}, keyCheck=lambda k: k % 2 == 0) 

43 

44 

45class DictFieldTest(unittest.TestCase): 

46 """Test DictField.""" 

47 

48 def testConstructor(self): 

49 try: 

50 

51 class BadKeytype(pexConfig.Config): 

52 d = pexConfig.DictField("...", keytype=list, itemtype=int) 

53 

54 except Exception: 

55 pass 

56 else: 

57 raise SyntaxError("Unsupported keyptype DictFields should not be allowed") 

58 

59 try: 

60 

61 class BadItemtype(pexConfig.Config): 

62 d = pexConfig.DictField("...", keytype=int, itemtype=dict) 

63 

64 except Exception: 

65 pass 

66 else: 

67 raise SyntaxError("Unsupported itemtype DictFields should not be allowed") 

68 

69 try: 

70 

71 class BadKeyCheck(pexConfig.Config): 

72 d = pexConfig.DictField("...", keytype=int, itemtype=int, keyCheck=4) 

73 

74 except Exception: 

75 pass 

76 else: 

77 raise SyntaxError("Non-callable keyCheck DictFields should not be allowed") 

78 

79 try: 

80 

81 class BadItemCheck(pexConfig.Config): 

82 d = pexConfig.DictField("...", keytype=int, itemtype=int, itemCheck=4) 

83 

84 except Exception: 

85 pass 

86 else: 

87 raise SyntaxError("Non-callable itemCheck DictFields should not be allowed") 

88 

89 try: 

90 

91 class BadDictCheck(pexConfig.Config): 

92 d = pexConfig.DictField("...", keytype=int, itemtype=int, dictCheck=4) 

93 

94 except Exception: 

95 pass 

96 else: 

97 raise SyntaxError("Non-callable dictCheck DictFields should not be allowed") 

98 

99 def testFieldTypeAnnotationRuntime(self): 

100 # test parsing type annotation for runtime keytype, itemtype 

101 testField = pexConfig.DictField[str, int](doc="test") 

102 self.assertEqual(testField.keytype, str) 

103 self.assertEqual(testField.itemtype, int) 

104 

105 # verify that forward references work correctly 

106 testField = pexConfig.DictField["float", "int"](doc="test") 

107 self.assertEqual(testField.keytype, float) 

108 self.assertEqual(testField.itemtype, int) 

109 

110 # verify that Field rejects single types 

111 with self.assertRaises(ValueError): 

112 pexConfig.DictField[int](doc="test") # type: ignore 

113 

114 # verify that Field raises in conflict with keytype, itemtype 

115 with self.assertRaises(ValueError): 

116 pexConfig.DictField[str, int](doc="test", keytype=int) 

117 

118 with self.assertRaises(ValueError): 

119 pexConfig.DictField[str, int](doc="test", itemtype=str) 

120 

121 # verify that Field does not raise if dtype agrees 

122 testField = pexConfig.DictField[int, str](doc="test", keytype=int, itemtype=str) 

123 self.assertEqual(testField.keytype, int) 

124 self.assertEqual(testField.itemtype, str) 

125 

126 def testAssignment(self): 

127 c = Config1() 

128 self.assertRaises(pexConfig.FieldValidationError, setattr, c, "d1", {3: 3}) 

129 self.assertRaises(pexConfig.FieldValidationError, setattr, c, "d1", {"a": 0}) 

130 self.assertRaises(pexConfig.FieldValidationError, setattr, c, "d1", [1.2, 3, 4]) 

131 c.d1 = None 

132 c.d1 = {"a": 1, "b": 2} 

133 self.assertRaises(pexConfig.FieldValidationError, setattr, c, "d3", {"hi": True}) 

134 c.d3 = {4: 5} 

135 self.assertEqual(c.d3, {4.0: 5.0}) 

136 d = {"a": None, "b": 4, "c": "foo"} 

137 c.d4 = d 

138 self.assertEqual(c.d4, d) 

139 c.d4["a"] = 12 

140 c.d4["b"] = "three" 

141 c.d4["c"] = None 

142 self.assertEqual(c.d4["a"], 12) 

143 self.assertEqual(c.d4["b"], "three") 

144 self.assertIsNone(c.d4["c"]) 

145 self.assertRaises(pexConfig.FieldValidationError, setattr, c, "d4", {"hi": [1, 2, 3]}) 

146 

147 def testValidate(self): 

148 c = Config1() 

149 self.assertRaises(pexConfig.FieldValidationError, Config1.validate, c) 

150 

151 c.d2 = {"a": "b"} 

152 c.validate() 

153 

154 def testKeyCheckValidation(self): 

155 c = Config1() 

156 c.d5 = {"k3": -1, "k4": 0.25} 

157 c.d6 = {6: "v3"} 

158 

159 with self.assertRaises( 

160 pexConfig.FieldValidationError, 

161 msg="Key check must reject dictionary assignment with invalid keys", 

162 ): 

163 c.d5 = {"k1": 1.5, "k2": 2.0} 

164 

165 with self.assertRaises( 

166 pexConfig.FieldValidationError, 

167 msg="Key check must reject invalid key addition", 

168 ): 

169 c.d6[3] = "v4" 

170 

171 def testInPlaceModification(self): 

172 c = Config1() 

173 self.assertRaises(pexConfig.FieldValidationError, c.d1.__setitem__, 2, 0) 

174 self.assertRaises(pexConfig.FieldValidationError, c.d1.__setitem__, "hi", 0) 

175 c.d1["hi"] = 10 

176 self.assertEqual(c.d1, {"hi": 10}) 

177 

178 c.d3 = {} 

179 c.d3[4] = 5 

180 self.assertEqual(c.d3, {4.0: 5.0}) 

181 

182 def testNoArbitraryAttributes(self): 

183 c = Config1() 

184 self.assertRaises(pexConfig.FieldValidationError, setattr, c.d1, "should", "fail") 

185 

186 def testEquality(self): 

187 """Test DictField.__eq__. 

188 

189 We create two dicts, with the keys explicitly added in a different 

190 order and test their equality. 

191 """ 

192 keys1 = ["A", "B", "C"] 

193 keys2 = ["X", "Y", "Z", "a", "b", "c", "d", "e"] 

194 

195 c1 = Config1() 

196 c1.d4 = dict.fromkeys(keys1, "") 

197 for k in keys2: 

198 c1.d4[k] = "" 

199 

200 c2 = Config1() 

201 for k in keys2 + keys1: 

202 c2.d4[k] = "" 

203 

204 self.assertTrue(pexConfig.compareConfigs("test", c1, c2)) 

205 

206 def testNoPickle(self): 

207 """Test that pickle support is disabled for the proxy container.""" 

208 c = Config1() 

209 with self.assertRaises(pexConfig.UnexpectedProxyUsageError): 

210 pickle.dumps(c.d4) 

211 

212 

213if __name__ == "__main__": 213 ↛ 214line 213 didn't jump to line 214 because the condition on line 213 was never true

214 unittest.main()