lsst.daf.base  16.0-2-gaa1012f
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  return [(name, _propertyContainerElementTypeName(self, name),
191  _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO),
192  self.getComment(name)) for name in self.getOrderedNames()]
193 
194 
195 def setstate(self, state):
196  for name, elemType, value, comment in state:
197  getattr(self, "set" + elemType)(name, value, comment)
198 
199 
200 @continueClass
202  # Mapping of type to method names;
203  # int types are omitted due to use of _guessIntegerType
204  _typeMenu = {bool: "Bool",
205  float: "Double",
206  str: "String",
207  DateTime: "DateTime",
208  PropertySet: "PropertySet",
209  PropertyList: "PropertySet",
210  }
211 
212  # Map unicode to String, but this only works on Python 2
213  # so catch the error and do nothing on Python 3.
214  try:
215  _typeMenu[unicode] = "String" # noqa F821
216  except Exception:
217  pass
218 
219  def get(self, name):
220  """Return an item as a scalar or array
221 
222  Return an array if the item is of numeric or string type and has
223  more than one value, otherwise return a scalar.
224 
225  .. deprecated:: 20180-06
226  `get` is superseded by `getArray` or `getScalar`
227 
228  Parameters
229  ----------
230  name : ``str``
231  Name of item
232 
233  Raises
234  ------
235  lsst.pex.exceptions.NotFoundError
236  If the item does not exist.
237  """
238  warnings.warn("Use getArray or getScalar instead", DeprecationWarning, stacklevel=2)
239  return _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO)
240 
241  def getArray(self, name):
242  """Return an item as an array if the item is numeric or string
243 
244  If the item is a ``PropertySet``, ``PropertyList`` or
245  ``lsst.daf.base.PersistablePtr`` then return the item as a scalar.
246 
247  Parameters
248  ----------
249  name : ``str``
250  Name of item
251 
252  Raises
253  ------
254  lsst.pex.exceptions.NotFoundError
255  If the item does not exist.
256  """
257  return _propertyContainerGet(self, name, returnStyle=ReturnStyle.ARRAY)
258 
259  def getScalar(self, name):
260  """Return an item as a scalar
261 
262  If the item has more than one value then the last value is returned
263 
264  Parameters
265  ----------
266  name : ``str``
267  Name of item
268 
269  Raises
270  ------
271  lsst.pex.exceptions.NotFoundError
272  If the item does not exist.
273  """
274  return _propertyContainerGet(self, name, returnStyle=ReturnStyle.SCALAR)
275 
276  def set(self, name, value):
277  """Set the value of an item
278 
279  If the item already exists it is silently replaced; the types
280  need not match.
281 
282  Parameters
283  ----------
284  name : ``str``
285  Name of item
286  value : any supported type
287  Value of item; may be a scalar or array
288  """
289  return _propertyContainerSet(self, name, value, self._typeMenu)
290 
291  def add(self, name, value):
292  """Append one or more values to a given item, which need not exist
293 
294  If the item exists then the new value(s) are appended;
295  otherwise it is like calling `set`
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  Notes
305  -----
306  If `value` is an ``lsst.daf.base.PropertySet`` or
307  ``lsst.daf.base.PropertyList`` then `value` replaces
308  the existing value. Also the item is added as a live
309  reference, so updating `value` will update this container
310  and vice-versa.
311 
312  Raises
313  ------
314  lsst::pex::exceptions::TypeError
315  If the type of `value` is incompatible with the existing value
316  of the item.
317  """
318  return _propertyContainerAdd(self, name, value, self._typeMenu)
319 
320  def toDict(self):
321  """Returns a (possibly nested) dictionary with all properties.
322  """
323 
324  d = {}
325  for name in self.names():
326  v = _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO)
327 
328  if isinstance(v, PropertySet):
329  d[name] = PropertySet.toDict(v)
330  else:
331  d[name] = v
332  return d
333 
334 
335 @continueClass
337  # Mapping of type to method names
338  _typeMenu = {bool: "Bool",
339  int: "Int",
340  float: "Double",
341  str: "String",
342  DateTime: "DateTime",
343  PropertySet: "PropertySet",
344  PropertyList: "PropertySet",
345  }
346 
347  # Map unicode to String, but this only works on Python 2
348  # so catch the error and do nothing on Python 3.
349  try:
350  _typeMenu[unicode] = "String" # noqa F821
351  except Exception:
352  pass
353 
354  def get(self, name):
355  """Return an item as a scalar or array
356 
357  Return an array if the item has more than one value,
358  otherwise return a scalar.
359 
360  .. deprecated:: 20180-06
361  `get` is superseded by `getArray` or `getScalar`
362 
363  Parameters
364  ----------
365  name : ``str``
366  Name of item
367 
368  Raises
369  ------
370  lsst.pex.exceptions.NotFoundError
371  If the item does not exist.
372  """
373  warnings.warn("Use getArray or getScalar instead", DeprecationWarning, stacklevel=2)
374  return _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO)
375 
376  def getArray(self, name):
377  """Return an item as an array
378 
379  Parameters
380  ----------
381  name : ``str``
382  Name of item
383 
384  Raises
385  ------
386  lsst.pex.exceptions.NotFoundError
387  If the item does not exist.
388  """
389  return _propertyContainerGet(self, name, returnStyle=ReturnStyle.ARRAY)
390 
391  def getScalar(self, name):
392  """Return an item as a scalar
393 
394  If the item has more than one value then the last value is returned
395 
396  Parameters
397  ----------
398  name : ``str``
399  Name of item
400 
401  Raises
402  ------
403  lsst.pex.exceptions.NotFoundError
404  If the item does not exist.
405  """
406  return _propertyContainerGet(self, name, returnStyle=ReturnStyle.SCALAR)
407 
408  def set(self, name, value, comment=None):
409  """Set the value of an item
410 
411  If the item already exists it is silently replaced; the types
412  need not match.
413 
414  Parameters
415  ----------
416  name : ``str``
417  Name of item
418  value : any supported type
419  Value of item; may be a scalar or array
420  """
421  args = []
422  if comment is not None:
423  args.append(comment)
424  return _propertyContainerSet(self, name, value, self._typeMenu, *args)
425 
426  def add(self, name, value, comment=None):
427  """Append one or more values to a given item, which need not exist
428 
429  If the item exists then the new value(s) are appended;
430  otherwise it is like calling `set`
431 
432  Parameters
433  ----------
434  name : ``str``
435  Name of item
436  value : any supported type
437  Value of item; may be a scalar or array
438 
439  Notes
440  -----
441  If `value` is an ``lsst.daf.base.PropertySet`` items are added
442  using dotted names (e.g. if name="a" and value contains
443  an item "b" which is another PropertySet and contains an
444  item "c" which is numeric or string, then the value of "c"
445  is added as "a.b.c", appended to the existing values of
446  "a.b.c" if any (in which case the types must be compatible).
447 
448  Raises
449  ------
450  lsst::pex::exceptions::TypeError
451  If the type of `value` is incompatible with the existing value
452  of the item.
453  """
454  args = []
455  if comment is not None:
456  args.append(comment)
457  return _propertyContainerAdd(self, name, value, self._typeMenu, *args)
458 
459  def toList(self):
460  orderedNames = self.getOrderedNames()
461  ret = []
462  for name in orderedNames:
463  if self.isArray(name):
464  values = _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO)
465  for v in values:
466  ret.append((name, v, self.getComment(name)))
467  else:
468  ret.append((name, _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO),
469  self.getComment(name)))
470  return ret
471 
472  def toOrderedDict(self):
473  """Return an ordered dictionary with all properties in the order that
474  they were inserted.
475  """
476  from collections import OrderedDict
477 
478  d = OrderedDict()
479  for name in self.getOrderedNames():
480  d[name] = _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO)
481  return d