Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/rich/pretty.py: 21%
396 statements
« prev ^ index » next coverage.py v7.0.1, created at 2022-12-25 06:11 +0000
« prev ^ index » next coverage.py v7.0.1, created at 2022-12-25 06:11 +0000
1import builtins
2import collections
3import dataclasses
4import inspect
5import os
6import sys
7from array import array
8from collections import Counter, UserDict, UserList, defaultdict, deque
9from dataclasses import dataclass, fields, is_dataclass
10from inspect import isclass
11from itertools import islice
12from types import MappingProxyType
13from typing import (
14 TYPE_CHECKING,
15 Any,
16 Callable,
17 DefaultDict,
18 Dict,
19 Iterable,
20 List,
21 Optional,
22 Sequence,
23 Set,
24 Tuple,
25 Union,
26)
28from rich.repr import RichReprResult
30try:
31 import attr as _attr_module
33 _has_attrs = True
34except ImportError: # pragma: no cover
35 _has_attrs = False
37from . import get_console
38from ._loop import loop_last
39from ._pick import pick_bool
40from .abc import RichRenderable
41from .cells import cell_len
42from .highlighter import ReprHighlighter
43from .jupyter import JupyterMixin, JupyterRenderable
44from .measure import Measurement
45from .text import Text
47if TYPE_CHECKING:
48 from .console import (
49 Console,
50 ConsoleOptions,
51 HighlighterType,
52 JustifyMethod,
53 OverflowMethod,
54 RenderResult,
55 )
58JUPYTER_CLASSES_TO_NOT_RENDER = {
59 # Matplotlib "Artists" manage their own rendering in a Jupyter notebook, and we should not try to render them too.
60 # "Typically, all [Matplotlib] visible elements in a figure are subclasses of Artist."
61 "matplotlib.artist.Artist",
62}
65def _is_attr_object(obj: Any) -> bool:
66 """Check if an object was created with attrs module."""
67 return _has_attrs and _attr_module.has(type(obj))
70def _get_attr_fields(obj: Any) -> Sequence["_attr_module.Attribute[Any]"]:
71 """Get fields for an attrs object."""
72 return _attr_module.fields(type(obj)) if _has_attrs else []
75def _is_dataclass_repr(obj: object) -> bool:
76 """Check if an instance of a dataclass contains the default repr.
78 Args:
79 obj (object): A dataclass instance.
81 Returns:
82 bool: True if the default repr is used, False if there is a custom repr.
83 """
84 # Digging in to a lot of internals here
85 # Catching all exceptions in case something is missing on a non CPython implementation
86 try:
87 return obj.__repr__.__code__.co_filename == dataclasses.__file__
88 except Exception: # pragma: no coverage
89 return False
92_dummy_namedtuple = collections.namedtuple("_dummy_namedtuple", [])
95def _has_default_namedtuple_repr(obj: object) -> bool:
96 """Check if an instance of namedtuple contains the default repr
98 Args:
99 obj (object): A namedtuple
101 Returns:
102 bool: True if the default repr is used, False if there's a custom repr.
103 """
104 obj_file = None
105 try:
106 obj_file = inspect.getfile(obj.__repr__)
107 except (OSError, TypeError):
108 # OSError handles case where object is defined in __main__ scope, e.g. REPL - no filename available.
109 # TypeError trapped defensively, in case of object without filename slips through.
110 pass
111 default_repr_file = inspect.getfile(_dummy_namedtuple.__repr__)
112 return obj_file == default_repr_file
115def _ipy_display_hook(
116 value: Any,
117 console: Optional["Console"] = None,
118 overflow: "OverflowMethod" = "ignore",
119 crop: bool = False,
120 indent_guides: bool = False,
121 max_length: Optional[int] = None,
122 max_string: Optional[int] = None,
123 max_depth: Optional[int] = None,
124 expand_all: bool = False,
125) -> None:
126 # needed here to prevent circular import:
127 from ._inspect import is_object_one_of_types
128 from .console import ConsoleRenderable
130 # always skip rich generated jupyter renderables or None values
131 if _safe_isinstance(value, JupyterRenderable) or value is None:
132 return
134 console = console or get_console()
135 if console.is_jupyter:
136 # Delegate rendering to IPython if the object (and IPython) supports it
137 # https://ipython.readthedocs.io/en/stable/config/integrating.html#rich-display
138 ipython_repr_methods = [
139 "_repr_html_",
140 "_repr_markdown_",
141 "_repr_json_",
142 "_repr_latex_",
143 "_repr_jpeg_",
144 "_repr_png_",
145 "_repr_svg_",
146 "_repr_mimebundle_",
147 ]
148 for repr_method in ipython_repr_methods:
149 method = getattr(value, repr_method, None)
150 if inspect.ismethod(method):
151 # Calling the method ourselves isn't ideal. The interface for the `_repr_*_` methods
152 # specifies that if they return None, then they should not be rendered
153 # by the notebook.
154 try:
155 repr_result = method()
156 except Exception:
157 continue # If the method raises, treat it as if it doesn't exist, try any others
158 if repr_result is not None:
159 return # Delegate rendering to IPython
161 # When in a Jupyter notebook let's avoid the display of some specific classes,
162 # as they result in the rendering of useless and noisy lines such as `<Figure size 432x288 with 1 Axes>`.
163 # What does this do?
164 # --> if the class has "matplotlib.artist.Artist" in its hierarchy for example, we don't render it.
165 if is_object_one_of_types(value, JUPYTER_CLASSES_TO_NOT_RENDER):
166 return
168 # certain renderables should start on a new line
169 if _safe_isinstance(value, ConsoleRenderable):
170 console.line()
172 console.print(
173 value
174 if _safe_isinstance(value, RichRenderable)
175 else Pretty(
176 value,
177 overflow=overflow,
178 indent_guides=indent_guides,
179 max_length=max_length,
180 max_string=max_string,
181 max_depth=max_depth,
182 expand_all=expand_all,
183 margin=12,
184 ),
185 crop=crop,
186 new_line_start=True,
187 )
190def _safe_isinstance(
191 obj: object, class_or_tuple: Union[type, Tuple[type, ...]]
192) -> bool:
193 """isinstance can fail in rare cases, for example types with no __class__"""
194 try:
195 return isinstance(obj, class_or_tuple)
196 except Exception:
197 return False
200def install(
201 console: Optional["Console"] = None,
202 overflow: "OverflowMethod" = "ignore",
203 crop: bool = False,
204 indent_guides: bool = False,
205 max_length: Optional[int] = None,
206 max_string: Optional[int] = None,
207 max_depth: Optional[int] = None,
208 expand_all: bool = False,
209) -> None:
210 """Install automatic pretty printing in the Python REPL.
212 Args:
213 console (Console, optional): Console instance or ``None`` to use global console. Defaults to None.
214 overflow (Optional[OverflowMethod], optional): Overflow method. Defaults to "ignore".
215 crop (Optional[bool], optional): Enable cropping of long lines. Defaults to False.
216 indent_guides (bool, optional): Enable indentation guides. Defaults to False.
217 max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
218 Defaults to None.
219 max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to None.
220 max_depth (int, optional): Maximum depth of nested data structures, or None for no maximum. Defaults to None.
221 expand_all (bool, optional): Expand all containers. Defaults to False.
222 max_frames (int): Maximum number of frames to show in a traceback, 0 for no maximum. Defaults to 100.
223 """
224 from rich import get_console
226 console = console or get_console()
227 assert console is not None
229 def display_hook(value: Any) -> None:
230 """Replacement sys.displayhook which prettifies objects with Rich."""
231 if value is not None:
232 assert console is not None
233 builtins._ = None # type: ignore[attr-defined]
234 console.print(
235 value
236 if _safe_isinstance(value, RichRenderable)
237 else Pretty(
238 value,
239 overflow=overflow,
240 indent_guides=indent_guides,
241 max_length=max_length,
242 max_string=max_string,
243 max_depth=max_depth,
244 expand_all=expand_all,
245 ),
246 crop=crop,
247 )
248 builtins._ = value # type: ignore[attr-defined]
250 try: # pragma: no cover
251 ip = get_ipython() # type: ignore[name-defined]
252 from IPython.core.formatters import BaseFormatter
254 class RichFormatter(BaseFormatter): # type: ignore[misc]
255 pprint: bool = True
257 def __call__(self, value: Any) -> Any:
258 if self.pprint:
259 return _ipy_display_hook(
260 value,
261 console=get_console(),
262 overflow=overflow,
263 indent_guides=indent_guides,
264 max_length=max_length,
265 max_string=max_string,
266 max_depth=max_depth,
267 expand_all=expand_all,
268 )
269 else:
270 return repr(value)
272 # replace plain text formatter with rich formatter
273 rich_formatter = RichFormatter()
274 ip.display_formatter.formatters["text/plain"] = rich_formatter
275 except Exception:
276 sys.displayhook = display_hook
279class Pretty(JupyterMixin):
280 """A rich renderable that pretty prints an object.
282 Args:
283 _object (Any): An object to pretty print.
284 highlighter (HighlighterType, optional): Highlighter object to apply to result, or None for ReprHighlighter. Defaults to None.
285 indent_size (int, optional): Number of spaces in indent. Defaults to 4.
286 justify (JustifyMethod, optional): Justify method, or None for default. Defaults to None.
287 overflow (OverflowMethod, optional): Overflow method, or None for default. Defaults to None.
288 no_wrap (Optional[bool], optional): Disable word wrapping. Defaults to False.
289 indent_guides (bool, optional): Enable indentation guides. Defaults to False.
290 max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
291 Defaults to None.
292 max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to None.
293 max_depth (int, optional): Maximum depth of nested data structures, or None for no maximum. Defaults to None.
294 expand_all (bool, optional): Expand all containers. Defaults to False.
295 margin (int, optional): Subtrace a margin from width to force containers to expand earlier. Defaults to 0.
296 insert_line (bool, optional): Insert a new line if the output has multiple new lines. Defaults to False.
297 """
299 def __init__(
300 self,
301 _object: Any,
302 highlighter: Optional["HighlighterType"] = None,
303 *,
304 indent_size: int = 4,
305 justify: Optional["JustifyMethod"] = None,
306 overflow: Optional["OverflowMethod"] = None,
307 no_wrap: Optional[bool] = False,
308 indent_guides: bool = False,
309 max_length: Optional[int] = None,
310 max_string: Optional[int] = None,
311 max_depth: Optional[int] = None,
312 expand_all: bool = False,
313 margin: int = 0,
314 insert_line: bool = False,
315 ) -> None:
316 self._object = _object
317 self.highlighter = highlighter or ReprHighlighter()
318 self.indent_size = indent_size
319 self.justify: Optional["JustifyMethod"] = justify
320 self.overflow: Optional["OverflowMethod"] = overflow
321 self.no_wrap = no_wrap
322 self.indent_guides = indent_guides
323 self.max_length = max_length
324 self.max_string = max_string
325 self.max_depth = max_depth
326 self.expand_all = expand_all
327 self.margin = margin
328 self.insert_line = insert_line
330 def __rich_console__(
331 self, console: "Console", options: "ConsoleOptions"
332 ) -> "RenderResult":
333 pretty_str = pretty_repr(
334 self._object,
335 max_width=options.max_width - self.margin,
336 indent_size=self.indent_size,
337 max_length=self.max_length,
338 max_string=self.max_string,
339 max_depth=self.max_depth,
340 expand_all=self.expand_all,
341 )
342 pretty_text = Text.from_ansi(
343 pretty_str,
344 justify=self.justify or options.justify,
345 overflow=self.overflow or options.overflow,
346 no_wrap=pick_bool(self.no_wrap, options.no_wrap),
347 style="pretty",
348 )
349 pretty_text = (
350 self.highlighter(pretty_text)
351 if pretty_text
352 else Text(
353 f"{type(self._object)}.__repr__ returned empty string",
354 style="dim italic",
355 )
356 )
357 if self.indent_guides and not options.ascii_only:
358 pretty_text = pretty_text.with_indent_guides(
359 self.indent_size, style="repr.indent"
360 )
361 if self.insert_line and "\n" in pretty_text:
362 yield ""
363 yield pretty_text
365 def __rich_measure__(
366 self, console: "Console", options: "ConsoleOptions"
367 ) -> "Measurement":
368 pretty_str = pretty_repr(
369 self._object,
370 max_width=options.max_width,
371 indent_size=self.indent_size,
372 max_length=self.max_length,
373 max_string=self.max_string,
374 expand_all=self.expand_all,
375 )
376 text_width = (
377 max(cell_len(line) for line in pretty_str.splitlines()) if pretty_str else 0
378 )
379 return Measurement(text_width, text_width)
382def _get_braces_for_defaultdict(_object: DefaultDict[Any, Any]) -> Tuple[str, str, str]:
383 return (
384 f"defaultdict({_object.default_factory!r}, {{",
385 "})",
386 f"defaultdict({_object.default_factory!r}, {{}})",
387 )
390def _get_braces_for_array(_object: "array[Any]") -> Tuple[str, str, str]:
391 return (f"array({_object.typecode!r}, [", "])", f"array({_object.typecode!r})")
394_BRACES: Dict[type, Callable[[Any], Tuple[str, str, str]]] = {
395 os._Environ: lambda _object: ("environ({", "})", "environ({})"),
396 array: _get_braces_for_array,
397 defaultdict: _get_braces_for_defaultdict,
398 Counter: lambda _object: ("Counter({", "})", "Counter()"),
399 deque: lambda _object: ("deque([", "])", "deque()"),
400 dict: lambda _object: ("{", "}", "{}"),
401 UserDict: lambda _object: ("{", "}", "{}"),
402 frozenset: lambda _object: ("frozenset({", "})", "frozenset()"),
403 list: lambda _object: ("[", "]", "[]"),
404 UserList: lambda _object: ("[", "]", "[]"),
405 set: lambda _object: ("{", "}", "set()"),
406 tuple: lambda _object: ("(", ")", "()"),
407 MappingProxyType: lambda _object: ("mappingproxy({", "})", "mappingproxy({})"),
408}
409_CONTAINERS = tuple(_BRACES.keys())
410_MAPPING_CONTAINERS = (dict, os._Environ, MappingProxyType, UserDict)
413def is_expandable(obj: Any) -> bool:
414 """Check if an object may be expanded by pretty print."""
415 return (
416 _safe_isinstance(obj, _CONTAINERS)
417 or (is_dataclass(obj))
418 or (hasattr(obj, "__rich_repr__"))
419 or _is_attr_object(obj)
420 ) and not isclass(obj)
423@dataclass
424class Node:
425 """A node in a repr tree. May be atomic or a container."""
427 key_repr: str = ""
428 value_repr: str = ""
429 open_brace: str = ""
430 close_brace: str = ""
431 empty: str = ""
432 last: bool = False
433 is_tuple: bool = False
434 is_namedtuple: bool = False
435 children: Optional[List["Node"]] = None
436 key_separator = ": "
437 separator: str = ", "
439 def iter_tokens(self) -> Iterable[str]:
440 """Generate tokens for this node."""
441 if self.key_repr:
442 yield self.key_repr
443 yield self.key_separator
444 if self.value_repr:
445 yield self.value_repr
446 elif self.children is not None:
447 if self.children:
448 yield self.open_brace
449 if self.is_tuple and not self.is_namedtuple and len(self.children) == 1:
450 yield from self.children[0].iter_tokens()
451 yield ","
452 else:
453 for child in self.children:
454 yield from child.iter_tokens()
455 if not child.last:
456 yield self.separator
457 yield self.close_brace
458 else:
459 yield self.empty
461 def check_length(self, start_length: int, max_length: int) -> bool:
462 """Check the length fits within a limit.
464 Args:
465 start_length (int): Starting length of the line (indent, prefix, suffix).
466 max_length (int): Maximum length.
468 Returns:
469 bool: True if the node can be rendered within max length, otherwise False.
470 """
471 total_length = start_length
472 for token in self.iter_tokens():
473 total_length += cell_len(token)
474 if total_length > max_length:
475 return False
476 return True
478 def __str__(self) -> str:
479 repr_text = "".join(self.iter_tokens())
480 return repr_text
482 def render(
483 self, max_width: int = 80, indent_size: int = 4, expand_all: bool = False
484 ) -> str:
485 """Render the node to a pretty repr.
487 Args:
488 max_width (int, optional): Maximum width of the repr. Defaults to 80.
489 indent_size (int, optional): Size of indents. Defaults to 4.
490 expand_all (bool, optional): Expand all levels. Defaults to False.
492 Returns:
493 str: A repr string of the original object.
494 """
495 lines = [_Line(node=self, is_root=True)]
496 line_no = 0
497 while line_no < len(lines):
498 line = lines[line_no]
499 if line.expandable and not line.expanded:
500 if expand_all or not line.check_length(max_width):
501 lines[line_no : line_no + 1] = line.expand(indent_size)
502 line_no += 1
504 repr_str = "\n".join(str(line) for line in lines)
505 return repr_str
508@dataclass
509class _Line:
510 """A line in repr output."""
512 parent: Optional["_Line"] = None
513 is_root: bool = False
514 node: Optional[Node] = None
515 text: str = ""
516 suffix: str = ""
517 whitespace: str = ""
518 expanded: bool = False
519 last: bool = False
521 @property
522 def expandable(self) -> bool:
523 """Check if the line may be expanded."""
524 return bool(self.node is not None and self.node.children)
526 def check_length(self, max_length: int) -> bool:
527 """Check this line fits within a given number of cells."""
528 start_length = (
529 len(self.whitespace) + cell_len(self.text) + cell_len(self.suffix)
530 )
531 assert self.node is not None
532 return self.node.check_length(start_length, max_length)
534 def expand(self, indent_size: int) -> Iterable["_Line"]:
535 """Expand this line by adding children on their own line."""
536 node = self.node
537 assert node is not None
538 whitespace = self.whitespace
539 assert node.children
540 if node.key_repr:
541 new_line = yield _Line(
542 text=f"{node.key_repr}{node.key_separator}{node.open_brace}",
543 whitespace=whitespace,
544 )
545 else:
546 new_line = yield _Line(text=node.open_brace, whitespace=whitespace)
547 child_whitespace = self.whitespace + " " * indent_size
548 tuple_of_one = node.is_tuple and len(node.children) == 1
549 for last, child in loop_last(node.children):
550 separator = "," if tuple_of_one else node.separator
551 line = _Line(
552 parent=new_line,
553 node=child,
554 whitespace=child_whitespace,
555 suffix=separator,
556 last=last and not tuple_of_one,
557 )
558 yield line
560 yield _Line(
561 text=node.close_brace,
562 whitespace=whitespace,
563 suffix=self.suffix,
564 last=self.last,
565 )
567 def __str__(self) -> str:
568 if self.last:
569 return f"{self.whitespace}{self.text}{self.node or ''}"
570 else:
571 return (
572 f"{self.whitespace}{self.text}{self.node or ''}{self.suffix.rstrip()}"
573 )
576def _is_namedtuple(obj: Any) -> bool:
577 """Checks if an object is most likely a namedtuple. It is possible
578 to craft an object that passes this check and isn't a namedtuple, but
579 there is only a minuscule chance of this happening unintentionally.
581 Args:
582 obj (Any): The object to test
584 Returns:
585 bool: True if the object is a namedtuple. False otherwise.
586 """
587 try:
588 fields = getattr(obj, "_fields", None)
589 except Exception:
590 # Being very defensive - if we cannot get the attr then its not a namedtuple
591 return False
592 return isinstance(obj, tuple) and isinstance(fields, tuple)
595def traverse(
596 _object: Any,
597 max_length: Optional[int] = None,
598 max_string: Optional[int] = None,
599 max_depth: Optional[int] = None,
600) -> Node:
601 """Traverse object and generate a tree.
603 Args:
604 _object (Any): Object to be traversed.
605 max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
606 Defaults to None.
607 max_string (int, optional): Maximum length of string before truncating, or None to disable truncating.
608 Defaults to None.
609 max_depth (int, optional): Maximum depth of data structures, or None for no maximum.
610 Defaults to None.
612 Returns:
613 Node: The root of a tree structure which can be used to render a pretty repr.
614 """
616 def to_repr(obj: Any) -> str:
617 """Get repr string for an object, but catch errors."""
618 if (
619 max_string is not None
620 and _safe_isinstance(obj, (bytes, str))
621 and len(obj) > max_string
622 ):
623 truncated = len(obj) - max_string
624 obj_repr = f"{obj[:max_string]!r}+{truncated}"
625 else:
626 try:
627 obj_repr = repr(obj)
628 except Exception as error:
629 obj_repr = f"<repr-error {str(error)!r}>"
630 return obj_repr
632 visited_ids: Set[int] = set()
633 push_visited = visited_ids.add
634 pop_visited = visited_ids.remove
636 def _traverse(obj: Any, root: bool = False, depth: int = 0) -> Node:
637 """Walk the object depth first."""
639 obj_id = id(obj)
640 if obj_id in visited_ids:
641 # Recursion detected
642 return Node(value_repr="...")
644 obj_type = type(obj)
645 py_version = (sys.version_info.major, sys.version_info.minor)
646 children: List[Node]
647 reached_max_depth = max_depth is not None and depth >= max_depth
649 def iter_rich_args(rich_args: Any) -> Iterable[Union[Any, Tuple[str, Any]]]:
650 for arg in rich_args:
651 if _safe_isinstance(arg, tuple):
652 if len(arg) == 3:
653 key, child, default = arg
654 if default == child:
655 continue
656 yield key, child
657 elif len(arg) == 2:
658 key, child = arg
659 yield key, child
660 elif len(arg) == 1:
661 yield arg[0]
662 else:
663 yield arg
665 try:
666 fake_attributes = hasattr(
667 obj, "awehoi234_wdfjwljet234_234wdfoijsdfmmnxpi492"
668 )
669 except Exception:
670 fake_attributes = False
672 rich_repr_result: Optional[RichReprResult] = None
673 if not fake_attributes:
674 try:
675 if hasattr(obj, "__rich_repr__") and not isclass(obj):
676 rich_repr_result = obj.__rich_repr__()
677 except Exception:
678 pass
680 if rich_repr_result is not None:
681 push_visited(obj_id)
682 angular = getattr(obj.__rich_repr__, "angular", False)
683 args = list(iter_rich_args(rich_repr_result))
684 class_name = obj.__class__.__name__
686 if args:
687 children = []
688 append = children.append
690 if reached_max_depth:
691 if angular:
692 node = Node(value_repr=f"<{class_name}...>")
693 else:
694 node = Node(value_repr=f"{class_name}(...)")
695 else:
696 if angular:
697 node = Node(
698 open_brace=f"<{class_name} ",
699 close_brace=">",
700 children=children,
701 last=root,
702 separator=" ",
703 )
704 else:
705 node = Node(
706 open_brace=f"{class_name}(",
707 close_brace=")",
708 children=children,
709 last=root,
710 )
711 for last, arg in loop_last(args):
712 if _safe_isinstance(arg, tuple):
713 key, child = arg
714 child_node = _traverse(child, depth=depth + 1)
715 child_node.last = last
716 child_node.key_repr = key
717 child_node.key_separator = "="
718 append(child_node)
719 else:
720 child_node = _traverse(arg, depth=depth + 1)
721 child_node.last = last
722 append(child_node)
723 else:
724 node = Node(
725 value_repr=f"<{class_name}>" if angular else f"{class_name}()",
726 children=[],
727 last=root,
728 )
729 pop_visited(obj_id)
730 elif _is_attr_object(obj) and not fake_attributes:
731 push_visited(obj_id)
732 children = []
733 append = children.append
735 attr_fields = _get_attr_fields(obj)
736 if attr_fields:
737 if reached_max_depth:
738 node = Node(value_repr=f"{obj.__class__.__name__}(...)")
739 else:
740 node = Node(
741 open_brace=f"{obj.__class__.__name__}(",
742 close_brace=")",
743 children=children,
744 last=root,
745 )
747 def iter_attrs() -> Iterable[
748 Tuple[str, Any, Optional[Callable[[Any], str]]]
749 ]:
750 """Iterate over attr fields and values."""
751 for attr in attr_fields:
752 if attr.repr:
753 try:
754 value = getattr(obj, attr.name)
755 except Exception as error:
756 # Can happen, albeit rarely
757 yield (attr.name, error, None)
758 else:
759 yield (
760 attr.name,
761 value,
762 attr.repr if callable(attr.repr) else None,
763 )
765 for last, (name, value, repr_callable) in loop_last(iter_attrs()):
766 if repr_callable:
767 child_node = Node(value_repr=str(repr_callable(value)))
768 else:
769 child_node = _traverse(value, depth=depth + 1)
770 child_node.last = last
771 child_node.key_repr = name
772 child_node.key_separator = "="
773 append(child_node)
774 else:
775 node = Node(
776 value_repr=f"{obj.__class__.__name__}()", children=[], last=root
777 )
778 pop_visited(obj_id)
779 elif (
780 is_dataclass(obj)
781 and not _safe_isinstance(obj, type)
782 and not fake_attributes
783 and (_is_dataclass_repr(obj) or py_version == (3, 6))
784 ):
785 push_visited(obj_id)
786 children = []
787 append = children.append
788 if reached_max_depth:
789 node = Node(value_repr=f"{obj.__class__.__name__}(...)")
790 else:
791 node = Node(
792 open_brace=f"{obj.__class__.__name__}(",
793 close_brace=")",
794 children=children,
795 last=root,
796 )
798 for last, field in loop_last(
799 field for field in fields(obj) if field.repr
800 ):
801 child_node = _traverse(getattr(obj, field.name), depth=depth + 1)
802 child_node.key_repr = field.name
803 child_node.last = last
804 child_node.key_separator = "="
805 append(child_node)
807 pop_visited(obj_id)
808 elif _is_namedtuple(obj) and _has_default_namedtuple_repr(obj):
809 push_visited(obj_id)
810 class_name = obj.__class__.__name__
811 if reached_max_depth:
812 # If we've reached the max depth, we still show the class name, but not its contents
813 node = Node(
814 value_repr=f"{class_name}(...)",
815 )
816 else:
817 children = []
818 append = children.append
819 node = Node(
820 open_brace=f"{class_name}(",
821 close_brace=")",
822 children=children,
823 empty=f"{class_name}()",
824 )
825 for last, (key, value) in loop_last(obj._asdict().items()):
826 child_node = _traverse(value, depth=depth + 1)
827 child_node.key_repr = key
828 child_node.last = last
829 child_node.key_separator = "="
830 append(child_node)
831 pop_visited(obj_id)
832 elif _safe_isinstance(obj, _CONTAINERS):
833 for container_type in _CONTAINERS:
834 if _safe_isinstance(obj, container_type):
835 obj_type = container_type
836 break
838 push_visited(obj_id)
840 open_brace, close_brace, empty = _BRACES[obj_type](obj)
842 if reached_max_depth:
843 node = Node(value_repr=f"{open_brace}...{close_brace}")
844 elif obj_type.__repr__ != type(obj).__repr__:
845 node = Node(value_repr=to_repr(obj), last=root)
846 elif obj:
847 children = []
848 node = Node(
849 open_brace=open_brace,
850 close_brace=close_brace,
851 children=children,
852 last=root,
853 )
854 append = children.append
855 num_items = len(obj)
856 last_item_index = num_items - 1
858 if _safe_isinstance(obj, _MAPPING_CONTAINERS):
859 iter_items = iter(obj.items())
860 if max_length is not None:
861 iter_items = islice(iter_items, max_length)
862 for index, (key, child) in enumerate(iter_items):
863 child_node = _traverse(child, depth=depth + 1)
864 child_node.key_repr = to_repr(key)
865 child_node.last = index == last_item_index
866 append(child_node)
867 else:
868 iter_values = iter(obj)
869 if max_length is not None:
870 iter_values = islice(iter_values, max_length)
871 for index, child in enumerate(iter_values):
872 child_node = _traverse(child, depth=depth + 1)
873 child_node.last = index == last_item_index
874 append(child_node)
875 if max_length is not None and num_items > max_length:
876 append(Node(value_repr=f"... +{num_items - max_length}", last=True))
877 else:
878 node = Node(empty=empty, children=[], last=root)
880 pop_visited(obj_id)
881 else:
882 node = Node(value_repr=to_repr(obj), last=root)
883 node.is_tuple = _safe_isinstance(obj, tuple)
884 node.is_namedtuple = _is_namedtuple(obj)
885 return node
887 node = _traverse(_object, root=True)
888 return node
891def pretty_repr(
892 _object: Any,
893 *,
894 max_width: int = 80,
895 indent_size: int = 4,
896 max_length: Optional[int] = None,
897 max_string: Optional[int] = None,
898 max_depth: Optional[int] = None,
899 expand_all: bool = False,
900) -> str:
901 """Prettify repr string by expanding on to new lines to fit within a given width.
903 Args:
904 _object (Any): Object to repr.
905 max_width (int, optional): Desired maximum width of repr string. Defaults to 80.
906 indent_size (int, optional): Number of spaces to indent. Defaults to 4.
907 max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
908 Defaults to None.
909 max_string (int, optional): Maximum length of string before truncating, or None to disable truncating.
910 Defaults to None.
911 max_depth (int, optional): Maximum depth of nested data structure, or None for no depth.
912 Defaults to None.
913 expand_all (bool, optional): Expand all containers regardless of available width. Defaults to False.
915 Returns:
916 str: A possibly multi-line representation of the object.
917 """
919 if _safe_isinstance(_object, Node):
920 node = _object
921 else:
922 node = traverse(
923 _object, max_length=max_length, max_string=max_string, max_depth=max_depth
924 )
925 repr_str: str = node.render(
926 max_width=max_width, indent_size=indent_size, expand_all=expand_all
927 )
928 return repr_str
931def pprint(
932 _object: Any,
933 *,
934 console: Optional["Console"] = None,
935 indent_guides: bool = True,
936 max_length: Optional[int] = None,
937 max_string: Optional[int] = None,
938 max_depth: Optional[int] = None,
939 expand_all: bool = False,
940) -> None:
941 """A convenience function for pretty printing.
943 Args:
944 _object (Any): Object to pretty print.
945 console (Console, optional): Console instance, or None to use default. Defaults to None.
946 max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
947 Defaults to None.
948 max_string (int, optional): Maximum length of strings before truncating, or None to disable. Defaults to None.
949 max_depth (int, optional): Maximum depth for nested data structures, or None for unlimited depth. Defaults to None.
950 indent_guides (bool, optional): Enable indentation guides. Defaults to True.
951 expand_all (bool, optional): Expand all containers. Defaults to False.
952 """
953 _console = get_console() if console is None else console
954 _console.print(
955 Pretty(
956 _object,
957 max_length=max_length,
958 max_string=max_string,
959 max_depth=max_depth,
960 indent_guides=indent_guides,
961 expand_all=expand_all,
962 overflow="ignore",
963 ),
964 soft_wrap=True,
965 )
968if __name__ == "__main__": # pragma: no cover
970 class BrokenRepr:
971 def __repr__(self) -> str:
972 1 / 0
973 return "this will fail"
975 from typing import NamedTuple
977 class StockKeepingUnit(NamedTuple):
978 name: str
979 description: str
980 price: float
981 category: str
982 reviews: List[str]
984 d = defaultdict(int)
985 d["foo"] = 5
986 data = {
987 "foo": [
988 1,
989 "Hello World!",
990 100.123,
991 323.232,
992 432324.0,
993 {5, 6, 7, (1, 2, 3, 4), 8},
994 ],
995 "bar": frozenset({1, 2, 3}),
996 "defaultdict": defaultdict(
997 list, {"crumble": ["apple", "rhubarb", "butter", "sugar", "flour"]}
998 ),
999 "counter": Counter(
1000 [
1001 "apple",
1002 "orange",
1003 "pear",
1004 "kumquat",
1005 "kumquat",
1006 "durian" * 100,
1007 ]
1008 ),
1009 "atomic": (False, True, None),
1010 "namedtuple": StockKeepingUnit(
1011 "Sparkling British Spring Water",
1012 "Carbonated spring water",
1013 0.9,
1014 "water",
1015 ["its amazing!", "its terrible!"],
1016 ),
1017 "Broken": BrokenRepr(),
1018 }
1019 data["foo"].append(data) # type: ignore[attr-defined]
1021 from rich import print
1023 # print(Pretty(data, indent_guides=True, max_string=20))
1025 class Thing:
1026 def __repr__(self) -> str:
1027 return "Hello\x1b[38;5;239m World!"
1029 print(Pretty(Thing()))