lsst.daf.base 22.0.1-7-g68429ac+c3c286018f
propertyContainerContinued.py
Go to the documentation of this file.
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
27import enum
28import numbers
29from collections.abc import Mapping, KeysView, ValuesView, ItemsView
30
31# Ensure that C++ exceptions are properly translated to Python
32import lsst.pex.exceptions # noqa: F401
33from lsst.utils import continueClass
34
35from .propertySet import PropertySet
36from .propertyList import PropertyList
37from ..dateTime import DateTime
38
39
40def 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
75def 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
112def 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
130def 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
145class ReturnStyle(enum.Enum):
146 ARRAY = enum.auto()
147 SCALAR = enum.auto()
148 AUTO = enum.auto()
149
150
151def _propertyContainerElementTypeName(container, name):
152 """Return name of the type of a particular element
153
154 Parameters
155 ----------
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
175def _propertyContainerGet(container, name, returnStyle):
176 """Get a value of unknown type as a scalar or array
177
178 Parameters
179 ----------
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
230def _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
246def _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 ----------
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
356def _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
380def _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
404def _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
417def _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 self.exists(name):
680 # dot-delimited names should work so cannot use "in".
681 self.remove(name)
682 else:
683 raise KeyError(f"{name} not present in dict")
684
685 def __str__(self):
686 return self.toString()
687
688 def __len__(self):
689 return self.nameCount(topLevelOnly=True)
690
691 def __iter__(self):
692 for n in self.names(topLevelOnly=True):
693 yield n
694
695 def keys(self):
696 return KeysView(self)
697
698 def items(self):
699 return ItemsView(self)
700
701 def values(self):
702 return ValuesView(self)
703
704 def pop(self, name, default=None):
705 """Remove the named key and return its value.
706
707 Parameters
708 ----------
709 name : `str`
710 Name of the key to remove. Can be hierarchical.
711 default : Any, optional
712 Value to return if the key is not present.
713
714 Returns
715 -------
716 value : Any
717 The value of the item as would be returned using `getScalar()`.
718
719 Raises
720 ------
721 KeyError
722 Raised if no default is given and the key is missing.
723 """
724 if self.exists(name):
725 value = self[name]
726 self.remove(name)
727 else:
728 if default is None:
729 raise KeyError(name)
730 value = default
731 return value
732
733 def __reduce__(self):
734 # It would be a bit simpler to use __setstate__ and __getstate__.
735 # However, implementing __setstate__ in Python causes segfaults
736 # because pickle creates a new instance by calling
737 # object.__new__(PropertyList, *args) which bypasses
738 # the pybind11 memory allocation step.
739 return (_makePropertySet, (getPropertySetState(self),))
740
741
742@continueClass
744 # Mapping of type to method names
745 _typeMenu = {bool: "Bool",
746 int: "Int",
747 float: "Double",
748 str: "String",
749 DateTime: "DateTime",
750 PropertySet: "PropertySet",
751 PropertyList: "PropertySet",
752 None: "Undef",
753 }
754
755 COMMENTSUFFIX = "#COMMENT"
756 """Special suffix used to indicate that a named item being assigned
757 using dict syntax is referring to a comment, not value."""
758
759 def get(self, name, default=None):
760 """Return an item as a scalar, else default.
761
762 Identical to `getScalar` except that a default value is returned
763 if the requested key is not present. If an array item is requested
764 the final value in the array will be returned.
765
766 Parameters
767 ----------
768 name : ``str``
769 Name of item
770 default : `object`, optional
771 Default value to use if the named item is not present.
772
773 Returns
774 -------
775 value : any type supported by container
776 Single value of any type supported by the container, else the
777 default value if the requested item is not present in the
778 container. For array items the most recently added value is
779 returned.
780 """
781 try:
782 return _propertyContainerGet(self, name, returnStyle=ReturnStyle.SCALAR)
783 except KeyError:
784 return default
785
786 def getArray(self, name):
787 """Return an item as a list.
788
789 Parameters
790 ----------
791 name : `str`
792 Name of item
793
794 Returns
795 -------
796 values : `list` of values
797 The contents of the item, guaranteed to be returned as a `list.`
798
799 Raises
800 ------
801 KeyError
802 Raised if the item does not exist.
803 """
804 return _propertyContainerGet(self, name, returnStyle=ReturnStyle.ARRAY)
805
806 def getScalar(self, name):
807 """Return an item as a scalar
808
809 If the item has more than one value then the last value is returned.
810
811 Parameters
812 ----------
813 name : `str`
814 Name of item.
815
816 Returns
817 -------
818 value : scalar item
819 Value stored in the item. If the item refers to an array the
820 most recently added value is returned.
821
822 Raises
823 ------
824 KeyError
825 Raised if the item does not exist.
826 """
827 return _propertyContainerGet(self, name, returnStyle=ReturnStyle.SCALAR)
828
829 def set(self, name, value, comment=None):
830 """Set the value of an item
831
832 If the item already exists it is silently replaced; the types
833 need not match.
834
835 Parameters
836 ----------
837 name : `str`
838 Name of item
839 value : any supported type
840 Value of item; may be a scalar or array
841 """
842 args = []
843 if comment is not None:
844 args.append(comment)
845 return _propertyContainerSet(self, name, value, self._typeMenu_typeMenu, *args)
846
847 def add(self, name, value, comment=None):
848 """Append one or more values to a given item, which need not exist
849
850 If the item exists then the new value(s) are appended;
851 otherwise it is like calling `set`
852
853 Parameters
854 ----------
855 name : `str`
856 Name of item
857 value : any supported type
858 Value of item; may be a scalar or array
859
860 Notes
861 -----
862 If `value` is an `lsst.daf.base.PropertySet` items are added
863 using dotted names (e.g. if name="a" and value contains
864 an item "b" which is another PropertySet and contains an
865 item "c" which is numeric or string, then the value of "c"
866 is added as "a.b.c", appended to the existing values of
867 "a.b.c" if any (in which case the types must be compatible).
868
869 Raises
870 ------
871 lsst::pex::exceptions::TypeError
872 Raise if the type of ``value`` is incompatible with the existing
873 value of the item.
874 """
875 args = []
876 if comment is not None:
877 args.append(comment)
878 return _propertyContainerAdd(self, name, value, self._typeMenu_typeMenu, *args)
879
880 def setComment(self, name, comment):
881 """Set the comment for an existing entry.
882
883 Parameters
884 ----------
885 name : `str`
886 Name of the key to receive updated comment.
887 comment : `comment`
888 New comment string.
889 """
890 # The only way to do this is to replace the existing entry with
891 # one that has the new comment
892 containerType = _propertyContainerElementTypeName(self, name)
893 if self.isArray(name):
894 value = self.getArraygetArray(name)
895 else:
896 value = self.getScalargetScalar(name)
897 getattr(self, f"set{containerType}")(name, value, comment)
898
899 def toList(self):
900 """Return a list of tuples of name, value, comment for each property
901 in the order that they were inserted.
902
903 Returns
904 -------
905 ret : `list` of `tuple`
906 Tuples of name, value, comment for each property in the order
907 in which they were inserted.
908 """
909 orderedNames = self.getOrderedNames()
910 ret = []
911 for name in orderedNames:
912 if self.isArray(name):
913 values = _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO)
914 for v in values:
915 ret.append((name, v, self.getComment(name)))
916 else:
917 ret.append((name, _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO),
918 self.getComment(name)))
919 return ret
920
921 def toOrderedDict(self):
922 """Return an ordered dictionary with all properties in the order that
923 they were inserted.
924
925 Returns
926 -------
927 d : `dict`
928 Ordered dictionary with all properties in the order that they
929 were inserted. Comments are not included.
930
931 Notes
932 -----
933 As of Python 3.6 dicts retain their insertion order.
934 """
935 d = {}
936 for name in self:
937 d[name] = _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO)
938 return d
939
940 # For PropertyList the two are equivalent
941 toDict = toOrderedDict
942
943 def __eq__(self, other):
944 # super() doesn't seem to work properly in @continueClass;
945 # note that super with arguments seems to work at first, but actually
946 # doesn't either.
947 if not PropertySet.__eq__(self, other):
948 return False
949
950 for name in self:
951 if self.getComment(name) != other.getComment(name):
952 return False
953
954 return True
955
956 def __copy__(self):
957 # Copy without having to go through pickle state
958 pl = PropertyList()
959 for itemName in self:
960 pl.copy(itemName, self, itemName)
961 return pl
962
963 def __deepcopy__(self, memo):
964 result = self.deepCopy()
965 memo[id(self)] = result
966 return result
967
968 def __iter__(self):
969 for n in self.getOrderedNames():
970 yield n
971
972 def __setitem__(self, name, value):
973 """Assigns the supplied value to the container.
974
975 Parameters
976 ----------
977 name : `str`
978 Name of item to update. If the name ends with
979 `PropertyList.COMMENTSUFFIX`, the comment is updated rather
980 than the value.
981 value : Value to assign
982 Can be any value supported by the container's ``set()``
983 method. `~collections.abc.Mapping` are converted to
984 `PropertySet` before assignment.
985
986 Notes
987 -----
988 Uses `PropertySet.set`, overwriting any previous value.
989 """
990 if name.endswith(self.COMMENTSUFFIXCOMMENTSUFFIX):
991 name = name[:-len(self.COMMENTSUFFIXCOMMENTSUFFIX)]
992 self.setCommentsetComment(name, value)
993 return
994 if isinstance(value, Mapping):
995 # Create a property set instead
996 ps = PropertySet()
997 for k, v in value.items():
998 ps[k] = v
999 value = ps
1000 self.setset(name, value)
1001
1002 def __reduce__(self):
1003 # It would be a bit simpler to use __setstate__ and __getstate__.
1004 # However, implementing __setstate__ in Python causes segfaults
1005 # because pickle creates a new instance by calling
1006 # object.__new__(PropertyList, *args) which bypasses
1007 # the pybind11 memory allocation step.
1008 return (_makePropertyList, (getPropertyListState(self),))
Class for storing ordered metadata with comments.
Definition: PropertyList.h:68
Class for storing generic metadata.
Definition: PropertySet.h:66