container.py 2.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. """Together with :mod:`~configupdater.block` this module forms the basis of
  2. the class hierarchy in **ConfigUpdater**.
  3. The :class:`Container` is the parent class of everything that can contain configuration
  4. blocks, e.g. a section or the entire file itself.
  5. """
  6. import sys
  7. from copy import deepcopy
  8. from textwrap import indent
  9. from typing import TYPE_CHECKING, Generic, Optional, TypeVar
  10. if sys.version_info[:2] >= (3, 9): # pragma: no cover
  11. from collections.abc import Iterator
  12. List = list
  13. else: # pragma: no cover
  14. from typing import Iterator, List
  15. if TYPE_CHECKING:
  16. from .block import Block # noqa
  17. T = TypeVar("T", bound="Block")
  18. C = TypeVar("C", bound="Container")
  19. class Container(Generic[T]):
  20. """Abstract Mixin Class describing a container that holds blocks of type ``T``"""
  21. def __init__(self):
  22. self._structure: List[T] = []
  23. def _repr_blocks(self) -> str:
  24. blocks = "\n".join(repr(block) for block in self._structure)
  25. blocks = indent(blocks, " " * 4)
  26. return f"[\n{blocks.rstrip()}\n]" if blocks.strip() else "[]"
  27. def __repr__(self) -> str:
  28. return f"<{self.__class__.__name__} {self._repr_blocks()}>"
  29. def __deepcopy__(self: C, memo: dict) -> C:
  30. clone = self._instantiate_copy()
  31. memo[id(self)] = clone
  32. return clone._copy_structure(self._structure, memo)
  33. def _copy_structure(self: C, structure: List[T], memo: dict) -> C:
  34. """``__deepcopy__`` auxiliary method also useful with multi-inheritance"""
  35. self._structure = [b.attach(self) for b in deepcopy(structure, memo)]
  36. return self
  37. def _instantiate_copy(self: C) -> C:
  38. """Auxiliary method that allows subclasses calling ``__deepcopy__``"""
  39. return self.__class__() # allow overwrite for different init args
  40. @property
  41. def structure(self) -> List[T]:
  42. return self._structure
  43. @property
  44. def first_block(self) -> Optional[T]:
  45. if self._structure:
  46. return self._structure[0]
  47. else:
  48. return None
  49. @property
  50. def last_block(self) -> Optional[T]:
  51. if self._structure:
  52. return self._structure[-1]
  53. else:
  54. return None
  55. def _remove_block(self: C, idx: int) -> C:
  56. """Remove block at index idx within container
  57. Use `.container_idx` of a block to get the index.
  58. Not meant for users, rather use block.detach() instead!
  59. """
  60. del self._structure[idx]
  61. return self
  62. def iter_blocks(self) -> Iterator[T]:
  63. """Iterate over all blocks inside container."""
  64. return iter(self._structure)
  65. def __len__(self) -> int:
  66. """Number of blocks in container"""
  67. return len(self._structure)
  68. def append(self: C, block: T) -> C:
  69. self._structure.append(block)
  70. return self