LSST Applications g0b6bd0c080+a72a5dd7e6,g1182afd7b4+2a019aa3bb,g17e5ecfddb+2b8207f7de,g1d67935e3f+06cf436103,g38293774b4+ac198e9f13,g396055baef+6a2097e274,g3b44f30a73+6611e0205b,g480783c3b1+98f8679e14,g48ccf36440+89c08d0516,g4b93dc025c+98f8679e14,g5c4744a4d9+a302e8c7f0,g613e996a0d+e1c447f2e0,g6c8d09e9e7+25247a063c,g7271f0639c+98f8679e14,g7a9cd813b8+124095ede6,g9d27549199+a302e8c7f0,ga1cf026fa3+ac198e9f13,ga32aa97882+7403ac30ac,ga786bb30fb+7a139211af,gaa63f70f4e+9994eb9896,gabf319e997+ade567573c,gba47b54d5d+94dc90c3ea,gbec6a3398f+06cf436103,gc6308e37c7+07dd123edb,gc655b1545f+ade567573c,gcc9029db3c+ab229f5caf,gd01420fc67+06cf436103,gd877ba84e5+06cf436103,gdb4cecd868+6f279b5b48,ge2d134c3d5+cc4dbb2e3f,ge448b5faa6+86d1ceac1d,gecc7e12556+98f8679e14,gf3ee170dca+25247a063c,gf4ac96e456+ade567573c,gf9f5ea5b4d+ac198e9f13,gff490e6085+8c2580be5c,w.2022.27
LSST Data Management Base Package
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.historyhistoryhistory.append(("Dict initialized", at, label))
46
47 def __setitem__(self, k, x, at=None, label="setitem", setHistory=True):
48 if self._config_config._frozen:
49 msg = "Cannot modify a frozen Config. Attempting to set item at key %r to value %s" % (k, x)
50 raise FieldValidationError(self._field_field, self._config_config, msg)
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 %s, expected type %s" % (k, _typeStr(k), _typeStr(self._field_field.keytype))
56 raise FieldValidationError(self._field_field, self._config_config, msg)
57
58 # validate itemtype
59 dtype = self._field_field.itemtype
60 if type(x) != self._field_field.itemtype and x != self._field_field.itemtype:
61 msg = "Value %s at key %r is of incorrect type %s. Expected type %s" % (
62 x,
63 k,
64 _typeStr(x),
65 _typeStr(self._field_field.itemtype),
66 )
67 raise FieldValidationError(self._field_field, self._config_config, msg)
68
69 if at is None:
70 at = getCallStack()
71 name = _joinNamePath(self._config_config._name, self._field_field.name, k)
72 oldValue = self._dict_dict.get(k, None)
73 if oldValue is None:
74 if x == dtype:
75 self._dict_dict[k] = dtype(__name=name, __at=at, __label=label)
76 else:
77 self._dict_dict[k] = dtype(__name=name, __at=at, __label=label, **x._storage)
78 if setHistory:
79 self.historyhistoryhistory.append(("Added item at key %s" % k, at, label))
80 else:
81 if x == dtype:
82 x = dtype()
83 oldValue.update(__at=at, __label=label, **x._storage)
84 if setHistory:
85 self.historyhistoryhistory.append(("Modified item at key %s" % k, at, label))
86
87 def __delitem__(self, k, at=None, label="delitem"):
88 if at is None:
89 at = getCallStack()
90 Dict.__delitem__(self, k, at, label, False)
91 self.historyhistoryhistory.append(("Removed item at key %s" % k, at, label))
92
93
95 """A configuration field (`~lsst.pex.config.Field` subclass) that is a
96 mapping of keys to `~lsst.pex.config.Config` instances.
97
98 ``ConfigDictField`` behaves like `DictField` except that the
99 ``itemtype`` must be a `~lsst.pex.config.Config` subclass.
100
101 Parameters
102 ----------
103 doc : `str`
104 A description of the configuration field.
105 keytype : {`int`, `float`, `complex`, `bool`, `str`}
106 The type of the mapping keys. All keys must have this type.
107 itemtype : `lsst.pex.config.Config`-type
108 The type of the values in the mapping. This must be
109 `~lsst.pex.config.Config` or a subclass.
110 default : optional
111 Unknown.
112 default : ``itemtype``-dtype, optional
113 Default value of this field.
114 optional : `bool`, optional
115 If `True`, this configuration `~lsst.pex.config.Field` is *optional*.
116 Default is `True`.
117 deprecated : None or `str`, optional
118 A description of why this Field is deprecated, including removal date.
119 If not None, the string is appended to the docstring for this Field.
120
121 Raises
122 ------
123 ValueError
124 Raised if the inputs are invalid:
125
126 - ``keytype`` or ``itemtype`` arguments are not supported types
127 (members of `ConfigDictField.supportedTypes`.
128 - ``dictCheck`` or ``itemCheck`` is not a callable function.
129
130 See also
131 --------
132 ChoiceField
133 ConfigChoiceField
134 ConfigField
135 ConfigurableField
136 DictField
137 Field
138 ListField
139 RangeField
140 RegistryField
141
142 Notes
143 -----
144 You can use ``ConfigDictField`` to create name-to-config mappings. One use
145 case is for configuring mappings for dataset types in a Butler. In this
146 case, the dataset type names are arbitrary and user-selected while the
147 mapping configurations are known and fixed.
148 """
149
150 DictClass = ConfigDict
151
153 self,
154 doc,
155 keytype,
156 itemtype,
157 default=None,
158 optional=False,
159 dictCheck=None,
160 itemCheck=None,
161 deprecated=None,
162 ):
163 source = getStackFrame()
164 self._setup_setup(
165 doc=doc,
166 dtype=ConfigDict,
167 default=default,
168 check=None,
169 optional=optional,
170 source=source,
171 deprecated=deprecated,
172 )
173 if keytype not in self.supportedTypessupportedTypes:
174 raise ValueError("'keytype' %s is not a supported type" % _typeStr(keytype))
175 elif not issubclass(itemtype, Config):
176 raise ValueError("'itemtype' %s is not a supported type" % _typeStr(itemtype))
177 if dictCheck is not None and not hasattr(dictCheck, "__call__"):
178 raise ValueError("'dictCheck' must be callable")
179 if itemCheck is not None and not hasattr(itemCheck, "__call__"):
180 raise ValueError("'itemCheck' must be callable")
181
182 self.keytypekeytypekeytype = keytype
183 self.itemtypeitemtypeitemtype = itemtype
184 self.dictCheckdictCheckdictCheck = dictCheck
185 self.itemCheckitemCheckitemCheck = itemCheck
186
187 def rename(self, instance):
188 configDict = self.__get____get____get____get__(instance)
189 if configDict is not None:
190 for k in configDict:
191 fullname = _joinNamePath(instance._name, self.name, k)
192 configDict[k]._rename(fullname)
193
194 def validate(self, instance):
195 value = self.__get____get____get____get__(instance)
196 if value is not None:
197 for k in value:
198 item = value[k]
199 item.validate()
200 if self.itemCheckitemCheckitemCheck is not None and not self.itemCheckitemCheckitemCheck(item):
201 msg = "Item at key %r is not a valid value: %s" % (k, item)
202 raise FieldValidationError(self, instance, msg)
203 DictField.validate(self, instance)
204
205 def toDict(self, instance):
206 configDict = self.__get____get____get____get__(instance)
207 if configDict is None:
208 return None
209
210 dict_ = {}
211 for k in configDict:
212 dict_[k] = configDict[k].toDict()
213
214 return dict_
215
216 def _collectImports(self, instance, imports):
217 # docstring inherited from Field
218 configDict = self.__get____get____get____get__(instance)
219 if configDict is not None:
220 for v in configDict.values():
221 v._collectImports()
222 imports |= v._imports
223
224 def save(self, outfile, instance):
225 configDict = self.__get____get____get____get__(instance)
226 fullname = _joinNamePath(instance._name, self.name)
227 if configDict is None:
228 outfile.write("{}={!r}\n".format(fullname, configDict))
229 return
230
231 outfile.write("{}={!r}\n".format(fullname, {}))
232 for v in configDict.values():
233 outfile.write("{}={}()\n".format(v._name, _typeStr(v)))
234 v._save(outfile)
235
236 def freeze(self, instance):
237 configDict = self.__get____get____get____get__(instance)
238 if configDict is not None:
239 for k in configDict:
240 configDict[k].freeze()
241
242 def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
243 """Compare two fields for equality.
244
245 Used by `lsst.pex.ConfigDictField.compare`.
246
247 Parameters
248 ----------
249 instance1 : `lsst.pex.config.Config`
250 Left-hand side config instance to compare.
251 instance2 : `lsst.pex.config.Config`
252 Right-hand side config instance to compare.
253 shortcut : `bool`
254 If `True`, this function returns as soon as an inequality if found.
255 rtol : `float`
256 Relative tolerance for floating point comparisons.
257 atol : `float`
258 Absolute tolerance for floating point comparisons.
259 output : callable
260 A callable that takes a string, used (possibly repeatedly) to
261 report inequalities.
262
263 Returns
264 -------
265 isEqual : bool
266 `True` if the fields are equal, `False` otherwise.
267
268 Notes
269 -----
270 Floating point comparisons are performed by `numpy.allclose`.
271 """
272 d1 = getattr(instance1, self.name)
273 d2 = getattr(instance2, self.name)
274 name = getComparisonName(
275 _joinNamePath(instance1._name, self.name), _joinNamePath(instance2._name, self.name)
276 )
277 if not compareScalars("keys for %s" % name, set(d1.keys()), set(d2.keys()), output=output):
278 return False
279 equal = True
280 for k, v1 in d1.items():
281 v2 = d2[k]
282 result = compareConfigs(
283 "%s[%r]" % (name, k), v1, v2, shortcut=shortcut, rtol=rtol, atol=atol, output=output
284 )
285 if not result and shortcut:
286 return False
287 equal = equal and result
288 return equal
table::Key< int > type
Definition: Detector.cc:163
"Field[FieldTypeVar]" __get__(self, None instance, Any owner=None, Any at=None, str label="default")
Definition: config.py:713
def __get__(self, instance, owner=None, at=None, label="default")
Definition: config.py:722
FieldTypeVar __get__(self, "Config" instance, Any owner=None, Any at=None, str label="default")
Definition: config.py:719
def _setup(self, doc, dtype, default, check, optional, source, deprecated)
Definition: config.py:494
def __init__(self, doc, keytype, itemtype, default=None, optional=False, dictCheck=None, itemCheck=None, deprecated=None)
def __delitem__(self, k, at=None, label="delitem")
def __setitem__(self, k, x, at=None, label="setitem", setHistory=True)
def __init__(self, config, field, value, at, label)
daf::base::PropertySet * set
Definition: fits.cc:912
std::shared_ptr< FrameSet > append(FrameSet const &first, FrameSet const &second)
Construct a FrameSet that performs two transformations in series.
Definition: functional.cc:33
def getCallStack(skip=0)
Definition: callStack.py:174
def getStackFrame(relative=0)
Definition: callStack.py:58
def compareConfigs(name, c1, c2, shortcut=True, rtol=1e-8, atol=1e-8, output=None)
Definition: comparison.py:111
def compareScalars(name, v1, v2, output, rtol=1e-8, atol=1e-8, dtype=None)
Definition: comparison.py:62
def getComparisonName(name1, name2)
Definition: comparison.py:40
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
Definition: history.py:174