LSST Applications g070148d5b3+33e5256705,g0d53e28543+25c8b88941,g0da5cf3356+2dd1178308,g1081da9e2a+62d12e78cb,g17e5ecfddb+7e422d6136,g1c76d35bf8+ede3a706f7,g295839609d+225697d880,g2e2c1a68ba+cc1f6f037e,g2ffcdf413f+853cd4dcde,g38293774b4+62d12e78cb,g3b44f30a73+d953f1ac34,g48ccf36440+885b902d19,g4b2f1765b6+7dedbde6d2,g5320a0a9f6+0c5d6105b6,g56b687f8c9+ede3a706f7,g5c4744a4d9+ef6ac23297,g5ffd174ac0+0c5d6105b6,g6075d09f38+66af417445,g667d525e37+2ced63db88,g670421136f+2ced63db88,g71f27ac40c+2ced63db88,g774830318a+463cbe8d1f,g7876bc68e5+1d137996f1,g7985c39107+62d12e78cb,g7fdac2220c+0fd8241c05,g96f01af41f+368e6903a7,g9ca82378b8+2ced63db88,g9d27549199+ef6ac23297,gabe93b2c52+e3573e3735,gb065e2a02a+3dfbe639da,gbc3249ced9+0c5d6105b6,gbec6a3398f+0c5d6105b6,gc9534b9d65+35b9f25267,gd01420fc67+0c5d6105b6,geee7ff78d7+a14128c129,gf63283c776+ede3a706f7,gfed783d017+0c5d6105b6,w.2022.47
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._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, self._config, msg)
51
52 # validate keytype
53 k = _autocast(k, self._field.keytype)
54 if type(k) != self._field.keytype:
55 msg = "Key %r is of type %s, expected type %s" % (k, _typeStr(k), _typeStr(self._field.keytype))
56 raise FieldValidationError(self._field, self._config, msg)
57
58 # validate itemtype
59 dtype = self._field.itemtype
60 if type(x) != self._field.itemtype and x != self._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.itemtype),
66 )
67 raise FieldValidationError(self._field, self._config, msg)
68
69 if at is None:
70 at = getCallStack()
71 name = _joinNamePath(self._config._name, self._field.name, k)
72 oldValue = self._dict.get(k, None)
73 if oldValue is None:
74 if x == dtype:
75 self._dict[k] = dtype(__name=name, __at=at, __label=label)
76 else:
77 self._dict[k] = dtype(__name=name, __at=at, __label=label, **x._storage)
78 if setHistory:
79 self.historyhistory.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.historyhistory.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.historyhistory.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(
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.supportedTypes:
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.keytypekeytype = keytype
183 self.itemtypeitemtype = itemtype
184 self.dictCheckdictCheck = dictCheck
185 self.itemCheckitemCheck = itemCheck
186
187 def rename(self, instance):
188 configDict = self.__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__(instance)
196 if value is not None:
197 for k in value:
198 item = value[k]
199 item.validate()
200 if self.itemCheckitemCheck is not None and not self.itemCheckitemCheck(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__(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__(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__(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__(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:927