Coverage for python/lsst/daf/base/propertyContainer/propertyContainerContinued.py : 17%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
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#
25__all__ = ["getPropertySetState", "getPropertyListState", "setPropertySetState", "setPropertyListState"]
27import enum
28import numbers
29from collections.abc import Mapping, KeysView, ValuesView, ItemsView
31# Ensure that C++ exceptions are properly translated to Python
32import lsst.pex.exceptions # noqa: F401
33from lsst.utils import continueClass
35from .propertySet import PropertySet
36from .propertyList import PropertyList
37from ..dateTime import DateTime
40def getPropertySetState(container, asLists=False):
41 """Get the state of a PropertySet in a form that can be pickled.
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.
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 - name (a `str`): the name of the item
57 - elementTypeName (a `str`): the suffix of a ``setX`` method name
58 which is appropriate for the data type. For example integer
59 data has ``elementTypeName="Int"` which corresponds to
60 the ``setInt`` method.
61 - value: the data for the item, in a form compatible
62 with the set method named by ``elementTypeName``
63 """
64 names = container.names(topLevelOnly=True)
65 sequence = list if asLists else tuple
66 return [sequence((name, _propertyContainerElementTypeName(container, name),
67 _propertyContainerGet(container, name, returnStyle=ReturnStyle.AUTO)))
68 for name in names]
71def getPropertyListState(container, asLists=False):
72 """Get the state of a PropertyList in a form that can be pickled.
74 Parameters
75 ----------
76 container : `PropertyList`
77 The property container.
78 asLists : `bool`, optional
79 If False, the default, `tuple` will be used for the contents. If true
80 a `list` will be used.
82 Returns
83 -------
84 state : `list` of `tuple` or `list` of `list`
85 The state, as a list of tuples (or lists), each of which contains
86 the following 4 items:
87 - name (a `str`): the name of the item
88 - elementTypeName (a `str`): the suffix of a ``setX`` method name
89 which is appropriate for the data type. For example integer
90 data has ``elementTypeName="Int"` which corresponds to
91 the ``setInt`` method.
92 - value: the data for the item, in a form compatible
93 with the set method named by ``elementTypeName``
94 - comment (a `str`): the comment. This item is only present
95 if ``container`` is a PropertyList.
96 """
97 sequence = list if asLists else tuple
98 return [sequence((name, _propertyContainerElementTypeName(container, name),
99 _propertyContainerGet(container, name, returnStyle=ReturnStyle.AUTO),
100 container.getComment(name)))
101 for name in container.getOrderedNames()]
104def setPropertySetState(container, state):
105 """Restore the state of a PropertySet, in place.
107 Parameters
108 ----------
109 container : `PropertySet`
110 The property container whose state is to be restored.
111 It should be empty to start with and is updated in place.
112 state : `list`
113 The state, as returned by `getPropertySetState`
114 """
115 for name, elemType, value in state:
116 if elemType is not None:
117 getattr(container, "set" + elemType)(name, value)
118 else:
119 raise ValueError(f"Unrecognized values for state restoration: ({name}, {elemType}, {value})")
122def setPropertyListState(container, state):
123 """Restore the state of a PropertyList, in place.
125 Parameters
126 ----------
127 container : `PropertyList`
128 The property container whose state is to be restored.
129 It should be empty to start with and is updated in place.
130 state : `list`
131 The state, as returned by ``getPropertyListState``
132 """
133 for name, elemType, value, comment in state:
134 getattr(container, "set" + elemType)(name, value, comment)
137class ReturnStyle(enum.Enum):
138 ARRAY = enum.auto()
139 SCALAR = enum.auto()
140 AUTO = enum.auto()
143def _propertyContainerElementTypeName(container, name):
144 """Return name of the type of a particular element"""
145 try:
146 t = container.typeOf(name)
147 except LookupError as e:
148 # KeyError is more commonly expected when asking for an element
149 # from a mapping.
150 raise KeyError(str(e))
151 for checkType in ("Bool", "Short", "Int", "Long", "LongLong", "UnsignedLongLong",
152 "Float", "Double", "String", "DateTime",
153 "PropertySet", "Undef"):
154 if t == getattr(container, "TYPE_" + checkType):
155 return checkType
156 return None
159def _propertyContainerGet(container, name, returnStyle):
160 """Get a value of unknown type as a scalar or array
162 Parameters
163 ----------
164 container : `lsst.daf.base.PropertySet` or `lsst.daf.base.PropertyList`
165 Container from which to get the value
166 name : `str`
167 Name of item
168 returnStyle : `ReturnStyle`
169 Control whether numeric or string data is returned as an array
170 or scalar (the other types, ``PropertyList``, ``PropertySet``
171 and ``PersistablePtr``, are always returned as a scalar):
172 - ReturnStyle.ARRAY: return numeric or string data types
173 as an array of values.
174 - ReturnStyle.SCALAR: return numeric or string data types
175 as a single value; if the item has multiple values then
176 return the last value.
177 - ReturnStyle.AUTO: (deprecated) return numeric or string data
178 as a scalar if there is just one item, or as an array
179 otherwise.
181 Raises
182 ------
183 KeyError
184 Raised if the specified key does not exist in the container.
185 TypeError
186 Raised if the value retrieved is of an unexpected type.
187 ValueError
188 Raised if the value for ``returnStyle`` is not correct.
189 """
190 if not container.exists(name):
191 raise KeyError(name + " not found")
192 if returnStyle not in ReturnStyle:
193 raise ValueError("returnStyle {} must be a ReturnStyle".format(returnStyle))
195 elemType = _propertyContainerElementTypeName(container, name)
196 if elemType and elemType != "PropertySet":
197 value = getattr(container, "getArray" + elemType)(name)
198 if returnStyle == ReturnStyle.ARRAY or (returnStyle == ReturnStyle.AUTO and len(value) > 1):
199 return value
200 return value[-1]
202 if container.isPropertySetPtr(name):
203 try:
204 return container.getAsPropertyListPtr(name)
205 except Exception:
206 return container.getAsPropertySetPtr(name)
207 try:
208 return container.getAsPersistablePtr(name)
209 except Exception:
210 pass
211 raise TypeError('Unknown PropertySet value type for ' + name)
214def _guessIntegerType(container, name, value):
215 """Given an existing container and name, determine the type
216 that should be used for the supplied value. The supplied value
217 is assumed to be a scalar.
219 On Python 3 all ints are LongLong but we need to be able to store them
220 in Int containers if that is what is being used (testing for truncation).
221 Int is assumed to mean 32bit integer (2147483647 to -2147483648).
223 If there is no pre-existing value we have to decide what to do. For now
224 we pick Int if the value is less than maxsize.
226 Returns None if the value supplied is a bool or not an integral value.
227 """
228 useType = None
229 maxInt = 2147483647
230 minInt = -2147483648
231 maxLongLong = 2**63 - 1
232 minLongLong = -2**63
233 maxU64 = 2**64 - 1
234 minU64 = 0
236 # We do not want to convert bool to int so let the system work that
237 # out itself
238 if isinstance(value, bool):
239 return useType
241 if isinstance(value, numbers.Integral):
242 try:
243 containerType = _propertyContainerElementTypeName(container, name)
244 except LookupError:
245 # nothing in the container so choose based on size.
246 if value <= maxInt and value >= minInt:
247 useType = "Int"
248 elif value <= maxLongLong and value >= minLongLong:
249 useType = "LongLong"
250 elif value <= maxU64 and value >= minU64:
251 useType = "UnsignedLongLong"
252 else:
253 raise RuntimeError("Unable to guess integer type for storing value: %d" % (value,))
254 else:
255 if containerType == "Int":
256 # Always use an Int even if we know it won't fit. The later
257 # code will trigger OverflowError if appropriate. Setting the
258 # type to LongLong here will trigger a TypeError instead so
259 # it's best to trigger a predictable OverflowError.
260 useType = "Int"
261 elif containerType == "LongLong":
262 useType = "LongLong"
263 elif containerType == "UnsignedLongLong":
264 useType = "UnsignedLongLong"
265 return useType
268def _propertyContainerSet(container, name, value, typeMenu, *args):
269 """Set a single Python value of unknown type"""
270 if hasattr(value, "__iter__") and not isinstance(value, (str, PropertySet, PropertyList)):
271 exemplar = value[0]
272 else:
273 exemplar = value
275 t = type(exemplar)
276 setType = _guessIntegerType(container, name, exemplar)
278 if setType is not None or t in typeMenu:
279 if setType is None:
280 setType = typeMenu[t]
281 return getattr(container, "set" + setType)(name, value, *args)
282 # Allow for subclasses
283 for checkType in typeMenu:
284 if (checkType is None and exemplar is None) or \
285 (checkType is not None and isinstance(exemplar, checkType)):
286 return getattr(container, "set" + typeMenu[checkType])(name, value, *args)
287 raise TypeError("Unknown value type for key '%s': %s" % (name, t))
290def _propertyContainerAdd(container, name, value, typeMenu, *args):
291 """Add a single Python value of unknown type"""
292 if hasattr(value, "__iter__"):
293 exemplar = value[0]
294 else:
295 exemplar = value
297 t = type(exemplar)
298 addType = _guessIntegerType(container, name, exemplar)
300 if addType is not None or t in typeMenu:
301 if addType is None:
302 addType = typeMenu[t]
303 return getattr(container, "add" + addType)(name, value, *args)
304 # Allow for subclasses
305 for checkType in typeMenu:
306 if (checkType is None and exemplar is None) or \
307 (checkType is not None and isinstance(exemplar, checkType)):
308 return getattr(container, "add" + typeMenu[checkType])(name, value, *args)
309 raise TypeError("Unknown value type for key '%s': %s" % (name, t))
312def _makePropertySet(state):
313 """Make a `PropertySet` from the state returned by `getPropertySetState`
315 Parameters
316 ----------
317 state : `list`
318 The data returned by `getPropertySetState`.
319 """
320 ps = PropertySet()
321 setPropertySetState(ps, state)
322 return ps
325def _makePropertyList(state):
326 """Make a `PropertyList` from the state returned by
327 `getPropertyListState`
329 Parameters
330 ----------
331 state : `list`
332 The data returned by `getPropertySetState`.
333 """
334 pl = PropertyList()
335 setPropertyListState(pl, state)
336 return pl
339@continueClass
340class PropertySet:
341 # Mapping of type to method names;
342 # int types are omitted due to use of _guessIntegerType
343 _typeMenu = {bool: "Bool",
344 float: "Double",
345 str: "String",
346 DateTime: "DateTime",
347 PropertySet: "PropertySet",
348 PropertyList: "PropertySet",
349 None: "Undef",
350 }
352 def get(self, name, default=None):
353 """Return an item as a scalar, else default.
355 Identical to `getScalar` except that a default value is returned
356 if the requested key is not present. If an array item is requested
357 the final value in the array will be returned.
359 Parameters
360 ----------
361 name : ``str``
362 Name of item
363 default : `object`, optional
364 Default value to use if the named item is not present.
366 Returns
367 -------
368 value : any type supported by container
369 Single value of any type supported by the container, else the
370 default value if the requested item is not present in the
371 container. For array items the most recently added value is
372 returned.
373 """
374 try:
375 return _propertyContainerGet(self, name, returnStyle=ReturnStyle.SCALAR)
376 except KeyError:
377 return default
379 def getArray(self, name):
380 """Return an item as an array if the item is numeric or string
382 If the item is a `PropertySet`, `PropertyList` or
383 `lsst.daf.base.PersistablePtr` then return the item as a scalar.
385 Parameters
386 ----------
387 name : `str`
388 Name of item
390 Returns
391 -------
392 values : `list` of any type supported by container
393 The contents of the item, guaranteed to be returned as a `list.`
395 Raises
396 ------
397 KeyError
398 Raised if the item does not exist.
399 """
400 return _propertyContainerGet(self, name, returnStyle=ReturnStyle.ARRAY)
402 def getScalar(self, name):
403 """Return an item as a scalar
405 If the item has more than one value then the last value is returned.
407 Parameters
408 ----------
409 name : `str`
410 Name of item
412 Returns
413 -------
414 value : scalar item
415 Value stored in the item. If the item refers to an array the
416 most recently added value is returned.
418 Raises
419 ------
420 KeyError
421 Raised if the item does not exist.
422 """
423 return _propertyContainerGet(self, name, returnStyle=ReturnStyle.SCALAR)
425 def set(self, name, value):
426 """Set the value of an item
428 If the item already exists it is silently replaced; the types
429 need not match.
431 Parameters
432 ----------
433 name : `str`
434 Name of item
435 value : any supported type
436 Value of item; may be a scalar or array
437 """
438 return _propertyContainerSet(self, name, value, self._typeMenu)
440 def add(self, name, value):
441 """Append one or more values to a given item, which need not exist
443 If the item exists then the new value(s) are appended;
444 otherwise it is like calling `set`
446 Parameters
447 ----------
448 name : `str`
449 Name of item
450 value : any supported type
451 Value of item; may be a scalar or array
453 Notes
454 -----
455 If ``value`` is an `lsst.daf.base.PropertySet` or
456 `lsst.daf.base.PropertyList` then ``value`` replaces
457 the existing value. Also the item is added as a live
458 reference, so updating ``value`` will update this container
459 and vice-versa.
461 Raises
462 ------
463 lsst::pex::exceptions::TypeError
464 Raised if the type of `value` is incompatible with the existing
465 value of the item.
466 """
467 return _propertyContainerAdd(self, name, value, self._typeMenu)
469 def update(self, addition):
470 """Update the current container with the supplied additions.
472 Parameters
473 ----------
474 addition : `collections.abc.Mapping` or `PropertySet`
475 The content to merge into the current container.
477 Notes
478 -----
479 This is not the same as calling `PropertySet.combine` since the
480 behavior differs when both mappings contain the same key. This
481 method updates by overwriting existing values completely with
482 the new value.
483 """
484 if isinstance(addition, PropertySet):
485 # To support array values we can not use the dict interface
486 # and instead use the copy() method which overwrites
487 for k in addition:
488 self.copy(k, addition, k)
489 else:
490 for k, v in addition.items():
491 self[k] = v
493 def toDict(self):
494 """Returns a (possibly nested) dictionary with all properties.
496 Returns
497 -------
498 d : `dict`
499 Dictionary with all names and values (no comments).
500 """
502 d = {}
503 for name in self.names():
504 v = _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO)
506 if isinstance(v, PropertySet):
507 d[name] = PropertySet.toDict(v)
508 else:
509 d[name] = v
510 return d
512 def __eq__(self, other):
513 if type(self) != type(other):
514 return False
516 if len(self) != len(other):
517 return False
519 for name in self:
520 if _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO) != \
521 _propertyContainerGet(other, name, returnStyle=ReturnStyle.AUTO):
522 return False
523 if self.typeOf(name) != other.typeOf(name):
524 return False
526 return True
528 def __copy__(self):
529 # Copy without having to go through pickle state
530 ps = PropertySet()
531 for itemName in self:
532 ps.copy(itemName, self, itemName)
533 return ps
535 def __deepcopy__(self, memo):
536 result = self.deepCopy()
537 memo[id(self)] = result
538 return result
540 def __contains__(self, name):
541 """Determines if the name is found at the top level hierarchy
542 of the container.
544 Notes
545 ------
546 Does not use `PropertySet.exists()`` because that includes support
547 for "."-delimited names. This method is consistent with the
548 items returned from ``__iter__``.
549 """
550 return name in self.names(topLevelOnly=True)
552 def __setitem__(self, name, value):
553 """Assigns the supplied value to the container.
555 Parameters
556 ----------
557 name : `str`
558 Name of item to update.
559 value : Value to assign
560 Can be any value supported by the container's ``set()``
561 method. `~collections.abc.Mapping` are converted to
562 `PropertySet` before assignment.
564 Notes
565 -----
566 Uses `PropertySet.set`, overwriting any previous value.
567 """
568 if isinstance(value, Mapping):
569 # Create a property set instead
570 ps = PropertySet()
571 for k, v in value.items():
572 ps[k] = v
573 value = ps
574 self.set(name, value)
576 def __getitem__(self, name):
577 """Returns a scalar item from the container.
579 Notes
580 -----
581 Uses `PropertySet.getScalar` to guarantee that a single value
582 will be returned.
583 """
584 return self.getScalar(name)
586 def __delitem__(self, name):
587 if name in self:
588 self.remove(name)
589 else:
590 raise KeyError(f"{name} not present in dict")
592 def __str__(self):
593 return self.toString()
595 def __len__(self):
596 return self.nameCount(topLevelOnly=True)
598 def __iter__(self):
599 for n in self.names(topLevelOnly=True):
600 yield n
602 def keys(self):
603 return KeysView(self)
605 def items(self):
606 return ItemsView(self)
608 def values(self):
609 return ValuesView(self)
611 def __reduce__(self):
612 # It would be a bit simpler to use __setstate__ and __getstate__.
613 # However, implementing __setstate__ in Python causes segfaults
614 # because pickle creates a new instance by calling
615 # object.__new__(PropertyList, *args) which bypasses
616 # the pybind11 memory allocation step.
617 return (_makePropertySet, (getPropertySetState(self),))
620@continueClass
621class PropertyList:
622 # Mapping of type to method names
623 _typeMenu = {bool: "Bool",
624 int: "Int",
625 float: "Double",
626 str: "String",
627 DateTime: "DateTime",
628 PropertySet: "PropertySet",
629 PropertyList: "PropertySet",
630 None: "Undef",
631 }
633 COMMENTSUFFIX = "#COMMENT"
634 """Special suffix used to indicate that a named item being assigned
635 using dict syntax is referring to a comment, not value."""
637 def get(self, name, default=None):
638 """Return an item as a scalar, else default.
640 Identical to `getScalar` except that a default value is returned
641 if the requested key is not present. If an array item is requested
642 the final value in the array will be returned.
644 Parameters
645 ----------
646 name : ``str``
647 Name of item
648 default : `object`, optional
649 Default value to use if the named item is not present.
651 Returns
652 -------
653 value : any type supported by container
654 Single value of any type supported by the container, else the
655 default value if the requested item is not present in the
656 container. For array items the most recently added value is
657 returned.
658 """
659 try:
660 return _propertyContainerGet(self, name, returnStyle=ReturnStyle.SCALAR)
661 except KeyError:
662 return default
664 def getArray(self, name):
665 """Return an item as a list.
667 Parameters
668 ----------
669 name : `str`
670 Name of item
672 Returns
673 -------
674 values : `list` of values
675 The contents of the item, guaranteed to be returned as a `list.`
677 Raises
678 ------
679 KeyError
680 Raised if the item does not exist.
681 """
682 return _propertyContainerGet(self, name, returnStyle=ReturnStyle.ARRAY)
684 def getScalar(self, name):
685 """Return an item as a scalar
687 If the item has more than one value then the last value is returned.
689 Parameters
690 ----------
691 name : `str`
692 Name of item.
694 Returns
695 -------
696 value : scalar item
697 Value stored in the item. If the item refers to an array the
698 most recently added value is returned.
700 Raises
701 ------
702 KeyError
703 Raised if the item does not exist.
704 """
705 return _propertyContainerGet(self, name, returnStyle=ReturnStyle.SCALAR)
707 def set(self, name, value, comment=None):
708 """Set the value of an item
710 If the item already exists it is silently replaced; the types
711 need not match.
713 Parameters
714 ----------
715 name : `str`
716 Name of item
717 value : any supported type
718 Value of item; may be a scalar or array
719 """
720 args = []
721 if comment is not None:
722 args.append(comment)
723 return _propertyContainerSet(self, name, value, self._typeMenu, *args)
725 def add(self, name, value, comment=None):
726 """Append one or more values to a given item, which need not exist
728 If the item exists then the new value(s) are appended;
729 otherwise it is like calling `set`
731 Parameters
732 ----------
733 name : `str`
734 Name of item
735 value : any supported type
736 Value of item; may be a scalar or array
738 Notes
739 -----
740 If `value` is an `lsst.daf.base.PropertySet` items are added
741 using dotted names (e.g. if name="a" and value contains
742 an item "b" which is another PropertySet and contains an
743 item "c" which is numeric or string, then the value of "c"
744 is added as "a.b.c", appended to the existing values of
745 "a.b.c" if any (in which case the types must be compatible).
747 Raises
748 ------
749 lsst::pex::exceptions::TypeError
750 Raise if the type of ``value`` is incompatible with the existing
751 value of the item.
752 """
753 args = []
754 if comment is not None:
755 args.append(comment)
756 return _propertyContainerAdd(self, name, value, self._typeMenu, *args)
758 def setComment(self, name, comment):
759 """Set the comment for an existing entry.
761 Parameters
762 ----------
763 name : `str`
764 Name of the key to receive updated comment.
765 comment : `comment`
766 New comment string.
767 """
768 # The only way to do this is to replace the existing entry with
769 # one that has the new comment
770 containerType = _propertyContainerElementTypeName(self, name)
771 if self.isArray(name):
772 value = self.getArray(name)
773 else:
774 value = self.getScalar(name)
775 getattr(self, f"set{containerType}")(name, value, comment)
777 def toList(self):
778 """Return a list of tuples of name, value, comment for each property
779 in the order that they were inserted.
781 Returns
782 -------
783 ret : `list` of `tuple`
784 Tuples of name, value, comment for each property in the order
785 in which they were inserted.
786 """
787 orderedNames = self.getOrderedNames()
788 ret = []
789 for name in orderedNames:
790 if self.isArray(name):
791 values = _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO)
792 for v in values:
793 ret.append((name, v, self.getComment(name)))
794 else:
795 ret.append((name, _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO),
796 self.getComment(name)))
797 return ret
799 def toOrderedDict(self):
800 """Return an ordered dictionary with all properties in the order that
801 they were inserted.
803 Returns
804 -------
805 d : `dict`
806 Ordered dictionary with all properties in the order that they
807 were inserted. Comments are not included.
809 Notes
810 -----
811 As of Python 3.6 dicts retain their insertion order.
812 """
813 d = {}
814 for name in self:
815 d[name] = _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO)
816 return d
818 # For PropertyList the two are equivalent
819 toDict = toOrderedDict
821 def __eq__(self, other):
822 # super() doesn't seem to work properly in @continueClass;
823 # note that super with arguments seems to work at first, but actually
824 # doesn't either.
825 if not PropertySet.__eq__(self, other):
826 return False
828 for name in self:
829 if self.getComment(name) != other.getComment(name):
830 return False
832 return True
834 def __copy__(self):
835 # Copy without having to go through pickle state
836 pl = PropertyList()
837 for itemName in self:
838 pl.copy(itemName, self, itemName)
839 return pl
841 def __deepcopy__(self, memo):
842 result = self.deepCopy()
843 memo[id(self)] = result
844 return result
846 def __iter__(self):
847 for n in self.getOrderedNames():
848 yield n
850 def __setitem__(self, name, value):
851 """Assigns the supplied value to the container.
853 Parameters
854 ----------
855 name : `str`
856 Name of item to update. If the name ends with
857 `PropertyList.COMMENTSUFFIX`, the comment is updated rather
858 than the value.
859 value : Value to assign
860 Can be any value supported by the container's ``set()``
861 method. `~collections.abc.Mapping` are converted to
862 `PropertySet` before assignment.
864 Notes
865 -----
866 Uses `PropertySet.set`, overwriting any previous value.
867 """
868 if name.endswith(self.COMMENTSUFFIX):
869 name = name[:-len(self.COMMENTSUFFIX)]
870 self.setComment(name, value)
871 return
872 if isinstance(value, Mapping):
873 # Create a property set instead
874 ps = PropertySet()
875 for k, v in value.items():
876 ps[k] = v
877 value = ps
878 self.set(name, value)
880 def __reduce__(self):
881 # It would be a bit simpler to use __setstate__ and __getstate__.
882 # However, implementing __setstate__ in Python causes segfaults
883 # because pickle creates a new instance by calling
884 # object.__new__(PropertyList, *args) which bypasses
885 # the pybind11 memory allocation step.
886 return (_makePropertyList, (getPropertyListState(self),))