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 - name (a `str`): the name of the item 

57 - elementTypeName (a `str`): the suffix of a ``setX`` method name 

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

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

60 the ``setInt`` method. 

61 - value: the data for the item, in a form compatible 

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

63 """ 

64 names = container.names(topLevelOnly=True) 

65 sequence = list if asLists else tuple 

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

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

68 for name in names] 

69 

70 

71def getPropertyListState(container, asLists=False): 

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

73 

74 Parameters 

75 ---------- 

76 container : `PropertyList` 

77 The property container. 

78 asLists : `bool`, optional 

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

80 a `list` will be used. 

81 

82 Returns 

83 ------- 

84 state : `list` of `tuple` or `list` of `list` 

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

86 the following 4 items: 

87 - name (a `str`): the name of the item 

88 - elementTypeName (a `str`): the suffix of a ``setX`` method name 

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

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

91 the ``setInt`` method. 

92 - value: the data for the item, in a form compatible 

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

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

95 if ``container`` is a PropertyList. 

96 """ 

97 sequence = list if asLists else tuple 

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

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

100 container.getComment(name))) 

101 for name in container.getOrderedNames()] 

102 

103 

104def setPropertySetState(container, state): 

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

106 

107 Parameters 

108 ---------- 

109 container : `PropertySet` 

110 The property container whose state is to be restored. 

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

112 state : `list` 

113 The state, as returned by `getPropertySetState` 

114 """ 

115 for name, elemType, value in state: 

116 if elemType is not None: 

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

118 else: 

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

120 

121 

122def setPropertyListState(container, state): 

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

124 

125 Parameters 

126 ---------- 

127 container : `PropertyList` 

128 The property container whose state is to be restored. 

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

130 state : `list` 

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

132 """ 

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

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

135 

136 

137class ReturnStyle(enum.Enum): 

138 ARRAY = enum.auto() 

139 SCALAR = enum.auto() 

140 AUTO = enum.auto() 

141 

142 

143def _propertyContainerElementTypeName(container, name): 

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

145 try: 

146 t = container.typeOf(name) 

147 except LookupError as e: 

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

149 # from a mapping. 

150 raise KeyError(str(e)) 

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

152 "Float", "Double", "String", "DateTime", 

153 "PropertySet", "Undef"): 

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

155 return checkType 

156 return None 

157 

158 

159def _propertyContainerGet(container, name, returnStyle): 

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

161 

162 Parameters 

163 ---------- 

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

165 Container from which to get the value 

166 name : `str` 

167 Name of item 

168 returnStyle : `ReturnStyle` 

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

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

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

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

173 as an array of values. 

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

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

176 return the last value. 

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

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

179 otherwise. 

180 

181 Raises 

182 ------ 

183 KeyError 

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

185 TypeError 

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

187 ValueError 

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

189 """ 

190 if not container.exists(name): 

191 raise KeyError(name + " not found") 

192 if returnStyle not in ReturnStyle: 

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

194 

195 elemType = _propertyContainerElementTypeName(container, name) 

196 if elemType and elemType != "PropertySet": 

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

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

199 return value 

200 return value[-1] 

201 

202 if container.isPropertySetPtr(name): 

203 try: 

204 return container.getAsPropertyListPtr(name) 

205 except Exception: 

206 return container.getAsPropertySetPtr(name) 

207 try: 

208 return container.getAsPersistablePtr(name) 

209 except Exception: 

210 pass 

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

212 

213 

214def _guessIntegerType(container, name, value): 

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

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

217 is assumed to be a scalar. 

218 

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

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

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

222 

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

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

225 

226 Returns None if the value supplied is a bool or not an integral value. 

227 """ 

228 useType = None 

229 maxInt = 2147483647 

230 minInt = -2147483648 

231 maxLongLong = 2**63 - 1 

232 minLongLong = -2**63 

233 maxU64 = 2**64 - 1 

234 minU64 = 0 

235 

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

237 # out itself 

238 if isinstance(value, bool): 

239 return useType 

240 

241 if isinstance(value, numbers.Integral): 

242 try: 

243 containerType = _propertyContainerElementTypeName(container, name) 

244 except LookupError: 

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

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

247 useType = "Int" 

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

249 useType = "LongLong" 

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

251 useType = "UnsignedLongLong" 

252 else: 

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

254 else: 

255 if containerType == "Int": 

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

257 # code will trigger OverflowError if appropriate. Setting the 

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

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

260 useType = "Int" 

261 elif containerType == "LongLong": 

262 useType = "LongLong" 

263 elif containerType == "UnsignedLongLong": 

264 useType = "UnsignedLongLong" 

265 return useType 

266 

267 

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

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

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

271 exemplar = value[0] 

272 else: 

273 exemplar = value 

274 

275 t = type(exemplar) 

276 setType = _guessIntegerType(container, name, exemplar) 

277 

278 if setType is not None or t in typeMenu: 

279 if setType is None: 

280 setType = typeMenu[t] 

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

282 # Allow for subclasses 

283 for checkType in typeMenu: 

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

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

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

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

288 

289 

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

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

292 if hasattr(value, "__iter__"): 

293 exemplar = value[0] 

294 else: 

295 exemplar = value 

296 

297 t = type(exemplar) 

298 addType = _guessIntegerType(container, name, exemplar) 

299 

300 if addType is not None or t in typeMenu: 

301 if addType is None: 

302 addType = typeMenu[t] 

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

304 # Allow for subclasses 

305 for checkType in typeMenu: 

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

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

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

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

310 

311 

312def _makePropertySet(state): 

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

314 

315 Parameters 

316 ---------- 

317 state : `list` 

318 The data returned by `getPropertySetState`. 

319 """ 

320 ps = PropertySet() 

321 setPropertySetState(ps, state) 

322 return ps 

323 

324 

325def _makePropertyList(state): 

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

327 `getPropertyListState` 

328 

329 Parameters 

330 ---------- 

331 state : `list` 

332 The data returned by `getPropertySetState`. 

333 """ 

334 pl = PropertyList() 

335 setPropertyListState(pl, state) 

336 return pl 

337 

338 

339@continueClass 

340class PropertySet: 

341 # Mapping of type to method names; 

342 # int types are omitted due to use of _guessIntegerType 

343 _typeMenu = {bool: "Bool", 

344 float: "Double", 

345 str: "String", 

346 DateTime: "DateTime", 

347 PropertySet: "PropertySet", 

348 PropertyList: "PropertySet", 

349 None: "Undef", 

350 } 

351 

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

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

354 

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

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

357 the final value in the array will be returned. 

358 

359 Parameters 

360 ---------- 

361 name : ``str`` 

362 Name of item 

363 default : `object`, optional 

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

365 

366 Returns 

367 ------- 

368 value : any type supported by container 

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

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

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

372 returned. 

373 """ 

374 try: 

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

376 except KeyError: 

377 return default 

378 

379 def getArray(self, name): 

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

381 

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

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

384 

385 Parameters 

386 ---------- 

387 name : `str` 

388 Name of item 

389 

390 Returns 

391 ------- 

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

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

394 

395 Raises 

396 ------ 

397 KeyError 

398 Raised if the item does not exist. 

399 """ 

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

401 

402 def getScalar(self, name): 

403 """Return an item as a scalar 

404 

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

406 

407 Parameters 

408 ---------- 

409 name : `str` 

410 Name of item 

411 

412 Returns 

413 ------- 

414 value : scalar item 

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

416 most recently added value is returned. 

417 

418 Raises 

419 ------ 

420 KeyError 

421 Raised if the item does not exist. 

422 """ 

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

424 

425 def set(self, name, value): 

426 """Set the value of an item 

427 

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

429 need not match. 

430 

431 Parameters 

432 ---------- 

433 name : `str` 

434 Name of item 

435 value : any supported type 

436 Value of item; may be a scalar or array 

437 """ 

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

439 

440 def add(self, name, value): 

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

442 

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

444 otherwise it is like calling `set` 

445 

446 Parameters 

447 ---------- 

448 name : `str` 

449 Name of item 

450 value : any supported type 

451 Value of item; may be a scalar or array 

452 

453 Notes 

454 ----- 

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

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

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

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

459 and vice-versa. 

460 

461 Raises 

462 ------ 

463 lsst::pex::exceptions::TypeError 

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

465 value of the item. 

466 """ 

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

468 

469 def update(self, addition): 

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

471 

472 Parameters 

473 ---------- 

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

475 The content to merge into the current container. 

476 

477 Notes 

478 ----- 

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

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

481 method updates by overwriting existing values completely with 

482 the new value. 

483 """ 

484 if isinstance(addition, PropertySet): 

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

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

487 for k in addition: 

488 self.copy(k, addition, k) 

489 else: 

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

491 self[k] = v 

492 

493 def toDict(self): 

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

495 

496 Returns 

497 ------- 

498 d : `dict` 

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

500 """ 

501 

502 d = {} 

503 for name in self.names(): 

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

505 

506 if isinstance(v, PropertySet): 

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

508 else: 

509 d[name] = v 

510 return d 

511 

512 def __eq__(self, other): 

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

514 return False 

515 

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

517 return False 

518 

519 for name in self: 

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

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

522 return False 

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

524 return False 

525 

526 return True 

527 

528 def __copy__(self): 

529 # Copy without having to go through pickle state 

530 ps = PropertySet() 

531 for itemName in self: 

532 ps.copy(itemName, self, itemName) 

533 return ps 

534 

535 def __deepcopy__(self, memo): 

536 result = self.deepCopy() 

537 memo[id(self)] = result 

538 return result 

539 

540 def __contains__(self, name): 

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

542 of the container. 

543 

544 Notes 

545 ------ 

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

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

548 items returned from ``__iter__``. 

549 """ 

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

551 

552 def __setitem__(self, name, value): 

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

554 

555 Parameters 

556 ---------- 

557 name : `str` 

558 Name of item to update. 

559 value : Value to assign 

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

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

562 `PropertySet` before assignment. 

563 

564 Notes 

565 ----- 

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

567 """ 

568 if isinstance(value, Mapping): 

569 # Create a property set instead 

570 ps = PropertySet() 

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

572 ps[k] = v 

573 value = ps 

574 self.set(name, value) 

575 

576 def __getitem__(self, name): 

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

578 

579 Notes 

580 ----- 

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

582 will be returned. 

583 """ 

584 return self.getScalar(name) 

585 

586 def __delitem__(self, name): 

587 if name in self: 

588 self.remove(name) 

589 else: 

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

591 

592 def __str__(self): 

593 return self.toString() 

594 

595 def __len__(self): 

596 return self.nameCount(topLevelOnly=True) 

597 

598 def __iter__(self): 

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

600 yield n 

601 

602 def keys(self): 

603 return KeysView(self) 

604 

605 def items(self): 

606 return ItemsView(self) 

607 

608 def values(self): 

609 return ValuesView(self) 

610 

611 def __reduce__(self): 

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

613 # However, implementing __setstate__ in Python causes segfaults 

614 # because pickle creates a new instance by calling 

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

616 # the pybind11 memory allocation step. 

617 return (_makePropertySet, (getPropertySetState(self),)) 

618 

619 

620@continueClass 

621class PropertyList: 

622 # Mapping of type to method names 

623 _typeMenu = {bool: "Bool", 

624 int: "Int", 

625 float: "Double", 

626 str: "String", 

627 DateTime: "DateTime", 

628 PropertySet: "PropertySet", 

629 PropertyList: "PropertySet", 

630 None: "Undef", 

631 } 

632 

633 COMMENTSUFFIX = "#COMMENT" 

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

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

636 

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

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

639 

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

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

642 the final value in the array will be returned. 

643 

644 Parameters 

645 ---------- 

646 name : ``str`` 

647 Name of item 

648 default : `object`, optional 

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

650 

651 Returns 

652 ------- 

653 value : any type supported by container 

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

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

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

657 returned. 

658 """ 

659 try: 

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

661 except KeyError: 

662 return default 

663 

664 def getArray(self, name): 

665 """Return an item as a list. 

666 

667 Parameters 

668 ---------- 

669 name : `str` 

670 Name of item 

671 

672 Returns 

673 ------- 

674 values : `list` of values 

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

676 

677 Raises 

678 ------ 

679 KeyError 

680 Raised if the item does not exist. 

681 """ 

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

683 

684 def getScalar(self, name): 

685 """Return an item as a scalar 

686 

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

688 

689 Parameters 

690 ---------- 

691 name : `str` 

692 Name of item. 

693 

694 Returns 

695 ------- 

696 value : scalar item 

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

698 most recently added value is returned. 

699 

700 Raises 

701 ------ 

702 KeyError 

703 Raised if the item does not exist. 

704 """ 

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

706 

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

708 """Set the value of an item 

709 

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

711 need not match. 

712 

713 Parameters 

714 ---------- 

715 name : `str` 

716 Name of item 

717 value : any supported type 

718 Value of item; may be a scalar or array 

719 """ 

720 args = [] 

721 if comment is not None: 

722 args.append(comment) 

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

724 

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

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

727 

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

729 otherwise it is like calling `set` 

730 

731 Parameters 

732 ---------- 

733 name : `str` 

734 Name of item 

735 value : any supported type 

736 Value of item; may be a scalar or array 

737 

738 Notes 

739 ----- 

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

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

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

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

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

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

746 

747 Raises 

748 ------ 

749 lsst::pex::exceptions::TypeError 

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

751 value of the item. 

752 """ 

753 args = [] 

754 if comment is not None: 

755 args.append(comment) 

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

757 

758 def setComment(self, name, comment): 

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

760 

761 Parameters 

762 ---------- 

763 name : `str` 

764 Name of the key to receive updated comment. 

765 comment : `comment` 

766 New comment string. 

767 """ 

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

769 # one that has the new comment 

770 containerType = _propertyContainerElementTypeName(self, name) 

771 if self.isArray(name): 

772 value = self.getArray(name) 

773 else: 

774 value = self.getScalar(name) 

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

776 

777 def toList(self): 

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

779 in the order that they were inserted. 

780 

781 Returns 

782 ------- 

783 ret : `list` of `tuple` 

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

785 in which they were inserted. 

786 """ 

787 orderedNames = self.getOrderedNames() 

788 ret = [] 

789 for name in orderedNames: 

790 if self.isArray(name): 

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

792 for v in values: 

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

794 else: 

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

796 self.getComment(name))) 

797 return ret 

798 

799 def toOrderedDict(self): 

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

801 they were inserted. 

802 

803 Returns 

804 ------- 

805 d : `dict` 

806 Ordered dictionary with all properties in the order that they 

807 were inserted. Comments are not included. 

808 

809 Notes 

810 ----- 

811 As of Python 3.6 dicts retain their insertion order. 

812 """ 

813 d = {} 

814 for name in self: 

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

816 return d 

817 

818 # For PropertyList the two are equivalent 

819 toDict = toOrderedDict 

820 

821 def __eq__(self, other): 

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

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

824 # doesn't either. 

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

826 return False 

827 

828 for name in self: 

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

830 return False 

831 

832 return True 

833 

834 def __copy__(self): 

835 # Copy without having to go through pickle state 

836 pl = PropertyList() 

837 for itemName in self: 

838 pl.copy(itemName, self, itemName) 

839 return pl 

840 

841 def __deepcopy__(self, memo): 

842 result = self.deepCopy() 

843 memo[id(self)] = result 

844 return result 

845 

846 def __iter__(self): 

847 for n in self.getOrderedNames(): 

848 yield n 

849 

850 def __setitem__(self, name, value): 

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

852 

853 Parameters 

854 ---------- 

855 name : `str` 

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

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

858 than the value. 

859 value : Value to assign 

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

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

862 `PropertySet` before assignment. 

863 

864 Notes 

865 ----- 

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

867 """ 

868 if name.endswith(self.COMMENTSUFFIX): 

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

870 self.setComment(name, value) 

871 return 

872 if isinstance(value, Mapping): 

873 # Create a property set instead 

874 ps = PropertySet() 

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

876 ps[k] = v 

877 value = ps 

878 self.set(name, value) 

879 

880 def __reduce__(self): 

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

882 # However, implementing __setstate__ in Python causes segfaults 

883 # because pickle creates a new instance by calling 

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

885 # the pybind11 memory allocation step. 

886 return (_makePropertyList, (getPropertyListState(self),))