Coverage for tests/test_taskmetadata.py: 7%

174 statements  

« prev     ^ index     » next       coverage.py v7.5.0, created at 2024-04-26 02:50 -0700

1# This file is part of pipe_base. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (https://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 <https://www.gnu.org/licenses/>. 

27 

28import math 

29import unittest 

30 

31try: 

32 import numpy 

33except ImportError: 

34 numpy = None 

35 

36from lsst.pipe.base import TaskMetadata 

37 

38 

39class TaskMetadataTestCase(unittest.TestCase): 

40 """Test task metadata.""" 

41 

42 def testTaskMetadata(self): 

43 """Full test of TaskMetadata API.""" 

44 meta = TaskMetadata() 

45 meta["test"] = 42 

46 self.assertEqual(meta["test"], 42) 

47 meta.add("test", 55) 

48 self.assertEqual(meta["test"], 55) 

49 meta.add("test", [1, 2]) 

50 self.assertEqual(meta.getScalar("test"), 2) 

51 self.assertEqual(meta.getArray("test"), [42, 55, 1, 2]) 

52 self.assertEqual(meta.get("test"), 2) 

53 meta["new.int"] = 30 

54 self.assertEqual(meta["new.int"], 30) 

55 self.assertEqual(meta.get("new.int", 20), 30) 

56 self.assertEqual(meta.get("not.present.at.all", 20), 20) 

57 self.assertEqual(meta["new"]["int"], 30) 

58 self.assertEqual(meta.get("new").get("int"), 30) 

59 self.assertEqual(meta.getArray("new.int"), [30]) 

60 self.assertEqual(meta.getScalar("new.int"), 30) 

61 self.assertIsInstance(meta["new"], TaskMetadata) 

62 self.assertIsInstance(meta.getScalar("new"), TaskMetadata) 

63 self.assertIsInstance(meta.getArray("new")[0], TaskMetadata) 

64 self.assertIsInstance(meta.get("new"), TaskMetadata) 

65 meta.add("new.int", 24) 

66 self.assertEqual(meta["new.int"], 24) 

67 meta["new.str"] = "str" 

68 self.assertEqual(meta["new.str"], "str") 

69 

70 meta["test"] = "string" 

71 self.assertEqual(meta["test"], "string") 

72 

73 self.assertIn("test", meta) 

74 self.assertIn("new", meta) 

75 self.assertIn("new.int", meta) 

76 self.assertNotIn("new2.int", meta) 

77 self.assertNotIn("test2", meta) 

78 

79 self.assertEqual(meta.paramNames(topLevelOnly=False), {"test", "new.int", "new.str"}) 

80 self.assertEqual(meta.paramNames(topLevelOnly=True), {"test"}) 

81 self.assertEqual(meta.names(), {"test", "new", "new.int", "new.str"}) 

82 self.assertEqual(meta.keys(), ("test", "new")) 

83 self.assertEqual(len(meta), 2) 

84 self.assertEqual(len(meta["new"]), 2) 

85 

86 meta["new_array"] = ("a", "b") 

87 self.assertEqual(meta["new_array"], "b") 

88 self.assertEqual(meta.getArray("new_array"), ["a", "b"]) 

89 meta.add("new_array", "c") 

90 self.assertEqual(meta["new_array"], "c") 

91 self.assertEqual(meta.getArray("new_array"), ["a", "b", "c"]) 

92 meta["new_array"] = [1, 2, 3] 

93 self.assertEqual(meta.getArray("new_array"), [1, 2, 3]) 

94 

95 meta["meta"] = 5 

96 meta["meta"] = TaskMetadata() 

97 self.assertIsInstance(meta["meta"], TaskMetadata) 

98 meta["meta.a.b"] = "deep" 

99 self.assertEqual(meta["meta.a.b"], "deep") 

100 self.assertIsInstance(meta["meta.a"], TaskMetadata) 

101 

102 meta.add("via_scalar", 22) 

103 self.assertEqual(meta["via_scalar"], 22) 

104 

105 del meta["test"] 

106 self.assertNotIn("test", meta) 

107 del meta["new.int"] 

108 self.assertNotIn("new.int", meta) 

109 self.assertIn("new", meta) 

110 with self.assertRaises(KeyError): 

111 del meta["test2"] 

112 with self.assertRaises(KeyError) as cm: 

113 # Check that deleting a hierarchy that is not present also 

114 # reports the correct key. 

115 del meta["new.a.b.c"] 

116 self.assertIn("new.a.b.c", str(cm.exception)) 

117 

118 with self.assertRaises(KeyError) as cm: 

119 # Something that doesn't exist at all. 

120 meta["something.a.b"] 

121 # Ensure that the full key hierarchy is reported in the error message. 

122 self.assertIn("something.a.b", str(cm.exception)) 

123 

124 with self.assertRaises(KeyError) as cm: 

125 # Something that does exist at level 2 but not further down. 

126 meta["new.str.a"] 

127 # Ensure that the full key hierarchy is reported in the error message. 

128 self.assertIn("new.str.a", str(cm.exception)) 

129 

130 with self.assertRaises(KeyError) as cm: 

131 # Something that only exists at level 1. 

132 meta["new.str3"] 

133 # Ensure that the full key hierarchy is reported in the error message. 

134 self.assertIn("new.str3", str(cm.exception)) 

135 

136 with self.assertRaises(KeyError) as cm: 

137 # Something that only exists at level 1 but as an array. 

138 meta.getArray("new.str3") 

139 # Ensure that the full key hierarchy is reported in the error message. 

140 self.assertIn("new.str3", str(cm.exception)) 

141 

142 with self.assertRaises(ValueError): 

143 meta.add("new", 1) 

144 

145 with self.assertRaises(KeyError): 

146 meta[42] 

147 

148 with self.assertRaises(KeyError): 

149 meta["not.present"] 

150 

151 with self.assertRaises(KeyError): 

152 meta["not_present"] 

153 

154 with self.assertRaises(KeyError): 

155 meta.getScalar("not_present") 

156 

157 with self.assertRaises(KeyError): 

158 meta.getArray("not_present") 

159 

160 def testValidation(self): 

161 """Test that validation works.""" 

162 meta = TaskMetadata() 

163 

164 class BadThing: 

165 pass 

166 

167 with self.assertRaises(ValueError): 

168 meta["bad"] = BadThing() 

169 

170 with self.assertRaises(ValueError): 

171 meta["bad_list"] = [BadThing()] 

172 

173 meta.add("int", 4) 

174 with self.assertRaises(ValueError): 

175 meta.add("int", "string") 

176 

177 with self.assertRaises(ValueError): 

178 meta.add("mapping", {}) 

179 

180 with self.assertRaises(ValueError): 

181 meta.add("int", ["string", "array"]) 

182 

183 with self.assertRaises(ValueError): 

184 meta["mixed"] = [1, "one"] 

185 

186 def test_nan(self): 

187 """Check that NaN round trips as a NaN.""" 

188 meta = TaskMetadata() 

189 meta["nan"] = float("NaN") 

190 new_meta = TaskMetadata.model_validate_json(meta.model_dump_json()) 

191 self.assertTrue(math.isnan(new_meta["nan"])) 

192 

193 def testDict(self): 

194 """Construct a TaskMetadata from a dictionary.""" 

195 d = {"a": "b", "c": 1, "d": [1, 2], "e": {"f": "g", "h": {"i": [3, 4]}}} 

196 

197 meta = TaskMetadata.from_dict(d) 

198 self.assertEqual(meta["a"], "b") 

199 self.assertEqual(meta["e.f"], "g") 

200 self.assertEqual(meta.getArray("d"), [1, 2]) 

201 self.assertEqual(meta["e.h.i"], 4) 

202 

203 d2 = meta.to_dict() 

204 self.assertEqual(d2, d) 

205 

206 j = meta.model_dump_json() 

207 meta2 = TaskMetadata.model_validate_json(j) 

208 self.assertEqual(meta2, meta) 

209 

210 # Round trip. 

211 meta3 = TaskMetadata.from_metadata(meta) 

212 self.assertEqual(meta3, meta) 

213 

214 # Add a new element that would be a single-element array. 

215 # This will not equate as equal because from_metadata will move 

216 # the item to the scalar part of the model and pydantic does not 

217 # see them as equal. 

218 meta3.add("e.new", 5) 

219 meta4 = TaskMetadata.from_metadata(meta3) 

220 self.assertNotEqual(meta4, meta3) 

221 self.assertEqual(meta4["e.new"], meta3["e.new"]) 

222 del meta4["e.new"] 

223 del meta3["e.new"] 

224 self.assertEqual(meta4, meta3) 

225 

226 @unittest.skipIf(not numpy, "Numpy is required for this test.") 

227 def testNumpy(self): 

228 meta = TaskMetadata() 

229 meta["int"] = numpy.int64(42) 

230 self.assertEqual(meta["int"], 42) 

231 self.assertEqual(type(meta["int"]), int) 

232 

233 meta["float"] = numpy.float64(3.14) 

234 self.assertEqual(meta["float"], 3.14) 

235 self.assertEqual(type(meta["float"]), float) 

236 

237 meta.add("floatArray", [numpy.float64(1.5), numpy.float64(3.0)]) 

238 self.assertEqual(meta.getArray("floatArray"), [1.5, 3.0]) 

239 self.assertEqual(type(meta["floatArray"]), float) 

240 

241 meta.add("intArray", [numpy.int64(1), numpy.int64(3)]) 

242 self.assertEqual(meta.getArray("intArray"), [1, 3]) 

243 self.assertEqual(type(meta["intArray"]), int) 

244 

245 with self.assertRaises(ValueError): 

246 meta.add("mixed", [1.5, numpy.float64(4.5)]) 

247 

248 with self.assertRaises(ValueError): 

249 meta["numpy"] = numpy.zeros(5) 

250 

251 def test_get_set_dict(self): 

252 """Test the get_dict and set_dict methods.""" 

253 obj = TaskMetadata() 

254 d1 = {"one": 1, "two": 2.0, "three": True, "four": {"a": 4, "b": "B"}, "five": {}} 

255 obj.set_dict("d", d1) 

256 obj.set_dict("e", {}) 

257 d2 = obj.get_dict("d") 

258 # Keys with empty-dict values may or may not be round-tripped. 

259 self.assertGreaterEqual(d2.keys(), {"one", "two", "three", "four"}) 

260 self.assertLessEqual(d2.keys(), {"one", "two", "three", "four", "five"}) 

261 self.assertEqual(d2["one"], d1["one"]) 

262 self.assertEqual(d2["two"], d1["two"]) 

263 self.assertEqual(d2["three"], d1["three"]) 

264 self.assertEqual(d2["four"], d1["four"]) 

265 self.assertEqual(d2.get("five", {}), d1["five"]) 

266 # Empty dict may or may not have been added, and retrieving it or 

267 # a key that was never added yields an empty dict. 

268 self.assertEqual(obj.get_dict("e"), {}) 

269 self.assertEqual(obj.get_dict("f"), {}) 

270 

271 

272if __name__ == "__main__": 

273 unittest.main()