Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# 

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 numbers 

29from collections.abc import Mapping, KeysView, ValuesView, ItemsView 

30 

31# Ensure that C++ exceptions are properly translated to Python 

32import lsst.pex.exceptions # noqa: F401 

33from lsst.utils import continueClass 

34 

35from .propertySet import PropertySet 

36from .propertyList import PropertyList 

37from ..dateTime import DateTime 

38 

39 

40def getPropertySetState(container, asLists=False): 

41 """Get the state of a PropertySet in a form that can be pickled. 

42 

43 Parameters 

44 ---------- 

45 container : `PropertySet` 

46 The property container. 

47 asLists : `bool`, optional 

48 If False, the default, `tuple` will be used for the contents. If true 

49 a `list` will be used. 

50 

51 Returns 

52 ------- 

53 state : `list` of `tuple` or `list` of `list` 

54 The state, as a list of tuples (or lists), each of which contains 

55 the following 3 items: 

56 

57 name (a `str`) 

58 the name of the item 

59 elementTypeName (a `str`) 

60 the suffix of a ``setX`` method name 

61 which is appropriate for the data type. For example integer 

62 data has ``elementTypeName="Int"` which corresponds to 

63 the ``setInt`` method. 

64 value 

65 the data for the item, in a form compatible 

66 with the set method named by ``elementTypeName`` 

67 """ 

68 names = container.names(topLevelOnly=True) 

69 sequence = list if asLists else tuple 

70 return [sequence((name, _propertyContainerElementTypeName(container, name), 

71 _propertyContainerGet(container, name, returnStyle=ReturnStyle.AUTO))) 

72 for name in names] 

73 

74 

75def getPropertyListState(container, asLists=False): 

76 """Get the state of a PropertyList in a form that can be pickled. 

77 

78 Parameters 

79 ---------- 

80 container : `PropertyList` 

81 The property container. 

82 asLists : `bool`, optional 

83 If False, the default, `tuple` will be used for the contents. If true 

84 a `list` will be used. 

85 

86 Returns 

87 ------- 

88 state : `list` of `tuple` or `list` of `list` 

89 The state, as a list of tuples (or lists), each of which contains 

90 the following 4 items: 

91 

92 name (a `str`): 

93 the name of the item 

94 elementTypeName (a `str`): 

95 the suffix of a ``setX`` method name 

96 which is appropriate for the data type. For example integer 

97 data has ``elementTypeName="Int"` which corresponds to 

98 the ``setInt`` method. 

99 value 

100 the data for the item, in a form compatible 

101 with the set method named by ``elementTypeName`` 

102 comment (a `str`): the comment. This item is only present 

103 if ``container`` is a PropertyList. 

104 """ 

105 sequence = list if asLists else tuple 

106 return [sequence((name, _propertyContainerElementTypeName(container, name), 

107 _propertyContainerGet(container, name, returnStyle=ReturnStyle.AUTO), 

108 container.getComment(name))) 

109 for name in container.getOrderedNames()] 

110 

111 

112def setPropertySetState(container, state): 

113 """Restore the state of a PropertySet, in place. 

114 

115 Parameters 

116 ---------- 

117 container : `PropertySet` 

118 The property container whose state is to be restored. 

119 It should be empty to start with and is updated in place. 

120 state : `list` 

121 The state, as returned by `getPropertySetState` 

122 """ 

123 for name, elemType, value in state: 

124 if elemType is not None: 

125 getattr(container, "set" + elemType)(name, value) 

126 else: 

127 raise ValueError(f"Unrecognized values for state restoration: ({name}, {elemType}, {value})") 

128 

129 

130def setPropertyListState(container, state): 

131 """Restore the state of a PropertyList, in place. 

132 

133 Parameters 

134 ---------- 

135 container : `PropertyList` 

136 The property container whose state is to be restored. 

137 It should be empty to start with and is updated in place. 

138 state : `list` 

139 The state, as returned by ``getPropertyListState`` 

140 """ 

141 for name, elemType, value, comment in state: 

142 getattr(container, "set" + elemType)(name, value, comment) 

143 

144 

145class ReturnStyle(enum.Enum): 

146 ARRAY = enum.auto() 

147 SCALAR = enum.auto() 

148 AUTO = enum.auto() 

149 

150 

151def _propertyContainerElementTypeName(container, name): 

152 """Return name of the type of a particular element 

153 

154 Parameters 

155 ---------- 

156 container : `lsst.daf.base.PropertySet` or `lsst.daf.base.PropertyList` 

157 Container including the element 

158 name : `str` 

159 Name of element 

160 """ 

161 try: 

162 t = container.typeOf(name) 

163 except LookupError as e: 

164 # KeyError is more commonly expected when asking for an element 

165 # from a mapping. 

166 raise KeyError(str(e)) 

167 for checkType in ("Bool", "Short", "Int", "Long", "LongLong", "UnsignedLongLong", 

168 "Float", "Double", "String", "DateTime", 

169 "PropertySet", "Undef"): 

170 if t == getattr(container, "TYPE_" + checkType): 

171 return checkType 

172 return None 

173 

174 

175def _propertyContainerGet(container, name, returnStyle): 

176 """Get a value of unknown type as a scalar or array 

177 

178 Parameters 

179 ---------- 

180 container : `lsst.daf.base.PropertySet` or `lsst.daf.base.PropertyList` 

181 Container from which to get the value 

182 name : `str` 

183 Name of item 

184 returnStyle : `ReturnStyle` 

185 Control whether numeric or string data is returned as an array 

186 or scalar (the other types, ``PropertyList``, ``PropertySet`` 

187 and ``PersistablePtr``, are always returned as a scalar): 

188 - ReturnStyle.ARRAY: return numeric or string data types 

189 as an array of values. 

190 - ReturnStyle.SCALAR: return numeric or string data types 

191 as a single value; if the item has multiple values then 

192 return the last value. 

193 - ReturnStyle.AUTO: (deprecated) return numeric or string data 

194 as a scalar if there is just one item, or as an array 

195 otherwise. 

196 

197 Raises 

198 ------ 

199 KeyError 

200 Raised if the specified key does not exist in the container. 

201 TypeError 

202 Raised if the value retrieved is of an unexpected type. 

203 ValueError 

204 Raised if the value for ``returnStyle`` is not correct. 

205 """ 

206 if not container.exists(name): 

207 raise KeyError(name + " not found") 

208 if returnStyle not in ReturnStyle: 

209 raise ValueError("returnStyle {} must be a ReturnStyle".format(returnStyle)) 

210 

211 elemType = _propertyContainerElementTypeName(container, name) 

212 if elemType and elemType != "PropertySet": 

213 value = getattr(container, "getArray" + elemType)(name) 

214 if returnStyle == ReturnStyle.ARRAY or (returnStyle == ReturnStyle.AUTO and len(value) > 1): 

215 return value 

216 return value[-1] 

217 

218 if container.isPropertySetPtr(name): 

219 try: 

220 return container.getAsPropertyListPtr(name) 

221 except Exception: 

222 return container.getAsPropertySetPtr(name) 

223 try: 

224 return container.getAsPersistablePtr(name) 

225 except Exception: 

226 pass 

227 raise TypeError('Unknown PropertySet value type for ' + name) 

228 

229 

230def _iterable(a): 

231 """Make input iterable. 

232 

233 Takes whatever is given to it and yields it back one element at a time. 

234 If it is not an iterable or it is a string or PropertySet/List, 

235 yields itself. 

236 """ 

237 if isinstance(a, (str, PropertyList, PropertySet)): 

238 yield a 

239 return 

240 try: 

241 yield from a 

242 except Exception: 

243 yield a 

244 

245 

246def _guessIntegerType(container, name, value): 

247 """Given an existing container and name, determine the type 

248 that should be used for the supplied value. The supplied value 

249 is assumed to be a scalar. 

250 

251 On Python 3 all ints are LongLong but we need to be able to store them 

252 in Int containers if that is what is being used (testing for truncation). 

253 Int is assumed to mean 32bit integer (2147483647 to -2147483648). 

254 

255 If there is no pre-existing value we have to decide what to do. For now 

256 we pick Int if the value is less than maxsize. 

257 

258 Parameters 

259 ---------- 

260 container : `lsst.daf.base.PropertySet` or `lsst.daf.base.PropertyList` 

261 Container from which to get the value 

262 

263 name : `str` 

264 Name of item 

265 

266 value : `object` 

267 Value to be assigned a type. Can be an iterable. 

268 

269 Returns 

270 ------- 

271 useType : `str` or none 

272 Type to use for the supplied value. `None` if the input is 

273 `bool` or a non-integral value. 

274 """ 

275 maxInt = 2147483647 

276 minInt = -2147483648 

277 maxLongLong = 2**63 - 1 

278 minLongLong = -2**63 

279 maxU64 = 2**64 - 1 

280 minU64 = 0 

281 

282 # Go through the values to find the range of supplied integers, 

283 # stopping early if we don't have an integer. 

284 min = None 

285 max = None 

286 for v in _iterable(value): 

287 # Do not try to convert a bool to an integer 

288 if not isinstance(v, numbers.Integral) or isinstance(v, bool): 

289 return None 

290 

291 if min is None: 

292 min = v 

293 max = v 

294 elif v < min: 

295 min = v 

296 elif v > max: 

297 max = v 

298 

299 # Safety net 

300 if min is None or max is None: 

301 raise RuntimeError(f"Internal logic failure calculating integer range of {value}") 

302 

303 def _choose_int_from_range(int_value, current_type): 

304 # If this is changing type from non-integer the current type 

305 # does not matter. 

306 if current_type not in {"Int", "LongLong", "UnsignedLongLong"}: 

307 current_type = None 

308 

309 if int_value <= maxInt and int_value >= minInt and current_type in (None, "Int"): 

310 # Use Int only if in range and either no current type or the 

311 # current type is an Int. 

312 use_type = "Int" 

313 elif int_value >= minLongLong and int_value < 0: 

314 # All large negatives must be LongLong if they did not fit 

315 # in Int clause above. 

316 use_type = "LongLong" 

317 elif int_value >= 0 and int_value <= maxLongLong and current_type in (None, "Int", "LongLong"): 

318 # Larger than Int or already a LongLong 

319 use_type = "LongLong" 

320 elif int_value <= maxU64 and int_value >= minU64: 

321 use_type = "UnsignedLongLong" 

322 else: 

323 raise RuntimeError("Unable to guess integer type for storing out of " 

324 f"range value: {int_value}") 

325 return use_type 

326 

327 try: 

328 containerType = _propertyContainerElementTypeName(container, name) 

329 except LookupError: 

330 containerType = None 

331 

332 useTypeMin = _choose_int_from_range(min, containerType) 

333 useTypeMax = _choose_int_from_range(max, containerType) 

334 

335 if useTypeMin == useTypeMax: 

336 return useTypeMin 

337 

338 # When different the combinations are: 

339 # Int + LongLong 

340 # Int + UnsignedLongLong 

341 # LongLong + UnsignedLongLong 

342 

343 choices = {useTypeMin, useTypeMax} 

344 if choices == {"Int", "LongLong"}: 

345 return "LongLong" 

346 

347 # If UnsignedLongLong is required things will break if the min 

348 # is negative. They will break whatever we choose if that is the case 

349 # but we have no choice but to return the UnsignedLongLong regardless. 

350 if "UnsignedLongLong" in choices: 

351 return "UnsignedLongLong" 

352 

353 raise RuntimeError(f"Logic error in guessing integer type from {min} and {max}") 

354 

355 

356def _propertyContainerSet(container, name, value, typeMenu, *args): 

357 """Set a single Python value of unknown type 

358 """ 

359 try: 

360 exemplar = next(_iterable(value)) 

361 except StopIteration: 

362 # Do nothing if nothing provided. This matches the behavior 

363 # of the explicit setX() methods. 

364 return 

365 t = type(exemplar) 

366 setType = _guessIntegerType(container, name, value) 

367 

368 if setType is not None or t in typeMenu: 

369 if setType is None: 

370 setType = typeMenu[t] 

371 return getattr(container, "set" + setType)(name, value, *args) 

372 # Allow for subclasses 

373 for checkType in typeMenu: 

374 if (checkType is None and exemplar is None) or \ 

375 (checkType is not None and isinstance(exemplar, checkType)): 

376 return getattr(container, "set" + typeMenu[checkType])(name, value, *args) 

377 raise TypeError("Unknown value type for key '%s': %s" % (name, t)) 

378 

379 

380def _propertyContainerAdd(container, name, value, typeMenu, *args): 

381 """Add a single Python value of unknown type 

382 """ 

383 try: 

384 exemplar = next(_iterable(value)) 

385 except StopIteration: 

386 # Adding an empty iterable to an existing entry is a no-op 

387 # since there is nothing to add. 

388 return 

389 t = type(exemplar) 

390 addType = _guessIntegerType(container, name, exemplar) 

391 

392 if addType is not None or t in typeMenu: 

393 if addType is None: 

394 addType = typeMenu[t] 

395 return getattr(container, "add" + addType)(name, value, *args) 

396 # Allow for subclasses 

397 for checkType in typeMenu: 

398 if (checkType is None and exemplar is None) or \ 

399 (checkType is not None and isinstance(exemplar, checkType)): 

400 return getattr(container, "add" + typeMenu[checkType])(name, value, *args) 

401 raise TypeError("Unknown value type for key '%s': %s" % (name, t)) 

402 

403 

404def _makePropertySet(state): 

405 """Make a `PropertySet` from the state returned by `getPropertySetState` 

406 

407 Parameters 

408 ---------- 

409 state : `list` 

410 The data returned by `getPropertySetState`. 

411 """ 

412 ps = PropertySet() 

413 setPropertySetState(ps, state) 

414 return ps 

415 

416 

417def _makePropertyList(state): 

418 """Make a `PropertyList` from the state returned by 

419 `getPropertyListState` 

420 

421 Parameters 

422 ---------- 

423 state : `list` 

424 The data returned by `getPropertySetState`. 

425 """ 

426 pl = PropertyList() 

427 setPropertyListState(pl, state) 

428 return pl 

429 

430 

431@continueClass 

432class PropertySet: 

433 # Mapping of type to method names; 

434 # int types are omitted due to use of _guessIntegerType 

435 _typeMenu = {bool: "Bool", 

436 float: "Double", 

437 str: "String", 

438 DateTime: "DateTime", 

439 PropertySet: "PropertySet", 

440 PropertyList: "PropertySet", 

441 None: "Undef", 

442 } 

443 

444 def get(self, name, default=None): 

445 """Return an item as a scalar, else default. 

446 

447 Identical to `getScalar` except that a default value is returned 

448 if the requested key is not present. If an array item is requested 

449 the final value in the array will be returned. 

450 

451 Parameters 

452 ---------- 

453 name : `str` 

454 Name of item 

455 default : `object`, optional 

456 Default value to use if the named item is not present. 

457 

458 Returns 

459 ------- 

460 value : any type supported by container 

461 Single value of any type supported by the container, else the 

462 default value if the requested item is not present in the 

463 container. For array items the most recently added value is 

464 returned. 

465 """ 

466 try: 

467 return _propertyContainerGet(self, name, returnStyle=ReturnStyle.SCALAR) 

468 except KeyError: 

469 return default 

470 

471 def getArray(self, name): 

472 """Return an item as an array if the item is numeric or string 

473 

474 If the item is a `PropertySet`, `PropertyList` or 

475 `lsst.daf.base.PersistablePtr` then return the item as a scalar. 

476 

477 Parameters 

478 ---------- 

479 name : `str` 

480 Name of item 

481 

482 Returns 

483 ------- 

484 values : `list` of any type supported by container 

485 The contents of the item, guaranteed to be returned as a `list.` 

486 

487 Raises 

488 ------ 

489 KeyError 

490 Raised if the item does not exist. 

491 """ 

492 return _propertyContainerGet(self, name, returnStyle=ReturnStyle.ARRAY) 

493 

494 def getScalar(self, name): 

495 """Return an item as a scalar 

496 

497 If the item has more than one value then the last value is returned. 

498 

499 Parameters 

500 ---------- 

501 name : `str` 

502 Name of item 

503 

504 Returns 

505 ------- 

506 value : scalar item 

507 Value stored in the item. If the item refers to an array the 

508 most recently added value is returned. 

509 

510 Raises 

511 ------ 

512 KeyError 

513 Raised if the item does not exist. 

514 """ 

515 return _propertyContainerGet(self, name, returnStyle=ReturnStyle.SCALAR) 

516 

517 def set(self, name, value): 

518 """Set the value of an item 

519 

520 If the item already exists it is silently replaced; the types 

521 need not match. 

522 

523 Parameters 

524 ---------- 

525 name : `str` 

526 Name of item 

527 value : any supported type 

528 Value of item; may be a scalar or array 

529 """ 

530 return _propertyContainerSet(self, name, value, self._typeMenu) 

531 

532 def add(self, name, value): 

533 """Append one or more values to a given item, which need not exist 

534 

535 If the item exists then the new value(s) are appended; 

536 otherwise it is like calling `set` 

537 

538 Parameters 

539 ---------- 

540 name : `str` 

541 Name of item 

542 value : any supported type 

543 Value of item; may be a scalar or array 

544 

545 Notes 

546 ----- 

547 If ``value`` is an `lsst.daf.base.PropertySet` or 

548 `lsst.daf.base.PropertyList` then ``value`` replaces 

549 the existing value. Also the item is added as a live 

550 reference, so updating ``value`` will update this container 

551 and vice-versa. 

552 

553 Raises 

554 ------ 

555 lsst::pex::exceptions::TypeError 

556 Raised if the type of `value` is incompatible with the existing 

557 value of the item. 

558 """ 

559 return _propertyContainerAdd(self, name, value, self._typeMenu) 

560 

561 def update(self, addition): 

562 """Update the current container with the supplied additions. 

563 

564 Parameters 

565 ---------- 

566 addition : `collections.abc.Mapping` or `PropertySet` 

567 The content to merge into the current container. 

568 

569 Notes 

570 ----- 

571 This is not the same as calling `PropertySet.combine` since the 

572 behavior differs when both mappings contain the same key. This 

573 method updates by overwriting existing values completely with 

574 the new value. 

575 """ 

576 if isinstance(addition, PropertySet): 

577 # To support array values we can not use the dict interface 

578 # and instead use the copy() method which overwrites 

579 for k in addition: 

580 self.copy(k, addition, k) 

581 else: 

582 for k, v in addition.items(): 

583 self[k] = v 

584 

585 def toDict(self): 

586 """Returns a (possibly nested) dictionary with all properties. 

587 

588 Returns 

589 ------- 

590 d : `dict` 

591 Dictionary with all names and values (no comments). 

592 """ 

593 

594 d = {} 

595 for name in self.names(): 

596 v = _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO) 

597 

598 if isinstance(v, PropertySet): 

599 d[name] = PropertySet.toDict(v) 

600 else: 

601 d[name] = v 

602 return d 

603 

604 def __eq__(self, other): 

605 if type(self) != type(other): 

606 return NotImplemented 

607 

608 if len(self) != len(other): 

609 return False 

610 

611 for name in self: 

612 if _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO) != \ 

613 _propertyContainerGet(other, name, returnStyle=ReturnStyle.AUTO): 

614 return False 

615 if self.typeOf(name) != other.typeOf(name): 

616 return False 

617 

618 return True 

619 

620 def __copy__(self): 

621 # Copy without having to go through pickle state 

622 ps = PropertySet() 

623 for itemName in self: 

624 ps.copy(itemName, self, itemName) 

625 return ps 

626 

627 def __deepcopy__(self, memo): 

628 result = self.deepCopy() 

629 memo[id(self)] = result 

630 return result 

631 

632 def __contains__(self, name): 

633 """Determines if the name is found at the top level hierarchy 

634 of the container. 

635 

636 Notes 

637 ------ 

638 Does not use `PropertySet.exists()`` because that includes support 

639 for "."-delimited names. This method is consistent with the 

640 items returned from ``__iter__``. 

641 """ 

642 return name in self.names(topLevelOnly=True) 

643 

644 def __setitem__(self, name, value): 

645 """Assigns the supplied value to the container. 

646 

647 Parameters 

648 ---------- 

649 name : `str` 

650 Name of item to update. 

651 value : Value to assign 

652 Can be any value supported by the container's ``set()`` 

653 method. `~collections.abc.Mapping` are converted to 

654 `PropertySet` before assignment. 

655 

656 Notes 

657 ----- 

658 Uses `PropertySet.set`, overwriting any previous value. 

659 """ 

660 if isinstance(value, Mapping): 

661 # Create a property set instead 

662 ps = PropertySet() 

663 for k, v in value.items(): 

664 ps[k] = v 

665 value = ps 

666 self.set(name, value) 

667 

668 def __getitem__(self, name): 

669 """Returns a scalar item from the container. 

670 

671 Notes 

672 ----- 

673 Uses `PropertySet.getScalar` to guarantee that a single value 

674 will be returned. 

675 """ 

676 return self.getScalar(name) 

677 

678 def __delitem__(self, name): 

679 if name in self: 

680 self.remove(name) 

681 else: 

682 raise KeyError(f"{name} not present in dict") 

683 

684 def __str__(self): 

685 return self.toString() 

686 

687 def __len__(self): 

688 return self.nameCount(topLevelOnly=True) 

689 

690 def __iter__(self): 

691 for n in self.names(topLevelOnly=True): 

692 yield n 

693 

694 def keys(self): 

695 return KeysView(self) 

696 

697 def items(self): 

698 return ItemsView(self) 

699 

700 def values(self): 

701 return ValuesView(self) 

702 

703 def __reduce__(self): 

704 # It would be a bit simpler to use __setstate__ and __getstate__. 

705 # However, implementing __setstate__ in Python causes segfaults 

706 # because pickle creates a new instance by calling 

707 # object.__new__(PropertyList, *args) which bypasses 

708 # the pybind11 memory allocation step. 

709 return (_makePropertySet, (getPropertySetState(self),)) 

710 

711 

712@continueClass 

713class PropertyList: 

714 # Mapping of type to method names 

715 _typeMenu = {bool: "Bool", 

716 int: "Int", 

717 float: "Double", 

718 str: "String", 

719 DateTime: "DateTime", 

720 PropertySet: "PropertySet", 

721 PropertyList: "PropertySet", 

722 None: "Undef", 

723 } 

724 

725 COMMENTSUFFIX = "#COMMENT" 

726 """Special suffix used to indicate that a named item being assigned 

727 using dict syntax is referring to a comment, not value.""" 

728 

729 def get(self, name, default=None): 

730 """Return an item as a scalar, else default. 

731 

732 Identical to `getScalar` except that a default value is returned 

733 if the requested key is not present. If an array item is requested 

734 the final value in the array will be returned. 

735 

736 Parameters 

737 ---------- 

738 name : ``str`` 

739 Name of item 

740 default : `object`, optional 

741 Default value to use if the named item is not present. 

742 

743 Returns 

744 ------- 

745 value : any type supported by container 

746 Single value of any type supported by the container, else the 

747 default value if the requested item is not present in the 

748 container. For array items the most recently added value is 

749 returned. 

750 """ 

751 try: 

752 return _propertyContainerGet(self, name, returnStyle=ReturnStyle.SCALAR) 

753 except KeyError: 

754 return default 

755 

756 def getArray(self, name): 

757 """Return an item as a list. 

758 

759 Parameters 

760 ---------- 

761 name : `str` 

762 Name of item 

763 

764 Returns 

765 ------- 

766 values : `list` of values 

767 The contents of the item, guaranteed to be returned as a `list.` 

768 

769 Raises 

770 ------ 

771 KeyError 

772 Raised if the item does not exist. 

773 """ 

774 return _propertyContainerGet(self, name, returnStyle=ReturnStyle.ARRAY) 

775 

776 def getScalar(self, name): 

777 """Return an item as a scalar 

778 

779 If the item has more than one value then the last value is returned. 

780 

781 Parameters 

782 ---------- 

783 name : `str` 

784 Name of item. 

785 

786 Returns 

787 ------- 

788 value : scalar item 

789 Value stored in the item. If the item refers to an array the 

790 most recently added value is returned. 

791 

792 Raises 

793 ------ 

794 KeyError 

795 Raised if the item does not exist. 

796 """ 

797 return _propertyContainerGet(self, name, returnStyle=ReturnStyle.SCALAR) 

798 

799 def set(self, name, value, comment=None): 

800 """Set the value of an item 

801 

802 If the item already exists it is silently replaced; the types 

803 need not match. 

804 

805 Parameters 

806 ---------- 

807 name : `str` 

808 Name of item 

809 value : any supported type 

810 Value of item; may be a scalar or array 

811 """ 

812 args = [] 

813 if comment is not None: 

814 args.append(comment) 

815 return _propertyContainerSet(self, name, value, self._typeMenu, *args) 

816 

817 def add(self, name, value, comment=None): 

818 """Append one or more values to a given item, which need not exist 

819 

820 If the item exists then the new value(s) are appended; 

821 otherwise it is like calling `set` 

822 

823 Parameters 

824 ---------- 

825 name : `str` 

826 Name of item 

827 value : any supported type 

828 Value of item; may be a scalar or array 

829 

830 Notes 

831 ----- 

832 If `value` is an `lsst.daf.base.PropertySet` items are added 

833 using dotted names (e.g. if name="a" and value contains 

834 an item "b" which is another PropertySet and contains an 

835 item "c" which is numeric or string, then the value of "c" 

836 is added as "a.b.c", appended to the existing values of 

837 "a.b.c" if any (in which case the types must be compatible). 

838 

839 Raises 

840 ------ 

841 lsst::pex::exceptions::TypeError 

842 Raise if the type of ``value`` is incompatible with the existing 

843 value of the item. 

844 """ 

845 args = [] 

846 if comment is not None: 

847 args.append(comment) 

848 return _propertyContainerAdd(self, name, value, self._typeMenu, *args) 

849 

850 def setComment(self, name, comment): 

851 """Set the comment for an existing entry. 

852 

853 Parameters 

854 ---------- 

855 name : `str` 

856 Name of the key to receive updated comment. 

857 comment : `comment` 

858 New comment string. 

859 """ 

860 # The only way to do this is to replace the existing entry with 

861 # one that has the new comment 

862 containerType = _propertyContainerElementTypeName(self, name) 

863 if self.isArray(name): 

864 value = self.getArray(name) 

865 else: 

866 value = self.getScalar(name) 

867 getattr(self, f"set{containerType}")(name, value, comment) 

868 

869 def toList(self): 

870 """Return a list of tuples of name, value, comment for each property 

871 in the order that they were inserted. 

872 

873 Returns 

874 ------- 

875 ret : `list` of `tuple` 

876 Tuples of name, value, comment for each property in the order 

877 in which they were inserted. 

878 """ 

879 orderedNames = self.getOrderedNames() 

880 ret = [] 

881 for name in orderedNames: 

882 if self.isArray(name): 

883 values = _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO) 

884 for v in values: 

885 ret.append((name, v, self.getComment(name))) 

886 else: 

887 ret.append((name, _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO), 

888 self.getComment(name))) 

889 return ret 

890 

891 def toOrderedDict(self): 

892 """Return an ordered dictionary with all properties in the order that 

893 they were inserted. 

894 

895 Returns 

896 ------- 

897 d : `dict` 

898 Ordered dictionary with all properties in the order that they 

899 were inserted. Comments are not included. 

900 

901 Notes 

902 ----- 

903 As of Python 3.6 dicts retain their insertion order. 

904 """ 

905 d = {} 

906 for name in self: 

907 d[name] = _propertyContainerGet(self, name, returnStyle=ReturnStyle.AUTO) 

908 return d 

909 

910 # For PropertyList the two are equivalent 

911 toDict = toOrderedDict 

912 

913 def __eq__(self, other): 

914 # super() doesn't seem to work properly in @continueClass; 

915 # note that super with arguments seems to work at first, but actually 

916 # doesn't either. 

917 if not PropertySet.__eq__(self, other): 

918 return False 

919 

920 for name in self: 

921 if self.getComment(name) != other.getComment(name): 

922 return False 

923 

924 return True 

925 

926 def __copy__(self): 

927 # Copy without having to go through pickle state 

928 pl = PropertyList() 

929 for itemName in self: 

930 pl.copy(itemName, self, itemName) 

931 return pl 

932 

933 def __deepcopy__(self, memo): 

934 result = self.deepCopy() 

935 memo[id(self)] = result 

936 return result 

937 

938 def __iter__(self): 

939 for n in self.getOrderedNames(): 

940 yield n 

941 

942 def __setitem__(self, name, value): 

943 """Assigns the supplied value to the container. 

944 

945 Parameters 

946 ---------- 

947 name : `str` 

948 Name of item to update. If the name ends with 

949 `PropertyList.COMMENTSUFFIX`, the comment is updated rather 

950 than the value. 

951 value : Value to assign 

952 Can be any value supported by the container's ``set()`` 

953 method. `~collections.abc.Mapping` are converted to 

954 `PropertySet` before assignment. 

955 

956 Notes 

957 ----- 

958 Uses `PropertySet.set`, overwriting any previous value. 

959 """ 

960 if name.endswith(self.COMMENTSUFFIX): 

961 name = name[:-len(self.COMMENTSUFFIX)] 

962 self.setComment(name, value) 

963 return 

964 if isinstance(value, Mapping): 

965 # Create a property set instead 

966 ps = PropertySet() 

967 for k, v in value.items(): 

968 ps[k] = v 

969 value = ps 

970 self.set(name, value) 

971 

972 def __reduce__(self): 

973 # It would be a bit simpler to use __setstate__ and __getstate__. 

974 # However, implementing __setstate__ in Python causes segfaults 

975 # because pickle creates a new instance by calling 

976 # object.__new__(PropertyList, *args) which bypasses 

977 # the pybind11 memory allocation step. 

978 return (_makePropertyList, (getPropertyListState(self),))