Coverage for tests / test_dictField.py: 17%
120 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-30 08:43 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-30 08:43 +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/>.
28import pickle
29import unittest
31import lsst.pex.config as pexConfig
34class Config1(pexConfig.Config):
35 """First test config."""
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)
45class DictFieldTest(unittest.TestCase):
46 """Test DictField."""
48 def testConstructor(self):
49 try:
51 class BadKeytype(pexConfig.Config):
52 d = pexConfig.DictField("...", keytype=list, itemtype=int)
54 except Exception:
55 pass
56 else:
57 raise SyntaxError("Unsupported keyptype DictFields should not be allowed")
59 try:
61 class BadItemtype(pexConfig.Config):
62 d = pexConfig.DictField("...", keytype=int, itemtype=dict)
64 except Exception:
65 pass
66 else:
67 raise SyntaxError("Unsupported itemtype DictFields should not be allowed")
69 try:
71 class BadKeyCheck(pexConfig.Config):
72 d = pexConfig.DictField("...", keytype=int, itemtype=int, keyCheck=4)
74 except Exception:
75 pass
76 else:
77 raise SyntaxError("Non-callable keyCheck DictFields should not be allowed")
79 try:
81 class BadItemCheck(pexConfig.Config):
82 d = pexConfig.DictField("...", keytype=int, itemtype=int, itemCheck=4)
84 except Exception:
85 pass
86 else:
87 raise SyntaxError("Non-callable itemCheck DictFields should not be allowed")
89 try:
91 class BadDictCheck(pexConfig.Config):
92 d = pexConfig.DictField("...", keytype=int, itemtype=int, dictCheck=4)
94 except Exception:
95 pass
96 else:
97 raise SyntaxError("Non-callable dictCheck DictFields should not be allowed")
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)
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)
110 # verify that Field rejects single types
111 with self.assertRaises(ValueError):
112 pexConfig.DictField[int](doc="test") # type: ignore
114 # verify that Field raises in conflict with keytype, itemtype
115 with self.assertRaises(ValueError):
116 pexConfig.DictField[str, int](doc="test", keytype=int)
118 with self.assertRaises(ValueError):
119 pexConfig.DictField[str, int](doc="test", itemtype=str)
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)
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]})
147 def testValidate(self):
148 c = Config1()
149 self.assertRaises(pexConfig.FieldValidationError, Config1.validate, c)
151 c.d2 = {"a": "b"}
152 c.validate()
154 def testKeyCheckValidation(self):
155 c = Config1()
156 c.d5 = {"k3": -1, "k4": 0.25}
157 c.d6 = {6: "v3"}
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}
165 with self.assertRaises(
166 pexConfig.FieldValidationError,
167 msg="Key check must reject invalid key addition",
168 ):
169 c.d6[3] = "v4"
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})
178 c.d3 = {}
179 c.d3[4] = 5
180 self.assertEqual(c.d3, {4.0: 5.0})
182 def testNoArbitraryAttributes(self):
183 c = Config1()
184 self.assertRaises(pexConfig.FieldValidationError, setattr, c.d1, "should", "fail")
186 def testEquality(self):
187 """Test DictField.__eq__.
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"]
195 c1 = Config1()
196 c1.d4 = dict.fromkeys(keys1, "")
197 for k in keys2:
198 c1.d4[k] = ""
200 c2 = Config1()
201 for k in keys2 + keys1:
202 c2.d4[k] = ""
204 self.assertTrue(pexConfig.compareConfigs("test", c1, c2))
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)
213if __name__ == "__main__": 213 ↛ 214line 213 didn't jump to line 214 because the condition on line 213 was never true
214 unittest.main()