lsst.pex.config  18.0.0-1-gc037db8+1
configDictField.py
Go to the documentation of this file.
1 #
2 # LSST Data Management System
3 # Copyright 2008, 2009, 2010 LSST Corporation.
4 #
5 # This product includes software developed by the
6 # LSST Project (http://www.lsst.org/).
7 #
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the LSST License Statement and
19 # the GNU General Public License along with this program. If not,
20 # see <http://www.lsstcorp.org/LegalNotices/>.
21 #
22 
23 from .config import Config, FieldValidationError, _autocast, _typeStr, _joinNamePath
24 from .dictField import Dict, DictField
25 from .comparison import compareConfigs, compareScalars, getComparisonName
26 from .callStack import getCallStack, getStackFrame
27 
28 __all__ = ["ConfigDictField"]
29 
30 
32  """Internal representation of a dictionary of configuration classes.
33 
34  Much like `Dict`, `ConfigDict` is a custom `MutableMapper` which tracks
35  the history of changes to any of its items.
36  """
37 
38  def __init__(self, config, field, value, at, label):
39  Dict.__init__(self, config, field, value, at, label, setHistory=False)
40  self.history.append(("Dict initialized", at, label))
41 
42  def __setitem__(self, k, x, at=None, label="setitem", setHistory=True):
43  if self._config._frozen:
44  msg = "Cannot modify a frozen Config. "\
45  "Attempting to set item at key %r to value %s" % (k, x)
46  raise FieldValidationError(self._field, self._config, msg)
47 
48  # validate keytype
49  k = _autocast(k, self._field.keytype)
50  if type(k) != self._field.keytype:
51  msg = "Key %r is of type %s, expected type %s" % \
52  (k, _typeStr(k), _typeStr(self._field.keytype))
53  raise FieldValidationError(self._field, self._config, msg)
54 
55  # validate itemtype
56  dtype = self._field.itemtype
57  if type(x) != self._field.itemtype and x != self._field.itemtype:
58  msg = "Value %s at key %r is of incorrect type %s. Expected type %s" % \
59  (x, k, _typeStr(x), _typeStr(self._field.itemtype))
60  raise FieldValidationError(self._field, self._config, msg)
61 
62  if at is None:
63  at = getCallStack()
64  name = _joinNamePath(self._config._name, self._field.name, k)
65  oldValue = self._dict.get(k, None)
66  if oldValue is None:
67  if x == dtype:
68  self._dict[k] = dtype(__name=name, __at=at, __label=label)
69  else:
70  self._dict[k] = dtype(__name=name, __at=at, __label=label, **x._storage)
71  if setHistory:
72  self.history.append(("Added item at key %s" % k, at, label))
73  else:
74  if x == dtype:
75  x = dtype()
76  oldValue.update(__at=at, __label=label, **x._storage)
77  if setHistory:
78  self.history.append(("Modified item at key %s" % k, at, label))
79 
80  def __delitem__(self, k, at=None, label="delitem"):
81  if at is None:
82  at = getCallStack()
83  Dict.__delitem__(self, k, at, label, False)
84  self.history.append(("Removed item at key %s" % k, at, label))
85 
86 
88  """A configuration field (`~lsst.pex.config.Field` subclass) that is a
89  mapping of keys to `~lsst.pex.config.Config` instances.
90 
91  ``ConfigDictField`` behaves like `DictField` except that the
92  ``itemtype`` must be a `~lsst.pex.config.Config` subclass.
93 
94  Parameters
95  ----------
96  doc : `str`
97  A description of the configuration field.
98  keytype : {`int`, `float`, `complex`, `bool`, `str`}
99  The type of the mapping keys. All keys must have this type.
100  itemtype : `lsst.pex.config.Config`-type
101  The type of the values in the mapping. This must be
102  `~lsst.pex.config.Config` or a subclass.
103  default : optional
104  Unknown.
105  default : ``itemtype``-dtype, optional
106  Default value of this field.
107  optional : `bool`, optional
108  If `True`, this configuration `~lsst.pex.config.Field` is *optional*.
109  Default is `True`.
110  deprecated : None or `str`, optional
111  A description of why this Field is deprecated, including removal date.
112  If not None, the string is appended to the docstring for this Field.
113 
114  Raises
115  ------
116  ValueError
117  Raised if the inputs are invalid:
118 
119  - ``keytype`` or ``itemtype`` arguments are not supported types
120  (members of `ConfigDictField.supportedTypes`.
121  - ``dictCheck`` or ``itemCheck`` is not a callable function.
122 
123  See also
124  --------
125  ChoiceField
126  ConfigChoiceField
127  ConfigField
128  ConfigurableField
129  DictField
130  Field
131  ListField
132  RangeField
133  RegistryField
134 
135  Notes
136  -----
137  You can use ``ConfigDictField`` to create name-to-config mappings. One use
138  case is for configuring mappings for dataset types in a Butler. In this
139  case, the dataset type names are arbitrary and user-selected while the
140  mapping configurations are known and fixed.
141  """
142 
143  DictClass = ConfigDict
144 
145  def __init__(self, doc, keytype, itemtype, default=None, optional=False, dictCheck=None, itemCheck=None,
146  deprecated=None):
147  source = getStackFrame()
148  self._setup(doc=doc, dtype=ConfigDict, default=default, check=None,
149  optional=optional, source=source, deprecated=deprecated)
150  if keytype not in self.supportedTypes:
151  raise ValueError("'keytype' %s is not a supported type" %
152  _typeStr(keytype))
153  elif not issubclass(itemtype, Config):
154  raise ValueError("'itemtype' %s is not a supported type" %
155  _typeStr(itemtype))
156  if dictCheck is not None and not hasattr(dictCheck, "__call__"):
157  raise ValueError("'dictCheck' must be callable")
158  if itemCheck is not None and not hasattr(itemCheck, "__call__"):
159  raise ValueError("'itemCheck' must be callable")
160 
161  self.keytype = keytype
162  self.itemtype = itemtype
163  self.dictCheck = dictCheck
164  self.itemCheck = itemCheck
165 
166  def rename(self, instance):
167  configDict = self.__get__(instance)
168  if configDict is not None:
169  for k in configDict:
170  fullname = _joinNamePath(instance._name, self.name, k)
171  configDict[k]._rename(fullname)
172 
173  def validate(self, instance):
174  value = self.__get__(instance)
175  if value is not None:
176  for k in value:
177  item = value[k]
178  item.validate()
179  if self.itemCheck is not None and not self.itemCheck(item):
180  msg = "Item at key %r is not a valid value: %s" % (k, item)
181  raise FieldValidationError(self, instance, msg)
182  DictField.validate(self, instance)
183 
184  def toDict(self, instance):
185  configDict = self.__get__(instance)
186  if configDict is None:
187  return None
188 
189  dict_ = {}
190  for k in configDict:
191  dict_[k] = configDict[k].toDict()
192 
193  return dict_
194 
195  def save(self, outfile, instance):
196  configDict = self.__get__(instance)
197  fullname = _joinNamePath(instance._name, self.name)
198  if configDict is None:
199  outfile.write(u"{}={!r}\n".format(fullname, configDict))
200  return
201 
202  outfile.write(u"{}={!r}\n".format(fullname, {}))
203  for v in configDict.values():
204  outfile.write(u"{}={}()\n".format(v._name, _typeStr(v)))
205  v._save(outfile)
206 
207  def freeze(self, instance):
208  configDict = self.__get__(instance)
209  if configDict is not None:
210  for k in configDict:
211  configDict[k].freeze()
212 
213  def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
214  """Compare two fields for equality.
215 
216  Used by `lsst.pex.ConfigDictField.compare`.
217 
218  Parameters
219  ----------
220  instance1 : `lsst.pex.config.Config`
221  Left-hand side config instance to compare.
222  instance2 : `lsst.pex.config.Config`
223  Right-hand side config instance to compare.
224  shortcut : `bool`
225  If `True`, this function returns as soon as an inequality if found.
226  rtol : `float`
227  Relative tolerance for floating point comparisons.
228  atol : `float`
229  Absolute tolerance for floating point comparisons.
230  output : callable
231  A callable that takes a string, used (possibly repeatedly) to report inequalities.
232 
233  Returns
234  -------
235  isEqual : bool
236  `True` if the fields are equal, `False` otherwise.
237 
238  Notes
239  -----
240  Floating point comparisons are performed by `numpy.allclose`.
241  """
242  d1 = getattr(instance1, self.name)
243  d2 = getattr(instance2, self.name)
244  name = getComparisonName(
245  _joinNamePath(instance1._name, self.name),
246  _joinNamePath(instance2._name, self.name)
247  )
248  if not compareScalars("keys for %s" % name, set(d1.keys()), set(d2.keys()), output=output):
249  return False
250  equal = True
251  for k, v1 in d1.items():
252  v2 = d2[k]
253  result = compareConfigs("%s[%r]" % (name, k), v1, v2, shortcut=shortcut,
254  rtol=rtol, atol=atol, output=output)
255  if not result and shortcut:
256  return False
257  equal = equal and result
258  return equal
def __init__(self, doc, keytype, itemtype, default=None, optional=False, dictCheck=None, itemCheck=None, deprecated=None)
def __setitem__(self, k, x, at=None, label="setitem", setHistory=True)
def compareConfigs(name, c1, c2, shortcut=True, rtol=1E-8, atol=1E-8, output=None)
Definition: comparison.py:105
def getCallStack(skip=0)
Definition: callStack.py:169
def __get__(self, instance, owner=None, at=None, label="default")
Definition: config.py:488
def getStackFrame(relative=0)
Definition: callStack.py:52
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
Definition: history.py:168
def __delitem__(self, k, at=None, label="delitem")
def compareScalars(name, v1, v2, output, rtol=1E-8, atol=1E-8, dtype=None)
Definition: comparison.py:56
def _setup(self, doc, dtype, default, check, optional, source, deprecated)
Definition: config.py:278
def getComparisonName(name1, name2)
Definition: comparison.py:34
def __init__(self, config, field, value, at, label)