Coverage for tests/test_taskmetadata.py: 7%
154 statements
« prev ^ index » next coverage.py v7.4.1, created at 2024-01-30 10:51 +0000
« prev ^ index » next coverage.py v7.4.1, created at 2024-01-30 10:51 +0000
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/>.
28import json
29import unittest
31try:
32 import numpy
33except ImportError:
34 numpy = None
36from lsst.pipe.base import TaskMetadata
39class TaskMetadataTestCase(unittest.TestCase):
40 """Test task metadata."""
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")
70 meta["test"] = "string"
71 self.assertEqual(meta["test"], "string")
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)
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)
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])
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)
102 meta.add("via_scalar", 22)
103 self.assertEqual(meta["via_scalar"], 22)
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))
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))
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))
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))
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))
142 with self.assertRaises(ValueError):
143 meta.add("new", 1)
145 with self.assertRaises(KeyError):
146 meta[42]
148 with self.assertRaises(KeyError):
149 meta["not.present"]
151 with self.assertRaises(KeyError):
152 meta["not_present"]
154 with self.assertRaises(KeyError):
155 meta.getScalar("not_present")
157 with self.assertRaises(KeyError):
158 meta.getArray("not_present")
160 def testValidation(self):
161 """Test that validation works."""
162 meta = TaskMetadata()
164 class BadThing:
165 pass
167 with self.assertRaises(ValueError):
168 meta["bad"] = BadThing()
170 with self.assertRaises(ValueError):
171 meta["bad_list"] = [BadThing()]
173 meta.add("int", 4)
174 with self.assertRaises(ValueError):
175 meta.add("int", "string")
177 with self.assertRaises(ValueError):
178 meta.add("mapping", {})
180 with self.assertRaises(ValueError):
181 meta.add("int", ["string", "array"])
183 with self.assertRaises(ValueError):
184 meta["mixed"] = [1, "one"]
186 def testDict(self):
187 """Construct a TaskMetadata from a dictionary."""
188 d = {"a": "b", "c": 1, "d": [1, 2], "e": {"f": "g", "h": {"i": [3, 4]}}}
190 meta = TaskMetadata.from_dict(d)
191 self.assertEqual(meta["a"], "b")
192 self.assertEqual(meta["e.f"], "g")
193 self.assertEqual(meta.getArray("d"), [1, 2])
194 self.assertEqual(meta["e.h.i"], 4)
196 d2 = meta.to_dict()
197 self.assertEqual(d2, d)
199 j = meta.model_dump_json()
200 meta2 = TaskMetadata.model_validate(json.loads(j))
201 self.assertEqual(meta2, meta)
203 # Round trip.
204 meta3 = TaskMetadata.from_metadata(meta)
205 self.assertEqual(meta3, meta)
207 # Add a new element that would be a single-element array.
208 # This will not equate as equal because from_metadata will move
209 # the item to the scalar part of the model and pydantic does not
210 # see them as equal.
211 meta3.add("e.new", 5)
212 meta4 = TaskMetadata.from_metadata(meta3)
213 self.assertNotEqual(meta4, meta3)
214 self.assertEqual(meta4["e.new"], meta3["e.new"])
215 del meta4["e.new"]
216 del meta3["e.new"]
217 self.assertEqual(meta4, meta3)
219 @unittest.skipIf(not numpy, "Numpy is required for this test.")
220 def testNumpy(self):
221 meta = TaskMetadata()
222 meta["int"] = numpy.int64(42)
223 self.assertEqual(meta["int"], 42)
224 self.assertEqual(type(meta["int"]), int)
226 meta["float"] = numpy.float64(3.14)
227 self.assertEqual(meta["float"], 3.14)
228 self.assertEqual(type(meta["float"]), float)
230 meta.add("floatArray", [numpy.float64(1.5), numpy.float64(3.0)])
231 self.assertEqual(meta.getArray("floatArray"), [1.5, 3.0])
232 self.assertEqual(type(meta["floatArray"]), float)
234 meta.add("intArray", [numpy.int64(1), numpy.int64(3)])
235 self.assertEqual(meta.getArray("intArray"), [1, 3])
236 self.assertEqual(type(meta["intArray"]), int)
238 with self.assertRaises(ValueError):
239 meta.add("mixed", [1.5, numpy.float64(4.5)])
241 with self.assertRaises(ValueError):
242 meta["numpy"] = numpy.zeros(5)
245if __name__ == "__main__":
246 unittest.main()