LSST Applications g070148d5b3+33e5256705,g0d53e28543+25c8b88941,g0da5cf3356+2dd1178308,g1081da9e2a+62d12e78cb,g17e5ecfddb+7e422d6136,g1c76d35bf8+ede3a706f7,g295839609d+225697d880,g2e2c1a68ba+cc1f6f037e,g2ffcdf413f+853cd4dcde,g38293774b4+62d12e78cb,g3b44f30a73+d953f1ac34,g48ccf36440+885b902d19,g4b2f1765b6+7dedbde6d2,g5320a0a9f6+0c5d6105b6,g56b687f8c9+ede3a706f7,g5c4744a4d9+ef6ac23297,g5ffd174ac0+0c5d6105b6,g6075d09f38+66af417445,g667d525e37+2ced63db88,g670421136f+2ced63db88,g71f27ac40c+2ced63db88,g774830318a+463cbe8d1f,g7876bc68e5+1d137996f1,g7985c39107+62d12e78cb,g7fdac2220c+0fd8241c05,g96f01af41f+368e6903a7,g9ca82378b8+2ced63db88,g9d27549199+ef6ac23297,gabe93b2c52+e3573e3735,gb065e2a02a+3dfbe639da,gbc3249ced9+0c5d6105b6,gbec6a3398f+0c5d6105b6,gc9534b9d65+35b9f25267,gd01420fc67+0c5d6105b6,geee7ff78d7+a14128c129,gf63283c776+ede3a706f7,gfed783d017+0c5d6105b6,w.2022.47
LSST Data Management Base Package
Loading...
Searching...
No Matches
propertyContainerContinued.py
Go to the documentation of this file.
2# LSST Data Management System
3#
4# Copyright 2008-2017 AURA/LSST.
5#
6# This product includes software developed by the
7# LSST Project (http://www.lsst.org/).
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the LSST License Statement and
20# the GNU General Public License along with this program. If not,
21# see <http://www.lsstcorp.org/LegalNotices/>.
22#
23
24
25__all__ = ["getPropertySetState", "getPropertyListState", "setPropertySetState", "setPropertyListState"]
26
27import enum
28import math
29import numbers
30import dataclasses
31from collections.abc import Mapping, KeysView, ValuesView, ItemsView
32
33# Ensure that C++ exceptions are properly translated to Python
34import lsst.pex.exceptions # noqa: F401
35from lsst.utils import continueClass
36
37from .propertySet import PropertySet
38from .propertyList import PropertyList
39from ..dateTime import DateTime
40
41# Map the type names to the internal type representation.
42_TYPE_MAP = {}
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
48 # Store both directions.
49 _TYPE_MAP[checkType] = type_obj
50
51
52def getPropertySetState(container, asLists=False):
53 """Get the state of a PropertySet in a form that can be pickled.
54
55 Parameters
56 ----------
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.
62
63 Returns
64 -------
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:
68
69 name (a `str`)
70 the name of the item
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.
76 value
77 the data for the item, in a form compatible
78 with the set method named by ``elementTypeName``
79 """
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)))
84 for name in names]
85
86
87def getPropertyListState(container, asLists=False):
88 """Get the state of a PropertyList in a form that can be pickled.
89
90 Parameters
91 ----------
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.
97
98 Returns
99 -------
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:
103
104 name (a `str`):
105 the name of the item
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.
111 value
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.
116 """
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()]
122
123
124def setPropertySetState(container, state):
125 """Restore the state of a PropertySet, in place.
126
127 Parameters
128 ----------
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.
132 state : `list`
133 The state, as returned by `getPropertySetState`
134 """
135 for name, elemType, value in state:
136 if elemType is not None:
137 getattr(container, "set" + elemType)(name, value)
138 else:
139 raise ValueError(f"Unrecognized values for state restoration: ({name}, {elemType}, {value})")
140
141
142def setPropertyListState(container, state):
143 """Restore the state of a PropertyList, in place.
144
145 Parameters
146 ----------
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.
150 state : `list`
151 The state, as returned by ``getPropertyListState``
152 """
153 for name, elemType, value, comment in state:
154 getattr(container, "set" + elemType)(name, value, comment)
155
156
157class ReturnStyle(enum.Enum):
158 ARRAY = enum.auto()
159 SCALAR = enum.auto()
160 AUTO = enum.auto()
161
162
163def _propertyContainerElementTypeName(container, name):
164 """Return name of the type of a particular element
165
166 Parameters
167 ----------
169 Container including the element
170 name : `str`
171 Name of element
172 """
173 try:
174 t = container.typeOf(name)
175 except LookupError as e:
176 # KeyError is more commonly expected when asking for an element
177 # from a mapping.
178 raise KeyError(str(e)) from None
179
180 return _TYPE_MAP.get(t, None)
181
182
183def _propertyContainerGet(container, name, returnStyle):
184 """Get a value of unknown type as a scalar or array
185
186 Parameters
187 ----------
189 Container from which to get the value
190 name : `str`
191 Name of item
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
203 otherwise.
204
205 Raises
206 ------
207 KeyError
208 Raised if the specified key does not exist in the container.
209 TypeError
210 Raised if the value retrieved is of an unexpected type.
211 ValueError
212 Raised if the value for ``returnStyle`` is not correct.
213 """
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))
218
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):
223 return value
224 return value[-1]
225
226 if container.isPropertySetPtr(name):
227 try:
228 return container.getAsPropertyListPtr(name)
229 except Exception:
230 return container.getAsPropertySetPtr(name)
231 try:
232 return container.getAsPersistablePtr(name)
233 except Exception:
234 pass
235 raise TypeError('Unknown PropertySet value type for ' + name)
236
237
238def _iterable(a):
239 """Make input iterable.
240
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,
243 yields itself.
244 """
245 if isinstance(a, (str, PropertyList, PropertySet)):
246 yield a
247 return
248 try:
249 yield from a
250 except Exception:
251 yield a
252
253
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.
258
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).
262
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.
265
266 Parameters
267 ----------
269 Container from which to get the value
270
271 name : `str`
272 Name of item
273
274 value : `object`
275 Value to be assigned a type. Can be an iterable.
276
277 Returns
278 -------
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.
282 """
283 maxInt = 2147483647
284 minInt = -2147483648
285 maxLongLong = 2**63 - 1
286 minLongLong = -2**63
287 maxU64 = 2**64 - 1
288 minU64 = 0
289
290 # Go through the values to find the range of supplied integers,
291 # stopping early if we don't have an integer.
292 min = None
293 max = None
294 for v in _iterable(value):
295 # Do not try to convert a bool to an integer
296 if not isinstance(v, numbers.Integral) or isinstance(v, bool):
297 return None
298
299 if min is None:
300 min = v
301 max = v
302 elif v < min:
303 min = v
304 elif v > max:
305 max = v
306
307 # Safety net
308 if min is None or max is None:
309 raise RuntimeError(f"Internal logic failure calculating integer range of {value}")
310
311 def _choose_int_from_range(int_value, current_type):
312 # If this is changing type from non-integer the current type
313 # does not matter.
314 if current_type not in {"Int", "LongLong", "UnsignedLongLong"}:
315 current_type = None
316
317 if int_value <= maxInt and int_value >= minInt and current_type in (None, "Int"):
318 # Use Int only if in range and either no current type or the
319 # current type is an Int.
320 use_type = "Int"
321 elif int_value >= minLongLong and int_value < 0:
322 # All large negatives must be LongLong if they did not fit
323 # in Int clause above.
324 use_type = "LongLong"
325 elif int_value >= 0 and int_value <= maxLongLong and current_type in (None, "Int", "LongLong"):
326 # Larger than Int or already a LongLong
327 use_type = "LongLong"
328 elif int_value <= maxU64 and int_value >= minU64:
329 use_type = "UnsignedLongLong"
330 else:
331 raise RuntimeError("Unable to guess integer type for storing out of "
332 f"range value: {int_value}")
333 return use_type
334
335 if container.exists(name):
336 containerType = _propertyContainerElementTypeName(container, name)
337 else:
338 containerType = None
339
340 useTypeMin = _choose_int_from_range(min, containerType)
341 useTypeMax = _choose_int_from_range(max, containerType)
342
343 if useTypeMin == useTypeMax:
344 return useTypeMin
345
346 # When different the combinations are:
347 # Int + LongLong
348 # Int + UnsignedLongLong
349 # LongLong + UnsignedLongLong
350
351 choices = {useTypeMin, useTypeMax}
352 if choices == {"Int", "LongLong"}:
353 return "LongLong"
354
355 # If UnsignedLongLong is required things will break if the min
356 # is negative. They will break whatever we choose if that is the case
357 # but we have no choice but to return the UnsignedLongLong regardless.
358 if "UnsignedLongLong" in choices:
359 return "UnsignedLongLong"
360
361 raise RuntimeError(f"Logic error in guessing integer type from {min} and {max}")
362
363
364def _propertyContainerSet(container, name, value, typeMenu, *args):
365 """Set a single Python value of unknown type
366 """
367 try:
368 exemplar = next(_iterable(value))
369 except StopIteration:
370 # Do nothing if nothing provided. This matches the behavior
371 # of the explicit setX() methods.
372 return
373 t = type(exemplar)
374 setType = _guessIntegerType(container, name, value)
375
376 if setType is not None or t in typeMenu:
377 if setType is None:
378 setType = typeMenu[t]
379 return getattr(container, "set" + setType)(name, value, *args)
380 # Allow for subclasses
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))
386
387
388def _propertyContainerAdd(container, name, value, typeMenu, *args):
389 """Add a single Python value of unknown type
390 """
391 try:
392 exemplar = next(_iterable(value))
393 except StopIteration:
394 # Adding an empty iterable to an existing entry is a no-op
395 # since there is nothing to add.
396 return
397 t = type(exemplar)
398 addType = _guessIntegerType(container, name, exemplar)
399
400 if addType is not None or t in typeMenu:
401 if addType is None:
402 addType = typeMenu[t]
403 return getattr(container, "add" + addType)(name, value, *args)
404 # Allow for subclasses
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))
410
411
412def _makePropertySet(state):
413 """Make a `PropertySet` from the state returned by `getPropertySetState`
414
415 Parameters
416 ----------
417 state : `list`
418 The data returned by `getPropertySetState`.
419 """
420 ps = PropertySet()
421 setPropertySetState(ps, state)
422 return ps
423
424
425def _makePropertyList(state):
426 """Make a `PropertyList` from the state returned by
427 `getPropertyListState`
428
429 Parameters
430 ----------
431 state : `list`
432 The data returned by `getPropertySetState`.
433 """
434 pl = PropertyList()
435 setPropertyListState(pl, state)
436 return pl
437
438
439@continueClass
441 # Mapping of type to method names;
442 # int types are omitted due to use of _guessIntegerType
443 _typeMenu = {bool: "Bool",
444 float: "Double",
445 str: "String",
446 DateTime: "DateTime",
447 PropertySet: "PropertySet",
448 PropertyList: "PropertySet",
449 None: "Undef",
450 }
451
452 @classmethod
453 def from_mapping(cls, metadata):
454 """Create a `PropertySet` from a mapping or dict-like object.
455
456 Parameters
457 ----------
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`.
464
465 Returns
466 -------
467 ps : `PropertySet`
468 The new `PropertySet`.
469 """
470 ps = cls()
471 d = None
472 if isinstance(metadata, Mapping):
473 d = metadata
474 elif dataclasses.is_dataclass(metadata):
475 d = dataclasses.asdict(metadata)
476 else:
477 for attr in ("to_dict", "toDict", "dict"):
478 if hasattr(metadata, attr):
479 d = getattr(metadata, attr)()
480 break
481 if d is None:
482 raise ValueError("Unable to extract mappings from the supplied metadata of type"
483 f" {type(metadata)}")
484 ps.update(d)
485 return ps
486
487 def get(self, name, default=None):
488 """Return an item as a scalar, else default.
489
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.
493
494 Parameters
495 ----------
496 name : `str`
497 Name of item
498 default : `object`, optional
499 Default value to use if the named item is not present.
500
501 Returns
502 -------
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
507 returned.
508 """
509 try:
510 return _propertyContainerGet(self, name, returnStyle=ReturnStyle.SCALAR)
511 except KeyError:
512 return default
513
514 def getArray(self, name):
515 """Return an item as an array if the item is numeric or string
516
517 If the item is a `PropertySet`, `PropertyList` or
518 `lsst.daf.base.PersistablePtr` then return the item as a scalar.
519
520 Parameters
521 ----------
522 name : `str`
523 Name of item
524
525 Returns
526 -------
527 values : `list` of any type supported by container
528 The contents of the item, guaranteed to be returned as a `list.`
529
530 Raises
531 ------
532 KeyError
533 Raised if the item does not exist.
534 """
535 return _propertyContainerGet(self, name, returnStyle=ReturnStyle.ARRAY)
536
537 def getScalar(self, name):
538 """Return an item as a scalar
539
540 If the item has more than one value then the last value is returned.
541
542 Parameters
543 ----------
544 name : `str`
545 Name of item
546
547 Returns
548 -------
549 value : scalar item
550 Value stored in the item. If the item refers to an array the
551 most recently added value is returned.
552
553 Raises
554 ------
555 KeyError
556 Raised if the item does not exist.
557 """
558 return _propertyContainerGet(self, name, returnStyle=ReturnStyle.SCALAR)
559
560 def set(self, name, value):
561 """Set the value of an item
562
563 If the item already exists it is silently replaced; the types
564 need not match.
565
566 Parameters
567 ----------
568 name : `str`
569 Name of item
570 value : any supported type
571 Value of item; may be a scalar or array
572 """
573 return _propertyContainerSet(self, name, value, self._typeMenu)
574
575 def add(self, name, value):
576 """Append one or more values to a given item, which need not exist
577
578 If the item exists then the new value(s) are appended;
579 otherwise it is like calling `set`
580
581 Parameters
582 ----------
583 name : `str`
584 Name of item
585 value : any supported type
586 Value of item; may be a scalar or array
587
588 Notes
589 -----
590 If ``value`` is an `lsst.daf.base.PropertySet` or
591 `lsst.daf.base.PropertyList` then ``value`` replaces
592 the existing value. Also the item is added as a live
593 reference, so updating ``value`` will update this container
594 and vice-versa.
595
596 Raises
597 ------
598 lsst::pex::exceptions::TypeError
599 Raised if the type of `value` is incompatible with the existing
600 value of the item.
601 """
602 return _propertyContainerAdd(self, name, value, self._typeMenu)
603
604 def update(self, addition):
605 """Update the current container with the supplied additions.
606
607 Parameters
608 ----------
609 addition : `collections.abc.Mapping` or `PropertySet`
610 The content to merge into the current container.
611
612 Notes
613 -----
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
617 the new value.
618 """
619 if isinstance(addition, PropertySet):
620 # To support array values we can not use the dict interface
621 # and instead use the copy() method which overwrites
622 for k in addition:
623 self.copy(k, addition, k)
624 else:
625 for k, v in addition.items():
626 self[k] = v
627
628 def toDict(self):
629 """Returns a (possibly nested) dictionary with all properties.
630
631 Returns
632 -------
633 d : `dict`
634 Dictionary with all names and values (no comments).
635 """
636
637 d = {}
638 for name in self.names():
639 v = _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO)
640
641 if isinstance(v, PropertySet):
642 d[name] = PropertySet.toDict(v)
643 else:
644 d[name] = v
645 return d
646
647 def __eq__(self, other):
648 if type(self) != type(other):
649 return NotImplemented
650
651 if len(self) != len(other):
652 return False
653
654 for name in self:
655 if (self_typeOf := self.typeOf(name)) != other.typeOf(name):
656 return False
657
658 if (v1 := _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO)) != \
659 (v2 := _propertyContainerGet(other, name, returnStyle=ReturnStyle.AUTO)):
660 # It is possible that we have floats that are NaN. When
661 # equating two PropertySets if there are fields with NaN
662 # these should equate equal.
663 if self_typeOf in (_TYPE_MAP["Float"], _TYPE_MAP["Double"]) \
664 and math.isnan(v1) and math.isnan(v2):
665 pass
666 else:
667 return False
668
669 return True
670
671 def __copy__(self):
672 # Copy without having to go through pickle state
673 ps = PropertySet()
674 for itemName in self:
675 ps.copy(itemName, self, itemName)
676 return ps
677
678 def __deepcopy__(self, memo):
679 result = self.deepCopy()
680 memo[id(self)] = result
681 return result
682
683 def __contains__(self, name):
684 """Determines if the name is found at the top level hierarchy
685 of the container.
686
687 Notes
688 ------
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__``.
692 """
693 return name in self.names(topLevelOnly=True)
694
695 def __setitem__(self, name, value):
696 """Assigns the supplied value to the container.
697
698 Parameters
699 ----------
700 name : `str`
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.
706
707 Notes
708 -----
709 Uses `PropertySet.set`, overwriting any previous value.
710 """
711 if isinstance(value, Mapping):
712 # Create a property set instead
713 ps = PropertySet()
714 for k, v in value.items():
715 ps[k] = v
716 value = ps
717 self.set(name, value)
718
719 def __getitem__(self, name):
720 """Returns a scalar item from the container.
721
722 Notes
723 -----
724 Uses `PropertySet.getScalar` to guarantee that a single value
725 will be returned.
726 """
727 return self.getScalar(name)
728
729 def __delitem__(self, name):
730 if self.exists(name):
731 # dot-delimited names should work so cannot use "in".
732 self.remove(name)
733 else:
734 raise KeyError(f"{name} not present in dict")
735
736 def __str__(self):
737 return self.toString()
738
739 def __len__(self):
740 return self.nameCount(topLevelOnly=True)
741
742 def __iter__(self):
743 for n in self.names(topLevelOnly=True):
744 yield n
745
746 def keys(self):
747 return KeysView(self)
748
749 def items(self):
750 return ItemsView(self)
751
752 def values(self):
753 return ValuesView(self)
754
755 def pop(self, name, default=None):
756 """Remove the named key and return its value.
757
758 Parameters
759 ----------
760 name : `str`
761 Name of the key to remove. Can be hierarchical.
762 default : Any, optional
763 Value to return if the key is not present.
764
765 Returns
766 -------
767 value : Any
768 The value of the item as would be returned using `getScalar()`.
769
770 Raises
771 ------
772 KeyError
773 Raised if no default is given and the key is missing.
774 """
775 if self.exists(name):
776 value = self[name]
777 self.remove(name)
778 else:
779 if default is None:
780 raise KeyError(name)
781 value = default
782 return value
783
784 def __reduce__(self):
785 # It would be a bit simpler to use __setstate__ and __getstate__.
786 # However, implementing __setstate__ in Python causes segfaults
787 # because pickle creates a new instance by calling
788 # object.__new__(PropertyList, *args) which bypasses
789 # the pybind11 memory allocation step.
790 return (_makePropertySet, (getPropertySetState(self),))
791
792
793@continueClass
795 # Mapping of type to method names
796 _typeMenu = {bool: "Bool",
797 int: "Int",
798 float: "Double",
799 str: "String",
800 DateTime: "DateTime",
801 PropertySet: "PropertySet",
802 PropertyList: "PropertySet",
803 None: "Undef",
804 }
805
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."""
809
810 def get(self, name, default=None):
811 """Return an item as a scalar, else default.
812
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.
816
817 Parameters
818 ----------
819 name : ``str``
820 Name of item
821 default : `object`, optional
822 Default value to use if the named item is not present.
823
824 Returns
825 -------
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
830 returned.
831 """
832 try:
833 return _propertyContainerGet(self, name, returnStyle=ReturnStyle.SCALAR)
834 except KeyError:
835 return default
836
837 def getArray(self, name):
838 """Return an item as a list.
839
840 Parameters
841 ----------
842 name : `str`
843 Name of item
844
845 Returns
846 -------
847 values : `list` of values
848 The contents of the item, guaranteed to be returned as a `list.`
849
850 Raises
851 ------
852 KeyError
853 Raised if the item does not exist.
854 """
855 return _propertyContainerGet(self, name, returnStyle=ReturnStyle.ARRAY)
856
857 def getScalar(self, name):
858 """Return an item as a scalar
859
860 If the item has more than one value then the last value is returned.
861
862 Parameters
863 ----------
864 name : `str`
865 Name of item.
866
867 Returns
868 -------
869 value : scalar item
870 Value stored in the item. If the item refers to an array the
871 most recently added value is returned.
872
873 Raises
874 ------
875 KeyError
876 Raised if the item does not exist.
877 """
878 return _propertyContainerGet(self, name, returnStyle=ReturnStyle.SCALAR)
879
880 def set(self, name, value, comment=None):
881 """Set the value of an item
882
883 If the item already exists it is silently replaced; the types
884 need not match.
885
886 Parameters
887 ----------
888 name : `str`
889 Name of item
890 value : any supported type
891 Value of item; may be a scalar or array
892 """
893 args = []
894 if comment is not None:
895 args.append(comment)
896 return _propertyContainerSet(self, name, value, self._typeMenu, *args)
897
898 def add(self, name, value, comment=None):
899 """Append one or more values to a given item, which need not exist
900
901 If the item exists then the new value(s) are appended;
902 otherwise it is like calling `set`
903
904 Parameters
905 ----------
906 name : `str`
907 Name of item
908 value : any supported type
909 Value of item; may be a scalar or array
910
911 Notes
912 -----
913 If `value` is an `lsst.daf.base.PropertySet` items are added
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).
919
920 Raises
921 ------
922 lsst::pex::exceptions::TypeError
923 Raise if the type of ``value`` is incompatible with the existing
924 value of the item.
925 """
926 args = []
927 if comment is not None:
928 args.append(comment)
929 return _propertyContainerAdd(self, name, value, self._typeMenu, *args)
930
931 def setComment(self, name, comment):
932 """Set the comment for an existing entry.
933
934 Parameters
935 ----------
936 name : `str`
937 Name of the key to receive updated comment.
938 comment : `comment`
939 New comment string.
940 """
941 # The only way to do this is to replace the existing entry with
942 # one that has the new comment
943 containerType = _propertyContainerElementTypeName(self, name)
944 if self.isArray(name):
945 value = self.getArray(name)
946 else:
947 value = self.getScalar(name)
948 getattr(self, f"set{containerType}")(name, value, comment)
949
950 def toList(self):
951 """Return a list of tuples of name, value, comment for each property
952 in the order that they were inserted.
953
954 Returns
955 -------
956 ret : `list` of `tuple`
957 Tuples of name, value, comment for each property in the order
958 in which they were inserted.
959 """
960 orderedNames = self.getOrderedNames()
961 ret = []
962 for name in orderedNames:
963 if self.isArray(name):
964 values = _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO)
965 for v in values:
966 ret.append((name, v, self.getComment(name)))
967 else:
968 ret.append((name, _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO),
969 self.getComment(name)))
970 return ret
971
972 def toOrderedDict(self):
973 """Return an ordered dictionary with all properties in the order that
974 they were inserted.
975
976 Returns
977 -------
978 d : `dict`
979 Ordered dictionary with all properties in the order that they
980 were inserted. Comments are not included.
981
982 Notes
983 -----
984 As of Python 3.6 dicts retain their insertion order.
985 """
986 d = {}
987 for name in self:
988 d[name] = _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO)
989 return d
990
991 # For PropertyList the two are equivalent
992 toDict = toOrderedDict
993
994 def __eq__(self, other):
995 # super() doesn't seem to work properly in @continueClass;
996 # note that super with arguments seems to work at first, but actually
997 # doesn't either.
998 if not PropertySet.__eq__(self, other):
999 return False
1000
1001 for name in self:
1002 if self.getComment(name) != other.getComment(name):
1003 return False
1004
1005 return True
1006
1007 def __copy__(self):
1008 # Copy without having to go through pickle state
1009 pl = PropertyList()
1010 for itemName in self:
1011 pl.copy(itemName, self, itemName)
1012 return pl
1013
1014 def __deepcopy__(self, memo):
1015 result = self.deepCopy()
1016 memo[id(self)] = result
1017 return result
1018
1019 def __iter__(self):
1020 for n in self.getOrderedNames():
1021 yield n
1022
1023 def __setitem__(self, name, value):
1024 """Assigns the supplied value to the container.
1025
1026 Parameters
1027 ----------
1028 name : `str`
1029 Name of item to update. If the name ends with
1030 `PropertyList.COMMENTSUFFIX`, the comment is updated rather
1031 than the value.
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.
1036
1037 Notes
1038 -----
1039 Uses `PropertySet.set`, overwriting any previous value.
1040 """
1041 if name.endswith(self.COMMENTSUFFIX):
1042 name = name[:-len(self.COMMENTSUFFIX)]
1043 self.setComment(name, value)
1044 return
1045 if isinstance(value, Mapping):
1046 # Create a property set instead
1047 ps = PropertySet()
1048 for k, v in value.items():
1049 ps[k] = v
1050 value = ps
1051 self.set(name, value)
1052
1053 def __reduce__(self):
1054 # It would be a bit simpler to use __setstate__ and __getstate__.
1055 # However, implementing __setstate__ in Python causes segfaults
1056 # because pickle creates a new instance by calling
1057 # object.__new__(PropertyList, *args) which bypasses
1058 # the pybind11 memory allocation step.
1059 return (_makePropertyList, (getPropertyListState(self),))
std::vector< SchemaItem< Flag > > * items
table::Key< int > id
Definition: Detector.cc:162
table::Key< int > type
Definition: Detector.cc:163
Class for storing ordered metadata with comments.
Definition: PropertyList.h:68
Class for storing generic metadata.
Definition: PropertySet.h:66
daf::base::PropertySet * set
Definition: fits.cc:927