Coverage for tests/test_storageClass.py: 14%
Shortcuts on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# This file is part of daf_butler.
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 program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <http://www.gnu.org/licenses/>.
22import logging
23import os
24import pickle
25import unittest
27from lsst.daf.butler import StorageClass, StorageClassConfig, StorageClassDelegate, StorageClassFactory
28from lsst.utils.introspection import get_full_type_name
30"""Tests related to the StorageClass infrastructure.
31"""
33TESTDIR = os.path.abspath(os.path.dirname(__file__))
36class PythonType:
37 """A dummy class to test the registry of Python types."""
39 pass
42class StorageClassFactoryTestCase(unittest.TestCase):
43 """Tests of the storage class infrastructure."""
45 def testCreation(self):
46 """Test that we can dynamically create storage class subclasses.
48 This is critical for testing the factory functions."""
49 className = "TestImage"
50 sc = StorageClass(className, pytype=dict)
51 self.assertIsInstance(sc, StorageClass)
52 self.assertEqual(sc.name, className)
53 self.assertEqual(str(sc), className)
54 self.assertFalse(sc.components)
55 self.assertTrue(sc.validateInstance({}))
56 self.assertFalse(sc.validateInstance(""))
58 r = repr(sc)
59 self.assertIn("StorageClass", r)
60 self.assertIn(className, r)
61 self.assertNotIn("parameters", r)
62 self.assertIn("pytype='dict'", r)
64 # Ensure we do not have a delegate
65 with self.assertRaises(TypeError):
66 sc.delegate()
68 # Allow no definition of python type
69 scn = StorageClass(className)
70 self.assertIs(scn.pytype, object)
72 # Include some components
73 scc = StorageClass(className, pytype=PythonType, components={"comp1": sc, "comp2": sc})
74 self.assertIn("comp1", scc.components)
75 r = repr(scc)
76 self.assertIn("comp1", r)
77 self.assertIn("lsst.daf.butler.core.storageClassDelegate.StorageClassDelegate", r)
79 # Ensure that we have a delegate
80 self.assertIsInstance(scc.delegate(), StorageClassDelegate)
82 # Check we can create a storageClass using the name of an importable
83 # type.
84 sc2 = StorageClass("TestImage2", "lsst.daf.butler.core.storageClass.StorageClassFactory")
85 self.assertIsInstance(sc2.pytype(), StorageClassFactory)
86 self.assertIn("butler.core", repr(sc2))
88 def testParameters(self):
89 """Test that we can set parameters and validate them"""
90 pt = ("a", "b")
91 ps = {"a", "b"}
92 pl = ["a", "b"]
93 for p in (pt, ps, pl):
94 sc1 = StorageClass("ParamClass", pytype=dict, parameters=p)
95 self.assertEqual(sc1.parameters, ps)
96 sc1.validateParameters(p)
98 sc1.validateParameters()
99 sc1.validateParameters({"a": None, "b": None})
100 sc1.validateParameters(
101 [
102 "a",
103 ]
104 )
105 with self.assertRaises(KeyError):
106 sc1.validateParameters({"a", "c"})
108 def testEquality(self):
109 """Test that StorageClass equality works"""
110 className = "TestImage"
111 sc1 = StorageClass(className, pytype=dict)
112 sc2 = StorageClass(className, pytype=dict)
113 self.assertEqual(sc1, sc2)
114 sc3 = StorageClass(className + "2", pytype=str)
115 self.assertNotEqual(sc1, sc3)
117 # Same StorageClass name but different python type
118 sc4 = StorageClass(className, pytype=str)
119 self.assertNotEqual(sc1, sc4)
121 # Parameters
122 scp = StorageClass("Params", pytype=PythonType, parameters=["a", "b", "c"])
123 scp1 = StorageClass("Params", pytype=PythonType, parameters=["a", "b", "c"])
124 scp2 = StorageClass("Params", pytype=PythonType, parameters=["a", "b", "d", "e"])
125 self.assertEqual(scp, scp1)
126 self.assertNotEqual(scp, scp2)
128 # Now with components
129 sc5 = StorageClass("Composite", pytype=PythonType, components={"comp1": sc1, "comp2": sc3})
130 sc6 = StorageClass("Composite", pytype=PythonType, components={"comp1": sc1, "comp2": sc3})
131 self.assertEqual(sc5, sc6)
132 self.assertNotEqual(sc5, sc3)
133 sc7 = StorageClass("Composite", pytype=PythonType, components={"comp1": sc4, "comp2": sc3})
134 self.assertNotEqual(sc5, sc7)
135 sc8 = StorageClass("Composite", pytype=PythonType, components={"comp2": sc3, "comp3": sc3})
136 self.assertNotEqual(sc5, sc8)
137 sc9 = StorageClass(
138 "Composite",
139 pytype=PythonType,
140 components={"comp1": sc1, "comp2": sc3},
141 delegate="lsst.daf.butler.Butler",
142 )
143 self.assertNotEqual(sc5, sc9)
145 def testRegistry(self):
146 """Check that storage classes can be created on the fly and stored
147 in a registry."""
148 className = "TestImage"
149 factory = StorageClassFactory()
150 newclass = StorageClass(className, pytype=PythonType)
151 factory.registerStorageClass(newclass)
152 sc = factory.getStorageClass(className)
153 self.assertIsInstance(sc, StorageClass)
154 self.assertEqual(sc.name, className)
155 self.assertFalse(sc.components)
156 self.assertEqual(sc.pytype, PythonType)
157 self.assertIn(sc, factory)
158 newclass2 = StorageClass("Temporary2", pytype=str)
159 self.assertNotIn(newclass2, factory)
160 factory.registerStorageClass(newclass2)
161 self.assertIn(newclass2, factory)
162 self.assertIn("Temporary2", factory)
163 self.assertNotIn("Temporary3", factory)
164 self.assertNotIn({}, factory)
166 # Make sure we can't register a storage class with the same name
167 # but different values
168 newclass3 = StorageClass("Temporary2", pytype=dict)
169 with self.assertRaises(ValueError):
170 factory.registerStorageClass(newclass3)
172 factory._unregisterStorageClass(newclass3.name)
173 self.assertNotIn(newclass3, factory)
174 self.assertNotIn(newclass3.name, factory)
175 factory.registerStorageClass(newclass3)
176 self.assertIn(newclass3, factory)
177 self.assertIn(newclass3.name, factory)
179 # Check you can silently insert something that is already there
180 factory.registerStorageClass(newclass3)
182 def testFactoryConfig(self):
183 factory = StorageClassFactory()
184 factory.addFromConfig(StorageClassConfig())
185 image = factory.getStorageClass("Image")
186 imageF = factory.getStorageClass("ImageF")
187 self.assertIsInstance(imageF, type(image))
188 self.assertNotEqual(imageF, image)
190 # Check component inheritance
191 exposure = factory.getStorageClass("Exposure")
192 exposureF = factory.getStorageClass("ExposureF")
193 self.assertIsInstance(exposureF, type(exposure))
194 self.assertIsInstance(exposure.components["image"], type(image))
195 self.assertNotIsInstance(exposure.components["image"], type(imageF))
196 self.assertIsInstance(exposureF.components["image"], type(image))
197 self.assertIsInstance(exposureF.components["image"], type(imageF))
198 self.assertIn("wcs", exposure.components)
199 self.assertIn("wcs", exposureF.components)
201 # Check parameters
202 factory.addFromConfig(os.path.join(TESTDIR, "config", "basic", "storageClasses.yaml"))
203 thing1 = factory.getStorageClass("ThingOne")
204 thing2 = factory.getStorageClass("ThingTwo")
205 self.assertIsInstance(thing2, type(thing1))
206 param1 = thing1.parameters
207 param2 = thing2.parameters
208 self.assertIn("param3", thing2.parameters)
209 self.assertNotIn("param3", thing1.parameters)
210 param2.remove("param3")
211 self.assertEqual(param1, param2)
213 # Check that we can't have a new StorageClass that does not
214 # inherit from StorageClass
215 with self.assertRaises(ValueError):
216 factory.makeNewStorageClass("ClassName", baseClass=StorageClassFactory)
218 sc = factory.makeNewStorageClass("ClassName")
219 self.assertIsInstance(sc(), StorageClass)
221 def testPickle(self):
222 """Test that we can pickle storageclasses."""
223 className = "TestImage"
224 sc = StorageClass(className, pytype=dict)
225 self.assertIsInstance(sc, StorageClass)
226 self.assertEqual(sc.name, className)
227 self.assertFalse(sc.components)
228 sc2 = pickle.loads(pickle.dumps(sc))
229 self.assertEqual(sc2, sc)
231 @classmethod
232 def _convert_type(cls, data):
233 # Test helper function. Fail if the list is empty.
234 if not len(data):
235 raise RuntimeError("Deliberate failure.")
236 return {"key": data}
238 def testConverters(self):
239 """Test conversion maps."""
241 className = "TestConverters"
242 converters = {
243 "lsst.daf.butler.tests.MetricsExample": "lsst.daf.butler.tests.MetricsExampleModel.from_metrics",
244 # Add some entries that will fail to import.
245 "lsst.daf.butler.bad.type": "lsst.daf.butler.tests.MetricsExampleModel.from_metrics",
246 "lsst.daf.butler.tests.MetricsExampleModel": "lsst.daf.butler.bad.function",
247 "lsst.daf.butler.Butler": "lsst.daf.butler.core.location.__all__",
248 "list": get_full_type_name(self._convert_type),
249 }
250 sc = StorageClass(className, pytype=dict, converters=converters)
251 self.assertEqual(len(sc.converters), 5) # Pre-filtering
252 sc2 = StorageClass("Test2", pytype=set)
253 sc3 = StorageClass("Test3", pytype="lsst.daf.butler.tests.MetricsExample")
255 self.assertIn("lsst.daf.butler.tests.MetricsExample", repr(sc))
256 # Initially the converter list is not filtered.
257 self.assertIn("lsst.daf.butler.bad.type", repr(sc))
258 self.assertNotIn("converters", repr(sc2))
260 self.assertTrue(sc.can_convert(sc))
261 self.assertFalse(sc.can_convert(sc2))
262 self.assertTrue(sc.can_convert(sc3))
264 # After we've processed the converters the bad ones will no longer
265 # be reported.
266 self.assertNotIn("lsst.daf.butler.bad.type", repr(sc))
267 self.assertEqual(len(sc.converters), 2)
269 self.assertIsNone(sc.coerce_type(None))
271 converted = sc.coerce_type([1, 2, 3])
272 self.assertEqual(converted, {"key": [1, 2, 3]})
274 # Try to coerce a type that is not supported.
275 with self.assertRaises(TypeError):
276 sc.coerce_type(set([1, 2, 3]))
278 # Coerce something that will fail to convert.
279 with self.assertLogs(level=logging.ERROR) as cm:
280 with self.assertRaises(RuntimeError):
281 sc.coerce_type([])
282 self.assertIn("failed to convert type list", cm.output[0])
285if __name__ == "__main__": 285 ↛ 286line 285 didn't jump to line 286, because the condition on line 285 was never true
286 unittest.main()