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 _guessIntegerType(container, name, value): 

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

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

233 is assumed to be a scalar. 

234 

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

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

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

238 

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

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

241 

242 Parameters 

243 ---------- 

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

245 Container from which to get the value 

246 

247 name : `str` 

248 Name of item 

249 

250 value : `object` 

251 Value to be assigned a type 

252 

253 Returns 

254 ------- 

255 useType : `str` or none 

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

257 `bool` or a non-integral value. 

258 """ 

259 useType = None 

260 maxInt = 2147483647 

261 minInt = -2147483648 

262 maxLongLong = 2**63 - 1 

263 minLongLong = -2**63 

264 maxU64 = 2**64 - 1 

265 minU64 = 0 

266 

267 # We do not want to convert bool to int so let the system work that 

268 # out itself 

269 if isinstance(value, bool): 

270 return useType 

271 

272 if isinstance(value, numbers.Integral): 

273 try: 

274 containerType = _propertyContainerElementTypeName(container, name) 

275 except LookupError: 

276 # nothing in the container so choose based on size. 

277 if value <= maxInt and value >= minInt: 

278 useType = "Int" 

279 elif value <= maxLongLong and value >= minLongLong: 

280 useType = "LongLong" 

281 elif value <= maxU64 and value >= minU64: 

282 useType = "UnsignedLongLong" 

283 else: 

284 raise RuntimeError("Unable to guess integer type for storing value: %d" % (value,)) 

285 else: 

286 if containerType == "Int": 

287 # Always use an Int even if we know it won't fit. The later 

288 # code will trigger OverflowError if appropriate. Setting the 

289 # type to LongLong here will trigger a TypeError instead so 

290 # it's best to trigger a predictable OverflowError. 

291 useType = "Int" 

292 elif containerType == "LongLong": 

293 useType = "LongLong" 

294 elif containerType == "UnsignedLongLong": 

295 useType = "UnsignedLongLong" 

296 return useType 

297 

298 

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

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

301 """ 

302 if hasattr(value, "__iter__") and not isinstance(value, (str, PropertySet, PropertyList)): 

303 exemplar = value[0] 

304 else: 

305 exemplar = value 

306 

307 t = type(exemplar) 

308 setType = _guessIntegerType(container, name, exemplar) 

309 

310 if setType is not None or t in typeMenu: 

311 if setType is None: 

312 setType = typeMenu[t] 

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

314 # Allow for subclasses 

315 for checkType in typeMenu: 

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

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

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

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

320 

321 

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

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

324 """ 

325 if hasattr(value, "__iter__"): 

326 exemplar = value[0] 

327 else: 

328 exemplar = value 

329 

330 t = type(exemplar) 

331 addType = _guessIntegerType(container, name, exemplar) 

332 

333 if addType is not None or t in typeMenu: 

334 if addType is None: 

335 addType = typeMenu[t] 

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

337 # Allow for subclasses 

338 for checkType in typeMenu: 

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

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

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

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

343 

344 

345def _makePropertySet(state): 

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

347 

348 Parameters 

349 ---------- 

350 state : `list` 

351 The data returned by `getPropertySetState`. 

352 """ 

353 ps = PropertySet() 

354 setPropertySetState(ps, state) 

355 return ps 

356 

357 

358def _makePropertyList(state): 

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

360 `getPropertyListState` 

361 

362 Parameters 

363 ---------- 

364 state : `list` 

365 The data returned by `getPropertySetState`. 

366 """ 

367 pl = PropertyList() 

368 setPropertyListState(pl, state) 

369 return pl 

370 

371 

372@continueClass 

373class PropertySet: 

374 # Mapping of type to method names; 

375 # int types are omitted due to use of _guessIntegerType 

376 _typeMenu = {bool: "Bool", 

377 float: "Double", 

378 str: "String", 

379 DateTime: "DateTime", 

380 PropertySet: "PropertySet", 

381 PropertyList: "PropertySet", 

382 None: "Undef", 

383 } 

384 

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

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

387 

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

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

390 the final value in the array will be returned. 

391 

392 Parameters 

393 ---------- 

394 name : `str` 

395 Name of item 

396 default : `object`, optional 

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

398 

399 Returns 

400 ------- 

401 value : any type supported by container 

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

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

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

405 returned. 

406 """ 

407 try: 

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

409 except KeyError: 

410 return default 

411 

412 def getArray(self, name): 

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

414 

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

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

417 

418 Parameters 

419 ---------- 

420 name : `str` 

421 Name of item 

422 

423 Returns 

424 ------- 

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

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

427 

428 Raises 

429 ------ 

430 KeyError 

431 Raised if the item does not exist. 

432 """ 

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

434 

435 def getScalar(self, name): 

436 """Return an item as a scalar 

437 

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

439 

440 Parameters 

441 ---------- 

442 name : `str` 

443 Name of item 

444 

445 Returns 

446 ------- 

447 value : scalar item 

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

449 most recently added value is returned. 

450 

451 Raises 

452 ------ 

453 KeyError 

454 Raised if the item does not exist. 

455 """ 

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

457 

458 def set(self, name, value): 

459 """Set the value of an item 

460 

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

462 need not match. 

463 

464 Parameters 

465 ---------- 

466 name : `str` 

467 Name of item 

468 value : any supported type 

469 Value of item; may be a scalar or array 

470 """ 

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

472 

473 def add(self, name, value): 

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

475 

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

477 otherwise it is like calling `set` 

478 

479 Parameters 

480 ---------- 

481 name : `str` 

482 Name of item 

483 value : any supported type 

484 Value of item; may be a scalar or array 

485 

486 Notes 

487 ----- 

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

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

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

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

492 and vice-versa. 

493 

494 Raises 

495 ------ 

496 lsst::pex::exceptions::TypeError 

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

498 value of the item. 

499 """ 

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

501 

502 def update(self, addition): 

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

504 

505 Parameters 

506 ---------- 

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

508 The content to merge into the current container. 

509 

510 Notes 

511 ----- 

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

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

514 method updates by overwriting existing values completely with 

515 the new value. 

516 """ 

517 if isinstance(addition, PropertySet): 

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

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

520 for k in addition: 

521 self.copy(k, addition, k) 

522 else: 

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

524 self[k] = v 

525 

526 def toDict(self): 

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

528 

529 Returns 

530 ------- 

531 d : `dict` 

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

533 """ 

534 

535 d = {} 

536 for name in self.names(): 

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

538 

539 if isinstance(v, PropertySet): 

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

541 else: 

542 d[name] = v 

543 return d 

544 

545 def __eq__(self, other): 

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

547 return False 

548 

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

550 return False 

551 

552 for name in self: 

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

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

555 return False 

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

557 return False 

558 

559 return True 

560 

561 def __copy__(self): 

562 # Copy without having to go through pickle state 

563 ps = PropertySet() 

564 for itemName in self: 

565 ps.copy(itemName, self, itemName) 

566 return ps 

567 

568 def __deepcopy__(self, memo): 

569 result = self.deepCopy() 

570 memo[id(self)] = result 

571 return result 

572 

573 def __contains__(self, name): 

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

575 of the container. 

576 

577 Notes 

578 ------ 

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

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

581 items returned from ``__iter__``. 

582 """ 

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

584 

585 def __setitem__(self, name, value): 

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

587 

588 Parameters 

589 ---------- 

590 name : `str` 

591 Name of item to update. 

592 value : Value to assign 

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

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

595 `PropertySet` before assignment. 

596 

597 Notes 

598 ----- 

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

600 """ 

601 if isinstance(value, Mapping): 

602 # Create a property set instead 

603 ps = PropertySet() 

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

605 ps[k] = v 

606 value = ps 

607 self.set(name, value) 

608 

609 def __getitem__(self, name): 

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

611 

612 Notes 

613 ----- 

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

615 will be returned. 

616 """ 

617 return self.getScalar(name) 

618 

619 def __delitem__(self, name): 

620 if name in self: 

621 self.remove(name) 

622 else: 

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

624 

625 def __str__(self): 

626 return self.toString() 

627 

628 def __len__(self): 

629 return self.nameCount(topLevelOnly=True) 

630 

631 def __iter__(self): 

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

633 yield n 

634 

635 def keys(self): 

636 return KeysView(self) 

637 

638 def items(self): 

639 return ItemsView(self) 

640 

641 def values(self): 

642 return ValuesView(self) 

643 

644 def __reduce__(self): 

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

646 # However, implementing __setstate__ in Python causes segfaults 

647 # because pickle creates a new instance by calling 

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

649 # the pybind11 memory allocation step. 

650 return (_makePropertySet, (getPropertySetState(self),)) 

651 

652 

653@continueClass 

654class PropertyList: 

655 # Mapping of type to method names 

656 _typeMenu = {bool: "Bool", 

657 int: "Int", 

658 float: "Double", 

659 str: "String", 

660 DateTime: "DateTime", 

661 PropertySet: "PropertySet", 

662 PropertyList: "PropertySet", 

663 None: "Undef", 

664 } 

665 

666 COMMENTSUFFIX = "#COMMENT" 

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

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

669 

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

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

672 

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

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

675 the final value in the array will be returned. 

676 

677 Parameters 

678 ---------- 

679 name : ``str`` 

680 Name of item 

681 default : `object`, optional 

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

683 

684 Returns 

685 ------- 

686 value : any type supported by container 

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

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

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

690 returned. 

691 """ 

692 try: 

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

694 except KeyError: 

695 return default 

696 

697 def getArray(self, name): 

698 """Return an item as a list. 

699 

700 Parameters 

701 ---------- 

702 name : `str` 

703 Name of item 

704 

705 Returns 

706 ------- 

707 values : `list` of values 

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

709 

710 Raises 

711 ------ 

712 KeyError 

713 Raised if the item does not exist. 

714 """ 

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

716 

717 def getScalar(self, name): 

718 """Return an item as a scalar 

719 

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

721 

722 Parameters 

723 ---------- 

724 name : `str` 

725 Name of item. 

726 

727 Returns 

728 ------- 

729 value : scalar item 

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

731 most recently added value is returned. 

732 

733 Raises 

734 ------ 

735 KeyError 

736 Raised if the item does not exist. 

737 """ 

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

739 

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

741 """Set the value of an item 

742 

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

744 need not match. 

745 

746 Parameters 

747 ---------- 

748 name : `str` 

749 Name of item 

750 value : any supported type 

751 Value of item; may be a scalar or array 

752 """ 

753 args = [] 

754 if comment is not None: 

755 args.append(comment) 

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

757 

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

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

760 

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

762 otherwise it is like calling `set` 

763 

764 Parameters 

765 ---------- 

766 name : `str` 

767 Name of item 

768 value : any supported type 

769 Value of item; may be a scalar or array 

770 

771 Notes 

772 ----- 

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

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

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

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

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

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

779 

780 Raises 

781 ------ 

782 lsst::pex::exceptions::TypeError 

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

784 value of the item. 

785 """ 

786 args = [] 

787 if comment is not None: 

788 args.append(comment) 

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

790 

791 def setComment(self, name, comment): 

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

793 

794 Parameters 

795 ---------- 

796 name : `str` 

797 Name of the key to receive updated comment. 

798 comment : `comment` 

799 New comment string. 

800 """ 

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

802 # one that has the new comment 

803 containerType = _propertyContainerElementTypeName(self, name) 

804 if self.isArray(name): 

805 value = self.getArray(name) 

806 else: 

807 value = self.getScalar(name) 

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

809 

810 def toList(self): 

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

812 in the order that they were inserted. 

813 

814 Returns 

815 ------- 

816 ret : `list` of `tuple` 

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

818 in which they were inserted. 

819 """ 

820 orderedNames = self.getOrderedNames() 

821 ret = [] 

822 for name in orderedNames: 

823 if self.isArray(name): 

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

825 for v in values: 

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

827 else: 

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

829 self.getComment(name))) 

830 return ret 

831 

832 def toOrderedDict(self): 

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

834 they were inserted. 

835 

836 Returns 

837 ------- 

838 d : `dict` 

839 Ordered dictionary with all properties in the order that they 

840 were inserted. Comments are not included. 

841 

842 Notes 

843 ----- 

844 As of Python 3.6 dicts retain their insertion order. 

845 """ 

846 d = {} 

847 for name in self: 

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

849 return d 

850 

851 # For PropertyList the two are equivalent 

852 toDict = toOrderedDict 

853 

854 def __eq__(self, other): 

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

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

857 # doesn't either. 

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

859 return False 

860 

861 for name in self: 

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

863 return False 

864 

865 return True 

866 

867 def __copy__(self): 

868 # Copy without having to go through pickle state 

869 pl = PropertyList() 

870 for itemName in self: 

871 pl.copy(itemName, self, itemName) 

872 return pl 

873 

874 def __deepcopy__(self, memo): 

875 result = self.deepCopy() 

876 memo[id(self)] = result 

877 return result 

878 

879 def __iter__(self): 

880 for n in self.getOrderedNames(): 

881 yield n 

882 

883 def __setitem__(self, name, value): 

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

885 

886 Parameters 

887 ---------- 

888 name : `str` 

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

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

891 than the value. 

892 value : Value to assign 

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

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

895 `PropertySet` before assignment. 

896 

897 Notes 

898 ----- 

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

900 """ 

901 if name.endswith(self.COMMENTSUFFIX): 

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

903 self.setComment(name, value) 

904 return 

905 if isinstance(value, Mapping): 

906 # Create a property set instead 

907 ps = PropertySet() 

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

909 ps[k] = v 

910 value = ps 

911 self.set(name, value) 

912 

913 def __reduce__(self): 

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

915 # However, implementing __setstate__ in Python causes segfaults 

916 # because pickle creates a new instance by calling 

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

918 # the pybind11 memory allocation step. 

919 return (_makePropertyList, (getPropertyListState(self),))