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