Source code for configupdater.block
"""Together with :mod:`~configupdater.container` this module forms the basis of
the class hierarchy in **ConfigUpdater**.
The :class:`Block` is the parent class of everything that can be nested inside a
configuration file, e.g. comments, sections, options and even sequences of white space.
"""
import sys
from copy import deepcopy
from inspect import cleandoc
from typing import TYPE_CHECKING, Optional, TypeVar, Union, cast
if sys.version_info[:2] >= (3, 9): # pragma: no cover
List = list
else: # pragma: no cover
from typing import List
if TYPE_CHECKING:
from .builder import BlockBuilder
from .container import Container
T = TypeVar("T")
B = TypeVar("B", bound="Block")
def _short_repr(block) -> str:
if isinstance(block, str):
return block
name = getattr(block, "raw_key", None) or getattr(block, "name", None)
name = f" {name!r}" if name else ""
return f"<{block.__class__.__name__}{name}>"
[docs]
class NotAttachedError(Exception):
"""{block} is not attached to a container yet. Try to insert it first."""
def __init__(self, block: Union[str, "Block"] = "The block"):
doc = cleandoc(cast(str, self.__class__.__doc__))
msg = doc.format(block=_short_repr(block))
super().__init__(msg)
[docs]
class AlreadyAttachedError(Exception):
"""{block} has been already attached to a container.
Try to remove it first using ``detach`` or create a copy using stdlib's
``copy.deepcopy``.
"""
def __init__(self, block: Union[str, "Block"] = "The block"):
doc = cleandoc(cast(str, self.__class__.__doc__))
msg = doc.format(block=_short_repr(block))
super().__init__(msg)
[docs]
class AssignMultilineValueError(Exception):
"""Trying to assign a multi-line value to {block}.
Use the ``set_values`` or ``append`` method to accomplish that.
"""
def __init__(self, block: Union[str, "Block"] = "The block"):
doc = cleandoc(cast(str, self.__class__.__doc__))
msg = doc.format(block=_short_repr(block))
super().__init__(msg)
class Block:
"""Abstract Block type holding lines
Block objects hold original lines from the configuration file and hold
a reference to a container wherein the object resides.
The type variable ``T`` is a reference for the type of the sibling blocks
inside the container.
"""
def __init__(self, container: Optional["Container"] = None):
self._container = container
self._lines: List[str] = []
self._updated = False
def __str__(self) -> str:
return "".join(self._lines)
def __repr__(self) -> str:
return f"<{self.__class__.__name__}: {str(self)!r}>"
def __eq__(self, other) -> bool:
if isinstance(other, self.__class__):
return str(self) == str(other)
else:
return False
def __deepcopy__(self: B, memo: dict) -> B:
clone = self._instantiate_copy()
clone._lines = deepcopy(self._lines, memo)
clone._updated = self._updated
memo[id(self)] = clone
return clone
def _instantiate_copy(self: B) -> B:
"""Auxiliary method that allows subclasses calling ``__deepcopy__``"""
return self.__class__(container=None) # allow overwrite for different init args
# ^ A fresh copy should always be made detached from any container
def add_line(self: B, line: str) -> B:
"""PRIVATE: this function is not part of the public API of Block.
It is only used internally by other classes of the package during parsing.
Add a line to the current block
Args:
line (str): one line to add
"""
self._lines.append(line)
return self
@property
def lines(self) -> List[str]:
return self._lines
@property
def container(self) -> "Container":
"""Container holding the block"""
if self._container is None:
raise NotAttachedError(self)
return self._container
@property
def container_idx(self: B) -> int:
"""Index of the block within its container"""
return self.container.structure.index(self)
@property
def updated(self) -> bool:
"""True if the option was changed/updated, otherwise False"""
# if no lines were added, treat it as updated since we added it
return self._updated or not self._lines
def _builder(self, idx: int) -> "BlockBuilder":
from .builder import BlockBuilder
return BlockBuilder(self.container, idx)
@property
def add_before(self) -> "BlockBuilder":
"""Block builder inserting a new block before the current block"""
return self._builder(self.container_idx)
@property
def add_after(self) -> "BlockBuilder":
"""Block builder inserting a new block after the current block"""
return self._builder(self.container_idx + 1)
@property
def next_block(self) -> Optional["Block"]:
"""Next block in the current container"""
idx = self.container_idx + 1
if idx < len(self.container):
return self.container.structure[idx]
else:
return None
@property
def previous_block(self) -> Optional["Block"]:
"""Previous block in the current container"""
idx = self.container_idx - 1
if idx >= 0:
return self.container.structure[idx]
else:
return None
def detach(self: B) -> B:
"""Remove and return this block from container"""
self.container._remove_block(self.container_idx)
self._container = None
return self
def has_container(self) -> bool:
"""Checks if this block has a container attached"""
return not (self._container is None)
def attach(self: B, container: "Container") -> B:
"""PRIVATE: Don't use this as a user!
Rather use `add_*` or the bracket notation
"""
if self._container is not None and self._container is not container:
raise AlreadyAttachedError(self)
self._container = container
return self
[docs]
class Space(Block):
"""Vertical space block of new lines"""