25__all__ = [
"getPropertySetState",
"getPropertyListState",
"setPropertySetState",
"setPropertyListState"]
37from .propertySet
import PropertySet
38from .propertyList
import PropertyList
39from ..dateTime
import DateTime
43for checkType
in (
"Bool",
"Short",
"Int",
"Long",
"LongLong",
"UnsignedLongLong",
44 "Float",
"Double",
"String",
"DateTime",
45 "PropertySet",
"Undef"):
46 type_obj = getattr(PropertySet,
"TYPE_" + checkType)
47 _TYPE_MAP[type_obj] = checkType
49 _TYPE_MAP[checkType] = type_obj
52def getPropertySetState(container, asLists=False):
53 """Get the state of a PropertySet in a form that can be pickled.
57 container : `PropertySet`
58 The property container.
59 asLists : `bool`, optional
60 If False, the default, `tuple` will be used
for the contents. If true
61 a `list` will be used.
65 state : `list` of `tuple`
or `list` of `list`
66 The state,
as a list of tuples (
or lists), each of which contains
67 the following 3 items:
71 elementTypeName (a `str`)
72 the suffix of a ``setX`` method name
73 which
is appropriate
for the data type. For example integer
74 data has ``elementTypeName=
"Int"` which corresponds to
75 the ``setInt`` method.
77 the data
for the item,
in a form compatible
78 with the set method named by ``elementTypeName``
80 names = container.names(topLevelOnly=True)
81 sequence = list
if asLists
else tuple
82 return [sequence((name, _propertyContainerElementTypeName(container, name),
83 _propertyContainerGet(container, name, returnStyle=ReturnStyle.AUTO)))
87def getPropertyListState(container, asLists=False):
88 """Get the state of a PropertyList in a form that can be pickled.
92 container : `PropertyList`
93 The property container.
94 asLists : `bool`, optional
95 If False, the default, `tuple` will be used
for the contents. If true
96 a `list` will be used.
100 state : `list` of `tuple`
or `list` of `list`
101 The state,
as a list of tuples (
or lists), each of which contains
102 the following 4 items:
106 elementTypeName (a `str`):
107 the suffix of a ``setX`` method name
108 which
is appropriate
for the data type. For example integer
109 data has ``elementTypeName=
"Int"` which corresponds to
110 the ``setInt`` method.
112 the data
for the item,
in a form compatible
113 with the set method named by ``elementTypeName``
114 comment (a `str`): the comment. This item
is only present
115 if ``container``
is a PropertyList.
117 sequence = list if asLists
else tuple
118 return [sequence((name, _propertyContainerElementTypeName(container, name),
119 _propertyContainerGet(container, name, returnStyle=ReturnStyle.AUTO),
120 container.getComment(name)))
121 for name
in container.getOrderedNames()]
124def setPropertySetState(container, state):
125 """Restore the state of a PropertySet, in place.
129 container : `PropertySet`
130 The property container whose state is to be restored.
131 It should be empty to start
with and is updated
in place.
133 The state,
as returned by `getPropertySetState`
135 for name, elemType, value
in state:
136 if elemType
is not None:
137 getattr(container,
"set" + elemType)(name, value)
139 raise ValueError(f
"Unrecognized values for state restoration: ({name}, {elemType}, {value})")
142def setPropertyListState(container, state):
143 """Restore the state of a PropertyList, in place.
147 container : `PropertyList`
148 The property container whose state is to be restored.
149 It should be empty to start
with and is updated
in place.
151 The state,
as returned by ``getPropertyListState``
153 for name, elemType, value, comment
in state:
154 getattr(container,
"set" + elemType)(name, value, comment)
163def _propertyContainerElementTypeName(container, name):
164 """Return name of the type of a particular element
169 Container including the element
174 t = container.typeOf(name)
175 except LookupError
as e:
178 raise KeyError(
str(e))
from None
180 return _TYPE_MAP.get(t,
None)
183def _propertyContainerGet(container, name, returnStyle):
184 """Get a value of unknown type as a scalar or array
189 Container
from which to get the value
192 returnStyle : `ReturnStyle`
193 Control whether numeric
or string data
is returned
as an array
194 or scalar (the other types, ``PropertyList``, ``PropertySet``
195 and ``PersistablePtr``, are always returned
as a scalar):
196 - ReturnStyle.ARRAY:
return numeric
or string data types
197 as an array of values.
198 - ReturnStyle.SCALAR:
return numeric
or string data types
199 as a single value;
if the item has multiple values then
200 return the last value.
201 - ReturnStyle.AUTO: (deprecated)
return numeric
or string data
202 as a scalar
if there
is just one item,
or as an array
208 Raised
if the specified key does
not exist
in the container.
210 Raised
if the value retrieved
is of an unexpected type.
212 Raised
if the value
for ``returnStyle``
is not correct.
214 if not container.exists(name):
215 raise KeyError(name +
" not found")
216 if returnStyle
not in ReturnStyle:
217 raise ValueError(
"returnStyle {} must be a ReturnStyle".format(returnStyle))
219 elemType = _propertyContainerElementTypeName(container, name)
220 if elemType
and elemType !=
"PropertySet":
221 value = getattr(container,
"getArray" + elemType)(name)
222 if returnStyle == ReturnStyle.ARRAY
or (returnStyle == ReturnStyle.AUTO
and len(value) > 1):
226 if container.isPropertySetPtr(name):
228 return container.getAsPropertyListPtr(name)
230 return container.getAsPropertySetPtr(name)
232 return container.getAsPersistablePtr(name)
235 raise TypeError(
'Unknown PropertySet value type for ' + name)
239 """Make input iterable.
241 Takes whatever is given to it
and yields it back one element at a time.
242 If it
is not an iterable
or it
is a string
or PropertySet/List,
245 if isinstance(a, (str, PropertyList, PropertySet)):
254def _guessIntegerType(container, name, value):
255 """Given an existing container and name, determine the type
256 that should be used for the supplied value. The supplied value
257 is assumed to be a scalar.
259 On Python 3 all ints are LongLong but we need to be able to store them
260 in Int containers
if that
is what
is being used (testing
for truncation).
261 Int
is assumed to mean 32bit integer (2147483647 to -2147483648).
263 If there
is no pre-existing value we have to decide what to do. For now
264 we pick Int
if the value
is less than maxsize.
269 Container
from which to get the value
275 Value to be assigned a type. Can be an iterable.
279 useType : `str`
or none
280 Type to use
for the supplied value. `
None`
if the input
is
281 `bool`
or a non-integral value.
285 maxLongLong = 2**63 - 1
294 for v
in _iterable(value):
296 if not isinstance(v, numbers.Integral)
or isinstance(v, bool):
308 if min
is None or max
is None:
309 raise RuntimeError(f
"Internal logic failure calculating integer range of {value}")
311 def _choose_int_from_range(int_value, current_type):
314 if current_type
not in {
"Int",
"LongLong",
"UnsignedLongLong"}:
317 if int_value <= maxInt
and int_value >= minInt
and current_type
in (
None,
"Int"):
321 elif int_value >= minLongLong
and int_value < 0:
324 use_type =
"LongLong"
325 elif int_value >= 0
and int_value <= maxLongLong
and current_type
in (
None,
"Int",
"LongLong"):
327 use_type =
"LongLong"
328 elif int_value <= maxU64
and int_value >= minU64:
329 use_type =
"UnsignedLongLong"
331 raise RuntimeError(
"Unable to guess integer type for storing out of "
332 f
"range value: {int_value}")
335 if container.exists(name):
336 containerType = _propertyContainerElementTypeName(container, name)
340 useTypeMin = _choose_int_from_range(min, containerType)
341 useTypeMax = _choose_int_from_range(max, containerType)
343 if useTypeMin == useTypeMax:
351 choices = {useTypeMin, useTypeMax}
352 if choices == {
"Int",
"LongLong"}:
358 if "UnsignedLongLong" in choices:
359 return "UnsignedLongLong"
361 raise RuntimeError(f
"Logic error in guessing integer type from {min} and {max}")
364def _propertyContainerSet(container, name, value, typeMenu, *args):
365 """Set a single Python value of unknown type
368 exemplar = next(_iterable(value))
369 except StopIteration:
374 setType = _guessIntegerType(container, name, value)
376 if setType
is not None or t
in typeMenu:
378 setType = typeMenu[t]
379 return getattr(container,
"set" + setType)(name, value, *args)
381 for checkType
in typeMenu:
382 if (checkType
is None and exemplar
is None)
or \
383 (checkType
is not None and isinstance(exemplar, checkType)):
384 return getattr(container,
"set" + typeMenu[checkType])(name, value, *args)
385 raise TypeError(
"Unknown value type for key '%s': %s" % (name, t))
388def _propertyContainerAdd(container, name, value, typeMenu, *args):
389 """Add a single Python value of unknown type
392 exemplar = next(_iterable(value))
393 except StopIteration:
398 addType = _guessIntegerType(container, name, exemplar)
400 if addType
is not None or t
in typeMenu:
402 addType = typeMenu[t]
403 return getattr(container,
"add" + addType)(name, value, *args)
405 for checkType
in typeMenu:
406 if (checkType
is None and exemplar
is None)
or \
407 (checkType
is not None and isinstance(exemplar, checkType)):
408 return getattr(container,
"add" + typeMenu[checkType])(name, value, *args)
409 raise TypeError(
"Unknown value type for key '%s': %s" % (name, t))
412def _makePropertySet(state):
413 """Make a `PropertySet` from the state returned by `getPropertySetState`
418 The data returned by `getPropertySetState`.
421 setPropertySetState(ps, state)
425def _makePropertyList(state):
426 """Make a `PropertyList` from the state returned by
427 `getPropertyListState`
432 The data returned by `getPropertySetState`.
435 setPropertyListState(pl, state)
443 _typeMenu = {bool:
"Bool",
446 DateTime:
"DateTime",
447 PropertySet:
"PropertySet",
448 PropertyList:
"PropertySet",
454 """Create a `PropertySet` from a mapping or dict-like object.
458 metadata : `collections.abc.Mapping`
459 Metadata from which to create the `PropertySet`.
460 Can be a mapping, a `~dataclasses.dataclass`
or anything that
461 supports ``
toDict()``, ``to_dict()``
or ``dict()`` method.
462 It
is assumed that the dictionary
is expanded recursively by these
463 methods
or that the Python type can be understood by `PropertySet`.
468 The new `PropertySet`.
472 if isinstance(metadata, Mapping):
474 elif dataclasses.is_dataclass(metadata):
475 d = dataclasses.asdict(metadata)
477 for attr
in (
"to_dict",
"toDict",
"dict"):
478 if hasattr(metadata, attr):
479 d = getattr(metadata, attr)()
482 raise ValueError(
"Unable to extract mappings from the supplied metadata of type"
483 f
" {type(metadata)}")
487 def get(self, name, default=None):
488 """Return an item as a scalar, else default.
490 Identical to `getScalar` except that a default value
is returned
491 if the requested key
is not present. If an array item
is requested
492 the final value
in the array will be returned.
498 default : `object`, optional
499 Default value to use
if the named item
is not present.
503 value : any type supported by container
504 Single value of any type supported by the container,
else the
505 default value
if the requested item
is not present
in the
506 container. For array items the most recently added value
is
510 return _propertyContainerGet(self, name, returnStyle=ReturnStyle.SCALAR)
515 """Return an item as an array if the item is numeric or string
517 If the item is a `PropertySet`, `PropertyList`
or
518 `lsst.daf.base.PersistablePtr` then
return the item
as a scalar.
527 values : `list` of any type supported by container
528 The contents of the item, guaranteed to be returned
as a `list.`
533 Raised
if the item does
not exist.
535 return _propertyContainerGet(self, name, returnStyle=ReturnStyle.ARRAY)
538 """Return an item as a scalar
540 If the item has more than one value then the last value is returned.
550 Value stored
in the item. If the item refers to an array the
551 most recently added value
is returned.
556 Raised
if the item does
not exist.
558 return _propertyContainerGet(self, name, returnStyle=ReturnStyle.SCALAR)
560 def set(self, name, value):
561 """Set the value of an item
563 If the item already exists it is silently replaced; the types
570 value : any supported type
571 Value of item; may be a scalar
or array
573 return _propertyContainerSet(self, name, value, self.
_typeMenu)
575 def add(self, name, value):
576 """Append one or more values to a given item, which need not exist
578 If the item exists then the new value(s) are appended;
579 otherwise it is like calling `set`
585 value : any supported type
586 Value of item; may be a scalar
or array
592 the existing value. Also the item
is added
as a live
593 reference, so updating ``value`` will update this container
598 lsst::pex::exceptions::TypeError
599 Raised
if the type of `value`
is incompatible
with the existing
602 return _propertyContainerAdd(self, name, value, self.
_typeMenu)
605 """Update the current container with the supplied additions.
609 addition : `collections.abc.Mapping` or `PropertySet`
610 The content to merge into the current container.
614 This
is not the same
as calling `PropertySet.combine` since the
615 behavior differs when both mappings contain the same key. This
616 method updates by overwriting existing values completely
with
619 if isinstance(addition, PropertySet):
623 self.copy(k, addition, k)
625 for k, v
in addition.items():
629 """Returns a (possibly nested) dictionary with all properties.
634 Dictionary with all names
and values (no comments).
638 for name
in self.names():
639 v = _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO)
641 if isinstance(v, PropertySet):
642 d[name] = PropertySet.toDict(v)
649 return NotImplemented
651 if len(self) != len(other):
655 if (self_typeOf := self.typeOf(name)) != other.typeOf(name):
658 if (v1 := _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO)) != \
659 (v2 := _propertyContainerGet(other, name, returnStyle=ReturnStyle.AUTO)):
663 if self_typeOf
in (_TYPE_MAP[
"Float"], _TYPE_MAP[
"Double"]) \
664 and math.isnan(v1)
and math.isnan(v2):
674 for itemName
in self:
675 ps.copy(itemName, self, itemName)
679 result = self.deepCopy()
680 memo[
id(self)] = result
684 """Determines if the name is found at the top level hierarchy
689 Does not use `PropertySet.exists()`` because that includes support
690 for "."-delimited names. This method
is consistent
with the
691 items returned
from ``__iter__``.
693 return name
in self.names(topLevelOnly=
True)
696 """Assigns the supplied value to the container.
701 Name of item to update.
702 value : Value to assign
703 Can be any value supported by the container's ``set()``
704 method. `~collections.abc.Mapping` are converted to
705 `PropertySet` before assignment.
709 Uses `PropertySet.set`, overwriting any previous value.
711 if isinstance(value, Mapping):
714 for k, v
in value.items():
717 self.
set(name, value)
720 """Returns a scalar item from the container.
724 Uses `PropertySet.getScalar` to guarantee that a single value
730 if self.exists(name):
734 raise KeyError(f
"{name} not present in dict")
737 return self.toString()
740 return self.nameCount(topLevelOnly=
True)
743 for n
in self.names(topLevelOnly=
True):
747 return KeysView(self)
750 return ItemsView(self)
753 return ValuesView(self)
755 def pop(self, name, default=None):
756 """Remove the named key and return its value.
761 Name of the key to remove. Can be hierarchical.
762 default : Any, optional
763 Value to return if the key
is not present.
768 The value of the item
as would be returned using `
getScalar()`.
773 Raised
if no default
is given
and the key
is missing.
775 if self.exists(name):
790 return (_makePropertySet, (getPropertySetState(self),))
796 _typeMenu = {bool:
"Bool",
800 DateTime:
"DateTime",
801 PropertySet:
"PropertySet",
802 PropertyList:
"PropertySet",
806 COMMENTSUFFIX =
"#COMMENT"
807 """Special suffix used to indicate that a named item being assigned
808 using dict syntax is referring to a comment,
not value.
"""
810 def get(self, name, default=None):
811 """Return an item as a scalar, else default.
813 Identical to `getScalar` except that a default value
is returned
814 if the requested key
is not present. If an array item
is requested
815 the final value
in the array will be returned.
821 default : `object`, optional
822 Default value to use
if the named item
is not present.
826 value : any type supported by container
827 Single value of any type supported by the container,
else the
828 default value
if the requested item
is not present
in the
829 container. For array items the most recently added value
is
833 return _propertyContainerGet(self, name, returnStyle=ReturnStyle.SCALAR)
838 """Return an item as a list.
847 values : `list` of values
848 The contents of the item, guaranteed to be returned as a `list.`
853 Raised
if the item does
not exist.
855 return _propertyContainerGet(self, name, returnStyle=ReturnStyle.ARRAY)
858 """Return an item as a scalar
860 If the item has more than one value then the last value is returned.
870 Value stored
in the item. If the item refers to an array the
871 most recently added value
is returned.
876 Raised
if the item does
not exist.
878 return _propertyContainerGet(self, name, returnStyle=ReturnStyle.SCALAR)
880 def set(self, name, value, comment=None):
881 """Set the value of an item
883 If the item already exists it is silently replaced; the types
890 value : any supported type
891 Value of item; may be a scalar
or array
894 if comment
is not None:
896 return _propertyContainerSet(self, name, value, self.
_typeMenu, *args)
898 def add(self, name, value, comment=None):
899 """Append one or more values to a given item, which need not exist
901 If the item exists then the new value(s) are appended;
902 otherwise it is like calling `set`
908 value : any supported type
909 Value of item; may be a scalar
or array
914 using dotted names (e.g.
if name=
"a" and value contains
915 an item
"b" which
is another PropertySet
and contains an
916 item
"c" which
is numeric
or string, then the value of
"c"
917 is added
as "a.b.c", appended to the existing values of
918 "a.b.c" if any (
in which case the types must be compatible).
922 lsst::pex::exceptions::TypeError
923 Raise
if the type of ``value``
is incompatible
with the existing
927 if comment
is not None:
929 return _propertyContainerAdd(self, name, value, self.
_typeMenu, *args)
932 """Set the comment for an existing entry.
937 Name of the key to receive updated comment.
943 containerType = _propertyContainerElementTypeName(self, name)
944 if self.isArray(name):
948 getattr(self, f
"set{containerType}")(name, value, comment)
951 """Return a list of tuples of name, value, comment for each property
952 in the order that they were inserted.
956 ret : `list` of `tuple`
957 Tuples of name, value, comment
for each property
in the order
958 in which they were inserted.
960 orderedNames = self.getOrderedNames()
962 for name
in orderedNames:
963 if self.isArray(name):
964 values = _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO)
966 ret.append((name, v, self.getComment(name)))
968 ret.append((name, _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO),
969 self.getComment(name)))
973 """Return an ordered dictionary with all properties in the order that
979 Ordered dictionary with all properties
in the order that they
980 were inserted. Comments are
not included.
984 As of Python 3.6 dicts retain their insertion order.
988 d[name] = _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO)
992 toDict = toOrderedDict
998 if not PropertySet.__eq__(self, other):
1002 if self.getComment(name) != other.getComment(name):
1010 for itemName
in self:
1011 pl.copy(itemName, self, itemName)
1015 result = self.deepCopy()
1016 memo[
id(self)] = result
1020 for n
in self.getOrderedNames():
1024 """Assigns the supplied value to the container.
1029 Name of item to update. If the name ends with
1030 `PropertyList.COMMENTSUFFIX`, the comment
is updated rather
1032 value : Value to assign
1033 Can be any value supported by the container
's ``set()``
1034 method. `~collections.abc.Mapping` are converted to
1035 `PropertySet` before assignment.
1039 Uses `PropertySet.set`, overwriting any previous value.
1045 if isinstance(value, Mapping):
1048 for k, v
in value.items():
1051 self.
set(name, value)
1059 return (_makePropertyList, (getPropertyListState(self),))
std::vector< SchemaItem< Flag > > * items
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)
daf::base::PropertySet * set