LSSTApplications  17.0+11,17.0+34,17.0+56,17.0+57,17.0+59,17.0+7,17.0-1-g377950a+33,17.0.1-1-g114240f+2,17.0.1-1-g4d4fbc4+28,17.0.1-1-g55520dc+49,17.0.1-1-g5f4ed7e+52,17.0.1-1-g6dd7d69+17,17.0.1-1-g8de6c91+11,17.0.1-1-gb9095d2+7,17.0.1-1-ge9fec5e+5,17.0.1-1-gf4e0155+55,17.0.1-1-gfc65f5f+50,17.0.1-1-gfc6fb1f+20,17.0.1-10-g87f9f3f+1,17.0.1-11-ge9de802+16,17.0.1-16-ga14f7d5c+4,17.0.1-17-gc79d625+1,17.0.1-17-gdae4c4a+8,17.0.1-2-g26618f5+29,17.0.1-2-g54f2ebc+9,17.0.1-2-gf403422+1,17.0.1-20-g2ca2f74+6,17.0.1-23-gf3eadeb7+1,17.0.1-3-g7e86b59+39,17.0.1-3-gb5ca14a,17.0.1-3-gd08d533+40,17.0.1-30-g596af8797,17.0.1-4-g59d126d+4,17.0.1-4-gc69c472+5,17.0.1-6-g5afd9b9+4,17.0.1-7-g35889ee+1,17.0.1-7-gc7c8782+18,17.0.1-9-gc4bbfb2+3,w.2019.22
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 
23 __all__ = ["DictField"]
24 
25 import collections.abc
26 
27 from .config import Field, FieldValidationError, _typeStr, _autocast, _joinNamePath
28 from .comparison import getComparisonName, compareScalars
29 from .callStack import getCallStack, getStackFrame
30 
31 
32 class Dict(collections.abc.MutableMapping):
33  """An internal mapping container.
34 
35  This class emulates a `dict`, but adds validation and provenance.
36  """
37 
38  def __init__(self, config, field, value, at, label, setHistory=True):
39  self._field = field
40  self._config = config
41  self._dict = {}
42  self._history = self._config._history.setdefault(self._field.name, [])
43  self.__doc__ = field.doc
44  if value is not None:
45  try:
46  for k in value:
47  # do not set history per-item
48  self.__setitem__(k, value[k], at=at, label=label, setHistory=False)
49  except TypeError:
50  msg = "Value %s is of incorrect type %s. Mapping type expected." % \
51  (value, _typeStr(value))
52  raise FieldValidationError(self._field, self._config, msg)
53  if setHistory:
54  self._history.append((dict(self._dict), at, label))
55 
56  history = property(lambda x: x._history)
57  """History (read-only).
58  """
59 
60  def __getitem__(self, k):
61  return self._dict[k]
62 
63  def __len__(self):
64  return len(self._dict)
65 
66  def __iter__(self):
67  return iter(self._dict)
68 
69  def __contains__(self, k):
70  return k in self._dict
71 
72  def __setitem__(self, k, x, at=None, label="setitem", setHistory=True):
73  if self._config._frozen:
74  msg = "Cannot modify a frozen Config. "\
75  "Attempting to set item at key %r to value %s" % (k, x)
76  raise FieldValidationError(self._field, self._config, msg)
77 
78  # validate keytype
79  k = _autocast(k, self._field.keytype)
80  if type(k) != self._field.keytype:
81  msg = "Key %r is of type %s, expected type %s" % \
82  (k, _typeStr(k), _typeStr(self._field.keytype))
83  raise FieldValidationError(self._field, self._config, msg)
84 
85  # validate itemtype
86  x = _autocast(x, self._field.itemtype)
87  if self._field.itemtype is None:
88  if type(x) not in self._field.supportedTypes and x is not None:
89  msg = "Value %s at key %r is of invalid type %s" % (x, k, _typeStr(x))
90  raise FieldValidationError(self._field, self._config, msg)
91  else:
92  if type(x) != self._field.itemtype and x is not None:
93  msg = "Value %s at key %r is of incorrect type %s. Expected type %s" % \
94  (x, k, _typeStr(x), _typeStr(self._field.itemtype))
95  raise FieldValidationError(self._field, self._config, msg)
96 
97  # validate item using itemcheck
98  if self._field.itemCheck is not None and not self._field.itemCheck(x):
99  msg = "Item at key %r is not a valid value: %s" % (k, x)
100  raise FieldValidationError(self._field, self._config, msg)
101 
102  if at is None:
103  at = getCallStack()
104 
105  self._dict[k] = x
106  if setHistory:
107  self._history.append((dict(self._dict), at, label))
108 
109  def __delitem__(self, k, at=None, label="delitem", setHistory=True):
110  if self._config._frozen:
111  raise FieldValidationError(self._field, self._config,
112  "Cannot modify a frozen Config")
113 
114  del self._dict[k]
115  if setHistory:
116  if at is None:
117  at = getCallStack()
118  self._history.append((dict(self._dict), at, label))
119 
120  def __repr__(self):
121  return repr(self._dict)
122 
123  def __str__(self):
124  return str(self._dict)
125 
126  def __setattr__(self, attr, value, at=None, label="assignment"):
127  if hasattr(getattr(self.__class__, attr, None), '__set__'):
128  # This allows properties to work.
129  object.__setattr__(self, attr, value)
130  elif attr in self.__dict__ or attr in ["_field", "_config", "_history", "_dict", "__doc__"]:
131  # This allows specific private attributes to work.
132  object.__setattr__(self, attr, value)
133  else:
134  # We throw everything else.
135  msg = "%s has no attribute %s" % (_typeStr(self._field), attr)
136  raise FieldValidationError(self._field, self._config, msg)
137 
138 
140  """A configuration field (`~lsst.pex.config.Field` subclass) that maps keys
141  and values.
142 
143  The types of both items and keys are restricted to these builtin types:
144  `int`, `float`, `complex`, `bool`, and `str`). All keys share the same type
145  and all values share the same type. Keys can have a different type from
146  values.
147 
148  Parameters
149  ----------
150  doc : `str`
151  A documentation string that describes the configuration field.
152  keytype : {`int`, `float`, `complex`, `bool`, `str`}
153  The type of the mapping keys. All keys must have this type.
154  itemtype : {`int`, `float`, `complex`, `bool`, `str`}
155  Type of the mapping values.
156  default : `dict`, optional
157  The default mapping.
158  optional : `bool`, optional
159  If `True`, the field doesn't need to have a set value.
160  dictCheck : callable
161  A function that validates the dictionary as a whole.
162  itemCheck : callable
163  A function that validates individual mapping values.
164 
165  See also
166  --------
167  ChoiceField
168  ConfigChoiceField
169  ConfigDictField
170  ConfigField
171  ConfigurableField
172  Field
173  ListField
174  RangeField
175  RegistryField
176 
177  Examples
178  --------
179  This field maps has `str` keys and `int` values:
180 
181  >>> from lsst.pex.config import Config, DictField
182  >>> class MyConfig(Config):
183  ... field = DictField(
184  ... doc="Example string-to-int mapping field.",
185  ... keytype=str, itemtype=int,
186  ... default={})
187  ...
188  >>> config = MyConfig()
189  >>> config.field['myKey'] = 42
190  >>> print(config.field)
191  {'myKey': 42}
192  """
193 
194  DictClass = Dict
195 
196  def __init__(self, doc, keytype, itemtype, default=None, optional=False, dictCheck=None, itemCheck=None):
197  source = getStackFrame()
198  self._setup(doc=doc, dtype=Dict, default=default, check=None,
199  optional=optional, source=source)
200  if keytype not in self.supportedTypes:
201  raise ValueError("'keytype' %s is not a supported type" %
202  _typeStr(keytype))
203  elif itemtype is not None and itemtype not in self.supportedTypes:
204  raise ValueError("'itemtype' %s is not a supported type" %
205  _typeStr(itemtype))
206  if dictCheck is not None and not hasattr(dictCheck, "__call__"):
207  raise ValueError("'dictCheck' must be callable")
208  if itemCheck is not None and not hasattr(itemCheck, "__call__"):
209  raise ValueError("'itemCheck' must be callable")
210 
211  self.keytype = keytype
212  self.itemtype = itemtype
213  self.dictCheck = dictCheck
214  self.itemCheck = itemCheck
215 
216  def validate(self, instance):
217  """Validate the field's value (for internal use only).
218 
219  Parameters
220  ----------
221  instance : `lsst.pex.config.Config`
222  The configuration that contains this field.
223 
224  Returns
225  -------
226  isValid : `bool`
227  `True` is returned if the field passes validation criteria (see
228  *Notes*). Otherwise `False`.
229 
230  Notes
231  -----
232  This method validates values according to the following criteria:
233 
234  - A non-optional field is not `None`.
235  - If a value is not `None`, is must pass the `ConfigField.dictCheck`
236  user callback functon.
237 
238  Individual item checks by the `ConfigField.itemCheck` user callback
239  function are done immediately when the value is set on a key. Those
240  checks are not repeated by this method.
241  """
242  Field.validate(self, instance)
243  value = self.__get__(instance)
244  if value is not None and self.dictCheck is not None \
245  and not self.dictCheck(value):
246  msg = "%s is not a valid value" % str(value)
247  raise FieldValidationError(self, instance, msg)
248 
249  def __set__(self, instance, value, at=None, label="assignment"):
250  if instance._frozen:
251  msg = "Cannot modify a frozen Config. "\
252  "Attempting to set field to value %s" % value
253  raise FieldValidationError(self, instance, msg)
254 
255  if at is None:
256  at = getCallStack()
257  if value is not None:
258  value = self.DictClass(instance, self, value, at=at, label=label)
259  else:
260  history = instance._history.setdefault(self.name, [])
261  history.append((value, at, label))
262 
263  instance._storage[self.name] = value
264 
265  def toDict(self, instance):
266  """Convert this field's key-value pairs into a regular `dict`.
267 
268  Parameters
269  ----------
270  instance : `lsst.pex.config.Config`
271  The configuration that contains this field.
272 
273  Returns
274  -------
275  result : `dict` or `None`
276  If this field has a value of `None`, then this method returns
277  `None`. Otherwise, this method returns the field's value as a
278  regular Python `dict`.
279  """
280  value = self.__get__(instance)
281  return dict(value) if value is not None else None
282 
283  def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
284  """Compare two fields for equality.
285 
286  Used by `lsst.pex.ConfigDictField.compare`.
287 
288  Parameters
289  ----------
290  instance1 : `lsst.pex.config.Config`
291  Left-hand side config instance to compare.
292  instance2 : `lsst.pex.config.Config`
293  Right-hand side config instance to compare.
294  shortcut : `bool`
295  If `True`, this function returns as soon as an inequality if found.
296  rtol : `float`
297  Relative tolerance for floating point comparisons.
298  atol : `float`
299  Absolute tolerance for floating point comparisons.
300  output : callable
301  A callable that takes a string, used (possibly repeatedly) to
302  report inequalities.
303 
304  Returns
305  -------
306  isEqual : bool
307  `True` if the fields are equal, `False` otherwise.
308 
309  Notes
310  -----
311  Floating point comparisons are performed by `numpy.allclose`.
312  """
313  d1 = getattr(instance1, self.name)
314  d2 = getattr(instance2, self.name)
315  name = getComparisonName(
316  _joinNamePath(instance1._name, self.name),
317  _joinNamePath(instance2._name, self.name)
318  )
319  if not compareScalars("isnone for %s" % name, d1 is None, d2 is None, output=output):
320  return False
321  if d1 is None and d2 is None:
322  return True
323  if not compareScalars("keys for %s" % name, set(d1.keys()), set(d2.keys()), output=output):
324  return False
325  equal = True
326  for k, v1 in d1.items():
327  v2 = d2[k]
328  result = compareScalars("%s[%r]" % (name, k), v1, v2, dtype=self.itemtype,
329  rtol=rtol, atol=atol, output=output)
330  if not result and shortcut:
331  return False
332  equal = equal and result
333  return equal
def __init__(self, config, field, value, at, label, setHistory=True)
Definition: dictField.py:38
def __setitem__(self, k, x, at=None, label="setitem", setHistory=True)
Definition: dictField.py:72
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:884
def getCallStack(skip=0)
Definition: callStack.py:169
def __get__(self, instance, owner=None, at=None, label="default")
Definition: config.py:476
def _setup(self, doc, dtype, default, check, optional, source)
Definition: config.py:273
def getStackFrame(relative=0)
Definition: callStack.py:52
table::Key< int > type
Definition: Detector.cc:167
def __delitem__(self, k, at=None, label="delitem", setHistory=True)
Definition: dictField.py:109
def validate(self, instance)
Definition: dictField.py:216
def __setattr__(self, attr, value, at=None, label="assignment")
Definition: dictField.py:126
def compareScalars(name, v1, v2, output, rtol=1E-8, atol=1E-8, dtype=None)
Definition: comparison.py:56
def __init__(self, doc, keytype, itemtype, default=None, optional=False, dictCheck=None, itemCheck=None)
Definition: dictField.py:196
def getComparisonName(name1, name2)
Definition: comparison.py:34
def __set__(self, instance, value, at=None, label="assignment")
Definition: dictField.py:249