lsst.pex.config  16.0-6-g0838257+3
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  """
33  Config-Insternal representation of a dict of config classes
34 
35  Much like Dict, ConfigDict is a custom MutableMapper which tracks the
36  history of changes to any of its items.
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  """
89  Defines a field which is a mapping between a POD and a config class.
90 
91  This behaves exactly like a DictField with the slight difference that
92  itemtype must be an subclass of Config.
93 
94  This allows config writters to create name-to-config mappings. One use case
95  is for configuring mappings for dataset types in a butler. In this case,
96  the dataset type names are arbitrary and user-selected; the mapping
97  configurations are known and fixed.
98  """
99 
100  DictClass = ConfigDict
101 
102  def __init__(self, doc, keytype, itemtype, default=None, optional=False, dictCheck=None, itemCheck=None):
103  source = getStackFrame()
104  self._setup(doc=doc, dtype=ConfigDict, default=default, check=None,
105  optional=optional, source=source)
106  if keytype not in self.supportedTypes:
107  raise ValueError("'keytype' %s is not a supported type" %
108  _typeStr(keytype))
109  elif not issubclass(itemtype, Config):
110  raise ValueError("'itemtype' %s is not a supported type" %
111  _typeStr(itemtype))
112  if dictCheck is not None and not hasattr(dictCheck, "__call__"):
113  raise ValueError("'dictCheck' must be callable")
114  if itemCheck is not None and not hasattr(itemCheck, "__call__"):
115  raise ValueError("'itemCheck' must be callable")
116 
117  self.keytype = keytype
118  self.itemtype = itemtype
119  self.dictCheck = dictCheck
120  self.itemCheck = itemCheck
121 
122  def rename(self, instance):
123  configDict = self.__get__(instance)
124  if configDict is not None:
125  for k in configDict:
126  fullname = _joinNamePath(instance._name, self.name, k)
127  configDict[k]._rename(fullname)
128 
129  def validate(self, instance):
130  value = self.__get__(instance)
131  if value is not None:
132  for k in value:
133  item = value[k]
134  item.validate()
135  if self.itemCheck is not None and not self.itemCheck(item):
136  msg = "Item at key %r is not a valid value: %s" % (k, item)
137  raise FieldValidationError(self, instance, msg)
138  DictField.validate(self, instance)
139 
140  def toDict(self, instance):
141  configDict = self.__get__(instance)
142  if configDict is None:
143  return None
144 
145  dict_ = {}
146  for k in configDict:
147  dict_[k] = configDict[k].toDict()
148 
149  return dict_
150 
151  def save(self, outfile, instance):
152  configDict = self.__get__(instance)
153  fullname = _joinNamePath(instance._name, self.name)
154  if configDict is None:
155  outfile.write(u"{}={!r}\n".format(fullname, configDict))
156  return
157 
158  outfile.write(u"{}={!r}\n".format(fullname, {}))
159  for v in configDict.values():
160  outfile.write(u"{}={}()\n".format(v._name, _typeStr(v)))
161  v._save(outfile)
162 
163  def freeze(self, instance):
164  configDict = self.__get__(instance)
165  if configDict is not None:
166  for k in configDict:
167  configDict[k].freeze()
168 
169  def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
170  """Helper function for Config.compare; used to compare two fields for equality.
171 
172  @param[in] instance1 LHS Config instance to compare.
173  @param[in] instance2 RHS Config instance to compare.
174  @param[in] shortcut If True, return as soon as an inequality is found.
175  @param[in] rtol Relative tolerance for floating point comparisons.
176  @param[in] atol Absolute tolerance for floating point comparisons.
177  @param[in] output If not None, a callable that takes a string, used (possibly repeatedly)
178  to report inequalities.
179 
180  Floating point comparisons are performed by numpy.allclose; refer to that for details.
181  """
182  d1 = getattr(instance1, self.name)
183  d2 = getattr(instance2, self.name)
184  name = getComparisonName(
185  _joinNamePath(instance1._name, self.name),
186  _joinNamePath(instance2._name, self.name)
187  )
188  if not compareScalars("keys for %s" % name, set(d1.keys()), set(d2.keys()), output=output):
189  return False
190  equal = True
191  for k, v1 in d1.items():
192  v2 = d2[k]
193  result = compareConfigs("%s[%r]" % (name, k), v1, v2, shortcut=shortcut,
194  rtol=rtol, atol=atol, output=output)
195  if not result and shortcut:
196  return False
197  equal = equal and result
198  return equal
def __init__(self, doc, keytype, itemtype, default=None, optional=False, dictCheck=None, itemCheck=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:67
def getCallStack(skip=0)
Definition: callStack.py:153
def __get__(self, instance, owner=None, at=None, label="default")
Definition: config.py:272
def _setup(self, doc, dtype, default, check, optional, source)
Definition: config.py:173
def getStackFrame(relative=0)
Definition: callStack.py:50
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
Definition: history.py:129
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:41
def getComparisonName(name1, name2)
Definition: comparison.py:35
def __init__(self, config, field, value, at, label)