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