Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/tomlkit/container.py: 48%

550 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2023-03-26 07:01 +0000

1import copy 

2 

3from typing import Any 

4from typing import Dict 

5from typing import Iterator 

6from typing import List 

7from typing import Optional 

8from typing import Tuple 

9from typing import Union 

10 

11from tomlkit._compat import decode 

12from tomlkit._utils import merge_dicts 

13from tomlkit.exceptions import KeyAlreadyPresent 

14from tomlkit.exceptions import NonExistentKey 

15from tomlkit.exceptions import TOMLKitError 

16from tomlkit.items import AoT 

17from tomlkit.items import Comment 

18from tomlkit.items import Item 

19from tomlkit.items import Key 

20from tomlkit.items import Null 

21from tomlkit.items import SingleKey 

22from tomlkit.items import Table 

23from tomlkit.items import Trivia 

24from tomlkit.items import Whitespace 

25from tomlkit.items import _CustomDict 

26from tomlkit.items import item as _item 

27 

28 

29_NOT_SET = object() 

30 

31 

32class Container(_CustomDict): 

33 """ 

34 A container for items within a TOMLDocument. 

35 

36 This class implements the `dict` interface with copy/deepcopy protocol. 

37 """ 

38 

39 def __init__(self, parsed: bool = False) -> None: 

40 self._map: Dict[Key, int] = {} 

41 self._body: List[Tuple[Optional[Key], Item]] = [] 

42 self._parsed = parsed 

43 self._table_keys = [] 

44 

45 @property 

46 def body(self) -> List[Tuple[Optional[Key], Item]]: 

47 return self._body 

48 

49 def unwrap(self) -> Dict[str, Any]: 

50 unwrapped = {} 

51 for k, v in self.items(): 

52 if k is None: 

53 continue 

54 

55 if isinstance(k, Key): 

56 k = k.key 

57 

58 if hasattr(v, "unwrap"): 

59 v = v.unwrap() 

60 

61 if k in unwrapped: 

62 merge_dicts(unwrapped[k], v) 

63 else: 

64 unwrapped[k] = v 

65 

66 return unwrapped 

67 

68 @property 

69 def value(self) -> Dict[str, Any]: 

70 d = {} 

71 for k, v in self._body: 

72 if k is None: 

73 continue 

74 

75 k = k.key 

76 v = v.value 

77 

78 if isinstance(v, Container): 

79 v = v.value 

80 

81 if k in d: 

82 merge_dicts(d[k], v) 

83 else: 

84 d[k] = v 

85 

86 return d 

87 

88 def parsing(self, parsing: bool) -> None: 

89 self._parsed = parsing 

90 

91 for _, v in self._body: 

92 if isinstance(v, Table): 

93 v.value.parsing(parsing) 

94 elif isinstance(v, AoT): 

95 for t in v.body: 

96 t.value.parsing(parsing) 

97 

98 def add( 

99 self, key: Union[Key, Item, str], item: Optional[Item] = None 

100 ) -> "Container": 

101 """ 

102 Adds an item to the current Container. 

103 

104 :Example: 

105 

106 >>> # add a key-value pair 

107 >>> doc.add('key', 'value') 

108 >>> # add a comment or whitespace or newline 

109 >>> doc.add(comment('# comment')) 

110 """ 

111 if item is None: 

112 if not isinstance(key, (Comment, Whitespace)): 

113 raise ValueError( 

114 "Non comment/whitespace items must have an associated key" 

115 ) 

116 

117 key, item = None, key 

118 

119 return self.append(key, item) 

120 

121 def _handle_dotted_key(self, key: Key, value: Item) -> None: 

122 names = tuple(iter(key)) 

123 name = names[0] 

124 name._dotted = True 

125 if name in self: 

126 if not isinstance(value, Table): 

127 table = Table(Container(True), Trivia(), False, is_super_table=True) 

128 _table = table 

129 for i, _name in enumerate(names[1:]): 

130 if i == len(names) - 2: 

131 _name.sep = key.sep 

132 

133 _table.append(_name, value) 

134 else: 

135 _name._dotted = True 

136 _table.append( 

137 _name, 

138 Table( 

139 Container(True), 

140 Trivia(), 

141 False, 

142 is_super_table=i < len(names) - 2, 

143 ), 

144 ) 

145 

146 _table = _table[_name] 

147 

148 value = table 

149 

150 self.append(name, value) 

151 

152 return 

153 else: 

154 table = Table(Container(True), Trivia(), False, is_super_table=True) 

155 self.append(name, table) 

156 

157 for i, _name in enumerate(names[1:]): 

158 if i == len(names) - 2: 

159 _name.sep = key.sep 

160 

161 table.append(_name, value) 

162 else: 

163 _name._dotted = True 

164 if _name in table.value: 

165 table = table.value[_name] 

166 else: 

167 table.append( 

168 _name, 

169 Table( 

170 Container(True), 

171 Trivia(), 

172 False, 

173 is_super_table=i < len(names) - 2, 

174 ), 

175 ) 

176 

177 table = table[_name] 

178 

179 def append(self, key: Union[Key, str, None], item: Item) -> "Container": 

180 """Similar to :meth:`add` but both key and value must be given.""" 

181 if not isinstance(key, Key) and key is not None: 

182 key = SingleKey(key) 

183 

184 if not isinstance(item, Item): 

185 item = _item(item) 

186 

187 if key is not None and key.is_multi(): 

188 self._handle_dotted_key(key, item) 

189 return self 

190 

191 if isinstance(item, (AoT, Table)) and item.name is None: 

192 item.name = key.key 

193 

194 prev = self._previous_item() 

195 prev_ws = isinstance(prev, Whitespace) or ends_with_whitespace(prev) 

196 if isinstance(item, Table): 

197 if not self._parsed: 

198 item.invalidate_display_name() 

199 if self._body and not (self._parsed or item.trivia.indent or prev_ws): 

200 item.trivia.indent = "\n" 

201 

202 if isinstance(item, AoT) and self._body and not self._parsed: 

203 item.invalidate_display_name() 

204 if item and not ("\n" in item[0].trivia.indent or prev_ws): 

205 item[0].trivia.indent = "\n" + item[0].trivia.indent 

206 

207 if key is not None and key in self: 

208 current_idx = self._map[key] 

209 if isinstance(current_idx, tuple): 

210 current_body_element = self._body[current_idx[-1]] 

211 else: 

212 current_body_element = self._body[current_idx] 

213 

214 current = current_body_element[1] 

215 

216 if isinstance(item, Table): 

217 if not isinstance(current, (Table, AoT)): 

218 raise KeyAlreadyPresent(key) 

219 

220 if item.is_aot_element(): 

221 # New AoT element found later on 

222 # Adding it to the current AoT 

223 if not isinstance(current, AoT): 

224 current = AoT([current, item], parsed=self._parsed) 

225 

226 self._replace(key, key, current) 

227 else: 

228 current.append(item) 

229 

230 return self 

231 elif current.is_aot(): 

232 if not item.is_aot_element(): 

233 # Tried to define a table after an AoT with the same name. 

234 raise KeyAlreadyPresent(key) 

235 

236 current.append(item) 

237 

238 return self 

239 elif current.is_super_table(): 

240 if item.is_super_table(): 

241 # We need to merge both super tables 

242 if ( 

243 self._table_keys[-1] != current_body_element[0] 

244 or key.is_dotted() 

245 or current_body_element[0].is_dotted() 

246 ): 

247 if not isinstance(current_idx, tuple): 

248 current_idx = (current_idx,) 

249 

250 self._map[key] = current_idx + (len(self._body),) 

251 self._body.append((key, item)) 

252 self._table_keys.append(key) 

253 

254 # Building a temporary proxy to check for errors 

255 OutOfOrderTableProxy(self, self._map[key]) 

256 

257 return self 

258 

259 # Create a new element to replace the old one 

260 current = copy.deepcopy(current) 

261 for k, v in item.value.body: 

262 current.append(k, v) 

263 self._body[ 

264 current_idx[-1] 

265 if isinstance(current_idx, tuple) 

266 else current_idx 

267 ] = (current_body_element[0], current) 

268 

269 return self 

270 elif current_body_element[0].is_dotted(): 

271 raise TOMLKitError("Redefinition of an existing table") 

272 elif not item.is_super_table(): 

273 raise KeyAlreadyPresent(key) 

274 elif isinstance(item, AoT): 

275 if not isinstance(current, AoT): 

276 # Tried to define an AoT after a table with the same name. 

277 raise KeyAlreadyPresent(key) 

278 

279 for table in item.body: 

280 current.append(table) 

281 

282 return self 

283 else: 

284 raise KeyAlreadyPresent(key) 

285 

286 is_table = isinstance(item, (Table, AoT)) 

287 if key is not None and self._body and not self._parsed: 

288 # If there is already at least one table in the current container 

289 # and the given item is not a table, we need to find the last 

290 # item that is not a table and insert after it 

291 # If no such item exists, insert at the top of the table 

292 key_after = None 

293 for i, (k, v) in enumerate(self._body): 

294 if isinstance(v, Null): 

295 continue # Null elements are inserted after deletion 

296 

297 if isinstance(v, Whitespace) and not v.is_fixed(): 

298 continue 

299 

300 if not is_table and isinstance(v, (Table, AoT)): 

301 break 

302 

303 key_after = k or i # last scalar, Array or InlineTable value 

304 

305 if key_after is not None: 

306 if isinstance(key_after, int): 

307 if key_after + 1 < len(self._body): 

308 return self._insert_at(key_after + 1, key, item) 

309 else: 

310 previous_item = self._body[-1][1] 

311 if not ( 

312 isinstance(previous_item, Whitespace) 

313 or ends_with_whitespace(previous_item) 

314 or is_table 

315 or "\n" in previous_item.trivia.trail 

316 ): 

317 previous_item.trivia.trail += "\n" 

318 else: 

319 return self._insert_after(key_after, key, item) 

320 else: 

321 return self._insert_at(0, key, item) 

322 

323 if key in self._map: 

324 current_idx = self._map[key] 

325 if isinstance(current_idx, tuple): 

326 current_idx = current_idx[-1] 

327 

328 current = self._body[current_idx][1] 

329 if key is not None and not isinstance(current, Table): 

330 raise KeyAlreadyPresent(key) 

331 

332 # Adding sub tables to a currently existing table 

333 if not isinstance(current_idx, tuple): 

334 current_idx = (current_idx,) 

335 

336 self._map[key] = current_idx + (len(self._body),) 

337 else: 

338 self._map[key] = len(self._body) 

339 

340 self._body.append((key, item)) 

341 if item.is_table(): 

342 self._table_keys.append(key) 

343 

344 if key is not None: 

345 dict.__setitem__(self, key.key, item.value) 

346 

347 return self 

348 

349 def _remove_at(self, idx: int) -> None: 

350 key = self._body[idx][0] 

351 index = self._map.get(key) 

352 if index is None: 

353 raise NonExistentKey(key) 

354 self._body[idx] = (None, Null()) 

355 

356 if isinstance(index, tuple): 

357 index = list(index) 

358 index.remove(idx) 

359 if len(index) == 1: 

360 index = index.pop() 

361 else: 

362 index = tuple(index) 

363 self._map[key] = index 

364 else: 

365 dict.__delitem__(self, key.key) 

366 self._map.pop(key) 

367 

368 def remove(self, key: Union[Key, str]) -> "Container": 

369 """Remove a key from the container.""" 

370 if not isinstance(key, Key): 

371 key = SingleKey(key) 

372 

373 idx = self._map.pop(key, None) 

374 if idx is None: 

375 raise NonExistentKey(key) 

376 

377 if isinstance(idx, tuple): 

378 for i in idx: 

379 self._body[i] = (None, Null()) 

380 else: 

381 self._body[idx] = (None, Null()) 

382 

383 dict.__delitem__(self, key.key) 

384 

385 return self 

386 

387 def _insert_after( 

388 self, key: Union[Key, str], other_key: Union[Key, str], item: Any 

389 ) -> "Container": 

390 if key is None: 

391 raise ValueError("Key cannot be null in insert_after()") 

392 

393 if key not in self: 

394 raise NonExistentKey(key) 

395 

396 if not isinstance(key, Key): 

397 key = SingleKey(key) 

398 

399 if not isinstance(other_key, Key): 

400 other_key = SingleKey(other_key) 

401 

402 item = _item(item) 

403 

404 idx = self._map[key] 

405 # Insert after the max index if there are many. 

406 if isinstance(idx, tuple): 

407 idx = max(idx) 

408 current_item = self._body[idx][1] 

409 if "\n" not in current_item.trivia.trail: 

410 current_item.trivia.trail += "\n" 

411 

412 # Increment indices after the current index 

413 for k, v in self._map.items(): 

414 if isinstance(v, tuple): 

415 new_indices = [] 

416 for v_ in v: 

417 if v_ > idx: 

418 v_ = v_ + 1 

419 

420 new_indices.append(v_) 

421 

422 self._map[k] = tuple(new_indices) 

423 elif v > idx: 

424 self._map[k] = v + 1 

425 

426 self._map[other_key] = idx + 1 

427 self._body.insert(idx + 1, (other_key, item)) 

428 

429 if key is not None: 

430 dict.__setitem__(self, other_key.key, item.value) 

431 

432 return self 

433 

434 def _insert_at(self, idx: int, key: Union[Key, str], item: Any) -> "Container": 

435 if idx > len(self._body) - 1: 

436 raise ValueError(f"Unable to insert at position {idx}") 

437 

438 if not isinstance(key, Key): 

439 key = SingleKey(key) 

440 

441 item = _item(item) 

442 

443 if idx > 0: 

444 previous_item = self._body[idx - 1][1] 

445 if not ( 

446 isinstance(previous_item, Whitespace) 

447 or ends_with_whitespace(previous_item) 

448 or isinstance(item, (AoT, Table)) 

449 or "\n" in previous_item.trivia.trail 

450 ): 

451 previous_item.trivia.trail += "\n" 

452 

453 # Increment indices after the current index 

454 for k, v in self._map.items(): 

455 if isinstance(v, tuple): 

456 new_indices = [] 

457 for v_ in v: 

458 if v_ >= idx: 

459 v_ = v_ + 1 

460 

461 new_indices.append(v_) 

462 

463 self._map[k] = tuple(new_indices) 

464 elif v >= idx: 

465 self._map[k] = v + 1 

466 

467 self._map[key] = idx 

468 self._body.insert(idx, (key, item)) 

469 

470 if key is not None: 

471 dict.__setitem__(self, key.key, item.value) 

472 

473 return self 

474 

475 def item(self, key: Union[Key, str]) -> Item: 

476 """Get an item for the given key.""" 

477 if not isinstance(key, Key): 

478 key = SingleKey(key) 

479 

480 idx = self._map.get(key, None) 

481 if idx is None: 

482 raise NonExistentKey(key) 

483 

484 if isinstance(idx, tuple): 

485 # The item we are getting is an out of order table 

486 # so we need a proxy to retrieve the proper objects 

487 # from the parent container 

488 return OutOfOrderTableProxy(self, idx) 

489 

490 return self._body[idx][1] 

491 

492 def last_item(self) -> Optional[Item]: 

493 """Get the last item.""" 

494 if self._body: 

495 return self._body[-1][1] 

496 

497 def as_string(self) -> str: 

498 """Render as TOML string.""" 

499 s = "" 

500 for k, v in self._body: 

501 if k is not None: 

502 if isinstance(v, Table): 

503 s += self._render_table(k, v) 

504 elif isinstance(v, AoT): 

505 s += self._render_aot(k, v) 

506 else: 

507 s += self._render_simple_item(k, v) 

508 else: 

509 s += self._render_simple_item(k, v) 

510 

511 return s 

512 

513 def _render_table( 

514 self, key: Key, table: Table, prefix: Optional[str] = None 

515 ) -> str: 

516 cur = "" 

517 

518 if table.display_name is not None: 

519 _key = table.display_name 

520 else: 

521 _key = key.as_string() 

522 

523 if prefix is not None: 

524 _key = prefix + "." + _key 

525 

526 if not table.is_super_table() or ( 

527 any( 

528 not isinstance(v, (Table, AoT, Whitespace, Null)) 

529 for _, v in table.value.body 

530 ) 

531 and not key.is_dotted() 

532 ): 

533 open_, close = "[", "]" 

534 if table.is_aot_element(): 

535 open_, close = "[[", "]]" 

536 

537 newline_in_table_trivia = ( 

538 "\n" if "\n" not in table.trivia.trail and len(table.value) > 0 else "" 

539 ) 

540 cur += ( 

541 f"{table.trivia.indent}" 

542 f"{open_}" 

543 f"{decode(_key)}" 

544 f"{close}" 

545 f"{table.trivia.comment_ws}" 

546 f"{decode(table.trivia.comment)}" 

547 f"{table.trivia.trail}" 

548 f"{newline_in_table_trivia}" 

549 ) 

550 elif table.trivia.indent == "\n": 

551 cur += table.trivia.indent 

552 

553 for k, v in table.value.body: 

554 if isinstance(v, Table): 

555 if v.is_super_table(): 

556 if k.is_dotted() and not key.is_dotted(): 

557 # Dotted key inside table 

558 cur += self._render_table(k, v) 

559 else: 

560 cur += self._render_table(k, v, prefix=_key) 

561 else: 

562 cur += self._render_table(k, v, prefix=_key) 

563 elif isinstance(v, AoT): 

564 cur += self._render_aot(k, v, prefix=_key) 

565 else: 

566 cur += self._render_simple_item( 

567 k, v, prefix=_key if key.is_dotted() else None 

568 ) 

569 

570 return cur 

571 

572 def _render_aot(self, key, aot, prefix=None): 

573 _key = key.as_string() 

574 if prefix is not None: 

575 _key = prefix + "." + _key 

576 

577 cur = "" 

578 _key = decode(_key) 

579 for table in aot.body: 

580 cur += self._render_aot_table(table, prefix=_key) 

581 

582 return cur 

583 

584 def _render_aot_table(self, table: Table, prefix: Optional[str] = None) -> str: 

585 cur = "" 

586 

587 _key = prefix or "" 

588 

589 if not table.is_super_table(): 

590 open_, close = "[[", "]]" 

591 

592 cur += ( 

593 f"{table.trivia.indent}" 

594 f"{open_}" 

595 f"{decode(_key)}" 

596 f"{close}" 

597 f"{table.trivia.comment_ws}" 

598 f"{decode(table.trivia.comment)}" 

599 f"{table.trivia.trail}" 

600 ) 

601 

602 for k, v in table.value.body: 

603 if isinstance(v, Table): 

604 if v.is_super_table(): 

605 if k.is_dotted(): 

606 # Dotted key inside table 

607 cur += self._render_table(k, v) 

608 else: 

609 cur += self._render_table(k, v, prefix=_key) 

610 else: 

611 cur += self._render_table(k, v, prefix=_key) 

612 elif isinstance(v, AoT): 

613 cur += self._render_aot(k, v, prefix=_key) 

614 else: 

615 cur += self._render_simple_item(k, v) 

616 

617 return cur 

618 

619 def _render_simple_item(self, key, item, prefix=None): 

620 if key is None: 

621 return item.as_string() 

622 

623 _key = key.as_string() 

624 if prefix is not None: 

625 _key = prefix + "." + _key 

626 

627 return ( 

628 f"{item.trivia.indent}" 

629 f"{decode(_key)}" 

630 f"{key.sep}" 

631 f"{decode(item.as_string())}" 

632 f"{item.trivia.comment_ws}" 

633 f"{decode(item.trivia.comment)}" 

634 f"{item.trivia.trail}" 

635 ) 

636 

637 def __len__(self) -> int: 

638 return dict.__len__(self) 

639 

640 def __iter__(self) -> Iterator[str]: 

641 return iter(dict.keys(self)) 

642 

643 # Dictionary methods 

644 def __getitem__(self, key: Union[Key, str]) -> Union[Item, "Container"]: 

645 if not isinstance(key, Key): 

646 key = SingleKey(key) 

647 

648 idx = self._map.get(key, None) 

649 if idx is None: 

650 raise NonExistentKey(key) 

651 

652 if isinstance(idx, tuple): 

653 # The item we are getting is an out of order table 

654 # so we need a proxy to retrieve the proper objects 

655 # from the parent container 

656 return OutOfOrderTableProxy(self, idx) 

657 

658 item = self._body[idx][1] 

659 if item.is_boolean(): 

660 return item.value 

661 

662 return item 

663 

664 def __setitem__(self, key: Union[Key, str], value: Any) -> None: 

665 if key is not None and key in self: 

666 old_key = next(filter(lambda k: k == key, self._map)) 

667 self._replace(old_key, key, value) 

668 else: 

669 self.append(key, value) 

670 

671 def __delitem__(self, key: Union[Key, str]) -> None: 

672 self.remove(key) 

673 

674 def setdefault(self, key: Union[Key, str], default: Any) -> Any: 

675 super().setdefault(key, default=default) 

676 return self[key] 

677 

678 def _replace( 

679 self, key: Union[Key, str], new_key: Union[Key, str], value: Item 

680 ) -> None: 

681 if not isinstance(key, Key): 

682 key = SingleKey(key) 

683 

684 idx = self._map.get(key, None) 

685 if idx is None: 

686 raise NonExistentKey(key) 

687 

688 self._replace_at(idx, new_key, value) 

689 

690 def _replace_at( 

691 self, idx: Union[int, Tuple[int]], new_key: Union[Key, str], value: Item 

692 ) -> None: 

693 value = _item(value) 

694 

695 if isinstance(idx, tuple): 

696 for i in idx[1:]: 

697 self._body[i] = (None, Null()) 

698 

699 idx = idx[0] 

700 

701 k, v = self._body[idx] 

702 if not isinstance(new_key, Key): 

703 if ( 

704 isinstance(value, (AoT, Table)) != isinstance(v, (AoT, Table)) 

705 or new_key != k.key 

706 ): 

707 new_key = SingleKey(new_key) 

708 else: # Inherit the sep of the old key 

709 new_key = k 

710 

711 del self._map[k] 

712 self._map[new_key] = idx 

713 if new_key != k: 

714 dict.__delitem__(self, k) 

715 

716 if isinstance(value, (AoT, Table)) != isinstance(v, (AoT, Table)): 

717 # new tables should appear after all non-table values 

718 self.remove(k) 

719 for i in range(idx, len(self._body)): 

720 if isinstance(self._body[i][1], (AoT, Table)): 

721 self._insert_at(i, new_key, value) 

722 idx = i 

723 break 

724 else: 

725 idx = -1 

726 self.append(new_key, value) 

727 else: 

728 # Copying trivia 

729 if not isinstance(value, (Whitespace, AoT)): 

730 value.trivia.indent = v.trivia.indent 

731 value.trivia.comment_ws = value.trivia.comment_ws or v.trivia.comment_ws 

732 value.trivia.comment = value.trivia.comment or v.trivia.comment 

733 value.trivia.trail = v.trivia.trail 

734 self._body[idx] = (new_key, value) 

735 

736 if hasattr(value, "invalidate_display_name"): 

737 value.invalidate_display_name() # type: ignore[attr-defined] 

738 

739 if isinstance(value, Table): 

740 # Insert a cosmetic new line for tables if: 

741 # - it does not have it yet OR is not followed by one 

742 # - it is not the last item 

743 last, _ = self._previous_item_with_index() 

744 idx = last if idx < 0 else idx 

745 has_ws = ends_with_whitespace(value) 

746 next_ws = idx < last and isinstance(self._body[idx + 1][1], Whitespace) 

747 if idx < last and not (next_ws or has_ws): 

748 value.append(None, Whitespace("\n")) 

749 

750 dict.__setitem__(self, new_key.key, value.value) 

751 

752 def __str__(self) -> str: 

753 return str(self.value) 

754 

755 def __repr__(self) -> str: 

756 return repr(self.value) 

757 

758 def __eq__(self, other: dict) -> bool: 

759 if not isinstance(other, dict): 

760 return NotImplemented 

761 

762 return self.value == other 

763 

764 def _getstate(self, protocol): 

765 return (self._parsed,) 

766 

767 def __reduce__(self): 

768 return self.__reduce_ex__(2) 

769 

770 def __reduce_ex__(self, protocol): 

771 return ( 

772 self.__class__, 

773 self._getstate(protocol), 

774 (self._map, self._body, self._parsed, self._table_keys), 

775 ) 

776 

777 def __setstate__(self, state): 

778 self._map = state[0] 

779 self._body = state[1] 

780 self._parsed = state[2] 

781 self._table_keys = state[3] 

782 

783 for key, item in self._body: 

784 if key is not None: 

785 dict.__setitem__(self, key.key, item.value) 

786 

787 def copy(self) -> "Container": 

788 return copy.copy(self) 

789 

790 def __copy__(self) -> "Container": 

791 c = self.__class__(self._parsed) 

792 for k, v in dict.items(self): 

793 dict.__setitem__(c, k, v) 

794 

795 c._body += self.body 

796 c._map.update(self._map) 

797 

798 return c 

799 

800 def _previous_item_with_index( 

801 self, idx: Optional[int] = None, ignore=(Null,) 

802 ) -> Optional[Tuple[int, Item]]: 

803 """Find the immediate previous item before index ``idx``""" 

804 if idx is None or idx > len(self._body): 

805 idx = len(self._body) 

806 for i in range(idx - 1, -1, -1): 

807 v = self._body[i][-1] 

808 if not isinstance(v, ignore): 

809 return i, v 

810 return None 

811 

812 def _previous_item( 

813 self, idx: Optional[int] = None, ignore=(Null,) 

814 ) -> Optional[Item]: 

815 """Find the immediate previous item before index ``idx``. 

816 If ``idx`` is not given, the last item is returned. 

817 """ 

818 prev = self._previous_item_with_index(idx, ignore) 

819 return prev[-1] if prev else None 

820 

821 

822class OutOfOrderTableProxy(_CustomDict): 

823 def __init__(self, container: Container, indices: Tuple[int]) -> None: 

824 self._container = container 

825 self._internal_container = Container(True) 

826 self._tables = [] 

827 self._tables_map = {} 

828 

829 for i in indices: 

830 _, item = self._container._body[i] 

831 

832 if isinstance(item, Table): 

833 self._tables.append(item) 

834 table_idx = len(self._tables) - 1 

835 for k, v in item.value.body: 

836 self._internal_container.append(k, v) 

837 self._tables_map[k] = table_idx 

838 if k is not None: 

839 dict.__setitem__(self, k.key, v) 

840 

841 def unwrap(self) -> str: 

842 return self._internal_container.unwrap() 

843 

844 @property 

845 def value(self): 

846 return self._internal_container.value 

847 

848 def __getitem__(self, key: Union[Key, str]) -> Any: 

849 if key not in self._internal_container: 

850 raise NonExistentKey(key) 

851 

852 return self._internal_container[key] 

853 

854 def __setitem__(self, key: Union[Key, str], item: Any) -> None: 

855 if key in self._tables_map: 

856 table = self._tables[self._tables_map[key]] 

857 table[key] = item 

858 elif self._tables: 

859 table = self._tables[0] 

860 table[key] = item 

861 else: 

862 self._container[key] = item 

863 

864 self._internal_container[key] = item 

865 if key is not None: 

866 dict.__setitem__(self, key, item) 

867 

868 def _remove_table(self, table: Table) -> None: 

869 """Remove table from the parent container""" 

870 self._tables.remove(table) 

871 for idx, item in enumerate(self._container._body): 

872 if item[1] is table: 

873 self._container._remove_at(idx) 

874 break 

875 

876 def __delitem__(self, key: Union[Key, str]) -> None: 

877 if key in self._tables_map: 

878 table = self._tables[self._tables_map[key]] 

879 del table[key] 

880 if not table and len(self._tables) > 1: 

881 self._remove_table(table) 

882 del self._tables_map[key] 

883 else: 

884 raise NonExistentKey(key) 

885 

886 del self._internal_container[key] 

887 if key is not None: 

888 dict.__delitem__(self, key) 

889 

890 def __iter__(self) -> Iterator[str]: 

891 return iter(dict.keys(self)) 

892 

893 def __len__(self) -> int: 

894 return dict.__len__(self) 

895 

896 def setdefault(self, key: Union[Key, str], default: Any) -> Any: 

897 super().setdefault(key, default=default) 

898 return self[key] 

899 

900 

901def ends_with_whitespace(it: Any) -> bool: 

902 """Returns ``True`` if the given item ``it`` is a ``Table`` or ``AoT`` object 

903 ending with a ``Whitespace``. 

904 """ 

905 return ( 

906 isinstance(it, Table) and isinstance(it.value._previous_item(), Whitespace) 

907 ) or (isinstance(it, AoT) and len(it) > 0 and isinstance(it[-1], Whitespace))