LSST Applications g0f08755f38+82efc23009,g12f32b3c4e+e7bdf1200e,g1653933729+a8ce1bb630,g1a0ca8cf93+50eff2b06f,g28da252d5a+52db39f6a5,g2bbee38e9b+37c5a29d61,g2bc492864f+37c5a29d61,g2cdde0e794+c05ff076ad,g3156d2b45e+41e33cbcdc,g347aa1857d+37c5a29d61,g35bb328faa+a8ce1bb630,g3a166c0a6a+37c5a29d61,g3e281a1b8c+fb992f5633,g414038480c+7f03dfc1b0,g41af890bb2+11b950c980,g5fbc88fb19+17cd334064,g6b1c1869cb+12dd639c9a,g781aacb6e4+a8ce1bb630,g80478fca09+72e9651da0,g82479be7b0+04c31367b4,g858d7b2824+82efc23009,g9125e01d80+a8ce1bb630,g9726552aa6+8047e3811d,ga5288a1d22+e532dc0a0b,gae0086650b+a8ce1bb630,gb58c049af0+d64f4d3760,gc28159a63d+37c5a29d61,gcf0d15dbbd+2acd6d4d48,gd7358e8bfb+778a810b6e,gda3e153d99+82efc23009,gda6a2b7d83+2acd6d4d48,gdaeeff99f8+1711a396fd,ge2409df99d+6b12de1076,ge79ae78c31+37c5a29d61,gf0baf85859+d0a5978c5a,gf3967379c6+4954f8c433,gfb92a5be7c+82efc23009,gfec2e1e490+2aaed99252,w.2024.46
LSST Data Management Base Package
Loading...
Searching...
No Matches
configDictField.py
Go to the documentation of this file.
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
28__all__ = ["ConfigDictField"]
29
30from .callStack import getCallStack, getStackFrame
31from .comparison import compareConfigs, compareScalars, getComparisonName
32from .config import Config, FieldValidationError, _autocast, _joinNamePath, _typeStr
33from .dictField import Dict, DictField
34
35
36class ConfigDict(Dict[str, Config]):
37 """Internal representation of a dictionary of configuration classes.
38
39 Much like `Dict`, `ConfigDict` is a custom `MutableMapper` which tracks
40 the history of changes to any of its items.
41
42 Parameters
43 ----------
44 config : `~lsst.pex.config.Config`
45 Config to use.
46 field : `~lsst.pex.config.ConfigDictField`
47 Field to use.
48 value : `~typing.Any`
49 Value to store in dict.
50 at : `list` of `~lsst.pex.config.callStack.StackFrame` or `None`, optional
51 Stack frame for history recording. Will be calculated if `None`.
52 label : `str`, optional
53 Label to use for history recording.
54 """
55
56 def __init__(self, config, field, value, at, label):
57 Dict.__init__(self, config, field, value, at, label, setHistory=False)
58 self.historyhistory.append(("Dict initialized", at, label))
59
60 def __setitem__(self, k, x, at=None, label="setitem", setHistory=True):
61 if self._config_config_config._frozen:
62 msg = f"Cannot modify a frozen Config. Attempting to set item at key {k!r} to value {x}"
64
65 # validate keytype
66 k = _autocast(k, self._field_field.keytype)
67 if type(k) is not self._field_field.keytype:
68 msg = f"Key {k!r} is of type {_typeStr(k)}, expected type {_typeStr(self._field.keytype)}"
70
71 # validate itemtype
72 dtype = self._field_field.itemtype
73 if type(x) is not self._field_field.itemtype and x != self._field_field.itemtype:
74 msg = (
75 f"Value {x} at key {k!r} is of incorrect type {_typeStr(x)}. "
76 f"Expected type {_typeStr(self._field.itemtype)}"
77 )
79
80 if at is None:
81 at = getCallStack()
82 name = _joinNamePath(self._config_config_config._name, self._field_field.name, k)
83 oldValue = self._dict.get(k, None)
84 if oldValue is None:
85 if x == dtype:
86 self._dict[k] = dtype(__name=name, __at=at, __label=label)
87 else:
88 self._dict[k] = dtype(__name=name, __at=at, __label=label, **x._storage)
89 if setHistory:
90 self.historyhistory.append((f"Added item at key {k}", at, label))
91 else:
92 if x == dtype:
93 x = dtype()
94 oldValue.update(__at=at, __label=label, **x._storage)
95 if setHistory:
96 self.historyhistory.append((f"Modified item at key {k}", at, label))
97
98 def __delitem__(self, k, at=None, label="delitem"):
99 if at is None:
100 at = getCallStack()
101 Dict.__delitem__(self, k, at, label, False)
102 self.historyhistory.append((f"Removed item at key {k}", at, label))
103
104
106 """A configuration field (`~lsst.pex.config.Field` subclass) that is a
107 mapping of keys to `~lsst.pex.config.Config` instances.
108
109 ``ConfigDictField`` behaves like `DictField` except that the
110 ``itemtype`` must be a `~lsst.pex.config.Config` subclass.
111
112 Parameters
113 ----------
114 doc : `str`
115 A description of the configuration field.
116 keytype : {`int`, `float`, `complex`, `bool`, `str`}
117 The type of the mapping keys. All keys must have this type.
118 itemtype : `lsst.pex.config.Config`-type
119 The type of the values in the mapping. This must be
120 `~lsst.pex.config.Config` or a subclass.
121 default : optional
122 Unknown.
123 default : ``itemtype``-dtype, optional
124 Default value of this field.
125 optional : `bool`, optional
126 If `True`, this configuration `~lsst.pex.config.Field` is *optional*.
127 Default is `True`.
128 dictCheck : `~collections.abc.Callable` or `None`, optional
129 Callable to check a dict.
130 itemCheck : `~collections.abc.Callable` or `None`, optional
131 Callable to check an item.
132 deprecated : None or `str`, optional
133 A description of why this Field is deprecated, including removal date.
134 If not None, the string is appended to the docstring for this Field.
135
136 Raises
137 ------
138 ValueError
139 Raised if the inputs are invalid:
140
141 - ``keytype`` or ``itemtype`` arguments are not supported types
142 (members of `ConfigDictField.supportedTypes`.
143 - ``dictCheck`` or ``itemCheck`` is not a callable function.
144
145 See Also
146 --------
147 ChoiceField
148 ConfigChoiceField
149 ConfigField
150 ConfigurableField
151 DictField
152 Field
153 ListField
154 RangeField
155 RegistryField
156
157 Notes
158 -----
159 You can use ``ConfigDictField`` to create name-to-config mappings. One use
160 case is for configuring mappings for dataset types in a Butler. In this
161 case, the dataset type names are arbitrary and user-selected while the
162 mapping configurations are known and fixed.
163 """
164
165 DictClass = ConfigDict
166
168 self,
169 doc,
170 keytype,
171 itemtype,
172 default=None,
173 optional=False,
174 dictCheck=None,
175 itemCheck=None,
176 deprecated=None,
177 ):
178 source = getStackFrame()
179 self._setup(
180 doc=doc,
181 dtype=ConfigDict,
182 default=default,
183 check=None,
184 optional=optional,
185 source=source,
186 deprecated=deprecated,
187 )
188 if keytype not in self.supportedTypes:
189 raise ValueError(f"'keytype' {_typeStr(keytype)} is not a supported type")
190 elif not issubclass(itemtype, Config):
191 raise ValueError(f"'itemtype' {_typeStr(itemtype)} is not a supported type")
192 if dictCheck is not None and not hasattr(dictCheck, "__call__"):
193 raise ValueError("'dictCheck' must be callable")
194 if itemCheck is not None and not hasattr(itemCheck, "__call__"):
195 raise ValueError("'itemCheck' must be callable")
196
197 self.keytypekeytype = keytype
198 self.itemtypeitemtype = itemtype
199 self.dictCheckdictCheck = dictCheck
200 self.itemCheckitemCheck = itemCheck
201
202 def rename(self, instance):
203 configDict = self.__get____get____get__(instance)
204 if configDict is not None:
205 for k in configDict:
206 fullname = _joinNamePath(instance._name, self.namenamenamename, k)
207 configDict[k]._rename(fullname)
208
209 def validate(self, instance):
210 value = self.__get____get____get__(instance)
211 if value is not None:
212 for k in value:
213 item = value[k]
214 item.validate()
215 if self.itemCheckitemCheck is not None and not self.itemCheckitemCheck(item):
216 msg = f"Item at key {k!r} is not a valid value: {item}"
217 raise FieldValidationError(self, instance, msg)
218 DictField.validate(self, instance)
219
220 def toDict(self, instance):
221 configDict = self.__get____get____get__(instance)
222 if configDict is None:
223 return None
224
225 dict_ = {}
226 for k in configDict:
227 dict_[k] = configDict[k].toDict()
228
229 return dict_
230
231 def _collectImports(self, instance, imports):
232 # docstring inherited from Field
233 configDict = self.__get____get____get__(instance)
234 if configDict is not None:
235 for v in configDict.values():
236 v._collectImports()
237 imports |= v._imports
238
239 def save(self, outfile, instance):
240 configDict = self.__get____get____get__(instance)
241 fullname = _joinNamePath(instance._name, self.namenamenamename)
242 if configDict is None:
243 outfile.write(f"{fullname}={configDict!r}\n")
244 return
245
246 outfile.write(f"{fullname}={{}}\n")
247 for v in configDict.values():
248 outfile.write(f"{v._name}={_typeStr(v)}()\n")
249 v._save(outfile)
250
251 def freeze(self, instance):
252 configDict = self.__get____get____get__(instance)
253 if configDict is not None:
254 for k in configDict:
255 configDict[k].freeze()
256
257 def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
258 """Compare two fields for equality.
259
260 Used by `lsst.pex.ConfigDictField.compare`.
261
262 Parameters
263 ----------
264 instance1 : `lsst.pex.config.Config`
265 Left-hand side config instance to compare.
266 instance2 : `lsst.pex.config.Config`
267 Right-hand side config instance to compare.
268 shortcut : `bool`
269 If `True`, this function returns as soon as an inequality if found.
270 rtol : `float`
271 Relative tolerance for floating point comparisons.
272 atol : `float`
273 Absolute tolerance for floating point comparisons.
274 output : callable
275 A callable that takes a string, used (possibly repeatedly) to
276 report inequalities.
277
278 Returns
279 -------
280 isEqual : bool
281 `True` if the fields are equal, `False` otherwise.
282
283 Notes
284 -----
285 Floating point comparisons are performed by `numpy.allclose`.
286 """
287 d1 = getattr(instance1, self.namenamenamename)
288 d2 = getattr(instance2, self.namenamenamename)
289 name = getComparisonName(
290 _joinNamePath(instance1._name, self.namenamenamename), _joinNamePath(instance2._name, self.namenamenamename)
291 )
292 if not compareScalars(f"keys for {name}", set(d1.keys()), set(d2.keys()), output=output):
293 return False
294 equal = True
295 for k, v1 in d1.items():
296 v2 = d2[k]
297 result = compareConfigs(
298 f"{name}[{k!r}]", v1, v2, shortcut=shortcut, rtol=rtol, atol=atol, output=output
299 )
300 if not result and shortcut:
301 return False
302 equal = equal and result
303 return equal
__get__(self, instance, owner=None, at=None, label="default")
Definition config.py:707
FieldTypeVar __get__(self, Config instance, Any owner=None, Any at=None, str label="default")
Definition config.py:705
Field[FieldTypeVar] __get__(self, None instance, Any owner=None, Any at=None, str label="default")
Definition config.py:700
_setup(self, doc, dtype, default, check, optional, source, deprecated)
Definition config.py:480
_compare(self, instance1, instance2, shortcut, rtol, atol, output)
__init__(self, doc, keytype, itemtype, default=None, optional=False, dictCheck=None, itemCheck=None, deprecated=None)
__init__(self, config, field, value, at, label)
__setitem__(self, k, x, at=None, label="setitem", setHistory=True)
__delitem__(self, k, at=None, label="delitem")