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
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-26 07:01 +0000
1import copy
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
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
29_NOT_SET = object()
32class Container(_CustomDict):
33 """
34 A container for items within a TOMLDocument.
36 This class implements the `dict` interface with copy/deepcopy protocol.
37 """
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 = []
45 @property
46 def body(self) -> List[Tuple[Optional[Key], Item]]:
47 return self._body
49 def unwrap(self) -> Dict[str, Any]:
50 unwrapped = {}
51 for k, v in self.items():
52 if k is None:
53 continue
55 if isinstance(k, Key):
56 k = k.key
58 if hasattr(v, "unwrap"):
59 v = v.unwrap()
61 if k in unwrapped:
62 merge_dicts(unwrapped[k], v)
63 else:
64 unwrapped[k] = v
66 return unwrapped
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
75 k = k.key
76 v = v.value
78 if isinstance(v, Container):
79 v = v.value
81 if k in d:
82 merge_dicts(d[k], v)
83 else:
84 d[k] = v
86 return d
88 def parsing(self, parsing: bool) -> None:
89 self._parsed = parsing
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)
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.
104 :Example:
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 )
117 key, item = None, key
119 return self.append(key, item)
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
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 )
146 _table = _table[_name]
148 value = table
150 self.append(name, value)
152 return
153 else:
154 table = Table(Container(True), Trivia(), False, is_super_table=True)
155 self.append(name, table)
157 for i, _name in enumerate(names[1:]):
158 if i == len(names) - 2:
159 _name.sep = key.sep
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 )
177 table = table[_name]
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)
184 if not isinstance(item, Item):
185 item = _item(item)
187 if key is not None and key.is_multi():
188 self._handle_dotted_key(key, item)
189 return self
191 if isinstance(item, (AoT, Table)) and item.name is None:
192 item.name = key.key
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"
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
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]
214 current = current_body_element[1]
216 if isinstance(item, Table):
217 if not isinstance(current, (Table, AoT)):
218 raise KeyAlreadyPresent(key)
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)
226 self._replace(key, key, current)
227 else:
228 current.append(item)
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)
236 current.append(item)
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,)
250 self._map[key] = current_idx + (len(self._body),)
251 self._body.append((key, item))
252 self._table_keys.append(key)
254 # Building a temporary proxy to check for errors
255 OutOfOrderTableProxy(self, self._map[key])
257 return self
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)
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)
279 for table in item.body:
280 current.append(table)
282 return self
283 else:
284 raise KeyAlreadyPresent(key)
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
297 if isinstance(v, Whitespace) and not v.is_fixed():
298 continue
300 if not is_table and isinstance(v, (Table, AoT)):
301 break
303 key_after = k or i # last scalar, Array or InlineTable value
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)
323 if key in self._map:
324 current_idx = self._map[key]
325 if isinstance(current_idx, tuple):
326 current_idx = current_idx[-1]
328 current = self._body[current_idx][1]
329 if key is not None and not isinstance(current, Table):
330 raise KeyAlreadyPresent(key)
332 # Adding sub tables to a currently existing table
333 if not isinstance(current_idx, tuple):
334 current_idx = (current_idx,)
336 self._map[key] = current_idx + (len(self._body),)
337 else:
338 self._map[key] = len(self._body)
340 self._body.append((key, item))
341 if item.is_table():
342 self._table_keys.append(key)
344 if key is not None:
345 dict.__setitem__(self, key.key, item.value)
347 return self
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())
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)
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)
373 idx = self._map.pop(key, None)
374 if idx is None:
375 raise NonExistentKey(key)
377 if isinstance(idx, tuple):
378 for i in idx:
379 self._body[i] = (None, Null())
380 else:
381 self._body[idx] = (None, Null())
383 dict.__delitem__(self, key.key)
385 return self
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()")
393 if key not in self:
394 raise NonExistentKey(key)
396 if not isinstance(key, Key):
397 key = SingleKey(key)
399 if not isinstance(other_key, Key):
400 other_key = SingleKey(other_key)
402 item = _item(item)
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"
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
420 new_indices.append(v_)
422 self._map[k] = tuple(new_indices)
423 elif v > idx:
424 self._map[k] = v + 1
426 self._map[other_key] = idx + 1
427 self._body.insert(idx + 1, (other_key, item))
429 if key is not None:
430 dict.__setitem__(self, other_key.key, item.value)
432 return self
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}")
438 if not isinstance(key, Key):
439 key = SingleKey(key)
441 item = _item(item)
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"
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
461 new_indices.append(v_)
463 self._map[k] = tuple(new_indices)
464 elif v >= idx:
465 self._map[k] = v + 1
467 self._map[key] = idx
468 self._body.insert(idx, (key, item))
470 if key is not None:
471 dict.__setitem__(self, key.key, item.value)
473 return self
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)
480 idx = self._map.get(key, None)
481 if idx is None:
482 raise NonExistentKey(key)
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)
490 return self._body[idx][1]
492 def last_item(self) -> Optional[Item]:
493 """Get the last item."""
494 if self._body:
495 return self._body[-1][1]
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)
511 return s
513 def _render_table(
514 self, key: Key, table: Table, prefix: Optional[str] = None
515 ) -> str:
516 cur = ""
518 if table.display_name is not None:
519 _key = table.display_name
520 else:
521 _key = key.as_string()
523 if prefix is not None:
524 _key = prefix + "." + _key
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 = "[[", "]]"
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
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 )
570 return cur
572 def _render_aot(self, key, aot, prefix=None):
573 _key = key.as_string()
574 if prefix is not None:
575 _key = prefix + "." + _key
577 cur = ""
578 _key = decode(_key)
579 for table in aot.body:
580 cur += self._render_aot_table(table, prefix=_key)
582 return cur
584 def _render_aot_table(self, table: Table, prefix: Optional[str] = None) -> str:
585 cur = ""
587 _key = prefix or ""
589 if not table.is_super_table():
590 open_, close = "[[", "]]"
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 )
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)
617 return cur
619 def _render_simple_item(self, key, item, prefix=None):
620 if key is None:
621 return item.as_string()
623 _key = key.as_string()
624 if prefix is not None:
625 _key = prefix + "." + _key
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 )
637 def __len__(self) -> int:
638 return dict.__len__(self)
640 def __iter__(self) -> Iterator[str]:
641 return iter(dict.keys(self))
643 # Dictionary methods
644 def __getitem__(self, key: Union[Key, str]) -> Union[Item, "Container"]:
645 if not isinstance(key, Key):
646 key = SingleKey(key)
648 idx = self._map.get(key, None)
649 if idx is None:
650 raise NonExistentKey(key)
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)
658 item = self._body[idx][1]
659 if item.is_boolean():
660 return item.value
662 return item
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)
671 def __delitem__(self, key: Union[Key, str]) -> None:
672 self.remove(key)
674 def setdefault(self, key: Union[Key, str], default: Any) -> Any:
675 super().setdefault(key, default=default)
676 return self[key]
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)
684 idx = self._map.get(key, None)
685 if idx is None:
686 raise NonExistentKey(key)
688 self._replace_at(idx, new_key, value)
690 def _replace_at(
691 self, idx: Union[int, Tuple[int]], new_key: Union[Key, str], value: Item
692 ) -> None:
693 value = _item(value)
695 if isinstance(idx, tuple):
696 for i in idx[1:]:
697 self._body[i] = (None, Null())
699 idx = idx[0]
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
711 del self._map[k]
712 self._map[new_key] = idx
713 if new_key != k:
714 dict.__delitem__(self, k)
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)
736 if hasattr(value, "invalidate_display_name"):
737 value.invalidate_display_name() # type: ignore[attr-defined]
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"))
750 dict.__setitem__(self, new_key.key, value.value)
752 def __str__(self) -> str:
753 return str(self.value)
755 def __repr__(self) -> str:
756 return repr(self.value)
758 def __eq__(self, other: dict) -> bool:
759 if not isinstance(other, dict):
760 return NotImplemented
762 return self.value == other
764 def _getstate(self, protocol):
765 return (self._parsed,)
767 def __reduce__(self):
768 return self.__reduce_ex__(2)
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 )
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]
783 for key, item in self._body:
784 if key is not None:
785 dict.__setitem__(self, key.key, item.value)
787 def copy(self) -> "Container":
788 return copy.copy(self)
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)
795 c._body += self.body
796 c._map.update(self._map)
798 return c
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
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
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 = {}
829 for i in indices:
830 _, item = self._container._body[i]
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)
841 def unwrap(self) -> str:
842 return self._internal_container.unwrap()
844 @property
845 def value(self):
846 return self._internal_container.value
848 def __getitem__(self, key: Union[Key, str]) -> Any:
849 if key not in self._internal_container:
850 raise NonExistentKey(key)
852 return self._internal_container[key]
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
864 self._internal_container[key] = item
865 if key is not None:
866 dict.__setitem__(self, key, item)
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
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)
886 del self._internal_container[key]
887 if key is not None:
888 dict.__delitem__(self, key)
890 def __iter__(self) -> Iterator[str]:
891 return iter(dict.keys(self))
893 def __len__(self) -> int:
894 return dict.__len__(self)
896 def setdefault(self, key: Union[Key, str], default: Any) -> Any:
897 super().setdefault(key, default=default)
898 return self[key]
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))