LSSTApplications  16.0-10-g0ee56ad+5,16.0-11-ga33d1f2+5,16.0-12-g3ef5c14+3,16.0-12-g71e5ef5+18,16.0-12-gbdf3636+3,16.0-13-g118c103+3,16.0-13-g8f68b0a+3,16.0-15-gbf5c1cb+4,16.0-16-gfd17674+3,16.0-17-g7c01f5c+3,16.0-18-g0a50484+1,16.0-20-ga20f992+8,16.0-21-g0e05fd4+6,16.0-21-g15e2d33+4,16.0-22-g62d8060+4,16.0-22-g847a80f+4,16.0-25-gf00d9b8+1,16.0-28-g3990c221+4,16.0-3-gf928089+3,16.0-32-g88a4f23+5,16.0-34-gd7987ad+3,16.0-37-gc7333cb+2,16.0-4-g10fc685+2,16.0-4-g18f3627+26,16.0-4-g5f3a788+26,16.0-5-gaf5c3d7+4,16.0-5-gcc1f4bb+1,16.0-6-g3b92700+4,16.0-6-g4412fcd+3,16.0-6-g7235603+4,16.0-69-g2562ce1b+2,16.0-8-g14ebd58+4,16.0-8-g2df868b+1,16.0-8-g4cec79c+6,16.0-8-gadf6c7a+1,16.0-8-gfc7ad86,16.0-82-g59ec2a54a+1,16.0-9-g5400cdc+2,16.0-9-ge6233d7+5,master-g2880f2d8cf+3,v17.0.rc1
LSSTDataManagementBasePackage
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__ = ["getPropertySetState", "getPropertyListState", "setPropertySetState", "setPropertyListState"]
26 
27 import enum
28 import numbers
29 import warnings
30 from collections.abc import Mapping, KeysView
31 
32 from lsst.utils import continueClass
33 
34 from .propertySet import PropertySet
35 from .propertyList import PropertyList
36 from ..dateTime import DateTime
37 
38 
39 def getPropertySetState(container, asLists=False):
40  """Get the state of a PropertySet in a form that can be pickled.
41 
42  Parameters
43  ----------
44  container : `PropertySet`
45  The property container.
46  asLists : `bool`, optional
47  If False, the default, `tuple` will be used for the contents. If true
48  a `list` will be used.
49 
50  Returns
51  -------
52  state : `list` of `tuple` or `list` of `list`
53  The state, as a list of tuples (or lists), each of which contains
54  the following 3 items:
55  - name (a `str`): the name of the item
56  - elementTypeName (a `str`): the suffix of a ``setX`` method name
57  which is appropriate for the data type. For example integer
58  data has ``elementTypeName="Int"` which corresponds to
59  the ``setInt`` method.
60  - value: the data for the item, in a form compatible
61  with the set method named by ``elementTypeName``
62  """
63  names = container.names(topLevelOnly=True)
64  sequence = list if asLists else tuple
65  return [sequence((name, _propertyContainerElementTypeName(container, name),
66  _propertyContainerGet(container, name, returnStyle=ReturnStyle.AUTO)))
67  for name in names]
68 
69 
70 def getPropertyListState(container, asLists=False):
71  """Get the state of a PropertyList in a form that can be pickled.
72 
73  Parameters
74  ----------
75  container : `PropertyList`
76  The property container.
77  asLists : `bool`, optional
78  If False, the default, `tuple` will be used for the contents. If true
79  a `list` will be used.
80 
81  Returns
82  -------
83  state : `list` of `tuple` or `list` of `list`
84  The state, as a list of tuples (or lists), each of which contains
85  the following 4 items:
86  - name (a `str`): the name of the item
87  - elementTypeName (a `str`): the suffix of a ``setX`` method name
88  which is appropriate for the data type. For example integer
89  data has ``elementTypeName="Int"` which corresponds to
90  the ``setInt`` method.
91  - value: the data for the item, in a form compatible
92  with the set method named by ``elementTypeName``
93  - comment (a `str`): the comment. This item is only present
94  if ``container`` is a PropertyList.
95  """
96  sequence = list if asLists else tuple
97  return [sequence((name, _propertyContainerElementTypeName(container, name),
98  _propertyContainerGet(container, name, returnStyle=ReturnStyle.AUTO),
99  container.getComment(name)))
100  for name in container.getOrderedNames()]
101 
102 
103 def setPropertySetState(container, state):
104  """Restore the state of a PropertySet, in place.
105 
106  Parameters
107  ----------
108  container : `PropertySet`
109  The property container whose state is to be restored.
110  It should be empty to start with and is updated in place.
111  state : `list`
112  The state, as returned by `getPropertySetState`
113  """
114  for name, elemType, value in state:
115  if elemType is not None:
116  getattr(container, "set" + elemType)(name, value)
117  else:
118  raise ValueError(f"Unrecognized values for state restoration: ({name}, {elemType}, {value})")
119 
120 
121 def setPropertyListState(container, state):
122  """Restore the state of a PropertyList, in place.
123 
124  Parameters
125  ----------
126  container : `PropertyList`
127  The property container whose state is to be restored.
128  It should be empty to start with and is updated in place.
129  state : `list`
130  The state, as returned by ``getPropertyListState``
131  """
132  for name, elemType, value, comment in state:
133  getattr(container, "set" + elemType)(name, value, comment)
134 
135 
136 class ReturnStyle(enum.Enum):
137  ARRAY = enum.auto()
138  SCALAR = enum.auto()
139  AUTO = enum.auto()
140 
141 
142 def _propertyContainerElementTypeName(container, name):
143  """Return name of the type of a particular element"""
144  try:
145  t = container.typeOf(name)
146  except LookupError:
147  # KeyError is more commonly expected when asking for an element
148  # from a mapping.
149  raise KeyError
150  for checkType in ("Bool", "Short", "Int", "Long", "LongLong", "Float", "Double", "String", "DateTime",
151  "PropertySet"):
152  if t == getattr(container, "TYPE_" + checkType):
153  return checkType
154  return None
155 
156 
157 def _propertyContainerGet(container, name, returnStyle):
158  """Get a value of unknown type as a scalar or array
159 
160  Parameters
161  ----------
162  container : `lsst.daf.base.PropertySet` or `lsst.daf.base.PropertyList`
163  Container from which to get the value
164  name : `str`
165  Name of item
166  returnStyle : `ReturnStyle`
167  Control whether numeric or string data is returned as an array
168  or scalar (the other types, ``PropertyList``, ``PropertySet``
169  and ``PersistablePtr``, are always returned as a scalar):
170  - ReturnStyle.ARRAY: return numeric or string data types
171  as an array of values.
172  - ReturnStyle.SCALAR: return numeric or string data types
173  as a single value; if the item has multiple values then
174  return the last value.
175  - ReturnStyle.AUTO: (deprecated) return numeric or string data
176  as a scalar if there is just one item, or as an array
177  otherwise.
178 
179  Raises
180  ------
181  KeyError
182  The specified key does not exist in the container.
183  TypeError
184  The value retrieved is of an unexpected type.
185  ValueError
186  The value for ``returnStyle`` is not correct.
187  """
188  if not container.exists(name):
189  raise KeyError(name + " not found")
190  if returnStyle not in ReturnStyle:
191  raise ValueError("returnStyle {} must be a ReturnStyle".format(returnStyle))
192 
193  elemType = _propertyContainerElementTypeName(container, name)
194  if elemType and elemType != "PropertySet":
195  value = getattr(container, "getArray" + elemType)(name)
196  if returnStyle == ReturnStyle.ARRAY or (returnStyle == ReturnStyle.AUTO and len(value) > 1):
197  return value
198  return value[-1]
199 
200  if container.isPropertySetPtr(name):
201  try:
202  return container.getAsPropertyListPtr(name)
203  except Exception:
204  return container.getAsPropertySetPtr(name)
205  try:
206  return container.getAsPersistablePtr(name)
207  except Exception:
208  pass
209  raise TypeError('Unknown PropertySet value type for ' + name)
210 
211 
212 def _guessIntegerType(container, name, value):
213  """Given an existing container and name, determine the type
214  that should be used for the supplied value. The supplied value
215  is assumed to be a scalar.
216 
217  On Python 3 all ints are LongLong but we need to be able to store them
218  in Int containers if that is what is being used (testing for truncation).
219  Int is assumed to mean 32bit integer (2147483647 to -2147483648).
220 
221  If there is no pre-existing value we have to decide what to do. For now
222  we pick Int if the value is less than maxsize.
223 
224  Returns None if the value supplied is a bool or not an integral value.
225  """
226  useType = None
227  maxInt = 2147483647
228  minInt = -2147483648
229 
230  # We do not want to convert bool to int so let the system work that
231  # out itself
232  if isinstance(value, bool):
233  return useType
234 
235  if isinstance(value, numbers.Integral):
236  try:
237  containerType = _propertyContainerElementTypeName(container, name)
238  except LookupError:
239  # nothing in the container so choose based on size. Safe option is to
240  # always use LongLong
241  if value <= maxInt and value >= minInt:
242  useType = "Int"
243  else:
244  useType = "LongLong"
245  else:
246  if containerType == "Int":
247  # Always use an Int even if we know it won't fit. The later
248  # code will trigger OverflowError if appropriate. Setting the
249  # type to LongLong here will trigger a TypeError instead so it's
250  # best to trigger a predictable OverflowError.
251  useType = "Int"
252  elif containerType == "LongLong":
253  useType = "LongLong"
254  return useType
255 
256 
257 def _propertyContainerSet(container, name, value, typeMenu, *args):
258  """Set a single Python value of unknown type"""
259  if hasattr(value, "__iter__") and not isinstance(value, (str, PropertySet, PropertyList)):
260  exemplar = value[0]
261  else:
262  exemplar = value
263 
264  t = type(exemplar)
265  setType = _guessIntegerType(container, name, exemplar)
266 
267  if setType is not None or t in typeMenu:
268  if setType is None:
269  setType = typeMenu[t]
270  return getattr(container, "set" + setType)(name, value, *args)
271  # Allow for subclasses
272  for checkType in typeMenu:
273  if isinstance(exemplar, checkType):
274  return getattr(container, "set" + typeMenu[checkType])(name, value, *args)
275  raise TypeError("Unknown value type for %s: %s" % (name, t))
276 
277 
278 def _propertyContainerAdd(container, name, value, typeMenu, *args):
279  """Add a single Python value of unknown type"""
280  if hasattr(value, "__iter__"):
281  exemplar = value[0]
282  else:
283  exemplar = value
284 
285  t = type(exemplar)
286  addType = _guessIntegerType(container, name, exemplar)
287 
288  if addType is not None or t in typeMenu:
289  if addType is None:
290  addType = typeMenu[t]
291  return getattr(container, "add" + addType)(name, value, *args)
292  # Allow for subclasses
293  for checkType in typeMenu:
294  if isinstance(exemplar, checkType):
295  return getattr(container, "add" + typeMenu[checkType])(name, value, *args)
296  raise TypeError("Unknown value type for %s: %s" % (name, t))
297 
298 
299 def _makePropertySet(state):
300  """Make a `PropertySet` from the state returned by `getPropertySetState`
301 
302  Parameters
303  ----------
304  state : `list`
305  The data returned by `getPropertySetState`.
306  """
307  ps = PropertySet()
308  setPropertySetState(ps, state)
309  return ps
310 
311 
312 def _makePropertyList(state):
313  """Make a `PropertyList` from the state returned by
314  `getPropertyListState`
315 
316  Parameters
317  ----------
318  state : `list`
319  The data returned by `getPropertySetState`.
320  """
321  pl = PropertyList()
322  setPropertyListState(pl, state)
323  return pl
324 
325 
326 @continueClass
328  # Mapping of type to method names;
329  # int types are omitted due to use of _guessIntegerType
330  _typeMenu = {bool: "Bool",
331  float: "Double",
332  str: "String",
333  DateTime: "DateTime",
334  PropertySet: "PropertySet",
335  PropertyList: "PropertySet",
336  }
337 
338  def get(self, name):
339  """Return an item as a scalar or array
340 
341  Return an array if the item is of numeric or string type and has
342  more than one value, otherwise return a scalar.
343 
344  .. deprecated:: 20180-06
345  `get` is superseded by `getArray` or `getScalar`
346 
347  Parameters
348  ----------
349  name : ``str``
350  Name of item
351 
352  Raises
353  ------
354  KeyError
355  If the item does not exist.
356  """
357  warnings.warn("Use getArray or getScalar instead", DeprecationWarning, stacklevel=2)
358  return _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO)
359 
360  def getArray(self, name):
361  """Return an item as an array if the item is numeric or string
362 
363  If the item is a `PropertySet`, `PropertyList` or
364  `lsst.daf.base.PersistablePtr` then return the item as a scalar.
365 
366  Parameters
367  ----------
368  name : `str`
369  Name of item
370 
371  Raises
372  ------
373  KeyError
374  If the item does not exist.
375  """
376  return _propertyContainerGet(self, name, returnStyle=ReturnStyle.ARRAY)
377 
378  def getScalar(self, name):
379  """Return an item as a scalar
380 
381  If the item has more than one value then the last value is returned
382 
383  Parameters
384  ----------
385  name : `str`
386  Name of item
387 
388  Raises
389  ------
390  KeyError
391  If the item does not exist.
392  """
393  return _propertyContainerGet(self, name, returnStyle=ReturnStyle.SCALAR)
394 
395  def set(self, name, value):
396  """Set the value of an item
397 
398  If the item already exists it is silently replaced; the types
399  need not match.
400 
401  Parameters
402  ----------
403  name : `str`
404  Name of item
405  value : any supported type
406  Value of item; may be a scalar or array
407  """
408  return _propertyContainerSet(self, name, value, self._typeMenu)
409 
410  def add(self, name, value):
411  """Append one or more values to a given item, which need not exist
412 
413  If the item exists then the new value(s) are appended;
414  otherwise it is like calling `set`
415 
416  Parameters
417  ----------
418  name : `str`
419  Name of item
420  value : any supported type
421  Value of item; may be a scalar or array
422 
423  Notes
424  -----
425  If ``value`` is an `lsst.daf.base.PropertySet` or
426  `lsst.daf.base.PropertyList` then ``value`` replaces
427  the existing value. Also the item is added as a live
428  reference, so updating ``value`` will update this container
429  and vice-versa.
430 
431  Raises
432  ------
433  lsst::pex::exceptions::TypeError
434  If the type of `value` is incompatible with the existing value
435  of the item.
436  """
437  return _propertyContainerAdd(self, name, value, self._typeMenu)
438 
439  def toDict(self):
440  """Returns a (possibly nested) dictionary with all properties.
441 
442  Returns
443  -------
444  d : `dict`
445  Dictionary with all names and values (no comments).
446  """
447 
448  d = {}
449  for name in self.names():
450  v = _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO)
451 
452  if isinstance(v, PropertySet):
453  d[name] = PropertySet.toDict(v)
454  else:
455  d[name] = v
456  return d
457 
458  def __eq__(self, other):
459  if type(self) != type(other):
460  return False
461 
462  if len(self) != len(other):
463  return False
464 
465  for name in self:
466  if _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO) != \
467  _propertyContainerGet(other, name, returnStyle=ReturnStyle.AUTO):
468  return False
469  if self.typeOf(name) != other.typeOf(name):
470  return False
471 
472  return True
473 
474  def __copy__(self):
475  # Copy without having to go through pickle state
476  ps = PropertySet()
477  for itemName in self:
478  ps.copy(itemName, self, itemName)
479  return ps
480 
481  def __deepcopy__(self, memo):
482  result = self.deepCopy()
483  memo[id(self)] = result
484  return result
485 
486  def __contains__(self, name):
487  # Do not use exists() because that includes "."-delimited names
488  return name in self.names(topLevelOnly=True)
489 
490  def __setitem__(self, name, value):
491  if isinstance(value, Mapping):
492  # Create a property set instead
493  ps = PropertySet()
494  for k, v in value.items():
495  ps[k] = v
496  value = ps
497  self.set(name, value)
498 
499  def __delitem__(self, name):
500  if name in self:
501  self.remove(name)
502  else:
503  raise KeyError(f"{name} not present in dict")
504 
505  def __str__(self):
506  return self.toString()
507 
508  def __len__(self):
509  return self.nameCount(topLevelOnly=True)
510 
511  def __iter__(self):
512  for n in self.names(topLevelOnly=True):
513  yield n
514 
515  def keys(self):
516  return KeysView(self)
517 
518  def __reduce__(self):
519  # It would be a bit simpler to use __setstate__ and __getstate__.
520  # However, implementing __setstate__ in Python causes segfaults
521  # because pickle creates a new instance by calling
522  # object.__new__(PropertyList, *args) which bypasses
523  # the pybind11 memory allocation step.
524  return (_makePropertySet, (getPropertySetState(self),))
525 
526 
527 @continueClass
529  # Mapping of type to method names
530  _typeMenu = {bool: "Bool",
531  int: "Int",
532  float: "Double",
533  str: "String",
534  DateTime: "DateTime",
535  PropertySet: "PropertySet",
536  PropertyList: "PropertySet",
537  }
538 
539  COMMENTSUFFIX = "#COMMENT"
540 
541  def get(self, name):
542  """Return an item as a scalar or array
543 
544  Return an array if the item has more than one value,
545  otherwise return a scalar.
546 
547  .. deprecated:: 20180-06
548  `get` is superseded by `getArray` or `getScalar`
549 
550  Parameters
551  ----------
552  name : `str`
553  Name of item
554 
555  Raises
556  ------
557  KeyError
558  If the item does not exist.
559  """
560  warnings.warn("Use getArray or getScalar instead", DeprecationWarning, stacklevel=2)
561  return _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO)
562 
563  def getArray(self, name):
564  """Return an item as an array
565 
566  Parameters
567  ----------
568  name : `str`
569  Name of item
570 
571  Raises
572  ------
573  KeyError
574  If the item does not exist.
575  """
576  return _propertyContainerGet(self, name, returnStyle=ReturnStyle.ARRAY)
577 
578  def getScalar(self, name):
579  """Return an item as a scalar
580 
581  If the item has more than one value then the last value is returned
582 
583  Parameters
584  ----------
585  name : `str`
586  Name of item
587 
588  Raises
589  ------
590  KeyError
591  If the item does not exist.
592  """
593  return _propertyContainerGet(self, name, returnStyle=ReturnStyle.SCALAR)
594 
595  def set(self, name, value, comment=None):
596  """Set the value of an item
597 
598  If the item already exists it is silently replaced; the types
599  need not match.
600 
601  Parameters
602  ----------
603  name : `str`
604  Name of item
605  value : any supported type
606  Value of item; may be a scalar or array
607  """
608  args = []
609  if comment is not None:
610  args.append(comment)
611  return _propertyContainerSet(self, name, value, self._typeMenu, *args)
612 
613  def add(self, name, value, comment=None):
614  """Append one or more values to a given item, which need not exist
615 
616  If the item exists then the new value(s) are appended;
617  otherwise it is like calling `set`
618 
619  Parameters
620  ----------
621  name : `str`
622  Name of item
623  value : any supported type
624  Value of item; may be a scalar or array
625 
626  Notes
627  -----
628  If `value` is an `lsst.daf.base.PropertySet` items are added
629  using dotted names (e.g. if name="a" and value contains
630  an item "b" which is another PropertySet and contains an
631  item "c" which is numeric or string, then the value of "c"
632  is added as "a.b.c", appended to the existing values of
633  "a.b.c" if any (in which case the types must be compatible).
634 
635  Raises
636  ------
637  lsst::pex::exceptions::TypeError
638  If the type of `value` is incompatible with the existing value
639  of the item.
640  """
641  args = []
642  if comment is not None:
643  args.append(comment)
644  return _propertyContainerAdd(self, name, value, self._typeMenu, *args)
645 
646  def setComment(self, name, comment):
647  """Set the comment for an existing entry.
648 
649  Parameters
650  ----------
651  name : `str`
652  Name of the key to receive updated comment.
653  comment : `comment`
654  New comment string.
655  """
656  # The only way to do this is to replace the existing entry with
657  # one that has the new comment
658  containerType = _propertyContainerElementTypeName(self, name)
659  if self.isArray(name):
660  value = self.getArray(name)
661  else:
662  value = self.getScalar(name)
663  getattr(self, f"set{containerType}")(name, value, comment)
664 
665  def toList(self):
666  """Return a list of tuples of name, value, comment for each property
667  in the order that they were inserted.
668 
669  Returns
670  -------
671  ret : `list` of `tuple`
672  Tuples of name, value, comment for each property in the order
673  in which they were inserted.
674  """
675  orderedNames = self.getOrderedNames()
676  ret = []
677  for name in orderedNames:
678  if self.isArray(name):
679  values = _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO)
680  for v in values:
681  ret.append((name, v, self.getComment(name)))
682  else:
683  ret.append((name, _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO),
684  self.getComment(name)))
685  return ret
686 
687  def toOrderedDict(self):
688  """Return an ordered dictionary with all properties in the order that
689  they were inserted.
690 
691  Returns
692  -------
693  d : `~collections.OrderedDict`
694  Ordered dictionary with all properties in the order that they
695  were inserted. Comments are not included.
696  """
697  from collections import OrderedDict
698 
699  d = OrderedDict()
700  for name in self.getOrderedNames():
701  d[name] = _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO)
702  return d
703 
704  def __eq__(self, other):
705  # super() doesn't seem to work properly in @continueClass;
706  # note that super with arguments seems to work at first, but actually
707  # doesn't either.
708  if not PropertySet.__eq__(self, other):
709  return False
710 
711  for name in self:
712  if self.getComment(name) != other.getComment(name):
713  return False
714 
715  return True
716 
717  def __copy__(self):
718  # Copy without having to go through pickle state
719  pl = PropertyList()
720  for itemName in self:
721  pl.copy(itemName, self, itemName)
722  return pl
723 
724  def __deepcopy__(self, memo):
725  result = self.deepCopy()
726  memo[id(self)] = result
727  return result
728 
729  def __iter__(self):
730  for n in self.getOrderedNames():
731  yield n
732 
733  def __setitem__(self, name, value):
734  if name.endswith(self.COMMENTSUFFIX):
735  name = name[:-len(self.COMMENTSUFFIX)]
736  self.setComment(name, value)
737  return
738  if isinstance(value, Mapping):
739  # Create a property set instead
740  ps = PropertySet()
741  for k, v in value.items():
742  ps[k] = v
743  value = ps
744  self.set(name, value)
745 
746  def __reduce__(self):
747  # It would be a bit simpler to use __setstate__ and __getstate__.
748  # However, implementing __setstate__ in Python causes segfaults
749  # because pickle creates a new instance by calling
750  # object.__new__(PropertyList, *args) which bypasses
751  # the pybind11 memory allocation step.
752  return (_makePropertyList, (getPropertyListState(self),))
table::Key< int > id
Definition: Detector.cc:163
table::Key< int > type
Definition: Detector.cc:164
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
Definition: history.py:168