Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/rich/pretty.py: 21%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1import builtins
2import collections
3import dataclasses
4import inspect
5import os
6import reprlib
7import sys
8from array import array
9from collections import Counter, UserDict, UserList, defaultdict, deque
10from dataclasses import dataclass, fields, is_dataclass
11from inspect import isclass
12from itertools import islice
13from types import MappingProxyType
14from 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)
30from rich.repr import RichReprResult
32try:
33 import attr as _attr_module
35 _has_attrs = hasattr(_attr_module, "ib")
36except ImportError: # pragma: no cover
37 _has_attrs = False
39from . import get_console
40from ._loop import loop_last
41from ._pick import pick_bool
42from .abc import RichRenderable
43from .cells import cell_len
44from .highlighter import ReprHighlighter
45from .jupyter import JupyterMixin, JupyterRenderable
46from .measure import Measurement
47from .text import Text
49if TYPE_CHECKING:
50 from .console import (
51 Console,
52 ConsoleOptions,
53 HighlighterType,
54 JustifyMethod,
55 OverflowMethod,
56 RenderResult,
57 )
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))
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 []
70def _is_dataclass_repr(obj: object) -> bool:
71 """Check if an instance of a dataclass contains the default repr.
73 Args:
74 obj (object): A dataclass instance.
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 here
80 # Catching all exceptions in case something is missing on a non CPython implementation
81 try:
82 return obj.__repr__.__code__.co_filename in (
83 dataclasses.__file__,
84 reprlib.__file__,
85 )
86 except Exception: # pragma: no coverage
87 return False
90_dummy_namedtuple = collections.namedtuple("_dummy_namedtuple", [])
93def _has_default_namedtuple_repr(obj: object) -> bool:
94 """Check if an instance of namedtuple contains the default repr
96 Args:
97 obj (object): A namedtuple
99 Returns:
100 bool: True if the default repr is used, False if there's a custom repr.
101 """
102 obj_file = None
103 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 pass
109 default_repr_file = inspect.getfile(_dummy_namedtuple.__repr__)
110 return obj_file == default_repr_file
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 ConsoleRenderable
127 # always skip rich generated jupyter renderables or None values
128 if _safe_isinstance(value, JupyterRenderable) or value is None:
129 return None
131 console = console or get_console()
133 with console.capture() as capture:
134 # certain renderables should start on a new line
135 if _safe_isinstance(value, ConsoleRenderable):
136 console.line()
137 console.print(
138 (
139 value
140 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 repr
157 # I'm not sure if this should be prevented at a lower level
158 return capture.get().rstrip("\n")
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 False
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.
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 rich import get_console
197 console = console or get_console()
198 assert console is not None
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 None
204 builtins._ = None # type: ignore[attr-defined]
205 console.print(
206 (
207 value
208 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]
223 try:
224 ip = get_ipython() # type: ignore[name-defined]
225 except NameError:
226 sys.displayhook = display_hook
227 else:
228 from IPython.core.formatters import BaseFormatter
230 class RichFormatter(BaseFormatter): # type: ignore[misc]
231 pprint: bool = True
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)
248 # replace plain text formatter with rich formatter
249 rich_formatter = RichFormatter()
250 ip.display_formatter.formatters["text/plain"] = rich_formatter
253class Pretty(JupyterMixin):
254 """A rich renderable that pretty prints an object.
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 """
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 = _object
291 self.highlighter = highlighter or ReprHighlighter()
292 self.indent_size = indent_size
293 self.justify: Optional["JustifyMethod"] = justify
294 self.overflow: Optional["OverflowMethod"] = overflow
295 self.no_wrap = no_wrap
296 self.indent_guides = indent_guides
297 self.max_length = max_length
298 self.max_string = max_string
299 self.max_depth = max_depth
300 self.expand_all = expand_all
301 self.margin = margin
302 self.insert_line = insert_line
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_text
326 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_text
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 0
353 )
354 return Measurement(text_width, text_width)
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 )
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 )
375def _get_braces_for_array(_object: "array[Any]") -> Tuple[str, str, str]:
376 return (f"array({_object.typecode!r}, [", "])", f"array({_object.typecode!r})")
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)
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)
408@dataclass
409class Node:
410 """A node in a repr tree. May be atomic or a container."""
412 key_repr: str = ""
413 value_repr: str = ""
414 open_brace: str = ""
415 close_brace: str = ""
416 empty: str = ""
417 last: bool = False
418 is_tuple: bool = False
419 is_namedtuple: bool = False
420 children: Optional[List["Node"]] = None
421 key_separator: str = ": "
422 separator: str = ", "
424 def iter_tokens(self) -> Iterable[str]:
425 """Generate tokens for this node."""
426 if self.key_repr:
427 yield self.key_repr
428 yield self.key_separator
429 if self.value_repr:
430 yield self.value_repr
431 elif self.children is not None:
432 if self.children:
433 yield self.open_brace
434 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.separator
442 yield self.close_brace
443 else:
444 yield self.empty
446 def check_length(self, start_length: int, max_length: int) -> bool:
447 """Check the length fits within a limit.
449 Args:
450 start_length (int): Starting length of the line (indent, prefix, suffix).
451 max_length (int): Maximum length.
453 Returns:
454 bool: True if the node can be rendered within max length, otherwise False.
455 """
456 total_length = start_length
457 for token in self.iter_tokens():
458 total_length += cell_len(token)
459 if total_length > max_length:
460 return False
461 return True
463 def __str__(self) -> str:
464 repr_text = "".join(self.iter_tokens())
465 return repr_text
467 def render(
468 self, max_width: int = 80, indent_size: int = 4, expand_all: bool = False
469 ) -> str:
470 """Render the node to a pretty repr.
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.
477 Returns:
478 str: A repr string of the original object.
479 """
480 lines = [_Line(node=self, is_root=True)]
481 line_no = 0
482 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 += 1
489 repr_str = "\n".join(str(line) for line in lines)
490 return repr_str
493@dataclass
494class _Line:
495 """A line in repr output."""
497 parent: Optional["_Line"] = None
498 is_root: bool = False
499 node: Optional[Node] = None
500 text: str = ""
501 suffix: str = ""
502 whitespace: str = ""
503 expanded: bool = False
504 last: bool = False
506 @property
507 def expandable(self) -> bool:
508 """Check if the line may be expanded."""
509 return bool(self.node is not None and self.node.children)
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 None
517 return self.node.check_length(start_length, max_length)
519 def expand(self, indent_size: int) -> Iterable["_Line"]:
520 """Expand this line by adding children on their own line."""
521 node = self.node
522 assert node is not None
523 whitespace = self.whitespace
524 assert node.children
525 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_size
533 tuple_of_one = node.is_tuple and len(node.children) == 1
534 for last, child in loop_last(node.children):
535 separator = "," if tuple_of_one else node.separator
536 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 line
545 yield _Line(
546 text=node.close_brace,
547 whitespace=whitespace,
548 suffix=self.suffix,
549 last=self.last,
550 )
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 )
561def _is_namedtuple(obj: Any) -> bool:
562 """Checks if an object is most likely a namedtuple. It is possible
563 to craft an object that passes this check and isn't a namedtuple, but
564 there is only a minuscule chance of this happening unintentionally.
566 Args:
567 obj (Any): The object to test
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 namedtuple
576 return False
577 return isinstance(obj, tuple) and isinstance(fields, tuple)
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.
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.
597 Returns:
598 Node: The root of a tree structure which can be used to render a pretty repr.
599 """
601 def to_repr(obj: Any) -> str:
602 """Get repr string for an object, but catch errors."""
603 if (
604 max_string is not None
605 and _safe_isinstance(obj, (bytes, str))
606 and len(obj) > max_string
607 ):
608 truncated = len(obj) - max_string
609 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_repr
617 visited_ids: Set[int] = set()
618 push_visited = visited_ids.add
619 pop_visited = visited_ids.remove
621 def _traverse(obj: Any, root: bool = False, depth: int = 0) -> Node:
622 """Walk the object depth first."""
624 obj_id = id(obj)
625 if obj_id in visited_ids:
626 # Recursion detected
627 return Node(value_repr="...")
629 obj_type = type(obj)
630 children: List[Node]
631 reached_max_depth = max_depth is not None and depth >= max_depth
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 = arg
638 if default == child:
639 continue
640 yield key, child
641 elif len(arg) == 2:
642 key, child = arg
643 yield key, child
644 elif len(arg) == 1:
645 yield arg[0]
646 else:
647 yield arg
649 try:
650 fake_attributes = hasattr(
651 obj, "awehoi234_wdfjwljet234_234wdfoijsdfmmnxpi492"
652 )
653 except Exception:
654 fake_attributes = False
656 rich_repr_result: Optional[RichReprResult] = None
657 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 pass
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__
670 if args:
671 children = []
672 append = children.append
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 = arg
698 child_node = _traverse(child, depth=depth + 1)
699 child_node.last = last
700 child_node.key_repr = key
701 child_node.key_separator = "="
702 append(child_node)
703 else:
704 child_node = _traverse(arg, depth=depth + 1)
705 child_node.last = last
706 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.append
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 )
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 rarely
741 yield (attr.name, error, None)
742 else:
743 yield (
744 attr.name,
745 value,
746 attr.repr if callable(attr.repr) else None,
747 )
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 = last
755 child_node.key_repr = name
756 child_node.key_separator = "="
757 append(child_node)
758 else:
759 node = Node(
760 value_repr=f"{obj.__class__.__name__}()", children=[], last=root
761 )
762 pop_visited(obj_id)
763 elif (
764 is_dataclass(obj)
765 and not _safe_isinstance(obj, type)
766 and not fake_attributes
767 and _is_dataclass_repr(obj)
768 ):
769 push_visited(obj_id)
770 children = []
771 append = children.append
772 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 )
783 for last, field in loop_last(
784 field
785 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.name
790 child_node.last = last
791 child_node.key_separator = "="
792 append(child_node)
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 contents
800 node = Node(
801 value_repr=f"{class_name}(...)",
802 )
803 else:
804 children = []
805 append = children.append
806 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 = key
815 child_node.last = last
816 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_type
823 break
825 push_visited(obj_id)
827 open_brace, close_brace, empty = _BRACES[obj_type](obj)
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.append
842 num_items = len(obj)
843 last_item_index = num_items - 1
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_index
853 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_index
861 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)
867 pop_visited(obj_id)
868 else:
869 node = Node(value_repr=to_repr(obj), last=root)
870 node.is_tuple = type(obj) == tuple
871 node.is_namedtuple = _is_namedtuple(obj)
872 return node
874 node = _traverse(_object, root=True)
875 return node
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.
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.
902 Returns:
903 str: A possibly multi-line representation of the object.
904 """
906 if _safe_isinstance(_object, Node):
907 node = _object
908 else:
909 node = traverse(
910 _object, max_length=max_length, max_string=max_string, max_depth=max_depth
911 )
912 repr_str: str = node.render(
913 max_width=max_width, indent_size=indent_size, expand_all=expand_all
914 )
915 return repr_str
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.
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 console
941 _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 )
955if __name__ == "__main__": # pragma: no cover
957 class BrokenRepr:
958 def __repr__(self) -> str:
959 1 / 0
960 return "this will fail"
962 from typing import NamedTuple
964 class StockKeepingUnit(NamedTuple):
965 name: str
966 description: str
967 price: float
968 category: str
969 reviews: List[str]
971 d = defaultdict(int)
972 d["foo"] = 5
973 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]
1008 from rich import print
1010 print(Pretty(data, indent_guides=True, max_string=20))
1012 class Thing:
1013 def __repr__(self) -> str:
1014 return "Hello\x1b[38;5;239m World!"
1016 print(Pretty(Thing()))