LSST Applications g0f08755f38+9c285cab97,g1635faa6d4+bcae251498,g1653933729+a8ce1bb630,g1a0ca8cf93+bf6eb00ceb,g28da252d5a+0829b12dee,g29321ee8c0+18ecbd06b3,g2bbee38e9b+9634bc57db,g2bc492864f+9634bc57db,g2cdde0e794+c2c89b37c4,g3156d2b45e+41e33cbcdc,g347aa1857d+9634bc57db,g35bb328faa+a8ce1bb630,g3a166c0a6a+9634bc57db,g3e281a1b8c+9f2c4e2fc3,g414038480c+077ccc18e7,g41af890bb2+fde0dd39b6,g5fbc88fb19+17cd334064,g7642f7d749+9c285cab97,g781aacb6e4+a8ce1bb630,g80478fca09+55a9465950,g82479be7b0+ed77629bff,g858d7b2824+9c285cab97,g9125e01d80+a8ce1bb630,g9726552aa6+10f999ec6a,ga5288a1d22+2a84bb7594,gacf8899fa4+c69c5206e8,gae0086650b+a8ce1bb630,gb58c049af0+d64f4d3760,gbd46683f8f+1c79523530,gc28159a63d+9634bc57db,gcf0d15dbbd+4b7d09cae4,gda3e153d99+9c285cab97,gda6a2b7d83+4b7d09cae4,gdaeeff99f8+1711a396fd,ge2409df99d+dfd3d5294a,ge79ae78c31+9634bc57db,gf0baf85859+147a0692ba,gf3967379c6+02b11634a5,w.2024.46
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__all__ = ["getPropertySetState", "getPropertyListState", "setPropertySetState", "setPropertyListState"]
25
26import enum
27import math
28import numbers
29import dataclasses
30from collections.abc import Mapping, KeysView, ValuesView, ItemsView
31from typing import TypeAlias, Union
32
33# Ensure that C++ exceptions are properly translated to Python
34import lsst.pex.exceptions # noqa: F401
35from lsst.utils import continueClass
36
37from .._dafBaseLib import PropertySet, PropertyList
38from ..dateTime import DateTime
39
40
41# Note that '|' syntax for unions doesn't work when we have to use a string
42# literal (and we do since it's recursive and not an annotation).
43NestedMetadataDict: TypeAlias = Mapping[str, Union[str, float, int, bool, "NestedMetadataDict"]]
44
45
46# Map the type names to the internal type representation.
47_TYPE_MAP = {}
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
53 # Store both directions.
54 _TYPE_MAP[checkType] = type_obj
55
56
57def getPropertySetState(container, asLists=False):
58 """Get the state of a PropertySet in a form that can be pickled.
59
60 Parameters
61 ----------
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.
67
68 Returns
69 -------
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:
73
74 name (a `str`)
75 the name of the item
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.
81 value
82 the data for the item, in a form compatible
83 with the set method named by ``elementTypeName``
84 """
85 names = container.names(topLevelOnly=True)
86 sequence = list if asLists else tuple
87 return [sequence((name, _propertyContainerElementTypeName(container, name),
88 _propertyContainerGet(container, name, returnStyle=ReturnStyle.AUTO)))
89 for name in names]
90
91
92def getPropertyListState(container, asLists=False):
93 """Get the state of a PropertyList in a form that can be pickled.
94
95 Parameters
96 ----------
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.
102
103 Returns
104 -------
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:
108
109 name (a `str`):
110 the name of the item
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.
116 value
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.
121 """
122 sequence = list if asLists else tuple
123 return [sequence((name, _propertyContainerElementTypeName(container, name),
124 _propertyContainerGet(container, name, returnStyle=ReturnStyle.AUTO),
125 container.getComment(name)))
126 for name in container.getOrderedNames()]
127
128
129def setPropertySetState(container, state):
130 """Restore the state of a PropertySet, in place.
131
132 Parameters
133 ----------
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.
137 state : `list`
138 The state, as returned by `getPropertySetState`
139 """
140 for name, elemType, value in state:
141 if elemType is not None:
142 getattr(container, "set" + elemType)(name, value)
143 else:
144 raise ValueError(f"Unrecognized values for state restoration: ({name}, {elemType}, {value})")
145
146
147def setPropertyListState(container, state):
148 """Restore the state of a PropertyList, in place.
149
150 Parameters
151 ----------
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.
155 state : `list`
156 The state, as returned by ``getPropertyListState``
157 """
158 for name, elemType, value, comment in state:
159 getattr(container, "set" + elemType)(name, value, comment)
160
161
162class ReturnStyle(enum.Enum):
163 ARRAY = enum.auto()
164 SCALAR = enum.auto()
165 AUTO = enum.auto()
166
167
169 """Return name of the type of a particular element
170
171 Parameters
172 ----------
173 container : `lsst.daf.base.PropertySet` or `lsst.daf.base.PropertyList`
174 Container including the element
175 name : `str`
176 Name of element
177 """
178 try:
179 t = container.typeOf(name)
180 except LookupError as e:
181 # KeyError is more commonly expected when asking for an element
182 # from a mapping.
183 raise KeyError(str(e)) from None
184
185 return _TYPE_MAP.get(t, None)
186
187
188def _propertyContainerGet(container, name, returnStyle):
189 """Get a value of unknown type as a scalar or array
190
191 Parameters
192 ----------
193 container : `lsst.daf.base.PropertySet` or `lsst.daf.base.PropertyList`
194 Container from which to get the value
195 name : `str`
196 Name of item
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
208 otherwise.
209
210 Raises
211 ------
212 KeyError
213 Raised if the specified key does not exist in the container.
214 TypeError
215 Raised if the value retrieved is of an unexpected type.
216 ValueError
217 Raised if the value for ``returnStyle`` is not correct.
218 """
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))
223
224 elemType = _propertyContainerElementTypeName(container, name)
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):
228 return value
229 return value[-1]
230
231 if container.isPropertySetPtr(name):
232 try:
233 return container.getAsPropertyListPtr(name)
234 except Exception:
235 return container.getAsPropertySetPtr(name)
236 try:
237 return container.getAsPersistablePtr(name)
238 except Exception:
239 pass
240 raise TypeError('Unknown PropertySet value type for ' + name)
241
242
244 """Make input iterable.
245
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,
248 yields itself.
249 """
250 if isinstance(a, (str, PropertyList, PropertySet)):
251 yield a
252 return
253 try:
254 yield from a
255 except Exception:
256 yield a
257
258
259def _guessIntegerType(container, name, value):
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.
263
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).
267
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.
270
271 Parameters
272 ----------
273 container : `lsst.daf.base.PropertySet` or `lsst.daf.base.PropertyList`
274 Container from which to get the value
275
276 name : `str`
277 Name of item
278
279 value : `object`
280 Value to be assigned a type. Can be an iterable.
281
282 Returns
283 -------
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.
287 """
288 maxInt = 2147483647
289 minInt = -2147483648
290 maxLongLong = 2**63 - 1
291 minLongLong = -2**63
292 maxU64 = 2**64 - 1
293 minU64 = 0
294
295 # Go through the values to find the range of supplied integers,
296 # stopping early if we don't have an integer.
297 min = None
298 max = None
299 for v in _iterable(value):
300 # Do not try to convert a bool to an integer
301 if not isinstance(v, numbers.Integral) or isinstance(v, bool):
302 return None
303
304 if min is None:
305 min = v
306 max = v
307 elif v < min:
308 min = v
309 elif v > max:
310 max = v
311
312 # Safety net
313 if min is None or max is None:
314 raise RuntimeError(f"Internal logic failure calculating integer range of {value}")
315
316 def _choose_int_from_range(int_value, current_type):
317 # If this is changing type from non-integer the current type
318 # does not matter.
319 if current_type not in {"Int", "LongLong", "UnsignedLongLong"}:
320 current_type = None
321
322 if int_value <= maxInt and int_value >= minInt and current_type in (None, "Int"):
323 # Use Int only if in range and either no current type or the
324 # current type is an Int.
325 use_type = "Int"
326 elif int_value >= minLongLong and int_value < 0:
327 # All large negatives must be LongLong if they did not fit
328 # in Int clause above.
329 use_type = "LongLong"
330 elif int_value >= 0 and int_value <= maxLongLong and current_type in (None, "Int", "LongLong"):
331 # Larger than Int or already a LongLong
332 use_type = "LongLong"
333 elif int_value <= maxU64 and int_value >= minU64:
334 use_type = "UnsignedLongLong"
335 else:
336 raise RuntimeError("Unable to guess integer type for storing out of "
337 f"range value: {int_value}")
338 return use_type
339
340 if container.exists(name):
341 containerType = _propertyContainerElementTypeName(container, name)
342 else:
343 containerType = None
344
345 useTypeMin = _choose_int_from_range(min, containerType)
346 useTypeMax = _choose_int_from_range(max, containerType)
347
348 if useTypeMin == useTypeMax:
349 return useTypeMin
350
351 # When different the combinations are:
352 # Int + LongLong
353 # Int + UnsignedLongLong
354 # LongLong + UnsignedLongLong
355
356 choices = {useTypeMin, useTypeMax}
357 if choices == {"Int", "LongLong"}:
358 return "LongLong"
359
360 # If UnsignedLongLong is required things will break if the min
361 # is negative. They will break whatever we choose if that is the case
362 # but we have no choice but to return the UnsignedLongLong regardless.
363 if "UnsignedLongLong" in choices:
364 return "UnsignedLongLong"
365
366 raise RuntimeError(f"Logic error in guessing integer type from {min} and {max}")
367
368
369def _propertyContainerSet(container, name, value, typeMenu, *args):
370 """Set a single Python value of unknown type
371 """
372 try:
373 exemplar = next(_iterable(value))
374 except StopIteration:
375 # Do nothing if nothing provided. This matches the behavior
376 # of the explicit setX() methods.
377 return
378 t = type(exemplar)
379 setType = _guessIntegerType(container, name, value)
380
381 if setType is not None or t in typeMenu:
382 if setType is None:
383 setType = typeMenu[t]
384 return getattr(container, "set" + setType)(name, value, *args)
385 # Allow for subclasses
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))
391
392
393def _propertyContainerAdd(container, name, value, typeMenu, *args):
394 """Add a single Python value of unknown type
395 """
396 try:
397 exemplar = next(_iterable(value))
398 except StopIteration:
399 # Adding an empty iterable to an existing entry is a no-op
400 # since there is nothing to add.
401 return
402 t = type(exemplar)
403 addType = _guessIntegerType(container, name, exemplar)
404
405 if addType is not None or t in typeMenu:
406 if addType is None:
407 addType = typeMenu[t]
408 return getattr(container, "add" + addType)(name, value, *args)
409 # Allow for subclasses
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))
415
416
418 """Make a `PropertySet` from the state returned by `getPropertySetState`
419
420 Parameters
421 ----------
422 state : `list`
423 The data returned by `getPropertySetState`.
424 """
425 ps = PropertySet()
426 setPropertySetState(ps, state)
427 return ps
428
429
431 """Make a `PropertyList` from the state returned by
432 `getPropertyListState`
433
434 Parameters
435 ----------
436 state : `list`
437 The data returned by `getPropertySetState`.
438 """
439 pl = PropertyList()
440 setPropertyListState(pl, state)
441 return pl
442
443
444@continueClass
446 # Mapping of type to method names;
447 # int types are omitted due to use of _guessIntegerType
448 _typeMenu = {bool: "Bool",
449 float: "Double",
450 str: "String",
451 DateTime: "DateTime",
452 PropertySet: "PropertySet",
453 PropertyList: "PropertySet",
454 None: "Undef",
455 }
456
457 @classmethod
458 def from_mapping(cls, metadata):
459 """Create a `PropertySet` from a mapping or dict-like object.
460
461 Parameters
462 ----------
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`.
469
470 Returns
471 -------
472 ps : `PropertySet`
473 The new `PropertySet`.
474 """
475 ps = cls()
476 d = None
477 if isinstance(metadata, Mapping):
478 d = metadata
479 elif dataclasses.is_dataclass(metadata):
480 d = dataclasses.asdict(metadata)
481 else:
482 for attr in ("to_dict", "toDict", "dict"):
483 if hasattr(metadata, attr):
484 d = getattr(metadata, attr)()
485 break
486 if d is None:
487 raise ValueError("Unable to extract mappings from the supplied metadata of type"
488 f" {type(metadata)}")
489 ps.update(d)
490 return ps
491
492 def get(self, name, default=None):
493 """Return an item as a scalar, else default.
494
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.
498
499 Parameters
500 ----------
501 name : `str`
502 Name of item
503 default : `object`, optional
504 Default value to use if the named item is not present.
505
506 Returns
507 -------
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
512 returned.
513 """
514 try:
515 return _propertyContainerGet(self, name, returnStyle=ReturnStyle.SCALAR)
516 except KeyError:
517 return default
518
519 def getArray(self, name):
520 """Return an item as an array if the item is numeric or string
521
522 If the item is a `PropertySet`, `PropertyList` or
523 `lsst.daf.base.PersistablePtr` then return the item as a scalar.
524
525 Parameters
526 ----------
527 name : `str`
528 Name of item
529
530 Returns
531 -------
532 values : `list` of any type supported by container
533 The contents of the item, guaranteed to be returned as a `list.`
534
535 Raises
536 ------
537 KeyError
538 Raised if the item does not exist.
539 """
540 return _propertyContainerGet(self, name, returnStyle=ReturnStyle.ARRAY)
541
542 def getScalar(self, name):
543 """Return an item as a scalar
544
545 If the item has more than one value then the last value is returned.
546
547 Parameters
548 ----------
549 name : `str`
550 Name of item
551
552 Returns
553 -------
554 value : scalar item
555 Value stored in the item. If the item refers to an array the
556 most recently added value is returned.
557
558 Raises
559 ------
560 KeyError
561 Raised if the item does not exist.
562 """
563 return _propertyContainerGet(self, name, returnStyle=ReturnStyle.SCALAR)
564
565 def set(self, name, value):
566 """Set the value of an item
567
568 If the item already exists it is silently replaced; the types
569 need not match.
570
571 Parameters
572 ----------
573 name : `str`
574 Name of item
575 value : any supported type
576 Value of item; may be a scalar or array
577 """
578 return _propertyContainerSet(self, name, value, self._typeMenu_typeMenu)
579
580 def add(self, name, value):
581 """Append one or more values to a given item, which need not exist
582
583 If the item exists then the new value(s) are appended;
584 otherwise it is like calling `set`
585
586 Parameters
587 ----------
588 name : `str`
589 Name of item
590 value : any supported type
591 Value of item; may be a scalar or array
592
593 Notes
594 -----
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
599 and vice-versa.
600
601 Raises
602 ------
603 lsst::pex::exceptions::TypeError
604 Raised if the type of `value` is incompatible with the existing
605 value of the item.
606 """
607 return _propertyContainerAdd(self, name, value, self._typeMenu_typeMenu)
608
609 def update(self, addition):
610 """Update the current container with the supplied additions.
611
612 Parameters
613 ----------
614 addition : `collections.abc.Mapping` or `PropertySet`
615 The content to merge into the current container.
616
617 Notes
618 -----
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
622 the new value.
623 """
624 if isinstance(addition, PropertySet):
625 # To support array values we can not use the dict interface
626 # and instead use the copy() method which overwrites
627 for k in addition:
628 self.copy(k, addition, k)
629 else:
630 for k, v in addition.items():
631 self[k] = v
632
633 def toDict(self):
634 """Returns a (possibly nested) dictionary with all properties.
635
636 Returns
637 -------
638 d : `dict`
639 Dictionary with all names and values (no comments).
640 """
641
642 d = {}
643 for name in self.names():
644 v = _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO)
645
646 if isinstance(v, PropertySet):
647 d[name] = PropertySet.toDict(v)
648 else:
649 d[name] = v
650 return d
651
652 def __eq__(self, other):
653 if type(self) is not type(other):
654 return NotImplemented
655
656 if len(self) != len(other):
657 return False
658
659 for name in self:
660 if (self_typeOf := self.typeOf(name)) != other.typeOf(name):
661 return False
662
663 if (v1 := _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO)) != \
664 (v2 := _propertyContainerGet(other, name, returnStyle=ReturnStyle.AUTO)):
665 # It is possible that we have floats that are NaN. When
666 # equating two PropertySets if there are fields with NaN
667 # these should equate equal.
668 if self_typeOf in (_TYPE_MAP["Float"], _TYPE_MAP["Double"]) \
669 and math.isnan(v1) and math.isnan(v2):
670 pass
671 else:
672 return False
673
674 return True
675
676 def __copy__(self):
677 # Copy without having to go through pickle state
678 ps = PropertySet()
679 for itemName in self:
680 ps.copy(itemName, self, itemName)
681 return ps
682
683 def __deepcopy__(self, memo):
684 result = self.deepCopy()
685 memo[id(self)] = result
686 return result
687
688 def __contains__(self, name):
689 """Determines if the name is found at the top level hierarchy
690 of the container.
691
692 Notes
693 ------
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__``.
697 """
698 return name in self.names(topLevelOnly=True)
699
700 def __setitem__(self, name, value):
701 """Assigns the supplied value to the container.
702
703 Parameters
704 ----------
705 name : `str`
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.
711
712 Notes
713 -----
714 Uses `PropertySet.set`, overwriting any previous value.
715 """
716 if isinstance(value, Mapping):
717 # Create a property set instead
718 ps = PropertySet()
719 for k, v in value.items():
720 ps[k] = v
721 value = ps
722 self.set(name, value)
723
724 def __getitem__(self, name):
725 """Returns a scalar item from the container.
726
727 Notes
728 -----
729 Uses `PropertySet.getScalar` to guarantee that a single value
730 will be returned.
731 """
732 return self.getScalar(name)
733
734 def __delitem__(self, name):
735 if self.exists(name):
736 # dot-delimited names should work so cannot use "in".
737 self.remove(name)
738 else:
739 raise KeyError(f"{name} not present in dict")
740
741 def __str__(self):
742 return self.toString()
743
744 def __len__(self):
745 return self.nameCount(topLevelOnly=True)
746
747 def __iter__(self):
748 for n in self.names(topLevelOnly=True):
749 yield n
750
751 def keys(self):
752 return KeysView(self)
753
754 def items(self):
755 return ItemsView(self)
756
757 def values(self):
758 return ValuesView(self)
759
760 def pop(self, name, default=None):
761 """Remove the named key and return its value.
762
763 Parameters
764 ----------
765 name : `str`
766 Name of the key to remove. Can be hierarchical.
767 default : Any, optional
768 Value to return if the key is not present.
769
770 Returns
771 -------
772 value : Any
773 The value of the item as would be returned using `getScalar()`.
774
775 Raises
776 ------
777 KeyError
778 Raised if no default is given and the key is missing.
779 """
780 if self.exists(name):
781 value = self[name]
782 self.remove(name)
783 else:
784 if default is None:
785 raise KeyError(name)
786 value = default
787 return value
788
789 def __reduce__(self):
790 # It would be a bit simpler to use __setstate__ and __getstate__.
791 # However, implementing __setstate__ in Python causes segfaults
792 # because pickle creates a new instance by calling
793 # object.__new__(PropertyList, *args) which bypasses
794 # the pybind11 memory allocation step.
795 return (_makePropertySet, (getPropertySetState(self),))
796
797 def get_dict(self, key: str) -> NestedMetadataDict:
798 """Return a possibly-hierarchical nested `dict`.
799
800 This implements the `lsst.pipe.base.GetDictMetadata` protocol for
801 consistency with `lsst.pipe.base.TaskMetadata` and `PropertyList`.
802
803 Parameters
804 ----------
805 key : `str`
806 String key associated with the mapping. May not have a ``.``
807 character.
808
809 Returns
810 -------
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.
815
816 Raises
817 ------
818 TypeError
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`).
822 """
823 if self.exists(key):
824 return self.getScalar(key).toDict()
825 else:
826 return {}
827
828 def set_dict(self, key: str, value: NestedMetadataDict) -> None:
829 """Assign a possibly-hierarchical nested `dict`.
830
831 This implements the `lsst.pipe.base.SetDictMetadata` protocol for
832 consistency with `lsst.pipe.base.TaskMetadata` and `PropertyList`.
833
834 Parameters
835 ----------
836 key : `str`
837 String key associated with the mapping. May not have a ``.``
838 character.
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 ``.``
843 character.
844 """
845 self.set(key, PropertySet.from_mapping(value))
846
847
848@continueClass
850 # Mapping of type to method names
851 _typeMenu = {bool: "Bool",
852 int: "Int",
853 float: "Double",
854 str: "String",
855 DateTime: "DateTime",
856 PropertySet: "PropertySet",
857 PropertyList: "PropertySet",
858 None: "Undef",
859 }
860
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."""
864
865 def get(self, name, default=None):
866 """Return an item as a scalar, else default.
867
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.
871
872 Parameters
873 ----------
874 name : ``str``
875 Name of item
876 default : `object`, optional
877 Default value to use if the named item is not present.
878
879 Returns
880 -------
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
885 returned.
886 """
887 try:
888 return _propertyContainerGet(self, name, returnStyle=ReturnStyle.SCALAR)
889 except KeyError:
890 return default
891
892 def getArray(self, name):
893 """Return an item as a list.
894
895 Parameters
896 ----------
897 name : `str`
898 Name of item
899
900 Returns
901 -------
902 values : `list` of values
903 The contents of the item, guaranteed to be returned as a `list.`
904
905 Raises
906 ------
907 KeyError
908 Raised if the item does not exist.
909 """
910 return _propertyContainerGet(self, name, returnStyle=ReturnStyle.ARRAY)
911
912 def getScalar(self, name):
913 """Return an item as a scalar
914
915 If the item has more than one value then the last value is returned.
916
917 Parameters
918 ----------
919 name : `str`
920 Name of item.
921
922 Returns
923 -------
924 value : scalar item
925 Value stored in the item. If the item refers to an array the
926 most recently added value is returned.
927
928 Raises
929 ------
930 KeyError
931 Raised if the item does not exist.
932 """
933 return _propertyContainerGet(self, name, returnStyle=ReturnStyle.SCALAR)
934
935 def set(self, name, value, comment=None):
936 """Set the value of an item
937
938 If the item already exists it is silently replaced; the types
939 need not match.
940
941 Parameters
942 ----------
943 name : `str`
944 Name of item
945 value : any supported type
946 Value of item; may be a scalar or array
947 """
948 args = []
949 if comment is not None:
950 args.append(comment)
951 return _propertyContainerSet(self, name, value, self._typeMenu_typeMenu, *args)
952
953 def add(self, name, value, comment=None):
954 """Append one or more values to a given item, which need not exist
955
956 If the item exists then the new value(s) are appended;
957 otherwise it is like calling `set`
958
959 Parameters
960 ----------
961 name : `str`
962 Name of item
963 value : any supported type
964 Value of item; may be a scalar or array
965
966 Notes
967 -----
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).
974
975 Raises
976 ------
977 lsst::pex::exceptions::TypeError
978 Raise if the type of ``value`` is incompatible with the existing
979 value of the item.
980 """
981 args = []
982 if comment is not None:
983 args.append(comment)
984 return _propertyContainerAdd(self, name, value, self._typeMenu_typeMenu, *args)
985
986 def setComment(self, name, comment):
987 """Set the comment for an existing entry.
988
989 Parameters
990 ----------
991 name : `str`
992 Name of the key to receive updated comment.
993 comment : `comment`
994 New comment string.
995 """
996 # The only way to do this is to replace the existing entry with
997 # one that has the new comment
998 containerType = _propertyContainerElementTypeName(self, name)
999 if self.isArray(name):
1000 value = self.getArray(name)
1001 else:
1002 value = self.getScalar(name)
1003 getattr(self, f"set{containerType}")(name, value, comment)
1004
1005 def toList(self):
1006 """Return a list of tuples of name, value, comment for each property
1007 in the order that they were inserted.
1008
1009 Returns
1010 -------
1011 ret : `list` of `tuple`
1012 Tuples of name, value, comment for each property in the order
1013 in which they were inserted.
1014 """
1015 orderedNames = self.getOrderedNames()
1016 ret = []
1017 for name in orderedNames:
1018 if self.isArray(name):
1019 values = _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO)
1020 for v in values:
1021 ret.append((name, v, self.getComment(name)))
1022 else:
1023 ret.append((name, _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO),
1024 self.getComment(name)))
1025 return ret
1026
1027 def toOrderedDict(self):
1028 """Return an ordered dictionary with all properties in the order that
1029 they were inserted.
1030
1031 Returns
1032 -------
1033 d : `dict`
1034 Ordered dictionary with all properties in the order that they
1035 were inserted. Comments are not included.
1036
1037 Notes
1038 -----
1039 As of Python 3.6 dicts retain their insertion order.
1040 """
1041 d = {}
1042 for name in self:
1043 d[name] = _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO)
1044 return d
1045
1046 # For PropertyList the two are equivalent
1047 toDict = toOrderedDict
1048
1049 def __eq__(self, other):
1050 # super() doesn't seem to work properly in @continueClass;
1051 # note that super with arguments seems to work at first, but actually
1052 # doesn't either.
1053 if not PropertySet.__eq__(self, other):
1054 return False
1055
1056 for name in self:
1057 if self.getComment(name) != other.getComment(name):
1058 return False
1059
1060 return True
1061
1062 def __copy__(self):
1063 # Copy without having to go through pickle state
1064 pl = PropertyList()
1065 for itemName in self:
1066 pl.copy(itemName, self, itemName)
1067 return pl
1068
1069 def __deepcopy__(self, memo):
1070 result = self.deepCopy()
1071 memo[id(self)] = result
1072 return result
1073
1074 def __iter__(self):
1075 for n in self.getOrderedNames():
1076 yield n
1077
1078 def __setitem__(self, name, value):
1079 """Assigns the supplied value to the container.
1080
1081 Parameters
1082 ----------
1083 name : `str`
1084 Name of item to update. If the name ends with
1085 `PropertyList.COMMENTSUFFIX`, the comment is updated rather
1086 than the value.
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.
1091
1092 Notes
1093 -----
1094 Uses `PropertySet.set`, overwriting any previous value.
1095 """
1096 if name.endswith(self.COMMENTSUFFIXCOMMENTSUFFIX):
1097 name = name[:-len(self.COMMENTSUFFIXCOMMENTSUFFIX)]
1098 self.setComment(name, value)
1099 return
1100 if isinstance(value, Mapping):
1101 # Create a property set instead
1102 ps = PropertySet()
1103 for k, v in value.items():
1104 ps[k] = v
1105 value = ps
1106 self.set(name, value)
1107
1108 def __reduce__(self):
1109 # It would be a bit simpler to use __setstate__ and __getstate__.
1110 # However, implementing __setstate__ in Python causes segfaults
1111 # because pickle creates a new instance by calling
1112 # object.__new__(PropertyList, *args) which bypasses
1113 # the pybind11 memory allocation step.
1114 return (_makePropertyList, (getPropertyListState(self),))
1115
1116 def get_dict(self, key: str) -> NestedMetadataDict:
1117 """Return a possibly-hierarchical nested `dict`.
1118
1119 This implements the `lsst.pipe.base.GetDictMetadata` protocol for
1120 consistency with `lsst.pipe.base.TaskMetadata` and `PropertySet`.
1121
1122 Parameters
1123 ----------
1124 key : `str`
1125 String key associated with the mapping. May not have a ``.``
1126 character.
1127
1128 Returns
1129 -------
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.
1134 """
1135 result: NestedMetadataDict = {}
1136 name: str
1137 for name in self.getOrderedNames():
1138 levels = name.split(".")
1139 if levels[0] == key:
1140 nested = result
1141 for level_key in levels[1:-1]:
1142 nested = result.setdefault(level_key, {})
1143 nested[levels[-1]] = self[name]
1144 return result
1145
1146 def set_dict(self, key: str, value: NestedMetadataDict) -> None:
1147 """Assign a possibly-hierarchical nested `dict`.
1148
1149 This implements the `lsst.pipe.base.SetDictMetadata` protocol for
1150 consistency with `lsst.pipe.base.TaskMetadata` and `PropertySet`.
1151
1152 Parameters
1153 ----------
1154 key : `str`
1155 String key associated with the mapping. May not have a ``.``
1156 character.
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.
1161 """
1162 self.set(key, PropertySet.from_mapping(value))
std::vector< SchemaItem< Flag > > * items
table::Key< int > id
Definition Detector.cc:162
daf::base::PropertySet * set
Definition fits.cc:931