lsst.daf.base  15.0-3-g94bc79c+1
propertyContainerContinued.py
Go to the documentation of this file.
1 #
2 # LSST Data Management System
3 #
4 # Copyright 2008-2017 AURA/LSST.
5 #
6 # This product includes software developed by the
7 # LSST Project (http://www.lsst.org/).
8 #
9 # This program is free software: you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation, either version 3 of the License, or
12 # (at your option) any later version.
13 #
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
18 #
19 # You should have received a copy of the LSST License Statement and
20 # the GNU General Public License along with this program. If not,
21 # see <http://www.lsstcorp.org/LegalNotices/>.
22 #
23 
24 
25 __all__ = ["getstate", "setstate"]
26 
27 import enum
28 import numbers
29 import warnings
30 
31 from lsst.utils import continueClass
32 
33 from .propertySet import PropertySet
34 from .propertyList import PropertyList
35 
37 from ..dateTime import DateTime
38 
39 
40 class ReturnStyle(enum.Enum):
41  ARRAY = enum.auto()
42  SCALAR = enum.auto()
43  AUTO = enum.auto()
44 
45 
46 def _propertyContainerElementTypeName(container, name):
47  """Return name of the type of a particular element"""
48  t = container.typeOf(name)
49  for checkType in ("Bool", "Short", "Int", "Long", "LongLong", "Float", "Double", "String", "DateTime"):
50  if t == getattr(container, "TYPE_" + checkType):
51  return checkType
52  return None
53 
54 
55 def _propertyContainerGet(container, name, returnStyle):
56  """Get a value of unknown type as a scalar or array
57 
58  Parameters
59  ----------
60  container : ``lsst.daf.base.PropertySet`` or ``lsst.daf.base.PropertyList``
61  Container from which to get the value
62  name : ``str``
63  Name of item
64  returnStyle : ``ReturnStyle``
65  Control whether data is returned as an array or scalar:
66  - ReturnStyle.ARRAY: return numeric or string data types
67  as an array of values. Raise TypeError for
68  PropertyList, PropertySet or PersistablePtr.
69  - ReturnStyle.SCALAR: return numeric or string data types
70  as a single value; if the item has multiple values then
71  return the last value. Return PropertyList, PropertySet
72  or PersistablePtr as a single item.
73  - ReturnStyle.AUTO: (deprecated) return numeric or string data
74  as a scalar if there is just one item, or as an array
75  otherwise. Return PropertyList, PropertySet
76  or PersistablePtr as a single item.
77 
78  Raises
79  ------
80  ValueError
81  If `returnStyle`=``ReturnStyle.ARRAY`` and the item is of type
82  ``PropertyList``, ``PropertySet`` or ``PersistablePtr``
83 
84  Notes
85  -----
86  `returnStyle` is handled as follows:
87  """
88  if not container.exists(name):
89  raise lsst.pex.exceptions.NotFoundError(name + " not found")
90  if returnStyle not in ReturnStyle:
91  raise ValueError("returnStyle {} must be a ReturnStyle".format(returnStyle))
92 
93  elemType = _propertyContainerElementTypeName(container, name)
94  if elemType:
95  value = getattr(container, "getArray" + elemType)(name)
96  if returnStyle == ReturnStyle.ARRAY or (returnStyle == ReturnStyle.AUTO and len(value) > 1):
97  return value
98  return value[-1]
99 
100  if returnStyle == ReturnStyle.ARRAY:
101  raise TypeError("Item {} is not numeric or string".format(name))
102 
103  try:
104  return container.getAsPropertyListPtr(name)
105  except Exception:
106  pass
107  if container.typeOf(name) == container.TYPE_PropertySet:
108  return container.getAsPropertySetPtr(name)
109  try:
110  return container.getAsPersistablePtr(name)
111  except Exception:
112  pass
113  raise lsst.pex.exceptions.TypeError('Unknown PropertySet value type for ' + name)
114 
115 
116 def _guessIntegerType(container, name, value):
117  """Given an existing container and name, determine the type
118  that should be used for the supplied value. The supplied value
119  is assumed to be a scalar.
120 
121  On Python 3 all ints are LongLong but we need to be able to store them
122  in Int containers if that is what is being used (testing for truncation).
123  Int is assumed to mean 32bit integer (2147483647 to -2147483648).
124 
125  If there is no pre-existing value we have to decide what to do. For now
126  we pick Int if the value is less than maxsize.
127 
128  Returns None if the value supplied is a bool or not an integral value.
129  """
130  useType = None
131  maxInt = 2147483647
132  minInt = -2147483648
133 
134  # We do not want to convert bool to int so let the system work that
135  # out itself
136  if isinstance(value, bool):
137  return useType
138 
139  if isinstance(value, numbers.Integral):
140  try:
141  containerType = _propertyContainerElementTypeName(container, name)
143  # nothing in the container so choose based on size. Safe option is to
144  # always use LongLong
145  if value <= maxInt and value >= minInt:
146  useType = "Int"
147  else:
148  useType = "LongLong"
149  else:
150  if containerType == "Int":
151  # Always use an Int even if we know it won't fit. The later
152  # code will trigger OverflowError if appropriate. Setting the
153  # type to LongLong here will trigger a TypeError instead so it's
154  # best to trigger a predictable OverflowError.
155  useType = "Int"
156  elif containerType == "LongLong":
157  useType = "LongLong"
158  return useType
159 
160 
161 def _propertyContainerSet(container, name, value, typeMenu, *args):
162  """Set a single Python value of unknown type"""
163  if hasattr(value, "__iter__") and not isinstance(value, str):
164  exemplar = value[0]
165  else:
166  exemplar = value
167 
168  t = type(exemplar)
169  setType = _guessIntegerType(container, name, exemplar)
170 
171  if setType is not None or t in typeMenu:
172  if setType is None:
173  setType = typeMenu[t]
174  return getattr(container, "set" + setType)(name, value, *args)
175  # Allow for subclasses
176  for checkType in typeMenu:
177  if isinstance(exemplar, checkType):
178  return getattr(container, "set" + typeMenu[checkType])(name, value, *args)
179  raise lsst.pex.exceptions.TypeError("Unknown value type for %s: %s" % (name, t))
180 
181 
182 def _propertyContainerAdd(container, name, value, typeMenu, *args):
183  """Add a single Python value of unknown type"""
184  if hasattr(value, "__iter__"):
185  exemplar = value[0]
186  else:
187  exemplar = value
188 
189  t = type(exemplar)
190  addType = _guessIntegerType(container, name, exemplar)
191 
192  if addType is not None or t in typeMenu:
193  if addType is None:
194  addType = typeMenu[t]
195  return getattr(container, "add" + addType)(name, value, *args)
196  # Allow for subclasses
197  for checkType in typeMenu:
198  if isinstance(exemplar, checkType):
199  return getattr(container, "add" + typeMenu[checkType])(name, value, *args)
200  raise lsst.pex.exceptions.TypeError("Unknown value type for %s: %s" % (name, t))
201 
202 
203 def getstate(self):
204  return [(name, _propertyContainerElementTypeName(self, name),
205  _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO),
206  self.getComment(name)) for name in self.getOrderedNames()]
207 
208 
209 def setstate(self, state):
210  for name, elemType, value, comment in state:
211  getattr(self, "set" + elemType)(name, value, comment)
212 
213 
214 @continueClass
216  # Mapping of type to method names;
217  # int types are omitted due to use of _guessIntegerType
218  _typeMenu = {bool: "Bool",
219  float: "Double",
220  str: "String",
221  DateTime: "DateTime",
222  PropertySet: "PropertySet",
223  PropertyList: "PropertySet",
224  }
225 
226  # Map unicode to String, but this only works on Python 2
227  # so catch the error and do nothing on Python 3.
228  try:
229  _typeMenu[unicode] = "String" # noqa F821
230  except Exception:
231  pass
232 
233  def get(self, name):
234  """Return an item as a scalar or array
235 
236  Return an array if the item is of numeric or string type and has
237  more than one value, otherwise return a scalar.
238 
239  .. deprecated:: 20180-06
240  `get` is superseded by `getArray` or `getScalar`
241 
242  Parameters
243  ----------
244  name : ``str``
245  Name of item
246 
247  Raises
248  ------
249  lsst.pex.exceptions.NotFoundError
250  If the item does not exist.
251  """
252  warnings.warn("Use getArray or getScalar instead", DeprecationWarning, stacklevel=2)
253  return _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO)
254 
255  def getArray(self, name):
256  """Return an item as an array; the item type must be numeric or string
257 
258  Parameters
259  ----------
260  name : ``str``
261  Name of item
262 
263  Raises
264  ------
265  lsst.pex.exceptions.NotFoundError
266  If the item does not exist.
267  TypeError
268  If item type is ``lsst.daf.base.PropertyList``,
269  ``lsst.daf.base.PropertySet``
270  or ``lsst.daf.base.PersistablePtr``.
271  """
272  return _propertyContainerGet(self, name, returnStyle=ReturnStyle.ARRAY)
273 
274  def getScalar(self, name):
275  """Return an item as a scalar
276 
277  If the item has more than one value then the last value is returned
278 
279  Parameters
280  ----------
281  name : ``str``
282  Name of item
283 
284  Raises
285  ------
286  lsst.pex.exceptions.NotFoundError
287  If the item does not exist.
288  """
289  return _propertyContainerGet(self, name, returnStyle=ReturnStyle.SCALAR)
290 
291  def set(self, name, value):
292  """Set the value of an item
293 
294  If the item already exists it is silently replaced; the types
295  need not match.
296 
297  Parameters
298  ----------
299  name : ``str``
300  Name of item
301  value : any supported type
302  Value of item; may be a scalar or array
303  """
304  return _propertyContainerSet(self, name, value, self._typeMenu)
305 
306  def add(self, name, value):
307  """Append one or more values to a given item, which need not exist
308 
309  If the item exists then the new value(s) are appended;
310  otherwise it is like calling `set`
311 
312  Parameters
313  ----------
314  name : ``str``
315  Name of item
316  value : any supported type
317  Value of item; may be a scalar or array
318 
319  Notes
320  -----
321  If `value` is an ``lsst.daf.base.PropertySet`` or
322  ``lsst.daf.base.PropertyList`` then `value` replaces
323  the existing value. Also the item is added as a live
324  reference, so updating `value` will update this container
325  and vice-versa.
326 
327  Raises
328  ------
329  lsst::pex::exceptions::TypeError
330  If the type of `value` is incompatible with the existing value
331  of the item.
332  """
333  return _propertyContainerAdd(self, name, value, self._typeMenu)
334 
335  def toDict(self):
336  """Returns a (possibly nested) dictionary with all properties.
337  """
338 
339  d = {}
340  for name in self.names():
341  v = _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO)
342 
343  if isinstance(v, PropertySet):
344  d[name] = PropertySet.toDict(v)
345  else:
346  d[name] = v
347  return d
348 
349 
350 @continueClass
352  # Mapping of type to method names
353  _typeMenu = {bool: "Bool",
354  int: "Int",
355  float: "Double",
356  str: "String",
357  DateTime: "DateTime",
358  PropertySet: "PropertySet",
359  PropertyList: "PropertySet",
360  }
361 
362  # Map unicode to String, but this only works on Python 2
363  # so catch the error and do nothing on Python 3.
364  try:
365  _typeMenu[unicode] = "String" # noqa F821
366  except Exception:
367  pass
368 
369  def get(self, name):
370  """Return an item as a scalar or array
371 
372  Return an array if the item has more than one value,
373  otherwise return a scalar.
374 
375  .. deprecated:: 20180-06
376  `get` is superseded by `getArray` or `getScalar`
377 
378  Parameters
379  ----------
380  name : ``str``
381  Name of item
382 
383  Raises
384  ------
385  lsst.pex.exceptions.NotFoundError
386  If the item does not exist.
387  """
388  warnings.warn("Use getArray or getScalar instead", DeprecationWarning, stacklevel=2)
389  return _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO)
390 
391  def getArray(self, name):
392  """Return an item as an array
393 
394  Parameters
395  ----------
396  name : ``str``
397  Name of item
398 
399  Raises
400  ------
401  lsst.pex.exceptions.NotFoundError
402  If the item does not exist.
403  """
404  return _propertyContainerGet(self, name, returnStyle=ReturnStyle.ARRAY)
405 
406  def getScalar(self, name):
407  """Return an item as a scalar
408 
409  If the item has more than one value then the last value is returned
410 
411  Parameters
412  ----------
413  name : ``str``
414  Name of item
415 
416  Raises
417  ------
418  lsst.pex.exceptions.NotFoundError
419  If the item does not exist.
420  """
421  return _propertyContainerGet(self, name, returnStyle=ReturnStyle.SCALAR)
422 
423  def set(self, name, value, comment=None):
424  """Set the value of an item
425 
426  If the item already exists it is silently replaced; the types
427  need not match.
428 
429  Parameters
430  ----------
431  name : ``str``
432  Name of item
433  value : any supported type
434  Value of item; may be a scalar or array
435  """
436  args = []
437  if comment is not None:
438  args.append(comment)
439  return _propertyContainerSet(self, name, value, self._typeMenu, *args)
440 
441  def add(self, name, value, comment=None):
442  """Append one or more values to a given item, which need not exist
443 
444  If the item exists then the new value(s) are appended;
445  otherwise it is like calling `set`
446 
447  Parameters
448  ----------
449  name : ``str``
450  Name of item
451  value : any supported type
452  Value of item; may be a scalar or array
453 
454  Notes
455  -----
456  If `value` is an ``lsst.daf.base.PropertySet`` items are added
457  using dotted names (e.g. if name="a" and value contains
458  an item "b" which is another PropertySet and contains an
459  item "c" which is numeric or string, then the value of "c"
460  is added as "a.b.c", appended to the existing values of
461  "a.b.c" if any (in which case the types must be compatible).
462 
463  Raises
464  ------
465  lsst::pex::exceptions::TypeError
466  If the type of `value` is incompatible with the existing value
467  of the item.
468  """
469  args = []
470  if comment is not None:
471  args.append(comment)
472  return _propertyContainerAdd(self, name, value, self._typeMenu, *args)
473 
474  def toList(self):
475  orderedNames = self.getOrderedNames()
476  ret = []
477  for name in orderedNames:
478  if self.isArray(name):
479  values = _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO)
480  for v in values:
481  ret.append((name, v, self.getComment(name)))
482  else:
483  ret.append((name, _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO),
484  self.getComment(name)))
485  return ret
486 
487  def toOrderedDict(self):
488  """Return an ordered dictionary with all properties in the order that
489  they were inserted.
490  """
491  from collections import OrderedDict
492 
493  d = OrderedDict()
494  for name in self.getOrderedNames():
495  d[name] = _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO)
496  return d