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