Source code for tensorbay.utility.repr

#!/usr/bin/env python3
#
# Copyright 2021 Graviti. Licensed under MIT License.
#

"""The implementation of the TensorBay repr."""

from abc import ABC
from enum import Enum, auto
from typing import (
    Any,
    Callable,
    ClassVar,
    Dict,
    Iterable,
    Mapping,
    Sequence,
    Tuple,
    Type,
    TypeVar,
    Union,
)


[docs]class ReprType(Enum): """ReprType is an enumeration type. It defines the repr strategy type and includes 'INSTANCE', 'SEQUENCE' and 'MAPPING'. """ INSTANCE = auto() SEQUENCE = auto() MAPPING = auto()
[docs]class ReprMixin: """ReprMixin provides customized repr config and method.""" _repr_type: ClassVar[ReprType] = ReprType.INSTANCE _repr_attrs: Iterable[str] = () _repr_maxlevel = 1 _repr_non_empty = False def __repr__(self) -> str: return _repr1(self, self._repr_maxlevel, self._repr_maxlevel, True) def __str__(self) -> str: return _repr1(self, self._repr_maxlevel, self._repr_maxlevel, False) def _repr_head(self) -> str: return self.__class__.__name__
class _ReprConfig: """Config for customized repr method. Arguments: indent: The indent width for different level repr string. maxlist: The max items displayed in the sequence repr string. maxdict: The max items displayed in the mapping repr string. """ def __init__(self, indent: int = 2, maxlist: int = 16, maxdict: int = 16) -> None: self.indent = indent self.maxlist = maxlist self.maxdict = maxdict @property def indent(self) -> int: """The getter of repr indent width. Returns: The indent width of repr config. """ return self._indent @indent.setter def indent(self, indent: int) -> None: """The setter of repr indent width. Arguments: indent: The width of the ident to be set. """ self._indent = indent self._indent_str = " " * indent repr_config = _ReprConfig() # Explicitly inherit from 'abc.ABC' to tell pylint it is a abstract class # Reference: # - https://github.com/PyCQA/pylint/issues/3098 # - https://github.com/PyCQA/pylint/pull/3446 class _ReprSequence(ReprMixin, Sequence[Any], ABC): ... class _ReprMapping(ReprMixin, Mapping[Any, Any], ABC): ... _ReprPrinter = Callable[[Any, int, int, bool], str] _PRINTERS: Dict[Union[Type[Any], ReprType], _ReprPrinter] = {} class _PrinterRegister: """Decorator class to register repr printer functions to '_PRINTERS'. Arguments: key: The key of the '_PRINTERS' for repr dispatch. """ _S = TypeVar("_S", bound=_ReprPrinter) def __init__(self, key: Union[Type[Any], ReprType]) -> None: self._key = key def __call__(self, printer: _S) -> _S: _PRINTERS[self._key] = printer return printer def _repr1(obj: Any, level: int, maxlevel: int, folding: bool) -> str: """Customized repr method. Arguments: obj: The object need to be transferred to repr string. level: The current repr level. maxlevel: The max repr level. folding: Whether fold the "repr" of the object. Returns: "repr" of the object. """ class_: Type[Any] = type(obj) printer = _PRINTERS.get(getattr(class_, "_repr_type", class_), None) return printer(obj, level, maxlevel, folding) if printer else repr(obj) @_PrinterRegister(ReprType.INSTANCE) def _repr_instance(obj: ReprMixin, level: int, maxlevel: int, folding: bool) -> str: """Customized repr method for `ReprType.INSTANCE`. Arguments: obj: The object need to be transferred to repr string. level: The current repr level. maxlevel: The max repr level. folding: Whether fold the "repr" of the object. Returns: "repr" of the object. """ # pylint: disable=protected-access return f"{obj._repr_head()}{_repr_attrs(obj, level, maxlevel, folding)}" @_PrinterRegister(ReprType.SEQUENCE) def _repr_sequence(obj: _ReprSequence, level: int, maxlevel: int, folding: bool) -> str: """Customized repr method for `ReprType.SEQUENCE`. Arguments: obj: The object need to be transferred to repr string. level: The current repr level. maxlevel: The max repr level. folding: Whether fold the "repr" of the object. Returns: "repr" of the object. """ # pylint: disable=protected-access return ( f"{obj._repr_head()} {_repr_builtin_list(obj, level, maxlevel, folding)}" f"{_repr_attrs(obj, level, maxlevel, folding)}" ) @_PrinterRegister(ReprType.MAPPING) def _repr_mapping(obj: _ReprMapping, level: int, maxlevel: int, folding: bool) -> str: """Customized repr method for `ReprType.MAPPING`. Arguments: obj: The object need to be transferred to repr string. level: The current repr level. maxlevel: The max repr level. folding: Whether fold the "repr" of the object. Returns: "repr" of the object. """ # pylint: disable=protected-access return ( f"{obj._repr_head()} {_repr_builtin_dict(obj, level, maxlevel, folding)}" f"{_repr_attrs(obj, level, maxlevel, folding)}" ) @_PrinterRegister(list) def _repr_builtin_list(obj: Sequence[Any], level: int, maxlevel: int, folding: bool) -> str: """Customized repr method for buildin type list. Arguments: obj: The object need to be transferred to repr string. level: The current repr level. maxlevel: The max repr level. folding: Whether fold the "repr" of the object. Returns: "repr" of the object. """ return _repr_builtin_sequence(obj, level, maxlevel, folding, "[", "]") @_PrinterRegister(tuple) def _repr_builtin_tuple(obj: Sequence[Any], level: int, maxlevel: int, folding: bool) -> str: """Customized repr method for buildin type tuple. Arguments: obj: The object need to be transferred to repr string. level: The current repr level. maxlevel: The max repr level. folding: Whether fold the "repr" of the object. Returns: "repr" of the object. """ return _repr_builtin_sequence(obj, level, maxlevel, folding, "(", ")") @_PrinterRegister(dict) def _repr_builtin_dict(obj: Mapping[Any, Any], level: int, maxlevel: int, folding: bool) -> str: """Customized repr method for buildin type dict. Arguments: obj: The object need to be transferred to repr string. level: The current repr level. maxlevel: The max repr level. folding: Whether fold the "repr" of the object. Returns: "repr" of the object. """ if getattr(obj, "_repr_non_empty", False) and level <= 0: return "{...}" object_length = len(obj) if object_length == 0: return "{}" if level <= 0: return "{...}" newlevel = level - 1 keys = tuple(obj) fold, unfold = _calculate_fold_number(object_length, repr_config.maxdict, folding) pieces = [ f"{repr(key)}: {_repr1(obj[key], newlevel, maxlevel, folding)}" for key in keys[:unfold] ] if fold: key = keys[-1] pieces.append(f"... ({fold} items are folded)") pieces.append(f"{repr(key)}: {_repr1(obj[key], newlevel, maxlevel, folding)}") return _join_with_indent(pieces, level, maxlevel, "{", "}") def _repr_attrs(obj: Any, level: int, maxlevel: int, folding: bool) -> str: """Customized repr method for object attributes. Arguments: obj: The object need to be transferred to repr string. level: The current repr level. maxlevel: The max repr level. folding: Whether fold the "repr" of the object. Returns: "repr" of the object. """ # pylint: disable=protected-access if not obj._repr_attrs: return "" attributes = [] for attribute in obj._repr_attrs: value = getattr(obj, attribute, ...) if value is not ...: attributes.append((attribute, value)) if not attributes: return "()" if level <= 0: return "(...)" newlevel = level - 1 pieces = (f"({key}): {_repr1(value, newlevel, maxlevel, folding)}" for key, value in attributes) return _join_with_indent(pieces, level, maxlevel, "(", ")") def _repr_builtin_sequence( # pylint: disable=too-many-arguments obj: Sequence[Any], level: int, maxlevel: int, folding: bool, left: str, right: str ) -> str: """Customized repr method for sequence. Arguments: obj: The object need to be transferred to repr string. level: The current repr level. maxlevel: The max repr level. folding: Whether fold the "repr" of the object. left: The left bracket symbol. right: The right bracket symbol. Returns: "repr" of the object. """ if getattr(obj, "_repr_non_empty", False) and level <= 0: return f"{left}...{right}" object_length = len(obj) if object_length == 0: return f"{left}{right}" if level <= 0: return f"{left}...{right}" newlevel = level - 1 fold, unfold = _calculate_fold_number(object_length, repr_config.maxlist, folding) pieces = [_repr1(item, newlevel, maxlevel, folding) for item in obj[:unfold]] if fold: pieces.append(f"... ({fold} items are folded)") pieces.append(_repr1(obj[-1], newlevel, maxlevel, folding)) return _join_with_indent(pieces, level, maxlevel, left, right) def _calculate_fold_number(length: int, max_length: int, folding: bool) -> Tuple[int, int]: """Calculate the number of the folded and unfolded items. Arguments: length: The object length. max_length: The configured max length. folding: Whether fold the "repr" of the object. Returns: The number of the folded items & the number of the unfolded items. """ if folding and length > max_length: return length - max_length + 1, max_length - 2 return 0, length def _join_with_indent( pieces: Iterable[str], level: int, maxlevel: int, left: str, right: str ) -> str: """Handle the indent, newline and "," of the string list. Arguments: pieces: The string list need to be transferred to repr string. level: The current repr level. maxlevel: The max repr level. left: The left bracket symbol. right: The right bracket symbol. Returns: Repr string with indent, newline and ",". """ # pylint: disable=protected-access inner_indent = repr_config._indent_str * (maxlevel - level + 1) outer_indent = repr_config._indent_str * (maxlevel - level) sep = f",\n{inner_indent}" return f"{left}\n{inner_indent}{sep.join(pieces)}\n{outer_indent}{right}"