lsst.daf.base  18.0.0-3-gf033a76
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  - 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]
69 
70 
71 def getPropertyListState(container, asLists=False):
72  """Get the state of a PropertyList in a form that can be pickled.
73 
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.
81 
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()]
102 
103 
104 def setPropertySetState(container, state):
105  """Restore the state of a PropertySet, in place.
106 
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})")
120 
121 
122 def setPropertyListState(container, state):
123  """Restore the state of a PropertyList, in place.
124 
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)
135 
136 
137 class ReturnStyle(enum.Enum):
138  ARRAY = enum.auto()
139  SCALAR = enum.auto()
140  AUTO = enum.auto()
141 
142 
143 def _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
157 
158 
159 def _propertyContainerGet(container, name, returnStyle):
160  """Get a value of unknown type as a scalar or array
161 
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.
180 
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))
194 
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]
201 
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)
212 
213 
214 def _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.
218 
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).
222 
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.
225 
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
235 
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
240 
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
266 
267 
268 def _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
274 
275  t = type(exemplar)
276  setType = _guessIntegerType(container, name, exemplar)
277 
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))
288 
289 
290 def _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
296 
297  t = type(exemplar)
298  addType = _guessIntegerType(container, name, exemplar)
299 
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))
310 
311 
312 def _makePropertySet(state):
313  """Make a `PropertySet` from the state returned by `getPropertySetState`
314 
315  Parameters
316  ----------
317  state : `list`
318  The data returned by `getPropertySetState`.
319  """
320  ps = PropertySet()
321  setPropertySetState(ps, state)
322  return ps
323 
324 
325 def _makePropertyList(state):
326  """Make a `PropertyList` from the state returned by
327  `getPropertyListState`
328 
329  Parameters
330  ----------
331  state : `list`
332  The data returned by `getPropertySetState`.
333  """
334  pl = PropertyList()
335  setPropertyListState(pl, state)
336  return pl
337 
338 
339 @continueClass
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  }
351 
352  def get(self, name, default=None):
353  """Return an item as a scalar, else default.
354 
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.
358 
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.
365 
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
378 
379  def getArray(self, name):
380  """Return an item as an array if the item is numeric or string
381 
382  If the item is a `PropertySet`, `PropertyList` or
383  `lsst.daf.base.PersistablePtr` then return the item as a scalar.
384 
385  Parameters
386  ----------
387  name : `str`
388  Name of item
389 
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.`
394 
395  Raises
396  ------
397  KeyError
398  Raised if the item does not exist.
399  """
400  return _propertyContainerGet(self, name, returnStyle=ReturnStyle.ARRAY)
401 
402  def getScalar(self, name):
403  """Return an item as a scalar
404 
405  If the item has more than one value then the last value is returned.
406 
407  Parameters
408  ----------
409  name : `str`
410  Name of item
411 
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.
417 
418  Raises
419  ------
420  KeyError
421  Raised if the item does not exist.
422  """
423  return _propertyContainerGet(self, name, returnStyle=ReturnStyle.SCALAR)
424 
425  def set(self, name, value):
426  """Set the value of an item
427 
428  If the item already exists it is silently replaced; the types
429  need not match.
430 
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)
439 
440  def add(self, name, value):
441  """Append one or more values to a given item, which need not exist
442 
443  If the item exists then the new value(s) are appended;
444  otherwise it is like calling `set`
445 
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
452 
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.
460 
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)
468 
469  def update(self, addition):
470  """Update the current container with the supplied additions.
471 
472  Parameters
473  ----------
474  addition : `collections.abc.Mapping` or `PropertySet`
475  The content to merge into the current container.
476 
477  Notes
478  -----
479  If the supplied parameter is a `PropertySet` then the
480  `PropertySet.combine` method will be used. If the supplied parameter
481  is a `collections.abc.Mapping` each item will be copied out of the
482  mapping and value assigned using `PropertySet.set`, overwriting
483  any previous values.
484  """
485  if isinstance(addition, PropertySet):
486  self.combine(addition)
487  else:
488  for k, v in addition.items():
489  self[k] = v
490 
491  def toDict(self):
492  """Returns a (possibly nested) dictionary with all properties.
493 
494  Returns
495  -------
496  d : `dict`
497  Dictionary with all names and values (no comments).
498  """
499 
500  d = {}
501  for name in self.names():
502  v = _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO)
503 
504  if isinstance(v, PropertySet):
505  d[name] = PropertySet.toDict(v)
506  else:
507  d[name] = v
508  return d
509 
510  def __eq__(self, other):
511  if type(self) != type(other):
512  return False
513 
514  if len(self) != len(other):
515  return False
516 
517  for name in self:
518  if _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO) != \
519  _propertyContainerGet(other, name, returnStyle=ReturnStyle.AUTO):
520  return False
521  if self.typeOf(name) != other.typeOf(name):
522  return False
523 
524  return True
525 
526  def __copy__(self):
527  # Copy without having to go through pickle state
528  ps = PropertySet()
529  for itemName in self:
530  ps.copy(itemName, self, itemName)
531  return ps
532 
533  def __deepcopy__(self, memo):
534  result = self.deepCopy()
535  memo[id(self)] = result
536  return result
537 
538  def __contains__(self, name):
539  """Determines if the name is found at the top level hierarchy
540  of the container.
541 
542  Notes
543  ------
544  Does not use `PropertySet.exists()`` because that includes support
545  for "."-delimited names. This method is consistent with the
546  items returned from ``__iter__``.
547  """
548  return name in self.names(topLevelOnly=True)
549 
550  def __setitem__(self, name, value):
551  """Assigns the supplied value to the container.
552 
553  Parameters
554  ----------
555  name : `str`
556  Name of item to update.
557  value : Value to assign
558  Can be any value supported by the container's ``set()``
559  method. `~collections.abc.Mapping` are converted to
560  `PropertySet` before assignment.
561 
562  Notes
563  -----
564  Uses `PropertySet.set`, overwriting any previous value.
565  """
566  if isinstance(value, Mapping):
567  # Create a property set instead
568  ps = PropertySet()
569  for k, v in value.items():
570  ps[k] = v
571  value = ps
572  self.set(name, value)
573 
574  def __getitem__(self, name):
575  """Returns a scalar item from the container.
576 
577  Notes
578  -----
579  Uses `PropertySet.getScalar` to guarantee that a single value
580  will be returned.
581  """
582  return self.getScalar(name)
583 
584  def __delitem__(self, name):
585  if name in self:
586  self.remove(name)
587  else:
588  raise KeyError(f"{name} not present in dict")
589 
590  def __str__(self):
591  return self.toString()
592 
593  def __len__(self):
594  return self.nameCount(topLevelOnly=True)
595 
596  def __iter__(self):
597  for n in self.names(topLevelOnly=True):
598  yield n
599 
600  def keys(self):
601  return KeysView(self)
602 
603  def items(self):
604  return ItemsView(self)
605 
606  def values(self):
607  return ValuesView(self)
608 
609  def __reduce__(self):
610  # It would be a bit simpler to use __setstate__ and __getstate__.
611  # However, implementing __setstate__ in Python causes segfaults
612  # because pickle creates a new instance by calling
613  # object.__new__(PropertyList, *args) which bypasses
614  # the pybind11 memory allocation step.
615  return (_makePropertySet, (getPropertySetState(self),))
616 
617 
618 @continueClass
620  # Mapping of type to method names
621  _typeMenu = {bool: "Bool",
622  int: "Int",
623  float: "Double",
624  str: "String",
625  DateTime: "DateTime",
626  PropertySet: "PropertySet",
627  PropertyList: "PropertySet",
628  None: "Undef",
629  }
630 
631  COMMENTSUFFIX = "#COMMENT"
632  """Special suffix used to indicate that a named item being assigned
633  using dict syntax is referring to a comment, not value."""
634 
635  def get(self, name, default=None):
636  """Return an item as a scalar, else default.
637 
638  Identical to `getScalar` except that a default value is returned
639  if the requested key is not present. If an array item is requested
640  the final value in the array will be returned.
641 
642  Parameters
643  ----------
644  name : ``str``
645  Name of item
646  default : `object`, optional
647  Default value to use if the named item is not present.
648 
649  Returns
650  -------
651  value : any type supported by container
652  Single value of any type supported by the container, else the
653  default value if the requested item is not present in the
654  container. For array items the most recently added value is
655  returned.
656  """
657  try:
658  return _propertyContainerGet(self, name, returnStyle=ReturnStyle.SCALAR)
659  except KeyError:
660  return default
661 
662  def getArray(self, name):
663  """Return an item as a list.
664 
665  Parameters
666  ----------
667  name : `str`
668  Name of item
669 
670  Returns
671  -------
672  values : `list` of values
673  The contents of the item, guaranteed to be returned as a `list.`
674 
675  Raises
676  ------
677  KeyError
678  Raised if the item does not exist.
679  """
680  return _propertyContainerGet(self, name, returnStyle=ReturnStyle.ARRAY)
681 
682  def getScalar(self, name):
683  """Return an item as a scalar
684 
685  If the item has more than one value then the last value is returned.
686 
687  Parameters
688  ----------
689  name : `str`
690  Name of item.
691 
692  Returns
693  -------
694  value : scalar item
695  Value stored in the item. If the item refers to an array the
696  most recently added value is returned.
697 
698  Raises
699  ------
700  KeyError
701  Raised if the item does not exist.
702  """
703  return _propertyContainerGet(self, name, returnStyle=ReturnStyle.SCALAR)
704 
705  def set(self, name, value, comment=None):
706  """Set the value of an item
707 
708  If the item already exists it is silently replaced; the types
709  need not match.
710 
711  Parameters
712  ----------
713  name : `str`
714  Name of item
715  value : any supported type
716  Value of item; may be a scalar or array
717  """
718  args = []
719  if comment is not None:
720  args.append(comment)
721  return _propertyContainerSet(self, name, value, self._typeMenu, *args)
722 
723  def add(self, name, value, comment=None):
724  """Append one or more values to a given item, which need not exist
725 
726  If the item exists then the new value(s) are appended;
727  otherwise it is like calling `set`
728 
729  Parameters
730  ----------
731  name : `str`
732  Name of item
733  value : any supported type
734  Value of item; may be a scalar or array
735 
736  Notes
737  -----
738  If `value` is an `lsst.daf.base.PropertySet` items are added
739  using dotted names (e.g. if name="a" and value contains
740  an item "b" which is another PropertySet and contains an
741  item "c" which is numeric or string, then the value of "c"
742  is added as "a.b.c", appended to the existing values of
743  "a.b.c" if any (in which case the types must be compatible).
744 
745  Raises
746  ------
747  lsst::pex::exceptions::TypeError
748  Raise if the type of ``value`` is incompatible with the existing
749  value of the item.
750  """
751  args = []
752  if comment is not None:
753  args.append(comment)
754  return _propertyContainerAdd(self, name, value, self._typeMenu, *args)
755 
756  def setComment(self, name, comment):
757  """Set the comment for an existing entry.
758 
759  Parameters
760  ----------
761  name : `str`
762  Name of the key to receive updated comment.
763  comment : `comment`
764  New comment string.
765  """
766  # The only way to do this is to replace the existing entry with
767  # one that has the new comment
768  containerType = _propertyContainerElementTypeName(self, name)
769  if self.isArray(name):
770  value = self.getArray(name)
771  else:
772  value = self.getScalar(name)
773  getattr(self, f"set{containerType}")(name, value, comment)
774 
775  def toList(self):
776  """Return a list of tuples of name, value, comment for each property
777  in the order that they were inserted.
778 
779  Returns
780  -------
781  ret : `list` of `tuple`
782  Tuples of name, value, comment for each property in the order
783  in which they were inserted.
784  """
785  orderedNames = self.getOrderedNames()
786  ret = []
787  for name in orderedNames:
788  if self.isArray(name):
789  values = _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO)
790  for v in values:
791  ret.append((name, v, self.getComment(name)))
792  else:
793  ret.append((name, _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO),
794  self.getComment(name)))
795  return ret
796 
797  def toOrderedDict(self):
798  """Return an ordered dictionary with all properties in the order that
799  they were inserted.
800 
801  Returns
802  -------
803  d : `dict`
804  Ordered dictionary with all properties in the order that they
805  were inserted. Comments are not included.
806 
807  Notes
808  -----
809  As of Python 3.6 dicts retain their insertion order.
810  """
811  d = {}
812  for name in self:
813  d[name] = _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO)
814  return d
815 
816  # For PropertyList the two are equivalent
817  toDict = toOrderedDict
818 
819  def __eq__(self, other):
820  # super() doesn't seem to work properly in @continueClass;
821  # note that super with arguments seems to work at first, but actually
822  # doesn't either.
823  if not PropertySet.__eq__(self, other):
824  return False
825 
826  for name in self:
827  if self.getComment(name) != other.getComment(name):
828  return False
829 
830  return True
831 
832  def __copy__(self):
833  # Copy without having to go through pickle state
834  pl = PropertyList()
835  for itemName in self:
836  pl.copy(itemName, self, itemName)
837  return pl
838 
839  def __deepcopy__(self, memo):
840  result = self.deepCopy()
841  memo[id(self)] = result
842  return result
843 
844  def __iter__(self):
845  for n in self.getOrderedNames():
846  yield n
847 
848  def __setitem__(self, name, value):
849  """Assigns the supplied value to the container.
850 
851  Parameters
852  ----------
853  name : `str`
854  Name of item to update. If the name ends with
855  `PropertyList.COMMENTSUFFIX`, the comment is updated rather
856  than the value.
857  value : Value to assign
858  Can be any value supported by the container's ``set()``
859  method. `~collections.abc.Mapping` are converted to
860  `PropertySet` before assignment.
861 
862  Notes
863  -----
864  Uses `PropertySet.set`, overwriting any previous value.
865  """
866  if name.endswith(self.COMMENTSUFFIX):
867  name = name[:-len(self.COMMENTSUFFIX)]
868  self.setComment(name, value)
869  return
870  if isinstance(value, Mapping):
871  # Create a property set instead
872  ps = PropertySet()
873  for k, v in value.items():
874  ps[k] = v
875  value = ps
876  self.set(name, value)
877 
878  def __reduce__(self):
879  # It would be a bit simpler to use __setstate__ and __getstate__.
880  # However, implementing __setstate__ in Python causes segfaults
881  # because pickle creates a new instance by calling
882  # object.__new__(PropertyList, *args) which bypasses
883  # the pybind11 memory allocation step.
884  return (_makePropertyList, (getPropertyListState(self),))