25__all__ = [
"getPropertySetState",
"getPropertyListState",
"setPropertySetState",
"setPropertyListState"]
31from collections.abc
import Mapping, KeysView, ValuesView, ItemsView
35from lsst.utils
import continueClass
37from .._dafBaseLib
import PropertySet, PropertyList
38from ..dateTime
import DateTime
42for checkType
in (
"Bool",
"Short",
"Int",
"Long",
"LongLong",
"UnsignedLongLong",
43 "Float",
"Double",
"String",
"DateTime",
44 "PropertySet",
"Undef"):
45 type_obj = getattr(PropertySet,
"TYPE_" + checkType)
46 _TYPE_MAP[type_obj] = checkType
48 _TYPE_MAP[checkType] = type_obj
51def getPropertySetState(container, asLists=False):
52 """Get the state of a PropertySet in a form that can be pickled.
56 container : `PropertySet`
57 The property container.
58 asLists : `bool`, optional
59 If False, the default, `tuple` will be used for the contents. If true
60 a `list` will be used.
64 state : `list` of `tuple` or `list` of `list`
65 The state, as a list of tuples (or lists), each of which contains
66 the following 3 items:
70 elementTypeName (a `str`)
71 the suffix of a ``setX`` method name
72 which is appropriate for the data type. For example integer
73 data has ``elementTypeName="Int"` which corresponds to
74 the ``setInt`` method.
76 the data for the item, in a form compatible
77 with the set method named by ``elementTypeName``
79 names = container.names(topLevelOnly=
True)
80 sequence = list
if asLists
else tuple
86def getPropertyListState(container, asLists=False):
87 """Get the state of a PropertyList in a form that can be pickled.
91 container : `PropertyList`
92 The property container.
93 asLists : `bool`, optional
94 If False, the default, `tuple` will be used for the contents. If true
95 a `list` will be used.
99 state : `list` of `tuple` or `list` of `list`
100 The state, as a list of tuples (or lists), each of which contains
101 the following 4 items:
105 elementTypeName (a `str`):
106 the suffix of a ``setX`` method name
107 which is appropriate for the data type. For example integer
108 data has ``elementTypeName="Int"` which corresponds to
109 the ``setInt`` method.
111 the data for the item, in a form compatible
112 with the set method named by ``elementTypeName``
113 comment (a `str`): the comment. This item is only present
114 if ``container`` is a PropertyList.
116 sequence = list
if asLists
else tuple
119 container.getComment(name)))
120 for name
in container.getOrderedNames()]
123def setPropertySetState(container, state):
124 """Restore the state of a PropertySet, in place.
128 container : `PropertySet`
129 The property container whose state is to be restored.
130 It should be empty to start with and is updated in place.
132 The state, as returned by `getPropertySetState`
134 for name, elemType, value
in state:
135 if elemType
is not None:
136 getattr(container,
"set" + elemType)(name, value)
138 raise ValueError(f
"Unrecognized values for state restoration: ({name}, {elemType}, {value})")
141def setPropertyListState(container, state):
142 """Restore the state of a PropertyList, in place.
146 container : `PropertyList`
147 The property container whose state is to be restored.
148 It should be empty to start with and is updated in place.
150 The state, as returned by ``getPropertyListState``
152 for name, elemType, value, comment
in state:
153 getattr(container,
"set" + elemType)(name, value, comment)
163 """Return name of the type of a particular element
167 container : `lsst.daf.base.PropertySet` or `lsst.daf.base.PropertyList`
168 Container including the element
173 t = container.typeOf(name)
174 except LookupError
as e:
177 raise KeyError(str(e))
from None
179 return _TYPE_MAP.get(t,
None)
183 """Get a value of unknown type as a scalar or array
187 container : `lsst.daf.base.PropertySet` or `lsst.daf.base.PropertyList`
188 Container from which to get the value
191 returnStyle : `ReturnStyle`
192 Control whether numeric or string data is returned as an array
193 or scalar (the other types, ``PropertyList``, ``PropertySet``
194 and ``PersistablePtr``, are always returned as a scalar):
195 - ReturnStyle.ARRAY: return numeric or string data types
196 as an array of values.
197 - ReturnStyle.SCALAR: return numeric or string data types
198 as a single value; if the item has multiple values then
199 return the last value.
200 - ReturnStyle.AUTO: (deprecated) return numeric or string data
201 as a scalar if there is just one item, or as an array
207 Raised if the specified key does not exist in the container.
209 Raised if the value retrieved is of an unexpected type.
211 Raised if the value for ``returnStyle`` is not correct.
213 if not container.exists(name):
214 raise KeyError(name +
" not found")
215 if returnStyle
not in ReturnStyle:
216 raise ValueError(
"returnStyle {} must be a ReturnStyle".format(returnStyle))
219 if elemType
and elemType !=
"PropertySet":
220 value = getattr(container,
"getArray" + elemType)(name)
221 if returnStyle == ReturnStyle.ARRAY
or (returnStyle == ReturnStyle.AUTO
and len(value) > 1):
225 if container.isPropertySetPtr(name):
227 return container.getAsPropertyListPtr(name)
229 return container.getAsPropertySetPtr(name)
231 return container.getAsPersistablePtr(name)
234 raise TypeError(
'Unknown PropertySet value type for ' + name)
238 """Make input iterable.
240 Takes whatever is given to it and yields it back one element at a time.
241 If it is not an iterable or it is a string or PropertySet/List,
244 if isinstance(a, (str, PropertyList, PropertySet)):
254 """Given an existing container and name, determine the type
255 that should be used for the supplied value. The supplied value
256 is assumed to be a scalar.
258 On Python 3 all ints are LongLong but we need to be able to store them
259 in Int containers if that is what is being used (testing for truncation).
260 Int is assumed to mean 32bit integer (2147483647 to -2147483648).
262 If there is no pre-existing value we have to decide what to do. For now
263 we pick Int if the value is less than maxsize.
267 container : `lsst.daf.base.PropertySet` or `lsst.daf.base.PropertyList`
268 Container from which to get the value
274 Value to be assigned a type. Can be an iterable.
278 useType : `str` or none
279 Type to use for the supplied value. `None` if the input is
280 `bool` or a non-integral value.
284 maxLongLong = 2**63 - 1
295 if not isinstance(v, numbers.Integral)
or isinstance(v, bool):
307 if min
is None or max
is None:
308 raise RuntimeError(f
"Internal logic failure calculating integer range of {value}")
310 def _choose_int_from_range(int_value, current_type):
313 if current_type
not in {
"Int",
"LongLong",
"UnsignedLongLong"}:
316 if int_value <= maxInt
and int_value >= minInt
and current_type
in (
None,
"Int"):
320 elif int_value >= minLongLong
and int_value < 0:
323 use_type =
"LongLong"
324 elif int_value >= 0
and int_value <= maxLongLong
and current_type
in (
None,
"Int",
"LongLong"):
326 use_type =
"LongLong"
327 elif int_value <= maxU64
and int_value >= minU64:
328 use_type =
"UnsignedLongLong"
330 raise RuntimeError(
"Unable to guess integer type for storing out of "
331 f
"range value: {int_value}")
334 if container.exists(name):
339 useTypeMin = _choose_int_from_range(min, containerType)
340 useTypeMax = _choose_int_from_range(max, containerType)
342 if useTypeMin == useTypeMax:
350 choices = {useTypeMin, useTypeMax}
351 if choices == {
"Int",
"LongLong"}:
357 if "UnsignedLongLong" in choices:
358 return "UnsignedLongLong"
360 raise RuntimeError(f
"Logic error in guessing integer type from {min} and {max}")
364 """Set a single Python value of unknown type
368 except StopIteration:
375 if setType
is not None or t
in typeMenu:
377 setType = typeMenu[t]
378 return getattr(container,
"set" + setType)(name, value, *args)
380 for checkType
in typeMenu:
381 if (checkType
is None and exemplar
is None)
or \
382 (checkType
is not None and isinstance(exemplar, checkType)):
383 return getattr(container,
"set" + typeMenu[checkType])(name, value, *args)
384 raise TypeError(
"Unknown value type for key '%s': %s" % (name, t))
388 """Add a single Python value of unknown type
392 except StopIteration:
399 if addType
is not None or t
in typeMenu:
401 addType = typeMenu[t]
402 return getattr(container,
"add" + addType)(name, value, *args)
404 for checkType
in typeMenu:
405 if (checkType
is None and exemplar
is None)
or \
406 (checkType
is not None and isinstance(exemplar, checkType)):
407 return getattr(container,
"add" + typeMenu[checkType])(name, value, *args)
408 raise TypeError(
"Unknown value type for key '%s': %s" % (name, t))
412 """Make a `PropertySet` from the state returned by `getPropertySetState`
417 The data returned by `getPropertySetState`.
420 setPropertySetState(ps, state)
425 """Make a `PropertyList` from the state returned by
426 `getPropertyListState`
431 The data returned by `getPropertySetState`.
434 setPropertyListState(pl, state)
442 _typeMenu = {bool:
"Bool",
445 DateTime:
"DateTime",
446 PropertySet:
"PropertySet",
447 PropertyList:
"PropertySet",
453 """Create a `PropertySet` from a mapping or dict-like object.
457 metadata : `collections.abc.Mapping`
458 Metadata from which to create the `PropertySet`.
459 Can be a mapping, a `~dataclasses.dataclass` or anything that
460 supports ``toDict()``, ``to_dict()`` or ``dict()`` method.
461 It is assumed that the dictionary is expanded recursively by these
462 methods or that the Python type can be understood by `PropertySet`.
467 The new `PropertySet`.
476 for attr
in (
"to_dict",
"toDict",
"dict"):
481 raise ValueError(
"Unable to extract mappings from the supplied metadata of type"
482 f
" {type(metadata)}")
486 def get(self, name, default=None):
487 """Return an item as a scalar, else default.
489 Identical to `getScalar` except that a default value is returned
490 if the requested key is not present. If an array item is requested
491 the final value in the array will be returned.
497 default : `object`, optional
498 Default value to use if the named item is not present.
502 value : any type supported by container
503 Single value of any type supported by the container, else the
504 default value if the requested item is not present in the
505 container. For array items the most recently added value is
514 """Return an item as an array if the item is numeric or string
516 If the item is a `PropertySet`, `PropertyList` or
517 `lsst.daf.base.PersistablePtr` then return the item as a scalar.
526 values : `list` of any type supported by container
527 The contents of the item, guaranteed to be returned as a `list.`
532 Raised if the item does not exist.
537 """Return an item as a scalar
539 If the item has more than one value then the last value is returned.
549 Value stored in the item. If the item refers to an array the
550 most recently added value is returned.
555 Raised if the item does not exist.
559 def set(self, name, value):
560 """Set the value of an item
562 If the item already exists it is silently replaced; the types
569 value : any supported type
570 Value of item; may be a scalar or array
574 def add(self, name, value):
575 """Append one or more values to a given item, which need not exist
577 If the item exists then the new value(s) are appended;
578 otherwise it is like calling `set`
584 value : any supported type
585 Value of item; may be a scalar or array
589 If ``value`` is an `lsst.daf.base.PropertySet` or
590 `lsst.daf.base.PropertyList` then ``value`` replaces
591 the existing value. Also the item is added as a live
592 reference, so updating ``value`` will update this container
597 lsst::pex::exceptions::TypeError
598 Raised if the type of `value` is incompatible with the existing
604 """Update the current container with the supplied additions.
608 addition : `collections.abc.Mapping` or `PropertySet`
609 The content to merge into the current container.
613 This is not the same as calling `PropertySet.combine` since the
614 behavior differs when both mappings contain the same key. This
615 method updates by overwriting existing values completely with
622 self.copy(k, addition, k)
628 """Returns a (possibly nested) dictionary with all properties.
633 Dictionary with all names and values (no comments).
637 for name
in self.names():
648 return NotImplemented
650 if len(self) !=
len(other):
654 if (self_typeOf := self.typeOf(name)) !=
other.typeOf(name):
662 if self_typeOf
in (_TYPE_MAP[
"Float"], _TYPE_MAP[
"Double"]) \
673 for itemName
in self:
674 ps.copy(itemName, self, itemName)
678 result = self.deepCopy()
679 memo[id(self)] = result
683 """Determines if the name is found at the top level hierarchy
688 Does not use `PropertySet.exists()`` because that includes support
689 for "."-delimited names. This method is consistent with the
690 items returned from ``__iter__``.
692 return name
in self.names(topLevelOnly=
True)
695 """Assigns the supplied value to the container.
700 Name of item to update.
701 value : Value to assign
702 Can be any value supported by the container's ``set()``
703 method. `~collections.abc.Mapping` are converted to
704 `PropertySet` before assignment.
708 Uses `PropertySet.set`, overwriting any previous value.
716 self.
set(name, value)
719 """Returns a scalar item from the container.
723 Uses `PropertySet.getScalar` to guarantee that a single value
729 if self.exists(name):
733 raise KeyError(f
"{name} not present in dict")
736 return self.toString()
739 return self.nameCount(topLevelOnly=
True)
742 for n
in self.names(topLevelOnly=
True):
746 return KeysView(self)
749 return ItemsView(self)
752 return ValuesView(self)
754 def pop(self, name, default=None):
755 """Remove the named key and return its value.
760 Name of the key to remove. Can be hierarchical.
761 default : Any, optional
762 Value to return if the key is not present.
767 The value of the item as would be returned using `getScalar()`.
772 Raised if no default is given and the key is missing.
774 if self.exists(name):
789 return (_makePropertySet, (getPropertySetState(self),))
795 _typeMenu = {bool:
"Bool",
799 DateTime:
"DateTime",
800 PropertySet:
"PropertySet",
801 PropertyList:
"PropertySet",
805 COMMENTSUFFIX =
"#COMMENT"
806 """Special suffix used to indicate that a named item being assigned
807 using dict syntax is referring to a comment, not value."""
809 def get(self, name, default=None):
810 """Return an item as a scalar, else default.
812 Identical to `getScalar` except that a default value is returned
813 if the requested key is not present. If an array item is requested
814 the final value in the array will be returned.
820 default : `object`, optional
821 Default value to use if the named item is not present.
825 value : any type supported by container
826 Single value of any type supported by the container, else the
827 default value if the requested item is not present in the
828 container. For array items the most recently added value is
837 """Return an item as a list.
846 values : `list` of values
847 The contents of the item, guaranteed to be returned as a `list.`
852 Raised if the item does not exist.
857 """Return an item as a scalar
859 If the item has more than one value then the last value is returned.
869 Value stored in the item. If the item refers to an array the
870 most recently added value is returned.
875 Raised if the item does not exist.
879 def set(self, name, value, comment=None):
880 """Set the value of an item
882 If the item already exists it is silently replaced; the types
889 value : any supported type
890 Value of item; may be a scalar or array
893 if comment
is not None:
897 def add(self, name, value, comment=None):
898 """Append one or more values to a given item, which need not exist
900 If the item exists then the new value(s) are appended;
901 otherwise it is like calling `set`
907 value : any supported type
908 Value of item; may be a scalar or array
912 If `value` is an `lsst.daf.base.PropertySet` items are added
913 using dotted names (e.g. if name="a" and value contains
914 an item "b" which is another PropertySet and contains an
915 item "c" which is numeric or string, then the value of "c"
916 is added as "a.b.c", appended to the existing values of
917 "a.b.c" if any (in which case the types must be compatible).
921 lsst::pex::exceptions::TypeError
922 Raise if the type of ``value`` is incompatible with the existing
926 if comment
is not None:
931 """Set the comment for an existing entry.
936 Name of the key to receive updated comment.
943 if self.isArray(name):
947 getattr(self, f
"set{containerType}")(name, value, comment)
950 """Return a list of tuples of name, value, comment for each property
951 in the order that they were inserted.
955 ret : `list` of `tuple`
956 Tuples of name, value, comment for each property in the order
957 in which they were inserted.
959 orderedNames = self.getOrderedNames()
961 for name
in orderedNames:
962 if self.isArray(name):
968 self.getComment(name)))
972 """Return an ordered dictionary with all properties in the order that
978 Ordered dictionary with all properties in the order that they
979 were inserted. Comments are not included.
983 As of Python 3.6 dicts retain their insertion order.
991 toDict = toOrderedDict
1009 for itemName
in self:
1010 pl.copy(itemName, self, itemName)
1014 result = self.deepCopy()
1015 memo[id(self)] = result
1019 for n
in self.getOrderedNames():
1023 """Assigns the supplied value to the container.
1028 Name of item to update. If the name ends with
1029 `PropertyList.COMMENTSUFFIX`, the comment is updated rather
1031 value : Value to assign
1032 Can be any value supported by the container's ``set()``
1033 method. `~collections.abc.Mapping` are converted to
1034 `PropertySet` before assignment.
1038 Uses `PropertySet.set`, overwriting any previous value.
1050 self.
set(name, value)
1058 return (_makePropertyList, (getPropertyListState(self),))
set(self, name, value, comment=None)
setComment(self, name, comment)
add(self, name, value, comment=None)
get(self, name, default=None)
__setitem__(self, name, value)
pop(self, name, default=None)
from_mapping(cls, metadata)
__setitem__(self, name, value)
get(self, name, default=None)
_propertyContainerAdd(container, name, value, typeMenu, *args)
_guessIntegerType(container, name, value)
_propertyContainerSet(container, name, value, typeMenu, *args)
_propertyContainerGet(container, name, returnStyle)
_propertyContainerElementTypeName(container, name)