LSST Applications 26.0.0,g0265f82a02+6660c170cc,g07994bdeae+30b05a742e,g0a0026dc87+17526d298f,g0a60f58ba1+17526d298f,g0e4bf8285c+96dd2c2ea9,g0ecae5effc+c266a536c8,g1e7d6db67d+6f7cb1f4bb,g26482f50c6+6346c0633c,g2bbee38e9b+6660c170cc,g2cc88a2952+0a4e78cd49,g3273194fdb+f6908454ef,g337abbeb29+6660c170cc,g337c41fc51+9a8f8f0815,g37c6e7c3d5+7bbafe9d37,g44018dc512+6660c170cc,g4a941329ef+4f7594a38e,g4c90b7bd52+5145c320d2,g58be5f913a+bea990ba40,g635b316a6c+8d6b3a3e56,g67924a670a+bfead8c487,g6ae5381d9b+81bc2a20b4,g93c4d6e787+26b17396bd,g98cecbdb62+ed2cb6d659,g98ffbb4407+81bc2a20b4,g9ddcbc5298+7f7571301f,ga1e77700b3+99e9273977,gae46bcf261+6660c170cc,gb2715bf1a1+17526d298f,gc86a011abf+17526d298f,gcf0d15dbbd+96dd2c2ea9,gdaeeff99f8+0d8dbea60f,gdb4ec4c597+6660c170cc,ge23793e450+96dd2c2ea9,gf041782ebf+171108ac67
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
43 def __init__(self, config, field, value, at, label):
44 Dict.__init__(self, config, field, value, at, label, setHistory=False)
45 self.historyhistory.append(("Dict initialized", at, label))
46
47 def __setitem__(self, k, x, at=None, label="setitem", setHistory=True):
48 if self._config_config_config._frozen:
49 msg = f"Cannot modify a frozen Config. Attempting to set item at key {k!r} to value {x}"
51
52 # validate keytype
53 k = _autocast(k, self._field_field.keytype)
54 if type(k) != self._field_field.keytype:
55 msg = "Key {!r} is of type {}, expected type {}".format(
56 k, _typeStr(k), _typeStr(self._field_field.keytype)
57 )
59
60 # validate itemtype
61 dtype = self._field_field.itemtype
62 if type(x) != self._field_field.itemtype and x != self._field_field.itemtype:
63 msg = "Value {} at key {!r} is of incorrect type {}. Expected type {}".format(
64 x,
65 k,
66 _typeStr(x),
67 _typeStr(self._field_field.itemtype),
68 )
70
71 if at is None:
72 at = getCallStack()
73 name = _joinNamePath(self._config_config_config._name, self._field_field.name, k)
74 oldValue = self._dict.get(k, None)
75 if oldValue is None:
76 if x == dtype:
77 self._dict[k] = dtype(__name=name, __at=at, __label=label)
78 else:
79 self._dict[k] = dtype(__name=name, __at=at, __label=label, **x._storage)
80 if setHistory:
81 self.historyhistory.append(("Added item at key %s" % k, at, label))
82 else:
83 if x == dtype:
84 x = dtype()
85 oldValue.update(__at=at, __label=label, **x._storage)
86 if setHistory:
87 self.historyhistory.append(("Modified item at key %s" % k, at, label))
88
89 def __delitem__(self, k, at=None, label="delitem"):
90 if at is None:
91 at = getCallStack()
92 Dict.__delitem__(self, k, at, label, False)
93 self.historyhistory.append(("Removed item at key %s" % k, at, label))
94
95
97 """A configuration field (`~lsst.pex.config.Field` subclass) that is a
98 mapping of keys to `~lsst.pex.config.Config` instances.
99
100 ``ConfigDictField`` behaves like `DictField` except that the
101 ``itemtype`` must be a `~lsst.pex.config.Config` subclass.
102
103 Parameters
104 ----------
105 doc : `str`
106 A description of the configuration field.
107 keytype : {`int`, `float`, `complex`, `bool`, `str`}
108 The type of the mapping keys. All keys must have this type.
109 itemtype : `lsst.pex.config.Config`-type
110 The type of the values in the mapping. This must be
111 `~lsst.pex.config.Config` or a subclass.
112 default : optional
113 Unknown.
114 default : ``itemtype``-dtype, optional
115 Default value of this field.
116 optional : `bool`, optional
117 If `True`, this configuration `~lsst.pex.config.Field` is *optional*.
118 Default is `True`.
119 deprecated : None or `str`, optional
120 A description of why this Field is deprecated, including removal date.
121 If not None, the string is appended to the docstring for this Field.
122
123 Raises
124 ------
125 ValueError
126 Raised if the inputs are invalid:
127
128 - ``keytype`` or ``itemtype`` arguments are not supported types
129 (members of `ConfigDictField.supportedTypes`.
130 - ``dictCheck`` or ``itemCheck`` is not a callable function.
131
132 See Also
133 --------
134 ChoiceField
135 ConfigChoiceField
136 ConfigField
137 ConfigurableField
138 DictField
139 Field
140 ListField
141 RangeField
142 RegistryField
143
144 Notes
145 -----
146 You can use ``ConfigDictField`` to create name-to-config mappings. One use
147 case is for configuring mappings for dataset types in a Butler. In this
148 case, the dataset type names are arbitrary and user-selected while the
149 mapping configurations are known and fixed.
150 """
151
152 DictClass = ConfigDict
153
155 self,
156 doc,
157 keytype,
158 itemtype,
159 default=None,
160 optional=False,
161 dictCheck=None,
162 itemCheck=None,
163 deprecated=None,
164 ):
165 source = getStackFrame()
166 self._setup(
167 doc=doc,
168 dtype=ConfigDict,
169 default=default,
170 check=None,
171 optional=optional,
172 source=source,
173 deprecated=deprecated,
174 )
175 if keytype not in self.supportedTypes:
176 raise ValueError("'keytype' %s is not a supported type" % _typeStr(keytype))
177 elif not issubclass(itemtype, Config):
178 raise ValueError("'itemtype' %s is not a supported type" % _typeStr(itemtype))
179 if dictCheck is not None and not hasattr(dictCheck, "__call__"):
180 raise ValueError("'dictCheck' must be callable")
181 if itemCheck is not None and not hasattr(itemCheck, "__call__"):
182 raise ValueError("'itemCheck' must be callable")
183
184 self.keytypekeytype = keytype
185 self.itemtypeitemtype = itemtype
186 self.dictCheckdictCheck = dictCheck
187 self.itemCheckitemCheck = itemCheck
188
189 def rename(self, instance):
190 configDict = self.__get____get____get__(instance)
191 if configDict is not None:
192 for k in configDict:
193 fullname = _joinNamePath(instance._name, self.namenamenamename, k)
194 configDict[k]._rename(fullname)
195
196 def validate(self, instance):
197 value = self.__get____get____get__(instance)
198 if value is not None:
199 for k in value:
200 item = value[k]
201 item.validate()
202 if self.itemCheckitemCheck is not None and not self.itemCheckitemCheck(item):
203 msg = f"Item at key {k!r} is not a valid value: {item}"
204 raise FieldValidationError(self, instance, msg)
205 DictField.validate(self, instance)
206
207 def toDict(self, instance):
208 configDict = self.__get____get____get__(instance)
209 if configDict is None:
210 return None
211
212 dict_ = {}
213 for k in configDict:
214 dict_[k] = configDict[k].toDict()
215
216 return dict_
217
218 def _collectImports(self, instance, imports):
219 # docstring inherited from Field
220 configDict = self.__get____get____get__(instance)
221 if configDict is not None:
222 for v in configDict.values():
223 v._collectImports()
224 imports |= v._imports
225
226 def save(self, outfile, instance):
227 configDict = self.__get____get____get__(instance)
228 fullname = _joinNamePath(instance._name, self.namenamenamename)
229 if configDict is None:
230 outfile.write(f"{fullname}={configDict!r}\n")
231 return
232
233 outfile.write(f"{fullname}={{}}\n")
234 for v in configDict.values():
235 outfile.write(f"{v._name}={_typeStr(v)}()\n")
236 v._save(outfile)
237
238 def freeze(self, instance):
239 configDict = self.__get____get____get__(instance)
240 if configDict is not None:
241 for k in configDict:
242 configDict[k].freeze()
243
244 def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
245 """Compare two fields for equality.
246
247 Used by `lsst.pex.ConfigDictField.compare`.
248
249 Parameters
250 ----------
251 instance1 : `lsst.pex.config.Config`
252 Left-hand side config instance to compare.
253 instance2 : `lsst.pex.config.Config`
254 Right-hand side config instance to compare.
255 shortcut : `bool`
256 If `True`, this function returns as soon as an inequality if found.
257 rtol : `float`
258 Relative tolerance for floating point comparisons.
259 atol : `float`
260 Absolute tolerance for floating point comparisons.
261 output : callable
262 A callable that takes a string, used (possibly repeatedly) to
263 report inequalities.
264
265 Returns
266 -------
267 isEqual : bool
268 `True` if the fields are equal, `False` otherwise.
269
270 Notes
271 -----
272 Floating point comparisons are performed by `numpy.allclose`.
273 """
274 d1 = getattr(instance1, self.namenamenamename)
275 d2 = getattr(instance2, self.namenamenamename)
276 name = getComparisonName(
277 _joinNamePath(instance1._name, self.namenamenamename), _joinNamePath(instance2._name, self.namenamenamename)
278 )
279 if not compareScalars("keys for %s" % name, set(d1.keys()), set(d2.keys()), output=output):
280 return False
281 equal = True
282 for k, v1 in d1.items():
283 v2 = d2[k]
284 result = compareConfigs(
285 f"{name}[{k!r}]", v1, v2, shortcut=shortcut, rtol=rtol, atol=atol, output=output
286 )
287 if not result and shortcut:
288 return False
289 equal = equal and result
290 return equal
table::Key< int > type
Definition Detector.cc:163
__get__(self, instance, owner=None, at=None, label="default")
Definition config.py:720
FieldTypeVar __get__(self, Config instance, Any owner=None, Any at=None, str label="default")
Definition config.py:717
Field[FieldTypeVar] __get__(self, None instance, Any owner=None, Any at=None, str label="default")
Definition config.py:711
_setup(self, doc, dtype, default, check, optional, source, deprecated)
Definition config.py:489
_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")
daf::base::PropertySet * set
Definition fits.cc:927