LSSTApplications  18.0.0+106,18.0.0+50,19.0.0,19.0.0+1,19.0.0+10,19.0.0+11,19.0.0+13,19.0.0+17,19.0.0+2,19.0.0-1-g20d9b18+6,19.0.0-1-g425ff20,19.0.0-1-g5549ca4,19.0.0-1-g580fafe+6,19.0.0-1-g6fe20d0+1,19.0.0-1-g7011481+9,19.0.0-1-g8c57eb9+6,19.0.0-1-gb5175dc+11,19.0.0-1-gdc0e4a7+9,19.0.0-1-ge272bc4+6,19.0.0-1-ge3aa853,19.0.0-10-g448f008b,19.0.0-12-g6990b2c,19.0.0-2-g0d9f9cd+11,19.0.0-2-g3d9e4fb2+11,19.0.0-2-g5037de4,19.0.0-2-gb96a1c4+3,19.0.0-2-gd955cfd+15,19.0.0-3-g2d13df8,19.0.0-3-g6f3c7dc,19.0.0-4-g725f80e+11,19.0.0-4-ga671dab3b+1,19.0.0-4-gad373c5+3,19.0.0-5-ga2acb9c+2,19.0.0-5-gfe96e6c+2,w.2020.01
LSSTDataManagementBasePackage
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 
30 from .config import Config, FieldValidationError, _autocast, _typeStr, _joinNamePath
31 from .dictField import Dict, DictField
32 from .comparison import compareConfigs, compareScalars, getComparisonName
33 from .callStack import getCallStack, getStackFrame
34 
35 
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.history.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. "\
50  "Attempting to set item at key %r to value %s" % (k, x)
51  raise FieldValidationError(self._field, self._config, msg)
52 
53  # validate keytype
54  k = _autocast(k, self._field.keytype)
55  if type(k) != self._field.keytype:
56  msg = "Key %r is of type %s, expected type %s" % \
57  (k, _typeStr(k), _typeStr(self._field.keytype))
58  raise FieldValidationError(self._field, self._config, msg)
59 
60  # validate itemtype
61  dtype = self._field.itemtype
62  if type(x) != self._field.itemtype and x != self._field.itemtype:
63  msg = "Value %s at key %r is of incorrect type %s. Expected type %s" % \
64  (x, k, _typeStr(x), _typeStr(self._field.itemtype))
65  raise FieldValidationError(self._field, self._config, msg)
66 
67  if at is None:
68  at = getCallStack()
69  name = _joinNamePath(self._config._name, self._field.name, k)
70  oldValue = self._dict.get(k, None)
71  if oldValue is None:
72  if x == dtype:
73  self._dict[k] = dtype(__name=name, __at=at, __label=label)
74  else:
75  self._dict[k] = dtype(__name=name, __at=at, __label=label, **x._storage)
76  if setHistory:
77  self.history.append(("Added item at key %s" % k, at, label))
78  else:
79  if x == dtype:
80  x = dtype()
81  oldValue.update(__at=at, __label=label, **x._storage)
82  if setHistory:
83  self.history.append(("Modified item at key %s" % k, at, label))
84 
85  def __delitem__(self, k, at=None, label="delitem"):
86  if at is None:
87  at = getCallStack()
88  Dict.__delitem__(self, k, at, label, False)
89  self.history.append(("Removed item at key %s" % k, at, label))
90 
91 
93  """A configuration field (`~lsst.pex.config.Field` subclass) that is a
94  mapping of keys to `~lsst.pex.config.Config` instances.
95 
96  ``ConfigDictField`` behaves like `DictField` except that the
97  ``itemtype`` must be a `~lsst.pex.config.Config` subclass.
98 
99  Parameters
100  ----------
101  doc : `str`
102  A description of the configuration field.
103  keytype : {`int`, `float`, `complex`, `bool`, `str`}
104  The type of the mapping keys. All keys must have this type.
105  itemtype : `lsst.pex.config.Config`-type
106  The type of the values in the mapping. This must be
107  `~lsst.pex.config.Config` or a subclass.
108  default : optional
109  Unknown.
110  default : ``itemtype``-dtype, optional
111  Default value of this field.
112  optional : `bool`, optional
113  If `True`, this configuration `~lsst.pex.config.Field` is *optional*.
114  Default is `True`.
115  deprecated : None or `str`, optional
116  A description of why this Field is deprecated, including removal date.
117  If not None, the string is appended to the docstring for this Field.
118 
119  Raises
120  ------
121  ValueError
122  Raised if the inputs are invalid:
123 
124  - ``keytype`` or ``itemtype`` arguments are not supported types
125  (members of `ConfigDictField.supportedTypes`.
126  - ``dictCheck`` or ``itemCheck`` is not a callable function.
127 
128  See also
129  --------
130  ChoiceField
131  ConfigChoiceField
132  ConfigField
133  ConfigurableField
134  DictField
135  Field
136  ListField
137  RangeField
138  RegistryField
139 
140  Notes
141  -----
142  You can use ``ConfigDictField`` to create name-to-config mappings. One use
143  case is for configuring mappings for dataset types in a Butler. In this
144  case, the dataset type names are arbitrary and user-selected while the
145  mapping configurations are known and fixed.
146  """
147 
148  DictClass = ConfigDict
149 
150  def __init__(self, doc, keytype, itemtype, default=None, optional=False, dictCheck=None, itemCheck=None,
151  deprecated=None):
152  source = getStackFrame()
153  self._setup(doc=doc, dtype=ConfigDict, default=default, check=None,
154  optional=optional, source=source, deprecated=deprecated)
155  if keytype not in self.supportedTypes:
156  raise ValueError("'keytype' %s is not a supported type" %
157  _typeStr(keytype))
158  elif not issubclass(itemtype, Config):
159  raise ValueError("'itemtype' %s is not a supported type" %
160  _typeStr(itemtype))
161  if dictCheck is not None and not hasattr(dictCheck, "__call__"):
162  raise ValueError("'dictCheck' must be callable")
163  if itemCheck is not None and not hasattr(itemCheck, "__call__"):
164  raise ValueError("'itemCheck' must be callable")
165 
166  self.keytype = keytype
167  self.itemtype = itemtype
168  self.dictCheck = dictCheck
169  self.itemCheck = itemCheck
170 
171  def rename(self, instance):
172  configDict = self.__get__(instance)
173  if configDict is not None:
174  for k in configDict:
175  fullname = _joinNamePath(instance._name, self.name, k)
176  configDict[k]._rename(fullname)
177 
178  def validate(self, instance):
179  value = self.__get__(instance)
180  if value is not None:
181  for k in value:
182  item = value[k]
183  item.validate()
184  if self.itemCheck is not None and not self.itemCheck(item):
185  msg = "Item at key %r is not a valid value: %s" % (k, item)
186  raise FieldValidationError(self, instance, msg)
187  DictField.validate(self, instance)
188 
189  def toDict(self, instance):
190  configDict = self.__get__(instance)
191  if configDict is None:
192  return None
193 
194  dict_ = {}
195  for k in configDict:
196  dict_[k] = configDict[k].toDict()
197 
198  return dict_
199 
200  def save(self, outfile, instance):
201  configDict = self.__get__(instance)
202  fullname = _joinNamePath(instance._name, self.name)
203  if configDict is None:
204  outfile.write(u"{}={!r}\n".format(fullname, configDict))
205  return
206 
207  outfile.write(u"{}={!r}\n".format(fullname, {}))
208  for v in configDict.values():
209  outfile.write(u"{}={}()\n".format(v._name, _typeStr(v)))
210  v._save(outfile)
211 
212  def freeze(self, instance):
213  configDict = self.__get__(instance)
214  if configDict is not None:
215  for k in configDict:
216  configDict[k].freeze()
217 
218  def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
219  """Compare two fields for equality.
220 
221  Used by `lsst.pex.ConfigDictField.compare`.
222 
223  Parameters
224  ----------
225  instance1 : `lsst.pex.config.Config`
226  Left-hand side config instance to compare.
227  instance2 : `lsst.pex.config.Config`
228  Right-hand side config instance to compare.
229  shortcut : `bool`
230  If `True`, this function returns as soon as an inequality if found.
231  rtol : `float`
232  Relative tolerance for floating point comparisons.
233  atol : `float`
234  Absolute tolerance for floating point comparisons.
235  output : callable
236  A callable that takes a string, used (possibly repeatedly) to
237  report inequalities.
238 
239  Returns
240  -------
241  isEqual : bool
242  `True` if the fields are equal, `False` otherwise.
243 
244  Notes
245  -----
246  Floating point comparisons are performed by `numpy.allclose`.
247  """
248  d1 = getattr(instance1, self.name)
249  d2 = getattr(instance2, self.name)
250  name = getComparisonName(
251  _joinNamePath(instance1._name, self.name),
252  _joinNamePath(instance2._name, self.name)
253  )
254  if not compareScalars("keys for %s" % name, set(d1.keys()), set(d2.keys()), output=output):
255  return False
256  equal = True
257  for k, v1 in d1.items():
258  v2 = d2[k]
259  result = compareConfigs("%s[%r]" % (name, k), v1, v2, shortcut=shortcut,
260  rtol=rtol, atol=atol, output=output)
261  if not result and shortcut:
262  return False
263  equal = equal and result
264  return equal
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
Definition: history.py:174
def getCallStack(skip=0)
Definition: callStack.py:175
def compareConfigs(name, c1, c2, shortcut=True, rtol=1E-8, atol=1E-8, output=None)
Definition: comparison.py:111
def __init__(self, doc, keytype, itemtype, default=None, optional=False, dictCheck=None, itemCheck=None, deprecated=None)
std::shared_ptr< FrameSet > append(FrameSet const &first, FrameSet const &second)
Construct a FrameSet that performs two transformations in series.
Definition: functional.cc:33
daf::base::PropertySet * set
Definition: fits.cc:902
def getStackFrame(relative=0)
Definition: callStack.py:58
table::Key< int > type
Definition: Detector.cc:163
def __get__(self, instance, owner=None, at=None, label="default")
Definition: config.py:498
def __delitem__(self, k, at=None, label="delitem")
def __init__(self, config, field, value, at, label)
def getComparisonName(name1, name2)
Definition: comparison.py:40
def compareScalars(name, v1, v2, output, rtol=1E-8, atol=1E-8, dtype=None)
Definition: comparison.py:62
def _setup(self, doc, dtype, default, check, optional, source, deprecated)
Definition: config.py:284
def __setitem__(self, k, x, at=None, label="setitem", setHistory=True)