24__all__ = [
"getPropertySetState",
"getPropertyListState",
"setPropertySetState",
"setPropertyListState"]
30from collections.abc
import Mapping, KeysView, ValuesView, ItemsView
31from typing
import TypeAlias, Union
35from lsst.utils
import continueClass
37from .._dafBaseLib
import PropertySet, PropertyList
38from ..dateTime
import DateTime
43NestedMetadataDict: TypeAlias = Mapping[str, Union[str, float, int, bool,
"NestedMetadataDict"]]
48for checkType
in (
"Bool",
"Short",
"Int",
"Long",
"LongLong",
"UnsignedLongLong",
49 "Float",
"Double",
"String",
"DateTime",
50 "PropertySet",
"Undef"):
51 type_obj = getattr(PropertySet,
"TYPE_" + checkType)
52 _TYPE_MAP[type_obj] = checkType
54 _TYPE_MAP[checkType] = type_obj
58 """Get the state of a PropertySet in a form that can be pickled.
62 container : `PropertySet`
63 The property container.
64 asLists : `bool`, optional
65 If False, the default, `tuple` will be used for the contents. If true
66 a `list` will be used.
70 state : `list` of `tuple` or `list` of `list`
71 The state, as a list of tuples (or lists), each of which contains
72 the following 3 items:
76 elementTypeName (a `str`)
77 the suffix of a ``setX`` method name
78 which is appropriate for the data type. For example integer
79 data has ``elementTypeName="Int"` which corresponds to
80 the ``setInt`` method.
82 the data for the item, in a form compatible
83 with the set method named by ``elementTypeName``
85 names = container.names(topLevelOnly=
True)
86 sequence = list
if asLists
else tuple
93 """Get the state of a PropertyList in a form that can be pickled.
97 container : `PropertyList`
98 The property container.
99 asLists : `bool`, optional
100 If False, the default, `tuple` will be used for the contents. If true
101 a `list` will be used.
105 state : `list` of `tuple` or `list` of `list`
106 The state, as a list of tuples (or lists), each of which contains
107 the following 4 items:
111 elementTypeName (a `str`):
112 the suffix of a ``setX`` method name
113 which is appropriate for the data type. For example integer
114 data has ``elementTypeName="Int"` which corresponds to
115 the ``setInt`` method.
117 the data for the item, in a form compatible
118 with the set method named by ``elementTypeName``
119 comment (a `str`): the comment. This item is only present
120 if ``container`` is a PropertyList.
122 sequence = list
if asLists
else tuple
125 container.getComment(name)))
126 for name
in container.getOrderedNames()]
130 """Restore the state of a PropertySet, in place.
134 container : `PropertySet`
135 The property container whose state is to be restored.
136 It should be empty to start with and is updated in place.
138 The state, as returned by `getPropertySetState`
140 for name, elemType, value
in state:
141 if elemType
is not None:
142 getattr(container,
"set" + elemType)(name, value)
144 raise ValueError(f
"Unrecognized values for state restoration: ({name}, {elemType}, {value})")
148 """Restore the state of a PropertyList, in place.
152 container : `PropertyList`
153 The property container whose state is to be restored.
154 It should be empty to start with and is updated in place.
156 The state, as returned by ``getPropertyListState``
158 for name, elemType, value, comment
in state:
159 getattr(container,
"set" + elemType)(name, value, comment)
169 """Return name of the type of a particular element
173 container : `lsst.daf.base.PropertySet` or `lsst.daf.base.PropertyList`
174 Container including the element
179 t = container.typeOf(name)
180 except LookupError
as e:
183 raise KeyError(str(e))
from None
185 return _TYPE_MAP.get(t,
None)
189 """Get a value of unknown type as a scalar or array
193 container : `lsst.daf.base.PropertySet` or `lsst.daf.base.PropertyList`
194 Container from which to get the value
197 returnStyle : `ReturnStyle`
198 Control whether numeric or string data is returned as an array
199 or scalar (the other types, ``PropertyList``, ``PropertySet``
200 and ``PersistablePtr``, are always returned as a scalar):
201 - ReturnStyle.ARRAY: return numeric or string data types
202 as an array of values.
203 - ReturnStyle.SCALAR: return numeric or string data types
204 as a single value; if the item has multiple values then
205 return the last value.
206 - ReturnStyle.AUTO: (deprecated) return numeric or string data
207 as a scalar if there is just one item, or as an array
213 Raised if the specified key does not exist in the container.
215 Raised if the value retrieved is of an unexpected type.
217 Raised if the value for ``returnStyle`` is not correct.
219 if not container.exists(name):
220 raise KeyError(name +
" not found")
221 if returnStyle
not in ReturnStyle:
222 raise ValueError(
"returnStyle {} must be a ReturnStyle".format(returnStyle))
225 if elemType
and elemType !=
"PropertySet":
226 value = getattr(container,
"getArray" + elemType)(name)
227 if returnStyle == ReturnStyle.ARRAY
or (returnStyle == ReturnStyle.AUTO
and len(value) > 1):
231 if container.isPropertySetPtr(name):
233 return container.getAsPropertyListPtr(name)
235 return container.getAsPropertySetPtr(name)
237 return container.getAsPersistablePtr(name)
240 raise TypeError(
'Unknown PropertySet value type for ' + name)
244 """Make input iterable.
246 Takes whatever is given to it and yields it back one element at a time.
247 If it is not an iterable or it is a string or PropertySet/List,
250 if isinstance(a, (str, PropertyList, PropertySet)):
260 """Given an existing container and name, determine the type
261 that should be used for the supplied value. The supplied value
262 is assumed to be a scalar.
264 On Python 3 all ints are LongLong but we need to be able to store them
265 in Int containers if that is what is being used (testing for truncation).
266 Int is assumed to mean 32bit integer (2147483647 to -2147483648).
268 If there is no pre-existing value we have to decide what to do. For now
269 we pick Int if the value is less than maxsize.
273 container : `lsst.daf.base.PropertySet` or `lsst.daf.base.PropertyList`
274 Container from which to get the value
280 Value to be assigned a type. Can be an iterable.
284 useType : `str` or none
285 Type to use for the supplied value. `None` if the input is
286 `bool` or a non-integral value.
290 maxLongLong = 2**63 - 1
301 if not isinstance(v, numbers.Integral)
or isinstance(v, bool):
313 if min
is None or max
is None:
314 raise RuntimeError(f
"Internal logic failure calculating integer range of {value}")
316 def _choose_int_from_range(int_value, current_type):
319 if current_type
not in {
"Int",
"LongLong",
"UnsignedLongLong"}:
322 if int_value <= maxInt
and int_value >= minInt
and current_type
in (
None,
"Int"):
326 elif int_value >= minLongLong
and int_value < 0:
329 use_type =
"LongLong"
330 elif int_value >= 0
and int_value <= maxLongLong
and current_type
in (
None,
"Int",
"LongLong"):
332 use_type =
"LongLong"
333 elif int_value <= maxU64
and int_value >= minU64:
334 use_type =
"UnsignedLongLong"
336 raise RuntimeError(
"Unable to guess integer type for storing out of "
337 f
"range value: {int_value}")
340 if container.exists(name):
345 useTypeMin = _choose_int_from_range(min, containerType)
346 useTypeMax = _choose_int_from_range(max, containerType)
348 if useTypeMin == useTypeMax:
356 choices = {useTypeMin, useTypeMax}
357 if choices == {
"Int",
"LongLong"}:
363 if "UnsignedLongLong" in choices:
364 return "UnsignedLongLong"
366 raise RuntimeError(f
"Logic error in guessing integer type from {min} and {max}")
370 """Set a single Python value of unknown type
374 except StopIteration:
381 if setType
is not None or t
in typeMenu:
383 setType = typeMenu[t]
384 return getattr(container,
"set" + setType)(name, value, *args)
386 for checkType
in typeMenu:
387 if (checkType
is None and exemplar
is None)
or \
388 (checkType
is not None and isinstance(exemplar, checkType)):
389 return getattr(container,
"set" + typeMenu[checkType])(name, value, *args)
390 raise TypeError(
"Unknown value type for key '%s': %s" % (name, t))
394 """Add a single Python value of unknown type
398 except StopIteration:
405 if addType
is not None or t
in typeMenu:
407 addType = typeMenu[t]
408 return getattr(container,
"add" + addType)(name, value, *args)
410 for checkType
in typeMenu:
411 if (checkType
is None and exemplar
is None)
or \
412 (checkType
is not None and isinstance(exemplar, checkType)):
413 return getattr(container,
"add" + typeMenu[checkType])(name, value, *args)
414 raise TypeError(
"Unknown value type for key '%s': %s" % (name, t))
418 """Make a `PropertySet` from the state returned by `getPropertySetState`
423 The data returned by `getPropertySetState`.
426 setPropertySetState(ps, state)
431 """Make a `PropertyList` from the state returned by
432 `getPropertyListState`
437 The data returned by `getPropertySetState`.
440 setPropertyListState(pl, state)
448 _typeMenu = {bool:
"Bool",
451 DateTime:
"DateTime",
452 PropertySet:
"PropertySet",
453 PropertyList:
"PropertySet",
459 """Create a `PropertySet` from a mapping or dict-like object.
463 metadata : `collections.abc.Mapping`
464 Metadata from which to create the `PropertySet`.
465 Can be a mapping, a `~dataclasses.dataclass` or anything that
466 supports ``toDict()``, ``to_dict()`` or ``dict()`` method.
467 It is assumed that the dictionary is expanded recursively by these
468 methods or that the Python type can be understood by `PropertySet`.
473 The new `PropertySet`.
482 for attr
in (
"to_dict",
"toDict",
"dict"):
487 raise ValueError(
"Unable to extract mappings from the supplied metadata of type"
488 f
" {type(metadata)}")
492 def get(self, name, default=None):
493 """Return an item as a scalar, else default.
495 Identical to `getScalar` except that a default value is returned
496 if the requested key is not present. If an array item is requested
497 the final value in the array will be returned.
503 default : `object`, optional
504 Default value to use if the named item is not present.
508 value : any type supported by container
509 Single value of any type supported by the container, else the
510 default value if the requested item is not present in the
511 container. For array items the most recently added value is
520 """Return an item as an array if the item is numeric or string
522 If the item is a `PropertySet`, `PropertyList` or
523 `lsst.daf.base.PersistablePtr` then return the item as a scalar.
532 values : `list` of any type supported by container
533 The contents of the item, guaranteed to be returned as a `list.`
538 Raised if the item does not exist.
543 """Return an item as a scalar
545 If the item has more than one value then the last value is returned.
555 Value stored in the item. If the item refers to an array the
556 most recently added value is returned.
561 Raised if the item does not exist.
565 def set(self, name, value):
566 """Set the value of an item
568 If the item already exists it is silently replaced; the types
575 value : any supported type
576 Value of item; may be a scalar or array
580 def add(self, name, value):
581 """Append one or more values to a given item, which need not exist
583 If the item exists then the new value(s) are appended;
584 otherwise it is like calling `set`
590 value : any supported type
591 Value of item; may be a scalar or array
595 If ``value`` is an `lsst.daf.base.PropertySet` or
596 `lsst.daf.base.PropertyList` then ``value`` replaces
597 the existing value. Also the item is added as a live
598 reference, so updating ``value`` will update this container
603 lsst::pex::exceptions::TypeError
604 Raised if the type of `value` is incompatible with the existing
610 """Update the current container with the supplied additions.
614 addition : `collections.abc.Mapping` or `PropertySet`
615 The content to merge into the current container.
619 This is not the same as calling `PropertySet.combine` since the
620 behavior differs when both mappings contain the same key. This
621 method updates by overwriting existing values completely with
628 self.copy(k, addition, k)
634 """Returns a (possibly nested) dictionary with all properties.
639 Dictionary with all names and values (no comments).
643 for name
in self.names():
654 return NotImplemented
656 if len(self) !=
len(other):
660 if (self_typeOf := self.typeOf(name)) !=
other.typeOf(name):
668 if self_typeOf
in (_TYPE_MAP[
"Float"], _TYPE_MAP[
"Double"]) \
679 for itemName
in self:
680 ps.copy(itemName, self, itemName)
684 result = self.deepCopy()
685 memo[id(self)] = result
689 """Determines if the name is found at the top level hierarchy
694 Does not use `PropertySet.exists()`` because that includes support
695 for "."-delimited names. This method is consistent with the
696 items returned from ``__iter__``.
698 return name
in self.names(topLevelOnly=
True)
701 """Assigns the supplied value to the container.
706 Name of item to update.
707 value : Value to assign
708 Can be any value supported by the container's ``set()``
709 method. `~collections.abc.Mapping` are converted to
710 `PropertySet` before assignment.
714 Uses `PropertySet.set`, overwriting any previous value.
722 self.
set(name, value)
725 """Returns a scalar item from the container.
729 Uses `PropertySet.getScalar` to guarantee that a single value
735 if self.exists(name):
739 raise KeyError(f
"{name} not present in dict")
742 return self.toString()
745 return self.nameCount(topLevelOnly=
True)
748 for n
in self.names(topLevelOnly=
True):
752 return KeysView(self)
755 return ItemsView(self)
758 return ValuesView(self)
760 def pop(self, name, default=None):
761 """Remove the named key and return its value.
766 Name of the key to remove. Can be hierarchical.
767 default : Any, optional
768 Value to return if the key is not present.
773 The value of the item as would be returned using `getScalar()`.
778 Raised if no default is given and the key is missing.
780 if self.exists(name):
795 return (_makePropertySet, (getPropertySetState(self),))
797 def get_dict(self, key: str) -> NestedMetadataDict:
798 """Return a possibly-hierarchical nested `dict`.
800 This implements the `lsst.pipe.base.GetDictMetadata` protocol for
801 consistency with `lsst.pipe.base.TaskMetadata` and `PropertyList`.
806 String key associated with the mapping. May not have a ``.``
811 value : `~collections.abc.Mapping`
812 Possibly-nested mapping, with `str` keys and values that are `int`,
813 `float`, `str`, `bool`, or another `dict` with the same key and
814 value types. Will be empty if ``key`` does not exist.
819 Raised if the value associated with this key is not a nested
820 dictionary, but does exist. Note that this behavior is not
821 consistent with `PropertyList` (which returns an empty `dict`).
828 def set_dict(self, key: str, value: NestedMetadataDict) ->
None:
829 """Assign a possibly-hierarchical nested `dict`.
831 This implements the `lsst.pipe.base.SetDictMetadata` protocol for
832 consistency with `lsst.pipe.base.TaskMetadata` and `PropertyList`.
837 String key associated with the mapping. May not have a ``.``
839 value : `~collections.abc.Mapping`
840 Possibly-nested mapping, with `str` keys and values that are `int`,
841 `float`, `str`, `bool`, or another `dict` with the same key and
842 value types. May not have a ``.``
851 _typeMenu = {bool:
"Bool",
855 DateTime:
"DateTime",
856 PropertySet:
"PropertySet",
857 PropertyList:
"PropertySet",
861 COMMENTSUFFIX =
"#COMMENT"
862 """Special suffix used to indicate that a named item being assigned
863 using dict syntax is referring to a comment, not value."""
865 def get(self, name, default=None):
866 """Return an item as a scalar, else default.
868 Identical to `getScalar` except that a default value is returned
869 if the requested key is not present. If an array item is requested
870 the final value in the array will be returned.
876 default : `object`, optional
877 Default value to use if the named item is not present.
881 value : any type supported by container
882 Single value of any type supported by the container, else the
883 default value if the requested item is not present in the
884 container. For array items the most recently added value is
893 """Return an item as a list.
902 values : `list` of values
903 The contents of the item, guaranteed to be returned as a `list.`
908 Raised if the item does not exist.
913 """Return an item as a scalar
915 If the item has more than one value then the last value is returned.
925 Value stored in the item. If the item refers to an array the
926 most recently added value is returned.
931 Raised if the item does not exist.
935 def set(self, name, value, comment=None):
936 """Set the value of an item
938 If the item already exists it is silently replaced; the types
945 value : any supported type
946 Value of item; may be a scalar or array
949 if comment
is not None:
953 def add(self, name, value, comment=None):
954 """Append one or more values to a given item, which need not exist
956 If the item exists then the new value(s) are appended;
957 otherwise it is like calling `set`
963 value : any supported type
964 Value of item; may be a scalar or array
968 If `value` is an `lsst.daf.base.PropertySet` items are added
969 using dotted names (e.g. if name="a" and value contains
970 an item "b" which is another PropertySet and contains an
971 item "c" which is numeric or string, then the value of "c"
972 is added as "a.b.c", appended to the existing values of
973 "a.b.c" if any (in which case the types must be compatible).
977 lsst::pex::exceptions::TypeError
978 Raise if the type of ``value`` is incompatible with the existing
982 if comment
is not None:
987 """Set the comment for an existing entry.
992 Name of the key to receive updated comment.
999 if self.isArray(name):
1003 getattr(self, f
"set{containerType}")(name, value, comment)
1006 """Return a list of tuples of name, value, comment for each property
1007 in the order that they were inserted.
1011 ret : `list` of `tuple`
1012 Tuples of name, value, comment for each property in the order
1013 in which they were inserted.
1015 orderedNames = self.getOrderedNames()
1017 for name
in orderedNames:
1018 if self.isArray(name):
1024 self.getComment(name)))
1028 """Return an ordered dictionary with all properties in the order that
1034 Ordered dictionary with all properties in the order that they
1035 were inserted. Comments are not included.
1039 As of Python 3.6 dicts retain their insertion order.
1047 toDict = toOrderedDict
1065 for itemName
in self:
1066 pl.copy(itemName, self, itemName)
1070 result = self.deepCopy()
1071 memo[id(self)] = result
1075 for n
in self.getOrderedNames():
1079 """Assigns the supplied value to the container.
1084 Name of item to update. If the name ends with
1085 `PropertyList.COMMENTSUFFIX`, the comment is updated rather
1087 value : Value to assign
1088 Can be any value supported by the container's ``set()``
1089 method. `~collections.abc.Mapping` are converted to
1090 `PropertySet` before assignment.
1094 Uses `PropertySet.set`, overwriting any previous value.
1106 self.
set(name, value)
1114 return (_makePropertyList, (getPropertyListState(self),))
1117 """Return a possibly-hierarchical nested `dict`.
1119 This implements the `lsst.pipe.base.GetDictMetadata` protocol for
1120 consistency with `lsst.pipe.base.TaskMetadata` and `PropertySet`.
1125 String key associated with the mapping. May not have a ``.``
1130 value : `~collections.abc.Mapping`
1131 Possibly-nested mapping, with `str` keys and values that are `int`,
1132 `float`, `str`, `bool`, or another `dict` with the same key and
1133 value types. Will be empty if ``key`` does not exist.
1135 result: NestedMetadataDict = {}
1137 for name
in self.getOrderedNames():
1139 if levels[0] == key:
1141 for level_key
in levels[1:-1]:
1143 nested[levels[-1]] = self[name]
1146 def set_dict(self, key: str, value: NestedMetadataDict) ->
None:
1147 """Assign a possibly-hierarchical nested `dict`.
1149 This implements the `lsst.pipe.base.SetDictMetadata` protocol for
1150 consistency with `lsst.pipe.base.TaskMetadata` and `PropertySet`.
1155 String key associated with the mapping. May not have a ``.``
1157 value : `~collections.abc.Mapping`
1158 Possibly-nested mapping, with `str` keys and values that are `int`,
1159 `float`, `str`, `bool`, or another `dict` with the same key and
1160 value types. Nested keys may not have a ``.`` character.
set(self, name, value, comment=None)
setComment(self, name, comment)
add(self, name, value, comment=None)
NestedMetadataDict get_dict(self, str key)
None set_dict(self, str key, NestedMetadataDict value)
get(self, name, default=None)
__setitem__(self, name, value)
pop(self, name, default=None)
None set_dict(self, str key, NestedMetadataDict value)
NestedMetadataDict get_dict(self, str key)
from_mapping(cls, metadata)
__setitem__(self, name, value)
get(self, name, default=None)
_propertyContainerAdd(container, name, value, typeMenu, *args)
_guessIntegerType(container, name, value)
getPropertyListState(container, asLists=False)
_propertyContainerSet(container, name, value, typeMenu, *args)
_propertyContainerGet(container, name, returnStyle)
setPropertySetState(container, state)
getPropertySetState(container, asLists=False)
setPropertyListState(container, state)
_propertyContainerElementTypeName(container, name)