Aluode/PerceptionLabPortable
0
1import builtins2import collections3import dataclasses4import inspect5import os6import reprlib7import sys8from array import array9from collections import Counter, UserDict, UserList, defaultdict, deque10from dataclasses import dataclass, fields, is_dataclass11from inspect import isclass12from itertools import islice13from types import MappingProxyType14from typing import (15 TYPE_CHECKING,16 Any,17 Callable,18 DefaultDict,19 Deque,20 Dict,21 Iterable,22 List,23 Optional,24 Sequence,25 Set,26 Tuple,27 Union,28)29 30from pip._vendor.rich.repr import RichReprResult31 32try:33 import attr as _attr_module34 35 _has_attrs = hasattr(_attr_module, "ib")36except ImportError: # pragma: no cover37 _has_attrs = False38 39from . import get_console40from ._loop import loop_last41from ._pick import pick_bool42from .abc import RichRenderable43from .cells import cell_len44from .highlighter import ReprHighlighter45from .jupyter import JupyterMixin, JupyterRenderable46from .measure import Measurement47from .text import Text48 49if TYPE_CHECKING:50 from .console import (51 Console,52 ConsoleOptions,53 HighlighterType,54 JustifyMethod,55 OverflowMethod,56 RenderResult,57 )58 59 60def _is_attr_object(obj: Any) -> bool:61 """Check if an object was created with attrs module."""62 return _has_attrs and _attr_module.has(type(obj))63 64 65def _get_attr_fields(obj: Any) -> Sequence["_attr_module.Attribute[Any]"]:66 """Get fields for an attrs object."""67 return _attr_module.fields(type(obj)) if _has_attrs else []68 69 70def _is_dataclass_repr(obj: object) -> bool:71 """Check if an instance of a dataclass contains the default repr.72 73 Args:74 obj (object): A dataclass instance.75 76 Returns:77 bool: True if the default repr is used, False if there is a custom repr.78 """79 # Digging in to a lot of internals here80 # Catching all exceptions in case something is missing on a non CPython implementation81 try:82 return obj.__repr__.__code__.co_filename in (83 dataclasses.__file__,84 reprlib.__file__,85 )86 except Exception: # pragma: no coverage87 return False88 89 90_dummy_namedtuple = collections.namedtuple("_dummy_namedtuple", [])91 92 93def _has_default_namedtuple_repr(obj: object) -> bool:94 """Check if an instance of namedtuple contains the default repr95 96 Args:97 obj (object): A namedtuple98 99 Returns:100 bool: True if the default repr is used, False if there's a custom repr.101 """102 obj_file = None103 try:104 obj_file = inspect.getfile(obj.__repr__)105 except (OSError, TypeError):106 # OSError handles case where object is defined in __main__ scope, e.g. REPL - no filename available.107 # TypeError trapped defensively, in case of object without filename slips through.108 pass109 default_repr_file = inspect.getfile(_dummy_namedtuple.__repr__)110 return obj_file == default_repr_file111 112 113def _ipy_display_hook(114 value: Any,115 console: Optional["Console"] = None,116 overflow: "OverflowMethod" = "ignore",117 crop: bool = False,118 indent_guides: bool = False,119 max_length: Optional[int] = None,120 max_string: Optional[int] = None,121 max_depth: Optional[int] = None,122 expand_all: bool = False,123) -> Union[str, None]:124 # needed here to prevent circular import:125 from .console import ConsoleRenderable126 127 # always skip rich generated jupyter renderables or None values128 if _safe_isinstance(value, JupyterRenderable) or value is None:129 return None130 131 console = console or get_console()132 133 with console.capture() as capture:134 # certain renderables should start on a new line135 if _safe_isinstance(value, ConsoleRenderable):136 console.line()137 console.print(138 (139 value140 if _safe_isinstance(value, RichRenderable)141 else Pretty(142 value,143 overflow=overflow,144 indent_guides=indent_guides,145 max_length=max_length,146 max_string=max_string,147 max_depth=max_depth,148 expand_all=expand_all,149 margin=12,150 )151 ),152 crop=crop,153 new_line_start=True,154 end="",155 )156 # strip trailing newline, not usually part of a text repr157 # I'm not sure if this should be prevented at a lower level158 return capture.get().rstrip("\n")159 160 161def _safe_isinstance(162 obj: object, class_or_tuple: Union[type, Tuple[type, ...]]163) -> bool:164 """isinstance can fail in rare cases, for example types with no __class__"""165 try:166 return isinstance(obj, class_or_tuple)167 except Exception:168 return False169 170 171def install(172 console: Optional["Console"] = None,173 overflow: "OverflowMethod" = "ignore",174 crop: bool = False,175 indent_guides: bool = False,176 max_length: Optional[int] = None,177 max_string: Optional[int] = None,178 max_depth: Optional[int] = None,179 expand_all: bool = False,180) -> None:181 """Install automatic pretty printing in the Python REPL.182 183 Args:184 console (Console, optional): Console instance or ``None`` to use global console. Defaults to None.185 overflow (Optional[OverflowMethod], optional): Overflow method. Defaults to "ignore".186 crop (Optional[bool], optional): Enable cropping of long lines. Defaults to False.187 indent_guides (bool, optional): Enable indentation guides. Defaults to False.188 max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.189 Defaults to None.190 max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to None.191 max_depth (int, optional): Maximum depth of nested data structures, or None for no maximum. Defaults to None.192 expand_all (bool, optional): Expand all containers. Defaults to False.193 max_frames (int): Maximum number of frames to show in a traceback, 0 for no maximum. Defaults to 100.194 """195 from pip._vendor.rich import get_console196 197 console = console or get_console()198 assert console is not None199 200 def display_hook(value: Any) -> None:201 """Replacement sys.displayhook which prettifies objects with Rich."""202 if value is not None:203 assert console is not None204 builtins._ = None # type: ignore[attr-defined]205 console.print(206 (207 value208 if _safe_isinstance(value, RichRenderable)209 else Pretty(210 value,211 overflow=overflow,212 indent_guides=indent_guides,213 max_length=max_length,214 max_string=max_string,215 max_depth=max_depth,216 expand_all=expand_all,217 )218 ),219 crop=crop,220 )221 builtins._ = value # type: ignore[attr-defined]222 223 try:224 ip = get_ipython() # type: ignore[name-defined]225 except NameError:226 sys.displayhook = display_hook227 else:228 from IPython.core.formatters import BaseFormatter229 230 class RichFormatter(BaseFormatter): # type: ignore[misc]231 pprint: bool = True232 233 def __call__(self, value: Any) -> Any:234 if self.pprint:235 return _ipy_display_hook(236 value,237 console=get_console(),238 overflow=overflow,239 indent_guides=indent_guides,240 max_length=max_length,241 max_string=max_string,242 max_depth=max_depth,243 expand_all=expand_all,244 )245 else:246 return repr(value)247 248 # replace plain text formatter with rich formatter249 rich_formatter = RichFormatter()250 ip.display_formatter.formatters["text/plain"] = rich_formatter251 252 253class Pretty(JupyterMixin):254 """A rich renderable that pretty prints an object.255 256 Args:257 _object (Any): An object to pretty print.258 highlighter (HighlighterType, optional): Highlighter object to apply to result, or None for ReprHighlighter. Defaults to None.259 indent_size (int, optional): Number of spaces in indent. Defaults to 4.260 justify (JustifyMethod, optional): Justify method, or None for default. Defaults to None.261 overflow (OverflowMethod, optional): Overflow method, or None for default. Defaults to None.262 no_wrap (Optional[bool], optional): Disable word wrapping. Defaults to False.263 indent_guides (bool, optional): Enable indentation guides. Defaults to False.264 max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.265 Defaults to None.266 max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to None.267 max_depth (int, optional): Maximum depth of nested data structures, or None for no maximum. Defaults to None.268 expand_all (bool, optional): Expand all containers. Defaults to False.269 margin (int, optional): Subtrace a margin from width to force containers to expand earlier. Defaults to 0.270 insert_line (bool, optional): Insert a new line if the output has multiple new lines. Defaults to False.271 """272 273 def __init__(274 self,275 _object: Any,276 highlighter: Optional["HighlighterType"] = None,277 *,278 indent_size: int = 4,279 justify: Optional["JustifyMethod"] = None,280 overflow: Optional["OverflowMethod"] = None,281 no_wrap: Optional[bool] = False,282 indent_guides: bool = False,283 max_length: Optional[int] = None,284 max_string: Optional[int] = None,285 max_depth: Optional[int] = None,286 expand_all: bool = False,287 margin: int = 0,288 insert_line: bool = False,289 ) -> None:290 self._object = _object291 self.highlighter = highlighter or ReprHighlighter()292 self.indent_size = indent_size293 self.justify: Optional["JustifyMethod"] = justify294 self.overflow: Optional["OverflowMethod"] = overflow295 self.no_wrap = no_wrap296 self.indent_guides = indent_guides297 self.max_length = max_length298 self.max_string = max_string299 self.max_depth = max_depth300 self.expand_all = expand_all301 self.margin = margin302 self.insert_line = insert_line303 304 def __rich_console__(305 self, console: "Console", options: "ConsoleOptions"306 ) -> "RenderResult":307 pretty_str = pretty_repr(308 self._object,309 max_width=options.max_width - self.margin,310 indent_size=self.indent_size,311 max_length=self.max_length,312 max_string=self.max_string,313 max_depth=self.max_depth,314 expand_all=self.expand_all,315 )316 pretty_text = Text.from_ansi(317 pretty_str,318 justify=self.justify or options.justify,319 overflow=self.overflow or options.overflow,320 no_wrap=pick_bool(self.no_wrap, options.no_wrap),321 style="pretty",322 )323 pretty_text = (324 self.highlighter(pretty_text)325 if pretty_text326 else Text(327 f"{type(self._object)}.__repr__ returned empty string",328 style="dim italic",329 )330 )331 if self.indent_guides and not options.ascii_only:332 pretty_text = pretty_text.with_indent_guides(333 self.indent_size, style="repr.indent"334 )335 if self.insert_line and "\n" in pretty_text:336 yield ""337 yield pretty_text338 339 def __rich_measure__(340 self, console: "Console", options: "ConsoleOptions"341 ) -> "Measurement":342 pretty_str = pretty_repr(343 self._object,344 max_width=options.max_width,345 indent_size=self.indent_size,346 max_length=self.max_length,347 max_string=self.max_string,348 max_depth=self.max_depth,349 expand_all=self.expand_all,350 )351 text_width = (352 max(cell_len(line) for line in pretty_str.splitlines()) if pretty_str else 0353 )354 return Measurement(text_width, text_width)355 356 357def _get_braces_for_defaultdict(_object: DefaultDict[Any, Any]) -> Tuple[str, str, str]:358 return (359 f"defaultdict({_object.default_factory!r}, {{",360 "})",361 f"defaultdict({_object.default_factory!r}, {{}})",362 )363 364 365def _get_braces_for_deque(_object: Deque[Any]) -> Tuple[str, str, str]:366 if _object.maxlen is None:367 return ("deque([", "])", "deque()")368 return (369 "deque([",370 f"], maxlen={_object.maxlen})",371 f"deque(maxlen={_object.maxlen})",372 )373 374 375def _get_braces_for_array(_object: "array[Any]") -> Tuple[str, str, str]:376 return (f"array({_object.typecode!r}, [", "])", f"array({_object.typecode!r})")377 378 379_BRACES: Dict[type, Callable[[Any], Tuple[str, str, str]]] = {380 os._Environ: lambda _object: ("environ({", "})", "environ({})"),381 array: _get_braces_for_array,382 defaultdict: _get_braces_for_defaultdict,383 Counter: lambda _object: ("Counter({", "})", "Counter()"),384 deque: _get_braces_for_deque,385 dict: lambda _object: ("{", "}", "{}"),386 UserDict: lambda _object: ("{", "}", "{}"),387 frozenset: lambda _object: ("frozenset({", "})", "frozenset()"),388 list: lambda _object: ("[", "]", "[]"),389 UserList: lambda _object: ("[", "]", "[]"),390 set: lambda _object: ("{", "}", "set()"),391 tuple: lambda _object: ("(", ")", "()"),392 MappingProxyType: lambda _object: ("mappingproxy({", "})", "mappingproxy({})"),393}394_CONTAINERS = tuple(_BRACES.keys())395_MAPPING_CONTAINERS = (dict, os._Environ, MappingProxyType, UserDict)396 397 398def is_expandable(obj: Any) -> bool:399 """Check if an object may be expanded by pretty print."""400 return (401 _safe_isinstance(obj, _CONTAINERS)402 or (is_dataclass(obj))403 or (hasattr(obj, "__rich_repr__"))404 or _is_attr_object(obj)405 ) and not isclass(obj)406 407 408@dataclass409class Node:410 """A node in a repr tree. May be atomic or a container."""411 412 key_repr: str = ""413 value_repr: str = ""414 open_brace: str = ""415 close_brace: str = ""416 empty: str = ""417 last: bool = False418 is_tuple: bool = False419 is_namedtuple: bool = False420 children: Optional[List["Node"]] = None421 key_separator: str = ": "422 separator: str = ", "423 424 def iter_tokens(self) -> Iterable[str]:425 """Generate tokens for this node."""426 if self.key_repr:427 yield self.key_repr428 yield self.key_separator429 if self.value_repr:430 yield self.value_repr431 elif self.children is not None:432 if self.children:433 yield self.open_brace434 if self.is_tuple and not self.is_namedtuple and len(self.children) == 1:435 yield from self.children[0].iter_tokens()436 yield ","437 else:438 for child in self.children:439 yield from child.iter_tokens()440 if not child.last:441 yield self.separator442 yield self.close_brace443 else:444 yield self.empty445 446 def check_length(self, start_length: int, max_length: int) -> bool:447 """Check the length fits within a limit.448 449 Args:450 start_length (int): Starting length of the line (indent, prefix, suffix).451 max_length (int): Maximum length.452 453 Returns:454 bool: True if the node can be rendered within max length, otherwise False.455 """456 total_length = start_length457 for token in self.iter_tokens():458 total_length += cell_len(token)459 if total_length > max_length:460 return False461 return True462 463 def __str__(self) -> str:464 repr_text = "".join(self.iter_tokens())465 return repr_text466 467 def render(468 self, max_width: int = 80, indent_size: int = 4, expand_all: bool = False469 ) -> str:470 """Render the node to a pretty repr.471 472 Args:473 max_width (int, optional): Maximum width of the repr. Defaults to 80.474 indent_size (int, optional): Size of indents. Defaults to 4.475 expand_all (bool, optional): Expand all levels. Defaults to False.476 477 Returns:478 str: A repr string of the original object.479 """480 lines = [_Line(node=self, is_root=True)]481 line_no = 0482 while line_no < len(lines):483 line = lines[line_no]484 if line.expandable and not line.expanded:485 if expand_all or not line.check_length(max_width):486 lines[line_no : line_no + 1] = line.expand(indent_size)487 line_no += 1488 489 repr_str = "\n".join(str(line) for line in lines)490 return repr_str491 492 493@dataclass494class _Line:495 """A line in repr output."""496 497 parent: Optional["_Line"] = None498 is_root: bool = False499 node: Optional[Node] = None500 text: str = ""501 suffix: str = ""502 whitespace: str = ""503 expanded: bool = False504 last: bool = False505 506 @property507 def expandable(self) -> bool:508 """Check if the line may be expanded."""509 return bool(self.node is not None and self.node.children)510 511 def check_length(self, max_length: int) -> bool:512 """Check this line fits within a given number of cells."""513 start_length = (514 len(self.whitespace) + cell_len(self.text) + cell_len(self.suffix)515 )516 assert self.node is not None517 return self.node.check_length(start_length, max_length)518 519 def expand(self, indent_size: int) -> Iterable["_Line"]:520 """Expand this line by adding children on their own line."""521 node = self.node522 assert node is not None523 whitespace = self.whitespace524 assert node.children525 if node.key_repr:526 new_line = yield _Line(527 text=f"{node.key_repr}{node.key_separator}{node.open_brace}",528 whitespace=whitespace,529 )530 else:531 new_line = yield _Line(text=node.open_brace, whitespace=whitespace)532 child_whitespace = self.whitespace + " " * indent_size533 tuple_of_one = node.is_tuple and len(node.children) == 1534 for last, child in loop_last(node.children):535 separator = "," if tuple_of_one else node.separator536 line = _Line(537 parent=new_line,538 node=child,539 whitespace=child_whitespace,540 suffix=separator,541 last=last and not tuple_of_one,542 )543 yield line544 545 yield _Line(546 text=node.close_brace,547 whitespace=whitespace,548 suffix=self.suffix,549 last=self.last,550 )551 552 def __str__(self) -> str:553 if self.last:554 return f"{self.whitespace}{self.text}{self.node or ''}"555 else:556 return (557 f"{self.whitespace}{self.text}{self.node or ''}{self.suffix.rstrip()}"558 )559 560 561def _is_namedtuple(obj: Any) -> bool:562 """Checks if an object is most likely a namedtuple. It is possible563 to craft an object that passes this check and isn't a namedtuple, but564 there is only a minuscule chance of this happening unintentionally.565 566 Args:567 obj (Any): The object to test568 569 Returns:570 bool: True if the object is a namedtuple. False otherwise.571 """572 try:573 fields = getattr(obj, "_fields", None)574 except Exception:575 # Being very defensive - if we cannot get the attr then its not a namedtuple576 return False577 return isinstance(obj, tuple) and isinstance(fields, tuple)578 579 580def traverse(581 _object: Any,582 max_length: Optional[int] = None,583 max_string: Optional[int] = None,584 max_depth: Optional[int] = None,585) -> Node:586 """Traverse object and generate a tree.587 588 Args:589 _object (Any): Object to be traversed.590 max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.591 Defaults to None.592 max_string (int, optional): Maximum length of string before truncating, or None to disable truncating.593 Defaults to None.594 max_depth (int, optional): Maximum depth of data structures, or None for no maximum.595 Defaults to None.596 597 Returns:598 Node: The root of a tree structure which can be used to render a pretty repr.599 """600 601 def to_repr(obj: Any) -> str:602 """Get repr string for an object, but catch errors."""603 if (604 max_string is not None605 and _safe_isinstance(obj, (bytes, str))606 and len(obj) > max_string607 ):608 truncated = len(obj) - max_string609 obj_repr = f"{obj[:max_string]!r}+{truncated}"610 else:611 try:612 obj_repr = repr(obj)613 except Exception as error:614 obj_repr = f"<repr-error {str(error)!r}>"615 return obj_repr616 617 visited_ids: Set[int] = set()618 push_visited = visited_ids.add619 pop_visited = visited_ids.remove620 621 def _traverse(obj: Any, root: bool = False, depth: int = 0) -> Node:622 """Walk the object depth first."""623 624 obj_id = id(obj)625 if obj_id in visited_ids:626 # Recursion detected627 return Node(value_repr="...")628 629 obj_type = type(obj)630 children: List[Node]631 reached_max_depth = max_depth is not None and depth >= max_depth632 633 def iter_rich_args(rich_args: Any) -> Iterable[Union[Any, Tuple[str, Any]]]:634 for arg in rich_args:635 if _safe_isinstance(arg, tuple):636 if len(arg) == 3:637 key, child, default = arg638 if default == child:639 continue640 yield key, child641 elif len(arg) == 2:642 key, child = arg643 yield key, child644 elif len(arg) == 1:645 yield arg[0]646 else:647 yield arg648 649 try:650 fake_attributes = hasattr(651 obj, "awehoi234_wdfjwljet234_234wdfoijsdfmmnxpi492"652 )653 except Exception:654 fake_attributes = False655 656 rich_repr_result: Optional[RichReprResult] = None657 if not fake_attributes:658 try:659 if hasattr(obj, "__rich_repr__") and not isclass(obj):660 rich_repr_result = obj.__rich_repr__()661 except Exception:662 pass663 664 if rich_repr_result is not None:665 push_visited(obj_id)666 angular = getattr(obj.__rich_repr__, "angular", False)667 args = list(iter_rich_args(rich_repr_result))668 class_name = obj.__class__.__name__669 670 if args:671 children = []672 append = children.append673 674 if reached_max_depth:675 if angular:676 node = Node(value_repr=f"<{class_name}...>")677 else:678 node = Node(value_repr=f"{class_name}(...)")679 else:680 if angular:681 node = Node(682 open_brace=f"<{class_name} ",683 close_brace=">",684 children=children,685 last=root,686 separator=" ",687 )688 else:689 node = Node(690 open_brace=f"{class_name}(",691 close_brace=")",692 children=children,693 last=root,694 )695 for last, arg in loop_last(args):696 if _safe_isinstance(arg, tuple):697 key, child = arg698 child_node = _traverse(child, depth=depth + 1)699 child_node.last = last700 child_node.key_repr = key701 child_node.key_separator = "="702 append(child_node)703 else:704 child_node = _traverse(arg, depth=depth + 1)705 child_node.last = last706 append(child_node)707 else:708 node = Node(709 value_repr=f"<{class_name}>" if angular else f"{class_name}()",710 children=[],711 last=root,712 )713 pop_visited(obj_id)714 elif _is_attr_object(obj) and not fake_attributes:715 push_visited(obj_id)716 children = []717 append = children.append718 719 attr_fields = _get_attr_fields(obj)720 if attr_fields:721 if reached_max_depth:722 node = Node(value_repr=f"{obj.__class__.__name__}(...)")723 else:724 node = Node(725 open_brace=f"{obj.__class__.__name__}(",726 close_brace=")",727 children=children,728 last=root,729 )730 731 def iter_attrs() -> (732 Iterable[Tuple[str, Any, Optional[Callable[[Any], str]]]]733 ):734 """Iterate over attr fields and values."""735 for attr in attr_fields:736 if attr.repr:737 try:738 value = getattr(obj, attr.name)739 except Exception as error:740 # Can happen, albeit rarely741 yield (attr.name, error, None)742 else:743 yield (744 attr.name,745 value,746 attr.repr if callable(attr.repr) else None,747 )748 749 for last, (name, value, repr_callable) in loop_last(iter_attrs()):750 if repr_callable:751 child_node = Node(value_repr=str(repr_callable(value)))752 else:753 child_node = _traverse(value, depth=depth + 1)754 child_node.last = last755 child_node.key_repr = name756 child_node.key_separator = "="757 append(child_node)758 else:759 node = Node(760 value_repr=f"{obj.__class__.__name__}()", children=[], last=root761 )762 pop_visited(obj_id)763 elif (764 is_dataclass(obj)765 and not _safe_isinstance(obj, type)766 and not fake_attributes767 and _is_dataclass_repr(obj)768 ):769 push_visited(obj_id)770 children = []771 append = children.append772 if reached_max_depth:773 node = Node(value_repr=f"{obj.__class__.__name__}(...)")774 else:775 node = Node(776 open_brace=f"{obj.__class__.__name__}(",777 close_brace=")",778 children=children,779 last=root,780 empty=f"{obj.__class__.__name__}()",781 )782 783 for last, field in loop_last(784 field785 for field in fields(obj)786 if field.repr and hasattr(obj, field.name)787 ):788 child_node = _traverse(getattr(obj, field.name), depth=depth + 1)789 child_node.key_repr = field.name790 child_node.last = last791 child_node.key_separator = "="792 append(child_node)793 794 pop_visited(obj_id)795 elif _is_namedtuple(obj) and _has_default_namedtuple_repr(obj):796 push_visited(obj_id)797 class_name = obj.__class__.__name__798 if reached_max_depth:799 # If we've reached the max depth, we still show the class name, but not its contents800 node = Node(801 value_repr=f"{class_name}(...)",802 )803 else:804 children = []805 append = children.append806 node = Node(807 open_brace=f"{class_name}(",808 close_brace=")",809 children=children,810 empty=f"{class_name}()",811 )812 for last, (key, value) in loop_last(obj._asdict().items()):813 child_node = _traverse(value, depth=depth + 1)814 child_node.key_repr = key815 child_node.last = last816 child_node.key_separator = "="817 append(child_node)818 pop_visited(obj_id)819 elif _safe_isinstance(obj, _CONTAINERS):820 for container_type in _CONTAINERS:821 if _safe_isinstance(obj, container_type):822 obj_type = container_type823 break824 825 push_visited(obj_id)826 827 open_brace, close_brace, empty = _BRACES[obj_type](obj)828 829 if reached_max_depth:830 node = Node(value_repr=f"{open_brace}...{close_brace}")831 elif obj_type.__repr__ != type(obj).__repr__:832 node = Node(value_repr=to_repr(obj), last=root)833 elif obj:834 children = []835 node = Node(836 open_brace=open_brace,837 close_brace=close_brace,838 children=children,839 last=root,840 )841 append = children.append842 num_items = len(obj)843 last_item_index = num_items - 1844 845 if _safe_isinstance(obj, _MAPPING_CONTAINERS):846 iter_items = iter(obj.items())847 if max_length is not None:848 iter_items = islice(iter_items, max_length)849 for index, (key, child) in enumerate(iter_items):850 child_node = _traverse(child, depth=depth + 1)851 child_node.key_repr = to_repr(key)852 child_node.last = index == last_item_index853 append(child_node)854 else:855 iter_values = iter(obj)856 if max_length is not None:857 iter_values = islice(iter_values, max_length)858 for index, child in enumerate(iter_values):859 child_node = _traverse(child, depth=depth + 1)860 child_node.last = index == last_item_index861 append(child_node)862 if max_length is not None and num_items > max_length:863 append(Node(value_repr=f"... +{num_items - max_length}", last=True))864 else:865 node = Node(empty=empty, children=[], last=root)866 867 pop_visited(obj_id)868 else:869 node = Node(value_repr=to_repr(obj), last=root)870 node.is_tuple = type(obj) == tuple871 node.is_namedtuple = _is_namedtuple(obj)872 return node873 874 node = _traverse(_object, root=True)875 return node876 877 878def pretty_repr(879 _object: Any,880 *,881 max_width: int = 80,882 indent_size: int = 4,883 max_length: Optional[int] = None,884 max_string: Optional[int] = None,885 max_depth: Optional[int] = None,886 expand_all: bool = False,887) -> str:888 """Prettify repr string by expanding on to new lines to fit within a given width.889 890 Args:891 _object (Any): Object to repr.892 max_width (int, optional): Desired maximum width of repr string. Defaults to 80.893 indent_size (int, optional): Number of spaces to indent. Defaults to 4.894 max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.895 Defaults to None.896 max_string (int, optional): Maximum length of string before truncating, or None to disable truncating.897 Defaults to None.898 max_depth (int, optional): Maximum depth of nested data structure, or None for no depth.899 Defaults to None.900 expand_all (bool, optional): Expand all containers regardless of available width. Defaults to False.901 902 Returns:903 str: A possibly multi-line representation of the object.904 """905 906 if _safe_isinstance(_object, Node):907 node = _object908 else:909 node = traverse(910 _object, max_length=max_length, max_string=max_string, max_depth=max_depth911 )912 repr_str: str = node.render(913 max_width=max_width, indent_size=indent_size, expand_all=expand_all914 )915 return repr_str916 917 918def pprint(919 _object: Any,920 *,921 console: Optional["Console"] = None,922 indent_guides: bool = True,923 max_length: Optional[int] = None,924 max_string: Optional[int] = None,925 max_depth: Optional[int] = None,926 expand_all: bool = False,927) -> None:928 """A convenience function for pretty printing.929 930 Args:931 _object (Any): Object to pretty print.932 console (Console, optional): Console instance, or None to use default. Defaults to None.933 max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.934 Defaults to None.935 max_string (int, optional): Maximum length of strings before truncating, or None to disable. Defaults to None.936 max_depth (int, optional): Maximum depth for nested data structures, or None for unlimited depth. Defaults to None.937 indent_guides (bool, optional): Enable indentation guides. Defaults to True.938 expand_all (bool, optional): Expand all containers. Defaults to False.939 """940 _console = get_console() if console is None else console941 _console.print(942 Pretty(943 _object,944 max_length=max_length,945 max_string=max_string,946 max_depth=max_depth,947 indent_guides=indent_guides,948 expand_all=expand_all,949 overflow="ignore",950 ),951 soft_wrap=True,952 )953 954 955if __name__ == "__main__": # pragma: no cover956 957 class BrokenRepr:958 def __repr__(self) -> str:959 1 / 0960 return "this will fail"961 962 from typing import NamedTuple963 964 class StockKeepingUnit(NamedTuple):965 name: str966 description: str967 price: float968 category: str969 reviews: List[str]970 971 d = defaultdict(int)972 d["foo"] = 5973 data = {974 "foo": [975 1,976 "Hello World!",977 100.123,978 323.232,979 432324.0,980 {5, 6, 7, (1, 2, 3, 4), 8},981 ],982 "bar": frozenset({1, 2, 3}),983 "defaultdict": defaultdict(984 list, {"crumble": ["apple", "rhubarb", "butter", "sugar", "flour"]}985 ),986 "counter": Counter(987 [988 "apple",989 "orange",990 "pear",991 "kumquat",992 "kumquat",993 "durian" * 100,994 ]995 ),996 "atomic": (False, True, None),997 "namedtuple": StockKeepingUnit(998 "Sparkling British Spring Water",999 "Carbonated spring water",1000 0.9,1001 "water",1002 ["its amazing!", "its terrible!"],1003 ),1004 "Broken": BrokenRepr(),1005 }1006 data["foo"].append(data) # type: ignore[attr-defined]1007 1008 from pip._vendor.rich import print1009 1010 print(Pretty(data, indent_guides=True, max_string=20))1011 1012 class Thing:1013 def __repr__(self) -> str:1014 return "Hello\x1b[38;5;239m World!"1015 1016 print(Pretty(Thing()))1017 