25 __all__ = [
"getPropertySetState",
"getPropertyListState",
"setPropertySetState",
"setPropertyListState"]
29 from collections.abc
import Mapping, KeysView, ValuesView, ItemsView
35 from .propertySet
import PropertySet
36 from .propertyList
import PropertyList
37 from ..dateTime
import DateTime
41 """Get the state of a PropertySet in a form that can be pickled.
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.
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:
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.
65 the data for the item, in a form compatible
66 with the set method named by ``elementTypeName``
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)))
76 """Get the state of a PropertyList in a form that can be pickled.
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.
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:
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.
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.
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()]
113 """Restore the state of a PropertySet, in place.
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.
121 The state, as returned by `getPropertySetState`
123 for name, elemType, value
in state:
124 if elemType
is not None:
125 getattr(container,
"set" + elemType)(name, value)
127 raise ValueError(f
"Unrecognized values for state restoration: ({name}, {elemType}, {value})")
131 """Restore the state of a PropertyList, in place.
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.
139 The state, as returned by ``getPropertyListState``
141 for name, elemType, value, comment
in state:
142 getattr(container,
"set" + elemType)(name, value, comment)
151 def _propertyContainerElementTypeName(container, name):
152 """Return name of the type of a particular element
156 container : `lsst.daf.base.PropertySet` or `lsst.daf.base.PropertyList`
157 Container including the element
162 t = container.typeOf(name)
163 except LookupError
as e:
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):
175 def _propertyContainerGet(container, name, returnStyle):
176 """Get a value of unknown type as a scalar or array
180 container : `lsst.daf.base.PropertySet` or `lsst.daf.base.PropertyList`
181 Container from which to get the value
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
200 Raised if the specified key does not exist in the container.
202 Raised if the value retrieved is of an unexpected type.
204 Raised if the value for ``returnStyle`` is not correct.
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))
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):
218 if container.isPropertySetPtr(name):
220 return container.getAsPropertyListPtr(name)
222 return container.getAsPropertySetPtr(name)
224 return container.getAsPersistablePtr(name)
227 raise TypeError(
'Unknown PropertySet value type for ' + name)
230 def _guessIntegerType(container, name, value):
231 """Given an existing container and name, determine the type
232 that should be used for the supplied value. The supplied value
233 is assumed to be a scalar.
235 On Python 3 all ints are LongLong but we need to be able to store them
236 in Int containers if that is what is being used (testing for truncation).
237 Int is assumed to mean 32bit integer (2147483647 to -2147483648).
239 If there is no pre-existing value we have to decide what to do. For now
240 we pick Int if the value is less than maxsize.
244 container : `lsst.daf.base.PropertySet` or `lsst.daf.base.PropertyList`
245 Container from which to get the value
251 Value to be assigned a type
255 useType : `str` or none
256 Type to use for the supplied value. `None` if the input is
257 `bool` or a non-integral value.
262 maxLongLong = 2**63 - 1
269 if isinstance(value, bool):
272 if isinstance(value, numbers.Integral):
274 containerType = _propertyContainerElementTypeName(container, name)
277 if value <= maxInt
and value >= minInt:
279 elif value <= maxLongLong
and value >= minLongLong:
281 elif value <= maxU64
and value >= minU64:
282 useType =
"UnsignedLongLong"
284 raise RuntimeError(
"Unable to guess integer type for storing value: %d" % (value,))
286 if containerType ==
"Int":
292 elif containerType ==
"LongLong":
294 elif containerType ==
"UnsignedLongLong":
295 useType =
"UnsignedLongLong"
299 def _propertyContainerSet(container, name, value, typeMenu, *args):
300 """Set a single Python value of unknown type
302 if hasattr(value,
"__iter__")
and not isinstance(value, (str, PropertySet, PropertyList)):
308 setType = _guessIntegerType(container, name, exemplar)
310 if setType
is not None or t
in typeMenu:
312 setType = typeMenu[t]
313 return getattr(container,
"set" + setType)(name, value, *args)
315 for checkType
in typeMenu:
316 if (checkType
is None and exemplar
is None)
or \
317 (checkType
is not None and isinstance(exemplar, checkType)):
318 return getattr(container,
"set" + typeMenu[checkType])(name, value, *args)
319 raise TypeError(
"Unknown value type for key '%s': %s" % (name, t))
322 def _propertyContainerAdd(container, name, value, typeMenu, *args):
323 """Add a single Python value of unknown type
325 if hasattr(value,
"__iter__"):
331 addType = _guessIntegerType(container, name, exemplar)
333 if addType
is not None or t
in typeMenu:
335 addType = typeMenu[t]
336 return getattr(container,
"add" + addType)(name, value, *args)
338 for checkType
in typeMenu:
339 if (checkType
is None and exemplar
is None)
or \
340 (checkType
is not None and isinstance(exemplar, checkType)):
341 return getattr(container,
"add" + typeMenu[checkType])(name, value, *args)
342 raise TypeError(
"Unknown value type for key '%s': %s" % (name, t))
345 def _makePropertySet(state):
346 """Make a `PropertySet` from the state returned by `getPropertySetState`
351 The data returned by `getPropertySetState`.
358 def _makePropertyList(state):
359 """Make a `PropertyList` from the state returned by
360 `getPropertyListState`
365 The data returned by `getPropertySetState`.
376 _typeMenu = {bool:
"Bool",
379 DateTime:
"DateTime",
380 PropertySet:
"PropertySet",
381 PropertyList:
"PropertySet",
385 def get(self, name, default=None):
386 """Return an item as a scalar, else default.
388 Identical to `getScalar` except that a default value is returned
389 if the requested key is not present. If an array item is requested
390 the final value in the array will be returned.
396 default : `object`, optional
397 Default value to use if the named item is not present.
401 value : any type supported by container
402 Single value of any type supported by the container, else the
403 default value if the requested item is not present in the
404 container. For array items the most recently added value is
408 return _propertyContainerGet(self, name, returnStyle=ReturnStyle.SCALAR)
413 """Return an item as an array if the item is numeric or string
415 If the item is a `PropertySet`, `PropertyList` or
416 `lsst.daf.base.PersistablePtr` then return the item as a scalar.
425 values : `list` of any type supported by container
426 The contents of the item, guaranteed to be returned as a `list.`
431 Raised if the item does not exist.
433 return _propertyContainerGet(self, name, returnStyle=ReturnStyle.ARRAY)
436 """Return an item as a scalar
438 If the item has more than one value then the last value is returned.
448 Value stored in the item. If the item refers to an array the
449 most recently added value is returned.
454 Raised if the item does not exist.
456 return _propertyContainerGet(self, name, returnStyle=ReturnStyle.SCALAR)
458 def set(self, name, value):
459 """Set the value of an item
461 If the item already exists it is silently replaced; the types
468 value : any supported type
469 Value of item; may be a scalar or array
471 return _propertyContainerSet(self, name, value, self.
_typeMenu)
473 def add(self, name, value):
474 """Append one or more values to a given item, which need not exist
476 If the item exists then the new value(s) are appended;
477 otherwise it is like calling `set`
483 value : any supported type
484 Value of item; may be a scalar or array
488 If ``value`` is an `lsst.daf.base.PropertySet` or
489 `lsst.daf.base.PropertyList` then ``value`` replaces
490 the existing value. Also the item is added as a live
491 reference, so updating ``value`` will update this container
496 lsst::pex::exceptions::TypeError
497 Raised if the type of `value` is incompatible with the existing
500 return _propertyContainerAdd(self, name, value, self.
_typeMenu)
503 """Update the current container with the supplied additions.
507 addition : `collections.abc.Mapping` or `PropertySet`
508 The content to merge into the current container.
512 This is not the same as calling `PropertySet.combine` since the
513 behavior differs when both mappings contain the same key. This
514 method updates by overwriting existing values completely with
517 if isinstance(addition, PropertySet):
521 self.copy(k, addition, k)
523 for k, v
in addition.items():
527 """Returns a (possibly nested) dictionary with all properties.
532 Dictionary with all names and values (no comments).
536 for name
in self.names():
537 v = _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO)
539 if isinstance(v, PropertySet):
540 d[name] = PropertySet.toDict(v)
549 if len(self) != len(other):
553 if _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO) != \
554 _propertyContainerGet(other, name, returnStyle=ReturnStyle.AUTO):
556 if self.typeOf(name) != other.typeOf(name):
564 for itemName
in self:
565 ps.copy(itemName, self, itemName)
569 result = self.deepCopy()
570 memo[
id(self)] = result
574 """Determines if the name is found at the top level hierarchy
579 Does not use `PropertySet.exists()`` because that includes support
580 for "."-delimited names. This method is consistent with the
581 items returned from ``__iter__``.
583 return name
in self.names(topLevelOnly=
True)
586 """Assigns the supplied value to the container.
591 Name of item to update.
592 value : Value to assign
593 Can be any value supported by the container's ``set()``
594 method. `~collections.abc.Mapping` are converted to
595 `PropertySet` before assignment.
599 Uses `PropertySet.set`, overwriting any previous value.
601 if isinstance(value, Mapping):
604 for k, v
in value.items():
607 self.
set(name, value)
610 """Returns a scalar item from the container.
614 Uses `PropertySet.getScalar` to guarantee that a single value
623 raise KeyError(f
"{name} not present in dict")
626 return self.toString()
629 return self.nameCount(topLevelOnly=
True)
632 for n
in self.names(topLevelOnly=
True):
636 return KeysView(self)
639 return ItemsView(self)
642 return ValuesView(self)
656 _typeMenu = {bool:
"Bool",
660 DateTime:
"DateTime",
661 PropertySet:
"PropertySet",
662 PropertyList:
"PropertySet",
666 COMMENTSUFFIX =
"#COMMENT"
667 """Special suffix used to indicate that a named item being assigned
668 using dict syntax is referring to a comment, not value."""
670 def get(self, name, default=None):
671 """Return an item as a scalar, else default.
673 Identical to `getScalar` except that a default value is returned
674 if the requested key is not present. If an array item is requested
675 the final value in the array will be returned.
681 default : `object`, optional
682 Default value to use if the named item is not present.
686 value : any type supported by container
687 Single value of any type supported by the container, else the
688 default value if the requested item is not present in the
689 container. For array items the most recently added value is
693 return _propertyContainerGet(self, name, returnStyle=ReturnStyle.SCALAR)
698 """Return an item as a list.
707 values : `list` of values
708 The contents of the item, guaranteed to be returned as a `list.`
713 Raised if the item does not exist.
715 return _propertyContainerGet(self, name, returnStyle=ReturnStyle.ARRAY)
718 """Return an item as a scalar
720 If the item has more than one value then the last value is returned.
730 Value stored in the item. If the item refers to an array the
731 most recently added value is returned.
736 Raised if the item does not exist.
738 return _propertyContainerGet(self, name, returnStyle=ReturnStyle.SCALAR)
740 def set(self, name, value, comment=None):
741 """Set the value of an item
743 If the item already exists it is silently replaced; the types
750 value : any supported type
751 Value of item; may be a scalar or array
754 if comment
is not None:
756 return _propertyContainerSet(self, name, value, self.
_typeMenu, *args)
758 def add(self, name, value, comment=None):
759 """Append one or more values to a given item, which need not exist
761 If the item exists then the new value(s) are appended;
762 otherwise it is like calling `set`
768 value : any supported type
769 Value of item; may be a scalar or array
773 If `value` is an `lsst.daf.base.PropertySet` items are added
774 using dotted names (e.g. if name="a" and value contains
775 an item "b" which is another PropertySet and contains an
776 item "c" which is numeric or string, then the value of "c"
777 is added as "a.b.c", appended to the existing values of
778 "a.b.c" if any (in which case the types must be compatible).
782 lsst::pex::exceptions::TypeError
783 Raise if the type of ``value`` is incompatible with the existing
787 if comment
is not None:
789 return _propertyContainerAdd(self, name, value, self.
_typeMenu, *args)
792 """Set the comment for an existing entry.
797 Name of the key to receive updated comment.
803 containerType = _propertyContainerElementTypeName(self, name)
804 if self.isArray(name):
808 getattr(self, f
"set{containerType}")(name, value, comment)
811 """Return a list of tuples of name, value, comment for each property
812 in the order that they were inserted.
816 ret : `list` of `tuple`
817 Tuples of name, value, comment for each property in the order
818 in which they were inserted.
820 orderedNames = self.getOrderedNames()
822 for name
in orderedNames:
823 if self.isArray(name):
824 values = _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO)
826 ret.append((name, v, self.getComment(name)))
828 ret.append((name, _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO),
829 self.getComment(name)))
833 """Return an ordered dictionary with all properties in the order that
839 Ordered dictionary with all properties in the order that they
840 were inserted. Comments are not included.
844 As of Python 3.6 dicts retain their insertion order.
848 d[name] = _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO)
852 toDict = toOrderedDict
858 if not PropertySet.__eq__(self, other):
862 if self.getComment(name) != other.getComment(name):
870 for itemName
in self:
871 pl.copy(itemName, self, itemName)
875 result = self.deepCopy()
876 memo[
id(self)] = result
880 for n
in self.getOrderedNames():
884 """Assigns the supplied value to the container.
889 Name of item to update. If the name ends with
890 `PropertyList.COMMENTSUFFIX`, the comment is updated rather
892 value : Value to assign
893 Can be any value supported by the container's ``set()``
894 method. `~collections.abc.Mapping` are converted to
895 `PropertySet` before assignment.
899 Uses `PropertySet.set`, overwriting any previous value.
905 if isinstance(value, Mapping):
908 for k, v
in value.items():
911 self.
set(name, value)