LSST Applications g180d380827+0f66a164bb,g2079a07aa2+86d27d4dc4,g2305ad1205+7d304bc7a0,g29320951ab+500695df56,g2bbee38e9b+0e5473021a,g337abbeb29+0e5473021a,g33d1c0ed96+0e5473021a,g3a166c0a6a+0e5473021a,g3ddfee87b4+e42ea45bea,g48712c4677+36a86eeaa5,g487adcacf7+2dd8f347ac,g50ff169b8f+96c6868917,g52b1c1532d+585e252eca,g591dd9f2cf+c70619cc9d,g5a732f18d5+53520f316c,g5ea96fc03c+341ea1ce94,g64a986408d+f7cd9c7162,g858d7b2824+f7cd9c7162,g8a8a8dda67+585e252eca,g99cad8db69+469ab8c039,g9ddcbc5298+9a081db1e4,ga1e77700b3+15fc3df1f7,gb0e22166c9+60f28cb32d,gba4ed39666+c2a2e4ac27,gbb8dafda3b+c92fc63c7e,gbd866b1f37+f7cd9c7162,gc120e1dc64+02c66aa596,gc28159a63d+0e5473021a,gc3e9b769f7+b0068a2d9f,gcf0d15dbbd+e42ea45bea,gdaeeff99f8+f9a426f77a,ge6526c86ff+84383d05b3,ge79ae78c31+0e5473021a,gee10cc3b42+585e252eca,gff1a9f87cc+f7cd9c7162,w.2024.17
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 = "Key {!r} is of type {}, expected type {}".format(
69 k, _typeStr(k), _typeStr(self._field_field.keytype)
70 )
72
73 # validate itemtype
74 dtype = self._field_field.itemtype
75 if type(x) is not self._field_field.itemtype and x != self._field_field.itemtype:
76 msg = "Value {} at key {!r} is of incorrect type {}. Expected type {}".format(
77 x,
78 k,
79 _typeStr(x),
80 _typeStr(self._field_field.itemtype),
81 )
83
84 if at is None:
85 at = getCallStack()
86 name = _joinNamePath(self._config_config_config._name, self._field_field.name, k)
87 oldValue = self._dict.get(k, None)
88 if oldValue is None:
89 if x == dtype:
90 self._dict[k] = dtype(__name=name, __at=at, __label=label)
91 else:
92 self._dict[k] = dtype(__name=name, __at=at, __label=label, **x._storage)
93 if setHistory:
94 self.historyhistory.append(("Added item at key %s" % k, at, label))
95 else:
96 if x == dtype:
97 x = dtype()
98 oldValue.update(__at=at, __label=label, **x._storage)
99 if setHistory:
100 self.historyhistory.append(("Modified item at key %s" % k, at, label))
101
102 def __delitem__(self, k, at=None, label="delitem"):
103 if at is None:
104 at = getCallStack()
105 Dict.__delitem__(self, k, at, label, False)
106 self.historyhistory.append(("Removed item at key %s" % k, at, label))
107
108
110 """A configuration field (`~lsst.pex.config.Field` subclass) that is a
111 mapping of keys to `~lsst.pex.config.Config` instances.
112
113 ``ConfigDictField`` behaves like `DictField` except that the
114 ``itemtype`` must be a `~lsst.pex.config.Config` subclass.
115
116 Parameters
117 ----------
118 doc : `str`
119 A description of the configuration field.
120 keytype : {`int`, `float`, `complex`, `bool`, `str`}
121 The type of the mapping keys. All keys must have this type.
122 itemtype : `lsst.pex.config.Config`-type
123 The type of the values in the mapping. This must be
124 `~lsst.pex.config.Config` or a subclass.
125 default : optional
126 Unknown.
127 default : ``itemtype``-dtype, optional
128 Default value of this field.
129 optional : `bool`, optional
130 If `True`, this configuration `~lsst.pex.config.Field` is *optional*.
131 Default is `True`.
132 dictCheck : `~collections.abc.Callable` or `None`, optional
133 Callable to check a dict.
134 itemCheck : `~collections.abc.Callable` or `None`, optional
135 Callable to check an item.
136 deprecated : None or `str`, optional
137 A description of why this Field is deprecated, including removal date.
138 If not None, the string is appended to the docstring for this Field.
139
140 Raises
141 ------
142 ValueError
143 Raised if the inputs are invalid:
144
145 - ``keytype`` or ``itemtype`` arguments are not supported types
146 (members of `ConfigDictField.supportedTypes`.
147 - ``dictCheck`` or ``itemCheck`` is not a callable function.
148
149 See Also
150 --------
151 ChoiceField
152 ConfigChoiceField
153 ConfigField
154 ConfigurableField
155 DictField
156 Field
157 ListField
158 RangeField
159 RegistryField
160
161 Notes
162 -----
163 You can use ``ConfigDictField`` to create name-to-config mappings. One use
164 case is for configuring mappings for dataset types in a Butler. In this
165 case, the dataset type names are arbitrary and user-selected while the
166 mapping configurations are known and fixed.
167 """
168
169 DictClass = ConfigDict
170
172 self,
173 doc,
174 keytype,
175 itemtype,
176 default=None,
177 optional=False,
178 dictCheck=None,
179 itemCheck=None,
180 deprecated=None,
181 ):
182 source = getStackFrame()
183 self._setup(
184 doc=doc,
185 dtype=ConfigDict,
186 default=default,
187 check=None,
188 optional=optional,
189 source=source,
190 deprecated=deprecated,
191 )
192 if keytype not in self.supportedTypes:
193 raise ValueError("'keytype' %s is not a supported type" % _typeStr(keytype))
194 elif not issubclass(itemtype, Config):
195 raise ValueError("'itemtype' %s is not a supported type" % _typeStr(itemtype))
196 if dictCheck is not None and not hasattr(dictCheck, "__call__"):
197 raise ValueError("'dictCheck' must be callable")
198 if itemCheck is not None and not hasattr(itemCheck, "__call__"):
199 raise ValueError("'itemCheck' must be callable")
200
201 self.keytypekeytype = keytype
202 self.itemtypeitemtype = itemtype
203 self.dictCheckdictCheck = dictCheck
204 self.itemCheckitemCheck = itemCheck
205
206 def rename(self, instance):
207 configDict = self.__get____get____get__(instance)
208 if configDict is not None:
209 for k in configDict:
210 fullname = _joinNamePath(instance._name, self.namenamenamename, k)
211 configDict[k]._rename(fullname)
212
213 def validate(self, instance):
214 value = self.__get____get____get__(instance)
215 if value is not None:
216 for k in value:
217 item = value[k]
218 item.validate()
219 if self.itemCheckitemCheck is not None and not self.itemCheckitemCheck(item):
220 msg = f"Item at key {k!r} is not a valid value: {item}"
221 raise FieldValidationError(self, instance, msg)
222 DictField.validate(self, instance)
223
224 def toDict(self, instance):
225 configDict = self.__get____get____get__(instance)
226 if configDict is None:
227 return None
228
229 dict_ = {}
230 for k in configDict:
231 dict_[k] = configDict[k].toDict()
232
233 return dict_
234
235 def _collectImports(self, instance, imports):
236 # docstring inherited from Field
237 configDict = self.__get____get____get__(instance)
238 if configDict is not None:
239 for v in configDict.values():
240 v._collectImports()
241 imports |= v._imports
242
243 def save(self, outfile, instance):
244 configDict = self.__get____get____get__(instance)
245 fullname = _joinNamePath(instance._name, self.namenamenamename)
246 if configDict is None:
247 outfile.write(f"{fullname}={configDict!r}\n")
248 return
249
250 outfile.write(f"{fullname}={{}}\n")
251 for v in configDict.values():
252 outfile.write(f"{v._name}={_typeStr(v)}()\n")
253 v._save(outfile)
254
255 def freeze(self, instance):
256 configDict = self.__get____get____get__(instance)
257 if configDict is not None:
258 for k in configDict:
259 configDict[k].freeze()
260
261 def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
262 """Compare two fields for equality.
263
264 Used by `lsst.pex.ConfigDictField.compare`.
265
266 Parameters
267 ----------
268 instance1 : `lsst.pex.config.Config`
269 Left-hand side config instance to compare.
270 instance2 : `lsst.pex.config.Config`
271 Right-hand side config instance to compare.
272 shortcut : `bool`
273 If `True`, this function returns as soon as an inequality if found.
274 rtol : `float`
275 Relative tolerance for floating point comparisons.
276 atol : `float`
277 Absolute tolerance for floating point comparisons.
278 output : callable
279 A callable that takes a string, used (possibly repeatedly) to
280 report inequalities.
281
282 Returns
283 -------
284 isEqual : bool
285 `True` if the fields are equal, `False` otherwise.
286
287 Notes
288 -----
289 Floating point comparisons are performed by `numpy.allclose`.
290 """
291 d1 = getattr(instance1, self.namenamenamename)
292 d2 = getattr(instance2, self.namenamenamename)
293 name = getComparisonName(
294 _joinNamePath(instance1._name, self.namenamenamename), _joinNamePath(instance2._name, self.namenamenamename)
295 )
296 if not compareScalars("keys for %s" % name, set(d1.keys()), set(d2.keys()), output=output):
297 return False
298 equal = True
299 for k, v1 in d1.items():
300 v2 = d2[k]
301 result = compareConfigs(
302 f"{name}[{k!r}]", v1, v2, shortcut=shortcut, rtol=rtol, atol=atol, output=output
303 )
304 if not result and shortcut:
305 return False
306 equal = equal and result
307 return equal
__get__(self, instance, owner=None, at=None, label="default")
Definition config.py:728
FieldTypeVar __get__(self, Config instance, Any owner=None, Any at=None, str label="default")
Definition config.py:725
Field[FieldTypeVar] __get__(self, None instance, Any owner=None, Any at=None, str label="default")
Definition config.py:719
_setup(self, doc, dtype, default, check, optional, source, deprecated)
Definition config.py:497
_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:931