LSSTApplications  8.0.0.0+107,8.0.0.1+13,9.1+18,9.2,master-g084aeec0a4,master-g0aced2eed8+6,master-g15627eb03c,master-g28afc54ef9,master-g3391ba5ea0,master-g3d0fb8ae5f,master-g4432ae2e89+36,master-g5c3c32f3ec+17,master-g60f1e072bb+1,master-g6a3ac32d1b,master-g76a88a4307+1,master-g7bce1f4e06+57,master-g8ff4092549+31,master-g98e65bf68e,master-ga6b77976b1+53,master-gae20e2b580+3,master-gb584cd3397+53,master-gc5448b162b+1,master-gc54cf9771d,master-gc69578ece6+1,master-gcbf758c456+22,master-gcec1da163f+63,master-gcf15f11bcc,master-gd167108223,master-gf44c96c709
LSSTDataManagementBasePackage
dictField.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 .comparison import *
27 
28 __all__=["DictField"]
29 
30 class Dict(collections.MutableMapping):
31  """
32  Config-Internal mapping container
33  Emulates a dict, but adds validation and provenance.
34  """
35 
36  def __init__(self, config, field, value, at, label, setHistory=True):
37  self._field = field
38  self._config = config
39  self._dict = {}
40  self._history = self._config._history.setdefault(self._field.name, [])
41  self.__doc__=field.doc
42  if value is not None:
43  try:
44  for k in value:
45  #do not set history per-item
46  self.__setitem__(k, value[k], at=at, label=label, setHistory=False)
47  except TypeError, e:
48  msg = "Value %s is of incorrect type %s. Mapping type expected."%\
49  (value, _typeStr(value))
50  raise FieldValidationError(self._field, self._config, msg)
51  if setHistory:
52  self._history.append((dict(self._dict), at, label))
53 
54 
55  """
56  Read-only history
57  """
58  history = property(lambda x: x._history)
59 
60  def __getitem__(self, k): return self._dict[k]
61 
62  def __len__(self): return len(self._dict)
63 
64  def __iter__(self): return iter(self._dict)
65 
66  def __contains__(self, k): return k in self._dict
67 
68  def __setitem__(self, k, x, at=None, label="setitem", setHistory=True):
69  if self._config._frozen:
70  msg = "Cannot modify a frozen Config. "\
71  "Attempting to set item at key %r to value %s"%(k, x)
72  raise FieldValidationError(self._field, self._config, msg)
73 
74  #validate keytype
75  k = _autocast(k, self._field.keytype)
76  if type(k) != self._field.keytype:
77  msg = "Key %r is of type %s, expected type %s"%\
78  (k, _typeStr(k), _typeStr(self._field.keytype))
79  raise FieldValidationError(self._field, self._config, msg)
80 
81  #validate itemtype
82  x = _autocast(x, self._field.itemtype)
83  if type(x) != self._field.itemtype and x is not None:
84  msg ="Value %s at key %r is of incorrect type %s. Expected type %s"%\
85  (x, k, _typeStr(x), _typeStr(self._field.itemtype))
86  raise FieldValidationError(self._field, self._config, msg)
87 
88  #validate item using itemcheck
89  if self._field.itemCheck is not None and not self._field.itemCheck(x):
90  msg="Item at key %r is not a valid value: %s"%(k, x)
91  raise FieldValidationError(self._field, self._config, msg)
92 
93  if at is None:
94  at = traceback.extract_stack()[:-1]
95 
96  self._dict[k]=x
97  if setHistory:
98  self._history.append((dict(self._dict), at, label))
99 
100  def __delitem__(self, k, at=None, label="delitem", setHistory=True):
101  if self._config._frozen:
102  raise FieldValidationError(self._field, self._config,
103  "Cannot modify a frozen Config")
104 
105  del self._dict[k]
106  if setHistory:
107  if at is None:
108  at = traceback.extract_stack()[:-1]
109  self._history.append((dict(self._dict), at, label))
110 
111  def __repr__(self): return repr(self._dict)
112 
113  def __str__(self): return str(self._dict)
114 
115  def __setattr__(self, attr, value, at=None, label="assignment"):
116  if hasattr(getattr(self.__class__, attr, None), '__set__'):
117  # This allows properties to work.
118  object.__setattr__(self, attr, value)
119  elif attr in self.__dict__ or attr in ["_field", "_config", "_history", "_dict", "__doc__"]:
120  # This allows specific private attributes to work.
121  object.__setattr__(self, attr, value)
122  else:
123  # We throw everything else.
124  msg = "%s has no attribute %s"%(_typeStr(self._field), attr)
125  raise FieldValidationError(self._field, self._config, msg)
126 
127 
128 class DictField(Field):
129  """
130  Defines a field which is a mapping of values
131 
132  Both key and item types are restricted to builtin POD types:
133  (int, float, complex, bool, str)
134 
135  Users can provide two check functions:
136  dictCheck: used to validate the dict as a whole, and
137  itemCheck: used to validate each item individually
138 
139  For example to define a field which is a mapping from names to int values:
140 
141  class MyConfig(Config):
142  field = DictField(
143  doc="example string-to-int mapping field",
144  keytype=str, itemtype=int,
145  default= {})
146  """
147  DictClass = Dict
148 
149  def __init__(self, doc, keytype, itemtype, default=None, optional=False, dictCheck=None, itemCheck=None):
150  source = traceback.extract_stack(limit=2)[0]
151  self._setup( doc=doc, dtype=Dict, default=default, check=None,
152  optional=optional, source=source)
153  if keytype not in self.supportedTypes:
154  raise ValueError("'keytype' %s is not a supported type"%\
155  _typeStr(keytype))
156  elif itemtype not in self.supportedTypes:
157  raise ValueError("'itemtype' %s is not a supported type"%\
158  _typeStr(itemtype))
159  if dictCheck is not None and not hasattr(dictCheck, "__call__"):
160  raise ValueError("'dictCheck' must be callable")
161  if itemCheck is not None and not hasattr(itemCheck, "__call__"):
162  raise ValueError("'itemCheck' must be callable")
163 
164  self.keytype = keytype
165  self.itemtype = itemtype
166  self.dictCheck = dictCheck
167  self.itemCheck = itemCheck
168 
169  def validate(self, instance):
170  """
171  DictField validation ensures that non-optional fields are not None,
172  and that non-None values comply with dictCheck.
173  Individual Item checks are applied at set time and are not re-checked.
174  """
175  Field.validate(self, instance)
176  value = self.__get__(instance)
177  if value is not None and self.dictCheck is not None \
178  and not self.dictCheck(value):
179  msg = "%s is not a valid value"%str(value)
180  raise FieldValidationError(self, instance, msg)
181 
182 
183  def __set__(self, instance, value, at=None, label="assignment"):
184  if instance._frozen:
185  msg = "Cannot modify a frozen Config. "\
186  "Attempting to set field to value %s"%value
187  raise FieldValidationError(self, instance, msg)
188 
189  if at is None:
190  at = traceback.extract_stack()[:-1]
191  if value is not None:
192  value = self.DictClass(instance, self, value, at=at, label=label)
193  else:
194  history = instance._history.setdefault(self.name, [])
195  history.append((value, at, label))
196 
197  instance._storage[self.name] = value
198 
199  def toDict(self, instance):
200  value = self.__get__(instance)
201  return dict(value) if value is not None else None
202 
203  def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
204  """Helper function for Config.compare; used to compare two fields for equality.
205 
206  @param[in] instance1 LHS Config instance to compare.
207  @param[in] instance2 RHS Config instance to compare.
208  @param[in] shortcut If True, return as soon as an inequality is found.
209  @param[in] rtol Relative tolerance for floating point comparisons.
210  @param[in] atol Absolute tolerance for floating point comparisons.
211  @param[in] output If not None, a callable that takes a string, used (possibly repeatedly)
212  to report inequalities.
213 
214  Floating point comparisons are performed by numpy.allclose; refer to that for details.
215  """
216  d1 = getattr(instance1, self.name)
217  d2 = getattr(instance2, self.name)
218  name = getComparisonName(
219  _joinNamePath(instance1._name, self.name),
220  _joinNamePath(instance2._name, self.name)
221  )
222  if not compareScalars("isnone for %s" % name, d1 is None, d2 is None, output=output):
223  return False
224  if d1 is None and d2 is None:
225  return True
226  if not compareScalars("keys for %s" % name, d1.keys(), d2.keys(), output=output):
227  return False
228  equal = True
229  for k, v1 in d1.iteritems():
230  v2 = d2[k]
231  result = compareScalars("%s[%r]" % (name, k), v1, v2, dtype=self.itemtype,
232  rtol=rtol, atol=atol, output=output)
233  if not result and shortcut:
234  return False
235  equal = equal and result
236  return equal