25__all__ = [
"getPropertySetState",
"getPropertyListState",
"setPropertySetState",
"setPropertyListState"]
30from collections.abc
import Mapping, KeysView, ValuesView, ItemsView
34from lsst.utils
import continueClass
36from .propertySet
import PropertySet
37from .propertyList
import PropertyList
38from ..dateTime
import DateTime
42 """Get the state of a PropertySet in a form that can be pickled.
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.
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:
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.
66 the data
for the item,
in a form compatible
67 with the set method named by ``elementTypeName``
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)))
77 """Get the state of a PropertyList in a form that can be pickled.
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.
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:
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.
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.
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()]
114 """Restore the state of a PropertySet, in place.
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.
122 The state,
as returned by `getPropertySetState`
124 for name, elemType, value
in state:
125 if elemType
is not None:
126 getattr(container,
"set" + elemType)(name, value)
128 raise ValueError(f
"Unrecognized values for state restoration: ({name}, {elemType}, {value})")
132 """Restore the state of a PropertyList, in place.
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.
140 The state,
as returned by ``getPropertyListState``
142 for name, elemType, value, comment
in state:
143 getattr(container,
"set" + elemType)(name, value, comment)
152def _propertyContainerElementTypeName(container, name):
153 """Return name of the type of a particular element
158 Container including the element
163 t = container.typeOf(name)
164 except LookupError
as e:
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):
176def _propertyContainerGet(container, name, returnStyle):
177 """Get a value of unknown type as a scalar or array
182 Container
from which to get the value
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
201 Raised
if the specified key does
not exist
in the container.
203 Raised
if the value retrieved
is of an unexpected type.
205 Raised
if the value
for ``returnStyle``
is not correct.
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):
219 if container.isPropertySetPtr(name):
221 return container.getAsPropertyListPtr(name)
223 return container.getAsPropertySetPtr(name)
225 return container.getAsPersistablePtr(name)
228 raise TypeError(
'Unknown PropertySet value type for ' + name)
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,
238 if isinstance(a, (str, PropertyList, PropertySet)):
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.
262 Container
from which to get the value
268 Value to be assigned a type. Can be an iterable.
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.
278 maxLongLong = 2**63 - 1
287 for v
in _iterable(value):
289 if not isinstance(v, numbers.Integral)
or isinstance(v, bool):
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):
307 if current_type
not in {
"Int",
"LongLong",
"UnsignedLongLong"}:
310 if int_value <= maxInt
and int_value >= minInt
and current_type
in (
None,
"Int"):
314 elif int_value >= minLongLong
and int_value < 0:
317 use_type =
"LongLong"
318 elif int_value >= 0
and int_value <= maxLongLong
and current_type
in (
None,
"Int",
"LongLong"):
320 use_type =
"LongLong"
321 elif int_value <= maxU64
and int_value >= minU64:
322 use_type =
"UnsignedLongLong"
324 raise RuntimeError(
"Unable to guess integer type for storing out of "
325 f
"range value: {int_value}")
329 containerType = _propertyContainerElementTypeName(container, name)
333 useTypeMin = _choose_int_from_range(min, containerType)
334 useTypeMax = _choose_int_from_range(max, containerType)
336 if useTypeMin == useTypeMax:
344 choices = {useTypeMin, useTypeMax}
345 if choices == {
"Int",
"LongLong"}:
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
361 exemplar = next(_iterable(value))
362 except StopIteration:
367 setType = _guessIntegerType(container, name, value)
369 if setType
is not None or t
in typeMenu:
371 setType = typeMenu[t]
372 return getattr(container,
"set" + setType)(name, value, *args)
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
385 exemplar = next(_iterable(value))
386 except StopIteration:
391 addType = _guessIntegerType(container, name, exemplar)
393 if addType
is not None or t
in typeMenu:
395 addType = typeMenu[t]
396 return getattr(container,
"add" + addType)(name, value, *args)
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`
411 The data returned by `getPropertySetState`.
418def _makePropertyList(state):
419 """Make a `PropertyList` from the state returned by
420 `getPropertyListState`
425 The data returned by `getPropertySetState`.
436 _typeMenu = {bool:
"Bool",
439 DateTime:
"DateTime",
440 PropertySet:
"PropertySet",
441 PropertyList:
"PropertySet",
447 """Create a `PropertySet` from a mapping or dict-like object.
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`.
461 The new `PropertySet`.
465 if isinstance(metadata, Mapping):
467 elif dataclasses.is_dataclass(metadata):
468 d = dataclasses.asdict(metadata)
470 for attr
in (
"to_dict",
"toDict",
"dict"):
471 if hasattr(metadata, attr):
472 d = getattr(metadata, attr)()
475 raise ValueError(
"Unable to extract mappings from the supplied metadata of type"
476 f
" {type(metadata)}")
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.
491 default : `object`, optional
492 Default value to use
if the named item
is not present.
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
503 return _propertyContainerGet(self, name, returnStyle=ReturnStyle.SCALAR)
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.
520 values : `list` of any type supported by container
521 The contents of the item, guaranteed to be returned
as a `list.`
526 Raised
if the item does
not exist.
528 return _propertyContainerGet(self, name, returnStyle=ReturnStyle.ARRAY)
531 """Return an item as a scalar
533 If the item has more than one value then the last value is returned.
543 Value stored
in the item. If the item refers to an array the
544 most recently added value
is returned.
549 Raised
if the item does
not exist.
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
563 value : any supported type
564 Value of item; may be a scalar
or array
566 return _propertyContainerSet(self, name, value, self.
_typeMenu_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`
578 value : any supported type
579 Value of item; may be a scalar
or array
585 the existing value. Also the item
is added
as a live
586 reference, so updating ``value`` will update this container
591 lsst::pex::exceptions::TypeError
592 Raised
if the type of `value`
is incompatible
with the existing
595 return _propertyContainerAdd(self, name, value, self.
_typeMenu_typeMenu)
598 """Update the current container with the supplied additions.
602 addition : `collections.abc.Mapping` or `PropertySet`
603 The content to merge into the current container.
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
612 if isinstance(addition, PropertySet):
616 self.copy(k, addition, k)
618 for k, v
in addition.items():
622 """Returns a (possibly nested) dictionary with all properties.
627 Dictionary with all names
and values (no comments).
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)
641 if type(self) != type(other):
642 return NotImplemented
644 if len(self) != len(other):
648 if _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO) != \
649 _propertyContainerGet(other, name, returnStyle=ReturnStyle.AUTO):
651 if self.typeOf(name) != other.typeOf(name):
659 for itemName
in self:
660 ps.copy(itemName, self, itemName)
664 result = self.deepCopy()
665 memo[id(self)] = result
669 """Determines if the name is found at the top level hierarchy
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__``.
678 return name
in self.names(topLevelOnly=
True)
681 """Assigns the supplied value to the container.
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.
694 Uses `PropertySet.set`, overwriting any previous value.
696 if isinstance(value, Mapping):
699 for k, v
in value.items():
702 self.
setset(name, value)
705 """Returns a scalar item from the container.
709 Uses `PropertySet.getScalar` to guarantee that a single value
715 if self.exists(name):
719 raise KeyError(f
"{name} not present in dict")
722 return self.toString()
725 return self.nameCount(topLevelOnly=
True)
728 for n
in self.names(topLevelOnly=
True):
732 return KeysView(self)
735 return ItemsView(self)
738 return ValuesView(self)
740 def pop(self, name, default=None):
741 """Remove the named key and return its value.
746 Name of the key to remove. Can be hierarchical.
747 default : Any, optional
748 Value to return if the key
is not present.
753 The value of the item
as would be returned using `
getScalar()`.
758 Raised
if no default
is given
and the key
is missing.
760 if self.exists(name):
781 _typeMenu = {bool:
"Bool",
785 DateTime:
"DateTime",
786 PropertySet:
"PropertySet",
787 PropertyList:
"PropertySet",
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.
806 default : `object`, optional
807 Default value to use
if the named item
is not present.
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
818 return _propertyContainerGet(self, name, returnStyle=ReturnStyle.SCALAR)
823 """Return an item as a list.
832 values : `list` of values
833 The contents of the item, guaranteed to be returned as a `list.`
838 Raised
if the item does
not exist.
840 return _propertyContainerGet(self, name, returnStyle=ReturnStyle.ARRAY)
843 """Return an item as a scalar
845 If the item has more than one value then the last value is returned.
855 Value stored
in the item. If the item refers to an array the
856 most recently added value
is returned.
861 Raised
if the item does
not exist.
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
875 value : any supported type
876 Value of item; may be a scalar
or array
879 if comment
is not None:
881 return _propertyContainerSet(self, name, value, self.
_typeMenu_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`
893 value : any supported type
894 Value of item; may be a scalar
or array
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).
907 lsst::pex::exceptions::TypeError
908 Raise
if the type of ``value``
is incompatible
with the existing
912 if comment
is not None:
914 return _propertyContainerAdd(self, name, value, self.
_typeMenu_typeMenu, *args)
917 """Set the comment for an existing entry.
922 Name of the key to receive updated comment.
928 containerType = _propertyContainerElementTypeName(self, name)
929 if self.isArray(name):
933 getattr(self, f
"set{containerType}")(name, value, comment)
936 """Return a list of tuples of name, value, comment for each property
937 in the order that they were inserted.
941 ret : `list` of `tuple`
942 Tuples of name, value, comment
for each property
in the order
943 in which they were inserted.
945 orderedNames = self.getOrderedNames()
947 for name
in orderedNames:
948 if self.isArray(name):
949 values = _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO)
951 ret.append((name, v, self.getComment(name)))
953 ret.append((name, _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO),
954 self.getComment(name)))
958 """Return an ordered dictionary with all properties in the order that
964 Ordered dictionary with all properties
in the order that they
965 were inserted. Comments are
not included.
969 As of Python 3.6 dicts retain their insertion order.
973 d[name] = _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO)
977 toDict = toOrderedDict
983 if not PropertySet.__eq__(self, other):
987 if self.getComment(name) != other.getComment(name):
995 for itemName
in self:
996 pl.copy(itemName, self, itemName)
1000 result = self.deepCopy()
1001 memo[id(self)] = result
1005 for n
in self.getOrderedNames():
1009 """Assigns the supplied value to the container.
1014 Name of item to update. If the name ends with
1015 `PropertyList.COMMENTSUFFIX`, the comment
is updated rather
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.
1024 Uses `PropertySet.set`, overwriting any previous value.
1030 if isinstance(value, Mapping):
1033 for k, v
in value.items():
1036 self.
setset(name, value)
Class for storing ordered metadata with comments.
Class for storing generic metadata.
def __deepcopy__(self, memo)
def set(self, name, value, comment=None)
def setComment(self, name, comment)
def get(self, name, default=None)
def __setitem__(self, name, value)
def add(self, name, value, comment=None)
def getScalar(self, name)
def __deepcopy__(self, memo)
def add(self, name, value)
def __contains__(self, name)
def from_mapping(cls, metadata)
def __setitem__(self, name, value)
def get(self, name, default=None)
def __getitem__(self, name)
def pop(self, name, default=None)
def getScalar(self, name)
def __delitem__(self, name)
def update(self, addition)
def set(self, name, value)
def setPropertyListState(container, state)
def setPropertySetState(container, state)
def getPropertyListState(container, asLists=False)
def getPropertySetState(container, asLists=False)