LSST Applications  21.0.0-147-g0e635eb1+1acddb5be5,22.0.0+052faf71bd,22.0.0+1ea9a8b2b2,22.0.0+6312710a6c,22.0.0+729191ecac,22.0.0+7589c3a021,22.0.0+9f079a9461,22.0.1-1-g7d6de66+b8044ec9de,22.0.1-1-g87000a6+536b1ee016,22.0.1-1-g8e32f31+6312710a6c,22.0.1-10-gd060f87+016f7cdc03,22.0.1-12-g9c3108e+df145f6f68,22.0.1-16-g314fa6d+c825727ab8,22.0.1-19-g93a5c75+d23f2fb6d8,22.0.1-19-gb93eaa13+aab3ef7709,22.0.1-2-g8ef0a89+b8044ec9de,22.0.1-2-g92698f7+9f079a9461,22.0.1-2-ga9b0f51+052faf71bd,22.0.1-2-gac51dbf+052faf71bd,22.0.1-2-gb66926d+6312710a6c,22.0.1-2-gcb770ba+09e3807989,22.0.1-20-g32debb5+b8044ec9de,22.0.1-23-gc2439a9a+fb0756638e,22.0.1-3-g496fd5d+09117f784f,22.0.1-3-g59f966b+1e6ba2c031,22.0.1-3-g849a1b8+f8b568069f,22.0.1-3-gaaec9c0+c5c846a8b1,22.0.1-32-g5ddfab5d3+60ce4897b0,22.0.1-4-g037fbe1+64e601228d,22.0.1-4-g8623105+b8044ec9de,22.0.1-5-g096abc9+d18c45d440,22.0.1-5-g15c806e+57f5c03693,22.0.1-7-gba73697+57f5c03693,master-g6e05de7fdc+c1283a92b8,master-g72cdda8301+729191ecac,w.2021.39
LSST Data Management Base Package
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 from collections.abc import Mapping, KeysView, ValuesView, ItemsView
30 
31 # Ensure that C++ exceptions are properly translated to Python
32 import lsst.pex.exceptions # noqa: F401
33 from lsst.utils import continueClass
34 
35 from .propertySet import PropertySet
36 from .propertyList import PropertyList
37 from ..dateTime import DateTime
38 
39 
40 def getPropertySetState(container, asLists=False):
41  """Get the state of a PropertySet in a form that can be pickled.
42 
43  Parameters
44  ----------
45  container : `PropertySet`
46  The property container.
47  asLists : `bool`, optional
48  If False, the default, `tuple` will be used for the contents. If true
49  a `list` will be used.
50 
51  Returns
52  -------
53  state : `list` of `tuple` or `list` of `list`
54  The state, as a list of tuples (or lists), each of which contains
55  the following 3 items:
56 
57  name (a `str`)
58  the name of the item
59  elementTypeName (a `str`)
60  the suffix of a ``setX`` method name
61  which is appropriate for the data type. For example integer
62  data has ``elementTypeName="Int"` which corresponds to
63  the ``setInt`` method.
64  value
65  the data for the item, in a form compatible
66  with the set method named by ``elementTypeName``
67  """
68  names = container.names(topLevelOnly=True)
69  sequence = list if asLists else tuple
70  return [sequence((name, _propertyContainerElementTypeName(container, name),
71  _propertyContainerGet(container, name, returnStyle=ReturnStyle.AUTO)))
72  for name in names]
73 
74 
75 def getPropertyListState(container, asLists=False):
76  """Get the state of a PropertyList in a form that can be pickled.
77 
78  Parameters
79  ----------
80  container : `PropertyList`
81  The property container.
82  asLists : `bool`, optional
83  If False, the default, `tuple` will be used for the contents. If true
84  a `list` will be used.
85 
86  Returns
87  -------
88  state : `list` of `tuple` or `list` of `list`
89  The state, as a list of tuples (or lists), each of which contains
90  the following 4 items:
91 
92  name (a `str`):
93  the name of the item
94  elementTypeName (a `str`):
95  the suffix of a ``setX`` method name
96  which is appropriate for the data type. For example integer
97  data has ``elementTypeName="Int"` which corresponds to
98  the ``setInt`` method.
99  value
100  the data for the item, in a form compatible
101  with the set method named by ``elementTypeName``
102  comment (a `str`): the comment. This item is only present
103  if ``container`` is a PropertyList.
104  """
105  sequence = list if asLists else tuple
106  return [sequence((name, _propertyContainerElementTypeName(container, name),
107  _propertyContainerGet(container, name, returnStyle=ReturnStyle.AUTO),
108  container.getComment(name)))
109  for name in container.getOrderedNames()]
110 
111 
112 def setPropertySetState(container, state):
113  """Restore the state of a PropertySet, in place.
114 
115  Parameters
116  ----------
117  container : `PropertySet`
118  The property container whose state is to be restored.
119  It should be empty to start with and is updated in place.
120  state : `list`
121  The state, as returned by `getPropertySetState`
122  """
123  for name, elemType, value in state:
124  if elemType is not None:
125  getattr(container, "set" + elemType)(name, value)
126  else:
127  raise ValueError(f"Unrecognized values for state restoration: ({name}, {elemType}, {value})")
128 
129 
130 def setPropertyListState(container, state):
131  """Restore the state of a PropertyList, in place.
132 
133  Parameters
134  ----------
135  container : `PropertyList`
136  The property container whose state is to be restored.
137  It should be empty to start with and is updated in place.
138  state : `list`
139  The state, as returned by ``getPropertyListState``
140  """
141  for name, elemType, value, comment in state:
142  getattr(container, "set" + elemType)(name, value, comment)
143 
144 
145 class ReturnStyle(enum.Enum):
146  ARRAY = enum.auto()
147  SCALAR = enum.auto()
148  AUTO = enum.auto()
149 
150 
151 def _propertyContainerElementTypeName(container, name):
152  """Return name of the type of a particular element
153 
154  Parameters
155  ----------
156  container : `lsst.daf.base.PropertySet` or `lsst.daf.base.PropertyList`
157  Container including the element
158  name : `str`
159  Name of element
160  """
161  try:
162  t = container.typeOf(name)
163  except LookupError as e:
164  # KeyError is more commonly expected when asking for an element
165  # from a mapping.
166  raise KeyError(str(e))
167  for checkType in ("Bool", "Short", "Int", "Long", "LongLong", "UnsignedLongLong",
168  "Float", "Double", "String", "DateTime",
169  "PropertySet", "Undef"):
170  if t == getattr(container, "TYPE_" + checkType):
171  return checkType
172  return None
173 
174 
175 def _propertyContainerGet(container, name, returnStyle):
176  """Get a value of unknown type as a scalar or array
177 
178  Parameters
179  ----------
180  container : `lsst.daf.base.PropertySet` or `lsst.daf.base.PropertyList`
181  Container from which to get the value
182  name : `str`
183  Name of item
184  returnStyle : `ReturnStyle`
185  Control whether numeric or string data is returned as an array
186  or scalar (the other types, ``PropertyList``, ``PropertySet``
187  and ``PersistablePtr``, are always returned as a scalar):
188  - ReturnStyle.ARRAY: return numeric or string data types
189  as an array of values.
190  - ReturnStyle.SCALAR: return numeric or string data types
191  as a single value; if the item has multiple values then
192  return the last value.
193  - ReturnStyle.AUTO: (deprecated) return numeric or string data
194  as a scalar if there is just one item, or as an array
195  otherwise.
196 
197  Raises
198  ------
199  KeyError
200  Raised if the specified key does not exist in the container.
201  TypeError
202  Raised if the value retrieved is of an unexpected type.
203  ValueError
204  Raised if the value for ``returnStyle`` is not correct.
205  """
206  if not container.exists(name):
207  raise KeyError(name + " not found")
208  if returnStyle not in ReturnStyle:
209  raise ValueError("returnStyle {} must be a ReturnStyle".format(returnStyle))
210 
211  elemType = _propertyContainerElementTypeName(container, name)
212  if elemType and elemType != "PropertySet":
213  value = getattr(container, "getArray" + elemType)(name)
214  if returnStyle == ReturnStyle.ARRAY or (returnStyle == ReturnStyle.AUTO and len(value) > 1):
215  return value
216  return value[-1]
217 
218  if container.isPropertySetPtr(name):
219  try:
220  return container.getAsPropertyListPtr(name)
221  except Exception:
222  return container.getAsPropertySetPtr(name)
223  try:
224  return container.getAsPersistablePtr(name)
225  except Exception:
226  pass
227  raise TypeError('Unknown PropertySet value type for ' + name)
228 
229 
230 def _iterable(a):
231  """Make input iterable.
232 
233  Takes whatever is given to it and yields it back one element at a time.
234  If it is not an iterable or it is a string or PropertySet/List,
235  yields itself.
236  """
237  if isinstance(a, (str, PropertyList, PropertySet)):
238  yield a
239  return
240  try:
241  yield from a
242  except Exception:
243  yield a
244 
245 
246 def _guessIntegerType(container, name, value):
247  """Given an existing container and name, determine the type
248  that should be used for the supplied value. The supplied value
249  is assumed to be a scalar.
250 
251  On Python 3 all ints are LongLong but we need to be able to store them
252  in Int containers if that is what is being used (testing for truncation).
253  Int is assumed to mean 32bit integer (2147483647 to -2147483648).
254 
255  If there is no pre-existing value we have to decide what to do. For now
256  we pick Int if the value is less than maxsize.
257 
258  Parameters
259  ----------
260  container : `lsst.daf.base.PropertySet` or `lsst.daf.base.PropertyList`
261  Container from which to get the value
262 
263  name : `str`
264  Name of item
265 
266  value : `object`
267  Value to be assigned a type. Can be an iterable.
268 
269  Returns
270  -------
271  useType : `str` or none
272  Type to use for the supplied value. `None` if the input is
273  `bool` or a non-integral value.
274  """
275  maxInt = 2147483647
276  minInt = -2147483648
277  maxLongLong = 2**63 - 1
278  minLongLong = -2**63
279  maxU64 = 2**64 - 1
280  minU64 = 0
281 
282  # Go through the values to find the range of supplied integers,
283  # stopping early if we don't have an integer.
284  min = None
285  max = None
286  for v in _iterable(value):
287  # Do not try to convert a bool to an integer
288  if not isinstance(v, numbers.Integral) or isinstance(v, bool):
289  return None
290 
291  if min is None:
292  min = v
293  max = v
294  elif v < min:
295  min = v
296  elif v > max:
297  max = v
298 
299  # Safety net
300  if min is None or max is None:
301  raise RuntimeError(f"Internal logic failure calculating integer range of {value}")
302 
303  def _choose_int_from_range(int_value, current_type):
304  # If this is changing type from non-integer the current type
305  # does not matter.
306  if current_type not in {"Int", "LongLong", "UnsignedLongLong"}:
307  current_type = None
308 
309  if int_value <= maxInt and int_value >= minInt and current_type in (None, "Int"):
310  # Use Int only if in range and either no current type or the
311  # current type is an Int.
312  use_type = "Int"
313  elif int_value >= minLongLong and int_value < 0:
314  # All large negatives must be LongLong if they did not fit
315  # in Int clause above.
316  use_type = "LongLong"
317  elif int_value >= 0 and int_value <= maxLongLong and current_type in (None, "Int", "LongLong"):
318  # Larger than Int or already a LongLong
319  use_type = "LongLong"
320  elif int_value <= maxU64 and int_value >= minU64:
321  use_type = "UnsignedLongLong"
322  else:
323  raise RuntimeError("Unable to guess integer type for storing out of "
324  f"range value: {int_value}")
325  return use_type
326 
327  try:
328  containerType = _propertyContainerElementTypeName(container, name)
329  except LookupError:
330  containerType = None
331 
332  useTypeMin = _choose_int_from_range(min, containerType)
333  useTypeMax = _choose_int_from_range(max, containerType)
334 
335  if useTypeMin == useTypeMax:
336  return useTypeMin
337 
338  # When different the combinations are:
339  # Int + LongLong
340  # Int + UnsignedLongLong
341  # LongLong + UnsignedLongLong
342 
343  choices = {useTypeMin, useTypeMax}
344  if choices == {"Int", "LongLong"}:
345  return "LongLong"
346 
347  # If UnsignedLongLong is required things will break if the min
348  # is negative. They will break whatever we choose if that is the case
349  # but we have no choice but to return the UnsignedLongLong regardless.
350  if "UnsignedLongLong" in choices:
351  return "UnsignedLongLong"
352 
353  raise RuntimeError(f"Logic error in guessing integer type from {min} and {max}")
354 
355 
356 def _propertyContainerSet(container, name, value, typeMenu, *args):
357  """Set a single Python value of unknown type
358  """
359  try:
360  exemplar = next(_iterable(value))
361  except StopIteration:
362  # Do nothing if nothing provided. This matches the behavior
363  # of the explicit setX() methods.
364  return
365  t = type(exemplar)
366  setType = _guessIntegerType(container, name, value)
367 
368  if setType is not None or t in typeMenu:
369  if setType is None:
370  setType = typeMenu[t]
371  return getattr(container, "set" + setType)(name, value, *args)
372  # Allow for subclasses
373  for checkType in typeMenu:
374  if (checkType is None and exemplar is None) or \
375  (checkType is not None and isinstance(exemplar, checkType)):
376  return getattr(container, "set" + typeMenu[checkType])(name, value, *args)
377  raise TypeError("Unknown value type for key '%s': %s" % (name, t))
378 
379 
380 def _propertyContainerAdd(container, name, value, typeMenu, *args):
381  """Add a single Python value of unknown type
382  """
383  try:
384  exemplar = next(_iterable(value))
385  except StopIteration:
386  # Adding an empty iterable to an existing entry is a no-op
387  # since there is nothing to add.
388  return
389  t = type(exemplar)
390  addType = _guessIntegerType(container, name, exemplar)
391 
392  if addType is not None or t in typeMenu:
393  if addType is None:
394  addType = typeMenu[t]
395  return getattr(container, "add" + addType)(name, value, *args)
396  # Allow for subclasses
397  for checkType in typeMenu:
398  if (checkType is None and exemplar is None) or \
399  (checkType is not None and isinstance(exemplar, checkType)):
400  return getattr(container, "add" + typeMenu[checkType])(name, value, *args)
401  raise TypeError("Unknown value type for key '%s': %s" % (name, t))
402 
403 
404 def _makePropertySet(state):
405  """Make a `PropertySet` from the state returned by `getPropertySetState`
406 
407  Parameters
408  ----------
409  state : `list`
410  The data returned by `getPropertySetState`.
411  """
412  ps = PropertySet()
413  setPropertySetState(ps, state)
414  return ps
415 
416 
417 def _makePropertyList(state):
418  """Make a `PropertyList` from the state returned by
419  `getPropertyListState`
420 
421  Parameters
422  ----------
423  state : `list`
424  The data returned by `getPropertySetState`.
425  """
426  pl = PropertyList()
427  setPropertyListState(pl, state)
428  return pl
429 
430 
431 @continueClass
433  # Mapping of type to method names;
434  # int types are omitted due to use of _guessIntegerType
435  _typeMenu = {bool: "Bool",
436  float: "Double",
437  str: "String",
438  DateTime: "DateTime",
439  PropertySet: "PropertySet",
440  PropertyList: "PropertySet",
441  None: "Undef",
442  }
443 
444  def get(self, name, default=None):
445  """Return an item as a scalar, else default.
446 
447  Identical to `getScalar` except that a default value is returned
448  if the requested key is not present. If an array item is requested
449  the final value in the array will be returned.
450 
451  Parameters
452  ----------
453  name : `str`
454  Name of item
455  default : `object`, optional
456  Default value to use if the named item is not present.
457 
458  Returns
459  -------
460  value : any type supported by container
461  Single value of any type supported by the container, else the
462  default value if the requested item is not present in the
463  container. For array items the most recently added value is
464  returned.
465  """
466  try:
467  return _propertyContainerGet(self, name, returnStyle=ReturnStyle.SCALAR)
468  except KeyError:
469  return default
470 
471  def getArray(self, name):
472  """Return an item as an array if the item is numeric or string
473 
474  If the item is a `PropertySet`, `PropertyList` or
475  `lsst.daf.base.PersistablePtr` then return the item as a scalar.
476 
477  Parameters
478  ----------
479  name : `str`
480  Name of item
481 
482  Returns
483  -------
484  values : `list` of any type supported by container
485  The contents of the item, guaranteed to be returned as a `list.`
486 
487  Raises
488  ------
489  KeyError
490  Raised if the item does not exist.
491  """
492  return _propertyContainerGet(self, name, returnStyle=ReturnStyle.ARRAY)
493 
494  def getScalar(self, name):
495  """Return an item as a scalar
496 
497  If the item has more than one value then the last value is returned.
498 
499  Parameters
500  ----------
501  name : `str`
502  Name of item
503 
504  Returns
505  -------
506  value : scalar item
507  Value stored in the item. If the item refers to an array the
508  most recently added value is returned.
509 
510  Raises
511  ------
512  KeyError
513  Raised if the item does not exist.
514  """
515  return _propertyContainerGet(self, name, returnStyle=ReturnStyle.SCALAR)
516 
517  def set(self, name, value):
518  """Set the value of an item
519 
520  If the item already exists it is silently replaced; the types
521  need not match.
522 
523  Parameters
524  ----------
525  name : `str`
526  Name of item
527  value : any supported type
528  Value of item; may be a scalar or array
529  """
530  return _propertyContainerSet(self, name, value, self._typeMenu_typeMenu)
531 
532  def add(self, name, value):
533  """Append one or more values to a given item, which need not exist
534 
535  If the item exists then the new value(s) are appended;
536  otherwise it is like calling `set`
537 
538  Parameters
539  ----------
540  name : `str`
541  Name of item
542  value : any supported type
543  Value of item; may be a scalar or array
544 
545  Notes
546  -----
547  If ``value`` is an `lsst.daf.base.PropertySet` or
548  `lsst.daf.base.PropertyList` then ``value`` replaces
549  the existing value. Also the item is added as a live
550  reference, so updating ``value`` will update this container
551  and vice-versa.
552 
553  Raises
554  ------
555  lsst::pex::exceptions::TypeError
556  Raised if the type of `value` is incompatible with the existing
557  value of the item.
558  """
559  return _propertyContainerAdd(self, name, value, self._typeMenu_typeMenu)
560 
561  def update(self, addition):
562  """Update the current container with the supplied additions.
563 
564  Parameters
565  ----------
566  addition : `collections.abc.Mapping` or `PropertySet`
567  The content to merge into the current container.
568 
569  Notes
570  -----
571  This is not the same as calling `PropertySet.combine` since the
572  behavior differs when both mappings contain the same key. This
573  method updates by overwriting existing values completely with
574  the new value.
575  """
576  if isinstance(addition, PropertySet):
577  # To support array values we can not use the dict interface
578  # and instead use the copy() method which overwrites
579  for k in addition:
580  self.copy(k, addition, k)
581  else:
582  for k, v in addition.items():
583  self[k] = v
584 
585  def toDict(self):
586  """Returns a (possibly nested) dictionary with all properties.
587 
588  Returns
589  -------
590  d : `dict`
591  Dictionary with all names and values (no comments).
592  """
593 
594  d = {}
595  for name in self.names():
596  v = _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO)
597 
598  if isinstance(v, PropertySet):
599  d[name] = PropertySet.toDict(v)
600  else:
601  d[name] = v
602  return d
603 
604  def __eq__(self, other):
605  if type(self) != type(other):
606  return NotImplemented
607 
608  if len(self) != len(other):
609  return False
610 
611  for name in self:
612  if _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO) != \
613  _propertyContainerGet(other, name, returnStyle=ReturnStyle.AUTO):
614  return False
615  if self.typeOf(name) != other.typeOf(name):
616  return False
617 
618  return True
619 
620  def __copy__(self):
621  # Copy without having to go through pickle state
622  ps = PropertySet()
623  for itemName in self:
624  ps.copy(itemName, self, itemName)
625  return ps
626 
627  def __deepcopy__(self, memo):
628  result = self.deepCopy()
629  memo[id(self)] = result
630  return result
631 
632  def __contains__(self, name):
633  """Determines if the name is found at the top level hierarchy
634  of the container.
635 
636  Notes
637  ------
638  Does not use `PropertySet.exists()`` because that includes support
639  for "."-delimited names. This method is consistent with the
640  items returned from ``__iter__``.
641  """
642  return name in self.names(topLevelOnly=True)
643 
644  def __setitem__(self, name, value):
645  """Assigns the supplied value to the container.
646 
647  Parameters
648  ----------
649  name : `str`
650  Name of item to update.
651  value : Value to assign
652  Can be any value supported by the container's ``set()``
653  method. `~collections.abc.Mapping` are converted to
654  `PropertySet` before assignment.
655 
656  Notes
657  -----
658  Uses `PropertySet.set`, overwriting any previous value.
659  """
660  if isinstance(value, Mapping):
661  # Create a property set instead
662  ps = PropertySet()
663  for k, v in value.items():
664  ps[k] = v
665  value = ps
666  self.setset(name, value)
667 
668  def __getitem__(self, name):
669  """Returns a scalar item from the container.
670 
671  Notes
672  -----
673  Uses `PropertySet.getScalar` to guarantee that a single value
674  will be returned.
675  """
676  return self.getScalargetScalar(name)
677 
678  def __delitem__(self, name):
679  if name in self:
680  self.remove(name)
681  else:
682  raise KeyError(f"{name} not present in dict")
683 
684  def __str__(self):
685  return self.toString()
686 
687  def __len__(self):
688  return self.nameCount(topLevelOnly=True)
689 
690  def __iter__(self):
691  for n in self.names(topLevelOnly=True):
692  yield n
693 
694  def keys(self):
695  return KeysView(self)
696 
697  def items(self):
698  return ItemsView(self)
699 
700  def values(self):
701  return ValuesView(self)
702 
703  def pop(self, name, default=None):
704  """Remove the named key and return its value.
705 
706  Parameters
707  ----------
708  name : `str`
709  Name of the key to remove.
710  default : Any, optional
711  Value to return if the key is not present.
712 
713  Returns
714  -------
715  value : Any
716  The value of the item as would be returned using `getScalar()`.
717 
718  Raises
719  ------
720  KeyError
721  Raised if no default is given and the key is missing.
722  """
723  if name in self:
724  value = self[name]
725  self.remove(name)
726  else:
727  if default is None:
728  raise KeyError(name)
729  value = default
730  return value
731 
732  def __reduce__(self):
733  # It would be a bit simpler to use __setstate__ and __getstate__.
734  # However, implementing __setstate__ in Python causes segfaults
735  # because pickle creates a new instance by calling
736  # object.__new__(PropertyList, *args) which bypasses
737  # the pybind11 memory allocation step.
738  return (_makePropertySet, (getPropertySetState(self),))
739 
740 
741 @continueClass
743  # Mapping of type to method names
744  _typeMenu = {bool: "Bool",
745  int: "Int",
746  float: "Double",
747  str: "String",
748  DateTime: "DateTime",
749  PropertySet: "PropertySet",
750  PropertyList: "PropertySet",
751  None: "Undef",
752  }
753 
754  COMMENTSUFFIX = "#COMMENT"
755  """Special suffix used to indicate that a named item being assigned
756  using dict syntax is referring to a comment, not value."""
757 
758  def get(self, name, default=None):
759  """Return an item as a scalar, else default.
760 
761  Identical to `getScalar` except that a default value is returned
762  if the requested key is not present. If an array item is requested
763  the final value in the array will be returned.
764 
765  Parameters
766  ----------
767  name : ``str``
768  Name of item
769  default : `object`, optional
770  Default value to use if the named item is not present.
771 
772  Returns
773  -------
774  value : any type supported by container
775  Single value of any type supported by the container, else the
776  default value if the requested item is not present in the
777  container. For array items the most recently added value is
778  returned.
779  """
780  try:
781  return _propertyContainerGet(self, name, returnStyle=ReturnStyle.SCALAR)
782  except KeyError:
783  return default
784 
785  def getArray(self, name):
786  """Return an item as a list.
787 
788  Parameters
789  ----------
790  name : `str`
791  Name of item
792 
793  Returns
794  -------
795  values : `list` of values
796  The contents of the item, guaranteed to be returned as a `list.`
797 
798  Raises
799  ------
800  KeyError
801  Raised if the item does not exist.
802  """
803  return _propertyContainerGet(self, name, returnStyle=ReturnStyle.ARRAY)
804 
805  def getScalar(self, name):
806  """Return an item as a scalar
807 
808  If the item has more than one value then the last value is returned.
809 
810  Parameters
811  ----------
812  name : `str`
813  Name of item.
814 
815  Returns
816  -------
817  value : scalar item
818  Value stored in the item. If the item refers to an array the
819  most recently added value is returned.
820 
821  Raises
822  ------
823  KeyError
824  Raised if the item does not exist.
825  """
826  return _propertyContainerGet(self, name, returnStyle=ReturnStyle.SCALAR)
827 
828  def set(self, name, value, comment=None):
829  """Set the value of an item
830 
831  If the item already exists it is silently replaced; the types
832  need not match.
833 
834  Parameters
835  ----------
836  name : `str`
837  Name of item
838  value : any supported type
839  Value of item; may be a scalar or array
840  """
841  args = []
842  if comment is not None:
843  args.append(comment)
844  return _propertyContainerSet(self, name, value, self._typeMenu_typeMenu, *args)
845 
846  def add(self, name, value, comment=None):
847  """Append one or more values to a given item, which need not exist
848 
849  If the item exists then the new value(s) are appended;
850  otherwise it is like calling `set`
851 
852  Parameters
853  ----------
854  name : `str`
855  Name of item
856  value : any supported type
857  Value of item; may be a scalar or array
858 
859  Notes
860  -----
861  If `value` is an `lsst.daf.base.PropertySet` items are added
862  using dotted names (e.g. if name="a" and value contains
863  an item "b" which is another PropertySet and contains an
864  item "c" which is numeric or string, then the value of "c"
865  is added as "a.b.c", appended to the existing values of
866  "a.b.c" if any (in which case the types must be compatible).
867 
868  Raises
869  ------
870  lsst::pex::exceptions::TypeError
871  Raise if the type of ``value`` is incompatible with the existing
872  value of the item.
873  """
874  args = []
875  if comment is not None:
876  args.append(comment)
877  return _propertyContainerAdd(self, name, value, self._typeMenu_typeMenu, *args)
878 
879  def setComment(self, name, comment):
880  """Set the comment for an existing entry.
881 
882  Parameters
883  ----------
884  name : `str`
885  Name of the key to receive updated comment.
886  comment : `comment`
887  New comment string.
888  """
889  # The only way to do this is to replace the existing entry with
890  # one that has the new comment
891  containerType = _propertyContainerElementTypeName(self, name)
892  if self.isArray(name):
893  value = self.getArraygetArray(name)
894  else:
895  value = self.getScalargetScalar(name)
896  getattr(self, f"set{containerType}")(name, value, comment)
897 
898  def toList(self):
899  """Return a list of tuples of name, value, comment for each property
900  in the order that they were inserted.
901 
902  Returns
903  -------
904  ret : `list` of `tuple`
905  Tuples of name, value, comment for each property in the order
906  in which they were inserted.
907  """
908  orderedNames = self.getOrderedNames()
909  ret = []
910  for name in orderedNames:
911  if self.isArray(name):
912  values = _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO)
913  for v in values:
914  ret.append((name, v, self.getComment(name)))
915  else:
916  ret.append((name, _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO),
917  self.getComment(name)))
918  return ret
919 
920  def toOrderedDict(self):
921  """Return an ordered dictionary with all properties in the order that
922  they were inserted.
923 
924  Returns
925  -------
926  d : `dict`
927  Ordered dictionary with all properties in the order that they
928  were inserted. Comments are not included.
929 
930  Notes
931  -----
932  As of Python 3.6 dicts retain their insertion order.
933  """
934  d = {}
935  for name in self:
936  d[name] = _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO)
937  return d
938 
939  # For PropertyList the two are equivalent
940  toDict = toOrderedDict
941 
942  def __eq__(self, other):
943  # super() doesn't seem to work properly in @continueClass;
944  # note that super with arguments seems to work at first, but actually
945  # doesn't either.
946  if not PropertySet.__eq__(self, other):
947  return False
948 
949  for name in self:
950  if self.getComment(name) != other.getComment(name):
951  return False
952 
953  return True
954 
955  def __copy__(self):
956  # Copy without having to go through pickle state
957  pl = PropertyList()
958  for itemName in self:
959  pl.copy(itemName, self, itemName)
960  return pl
961 
962  def __deepcopy__(self, memo):
963  result = self.deepCopy()
964  memo[id(self)] = result
965  return result
966 
967  def __iter__(self):
968  for n in self.getOrderedNames():
969  yield n
970 
971  def __setitem__(self, name, value):
972  """Assigns the supplied value to the container.
973 
974  Parameters
975  ----------
976  name : `str`
977  Name of item to update. If the name ends with
978  `PropertyList.COMMENTSUFFIX`, the comment is updated rather
979  than the value.
980  value : Value to assign
981  Can be any value supported by the container's ``set()``
982  method. `~collections.abc.Mapping` are converted to
983  `PropertySet` before assignment.
984 
985  Notes
986  -----
987  Uses `PropertySet.set`, overwriting any previous value.
988  """
989  if name.endswith(self.COMMENTSUFFIXCOMMENTSUFFIX):
990  name = name[:-len(self.COMMENTSUFFIXCOMMENTSUFFIX)]
991  self.setCommentsetComment(name, value)
992  return
993  if isinstance(value, Mapping):
994  # Create a property set instead
995  ps = PropertySet()
996  for k, v in value.items():
997  ps[k] = v
998  value = ps
999  self.setset(name, value)
1000 
1001  def __reduce__(self):
1002  # It would be a bit simpler to use __setstate__ and __getstate__.
1003  # However, implementing __setstate__ in Python causes segfaults
1004  # because pickle creates a new instance by calling
1005  # object.__new__(PropertyList, *args) which bypasses
1006  # the pybind11 memory allocation step.
1007  return (_makePropertyList, (getPropertyListState(self),))
table::Key< int > id
Definition: Detector.cc:162
table::Key< int > type
Definition: Detector.cc:163
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
Definition: history.py:174