LSSTApplications  10.0-2-g4f67435,11.0.rc2+1,11.0.rc2+12,11.0.rc2+3,11.0.rc2+4,11.0.rc2+5,11.0.rc2+6,11.0.rc2+7,11.0.rc2+8
LSSTDataManagementBasePackage
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 import traceback
23 
24 from .config import Config, FieldValidationError, _typeStr, _joinNamePath
25 from .dictField import Dict, DictField
26 from .comparison import compareConfigs, compareScalars, getComparisonName
27 
28 __all__=["ConfigDictField"]
29 
30 class ConfigDict(Dict):
31  """
32  Config-Insternal representation of a dict of config classes
33 
34  Much like Dict, ConfigDict is a custom MutableMapper which tracks the
35  history of changes to any of its items.
36  """
37  def __init__(self, config, field, value, at, label):
38  Dict.__init__(self, config, field, value, at, label, setHistory=False)
39  self.history.append(("Dict initialized", at, label))
40 
41  def __setitem__(self, k, x, at=None, label="setitem", setHistory=True):
42  if self._config._frozen:
43  msg = "Cannot modify a frozen Config. "\
44  "Attempting to set item at key %r to value %s"%(k, x)
45  raise FieldValidationError(self._field, self._config, msg)
46 
47  #validate keytype
48  if type(k) != self._field.keytype:
49  msg = "Key %r is of type %s, expected type %s"%\
50  (k, _typeStr(k), _typeStr(self._field.keytype))
51  raise FieldValidationError(self._field, self._config, msg)
52 
53  #validate itemtype
54  dtype = self._field.itemtype
55  if type(x) != self._field.itemtype and x != self._field.itemtype:
56  msg = "Value %s at key %r is of incorrect type %s. Expected type %s"%\
57  (x, k, _typeStr(x), _typeStr(self._field.itemtype))
58  raise FieldValidationError(self._field, self._config, msg)
59 
60  if at is None:
61  at = traceback.extract_stack()[:-1]
62  name = _joinNamePath(self._config._name, self._field.name, k)
63  oldValue = self._dict.get(k, None)
64  if oldValue is None:
65  if x == dtype:
66  self._dict[k] = dtype(__name=name, __at=at, __label=label)
67  else:
68  self._dict[k] = dtype(__name=name, __at=at, __label=label, **x._storage)
69  if setHistory:
70  self.history.append(("Added item at key %s"%k, at, label))
71  else:
72  if value == dtype:
73  value = dtype()
74  oldValue.update(__at=at, __label=label, **value._storage)
75  if setHistory:
76  self.history.append(("Modified item at key %s"%k, at, label))
77 
78 
79  def __delitem__(self, k, at=None, label="delitem"):
80  if at is None:
81  at = traceback.extract_stack()[:-1]
82  Dict.__delitem__(self, k, at, label, False)
83  self.history.append(("Removed item at key %s"%k, at, label))
84 
85 class ConfigDictField(DictField):
86  """
87  Defines a field which is a mapping between a POD and a config class.
88 
89  This behaves exactly like a DictField with the slight difference that
90  itemtype must be an subclass of Config.
91 
92  This allows config writters to create name-to-config mappings. One use case
93  is for configuring mappings for dataset types in a butler. In this case,
94  the dataset type names are arbitrary and user-selected; the mapping
95  configurations are known and fixed.
96  """
97 
98  DictClass = ConfigDict
99  def __init__(self, doc, keytype, itemtype, default=None, optional=False, dictCheck=None, itemCheck=None):
100  source = traceback.extract_stack(limit=2)[0]
101  self._setup( doc=doc, dtype=ConfigDict, default=default, check=None,
102  optional=optional, source=source)
103  if keytype not in self.supportedTypes:
104  raise ValueError("'keytype' %s is not a supported type"%\
105  _typeStr(keytype))
106  elif not issubclass(itemtype, Config):
107  raise ValueError("'itemtype' %s is not a supported type"%\
108  _typeStr(itemtype))
109  if dictCheck is not None and not hasattr(dictCheck, "__call__"):
110  raise ValueError("'dictCheck' must be callable")
111  if itemCheck is not None and not hasattr(itemCheck, "__call__"):
112  raise ValueError("'itemCheck' must be callable")
113 
114  self.keytype = keytype
115  self.itemtype = itemtype
116  self.dictCheck = dictCheck
117  self.itemCheck = itemCheck
118 
119  def rename(self, instance):
120  configDict = self.__get__(instance)
121  if configDict is not None:
122  for k in configDict:
123  fullname = _joinNamePath(instance._name, self.name, k)
124  configDict[k]._rename(fullname)
125 
126 
127  def validate(self, instance):
128  value = self.__get__(instance)
129  if value is not None:
130  for k in value:
131  item = value[k]
132  item.validate()
133  if self.itemCheck is not None and not self.itemCheck(item):
134  msg="Item at key %r is not a valid value: %s"%(k, item)
135  raise FieldValidationError(self, instance, msg)
136  DictField.validate(self, instance)
137 
138  def toDict(self, instance):
139  configDict = self.__get__(instance)
140  if configDict is None:
141  return None
142 
143  dict_ = {}
144  for k in configDict:
145  dict_[k]= configDict[k].toDict()
146 
147  return dict_
148 
149  def save(self, outfile, instance):
150  configDict = self.__get__(instance)
151  fullname = _joinNamePath(instance._name, self.name)
152  if configDict is None:
153  print >>outfile, "%s=%r"%(fullname, configDict)
154  return
155 
156  print >>outfile, "%s=%r"%(fullname, {})
157  for v in configDict.itervalues():
158  print >>outfile, "%s=%s()"%(v._name, _typeStr(v))
159  v._save(outfile)
160 
161  def freeze(self, instance):
162  configDict = self.__get__(instance)
163  if configDict is not None:
164  for k in configDict:
165  configDict[k].freeze()
166 
167  def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
168  """Helper function for Config.compare; used to compare two fields for equality.
169 
170  @param[in] instance1 LHS Config instance to compare.
171  @param[in] instance2 RHS Config instance to compare.
172  @param[in] shortcut If True, return as soon as an inequality is found.
173  @param[in] rtol Relative tolerance for floating point comparisons.
174  @param[in] atol Absolute tolerance for floating point comparisons.
175  @param[in] output If not None, a callable that takes a string, used (possibly repeatedly)
176  to report inequalities.
177 
178  Floating point comparisons are performed by numpy.allclose; refer to that for details.
179  """
180  d1 = getattr(instance1, self.name)
181  d2 = getattr(instance2, self.name)
182  name = getComparisonName(
183  _joinNamePath(instance1._name, self.name),
184  _joinNamePath(instance2._name, self.name)
185  )
186  if not compareScalars("keys for %s" % name, d1.keys(), d2.keys(), output=output):
187  return False
188  equal = True
189  for k, v1 in d1.iteritems():
190  v2 = d2[k]
191  result = compareConfigs("%s[%r]" % (name, k), v1, v2, shortcut=shortcut,
192  rtol=rtol, atol=atol, output=output)
193  if not result and shortcut:
194  return False
195  equal = equal and result
196  return equal