Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/rich/pretty.py: 20%
393 statements
« prev ^ index » next coverage.py v7.3.1, created at 2023-09-18 06:13 +0000
« prev ^ index » next coverage.py v7.3.1, created at 2023-09-18 06:13 +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 = hasattr(_attr_module, "ib")
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 )
58def _is_attr_object(obj: Any) -> bool:
59 """Check if an object was created with attrs module."""
60 return _has_attrs and _attr_module.has(type(obj))
63def _get_attr_fields(obj: Any) -> Sequence["_attr_module.Attribute[Any]"]:
64 """Get fields for an attrs object."""
65 return _attr_module.fields(type(obj)) if _has_attrs else []
68def _is_dataclass_repr(obj: object) -> bool:
69 """Check if an instance of a dataclass contains the default repr.
71 Args:
72 obj (object): A dataclass instance.
74 Returns:
75 bool: True if the default repr is used, False if there is a custom repr.
76 """
77 # Digging in to a lot of internals here
78 # Catching all exceptions in case something is missing on a non CPython implementation
79 try:
80 return obj.__repr__.__code__.co_filename == dataclasses.__file__
81 except Exception: # pragma: no coverage
82 return False
85_dummy_namedtuple = collections.namedtuple("_dummy_namedtuple", [])
88def _has_default_namedtuple_repr(obj: object) -> bool:
89 """Check if an instance of namedtuple contains the default repr
91 Args:
92 obj (object): A namedtuple
94 Returns:
95 bool: True if the default repr is used, False if there's a custom repr.
96 """
97 obj_file = None
98 try:
99 obj_file = inspect.getfile(obj.__repr__)
100 except (OSError, TypeError):
101 # OSError handles case where object is defined in __main__ scope, e.g. REPL - no filename available.
102 # TypeError trapped defensively, in case of object without filename slips through.
103 pass
104 default_repr_file = inspect.getfile(_dummy_namedtuple.__repr__)
105 return obj_file == default_repr_file
108def _ipy_display_hook(
109 value: Any,
110 console: Optional["Console"] = None,
111 overflow: "OverflowMethod" = "ignore",
112 crop: bool = False,
113 indent_guides: bool = False,
114 max_length: Optional[int] = None,
115 max_string: Optional[int] = None,
116 max_depth: Optional[int] = None,
117 expand_all: bool = False,
118) -> Union[str, None]:
119 # needed here to prevent circular import:
120 from .console import ConsoleRenderable
122 # always skip rich generated jupyter renderables or None values
123 if _safe_isinstance(value, JupyterRenderable) or value is None:
124 return None
126 console = console or get_console()
128 with console.capture() as capture:
129 # certain renderables should start on a new line
130 if _safe_isinstance(value, ConsoleRenderable):
131 console.line()
132 console.print(
133 value
134 if _safe_isinstance(value, RichRenderable)
135 else Pretty(
136 value,
137 overflow=overflow,
138 indent_guides=indent_guides,
139 max_length=max_length,
140 max_string=max_string,
141 max_depth=max_depth,
142 expand_all=expand_all,
143 margin=12,
144 ),
145 crop=crop,
146 new_line_start=True,
147 end="",
148 )
149 # strip trailing newline, not usually part of a text repr
150 # I'm not sure if this should be prevented at a lower level
151 return capture.get().rstrip("\n")
154def _safe_isinstance(
155 obj: object, class_or_tuple: Union[type, Tuple[type, ...]]
156) -> bool:
157 """isinstance can fail in rare cases, for example types with no __class__"""
158 try:
159 return isinstance(obj, class_or_tuple)
160 except Exception:
161 return False
164def install(
165 console: Optional["Console"] = None,
166 overflow: "OverflowMethod" = "ignore",
167 crop: bool = False,
168 indent_guides: bool = False,
169 max_length: Optional[int] = None,
170 max_string: Optional[int] = None,
171 max_depth: Optional[int] = None,
172 expand_all: bool = False,
173) -> None:
174 """Install automatic pretty printing in the Python REPL.
176 Args:
177 console (Console, optional): Console instance or ``None`` to use global console. Defaults to None.
178 overflow (Optional[OverflowMethod], optional): Overflow method. Defaults to "ignore".
179 crop (Optional[bool], optional): Enable cropping of long lines. Defaults to False.
180 indent_guides (bool, optional): Enable indentation guides. Defaults to False.
181 max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
182 Defaults to None.
183 max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to None.
184 max_depth (int, optional): Maximum depth of nested data structures, or None for no maximum. Defaults to None.
185 expand_all (bool, optional): Expand all containers. Defaults to False.
186 max_frames (int): Maximum number of frames to show in a traceback, 0 for no maximum. Defaults to 100.
187 """
188 from rich import get_console
190 console = console or get_console()
191 assert console is not None
193 def display_hook(value: Any) -> None:
194 """Replacement sys.displayhook which prettifies objects with Rich."""
195 if value is not None:
196 assert console is not None
197 builtins._ = None # type: ignore[attr-defined]
198 console.print(
199 value
200 if _safe_isinstance(value, RichRenderable)
201 else Pretty(
202 value,
203 overflow=overflow,
204 indent_guides=indent_guides,
205 max_length=max_length,
206 max_string=max_string,
207 max_depth=max_depth,
208 expand_all=expand_all,
209 ),
210 crop=crop,
211 )
212 builtins._ = value # type: ignore[attr-defined]
214 try:
215 ip = get_ipython() # type: ignore[name-defined]
216 except NameError:
217 sys.displayhook = display_hook
218 else:
219 from IPython.core.formatters import BaseFormatter
221 class RichFormatter(BaseFormatter): # type: ignore[misc]
222 pprint: bool = True
224 def __call__(self, value: Any) -> Any:
225 if self.pprint:
226 return _ipy_display_hook(
227 value,
228 console=get_console(),
229 overflow=overflow,
230 indent_guides=indent_guides,
231 max_length=max_length,
232 max_string=max_string,
233 max_depth=max_depth,
234 expand_all=expand_all,
235 )
236 else:
237 return repr(value)
239 # replace plain text formatter with rich formatter
240 rich_formatter = RichFormatter()
241 ip.display_formatter.formatters["text/plain"] = rich_formatter
244class Pretty(JupyterMixin):
245 """A rich renderable that pretty prints an object.
247 Args:
248 _object (Any): An object to pretty print.
249 highlighter (HighlighterType, optional): Highlighter object to apply to result, or None for ReprHighlighter. Defaults to None.
250 indent_size (int, optional): Number of spaces in indent. Defaults to 4.
251 justify (JustifyMethod, optional): Justify method, or None for default. Defaults to None.
252 overflow (OverflowMethod, optional): Overflow method, or None for default. Defaults to None.
253 no_wrap (Optional[bool], optional): Disable word wrapping. Defaults to False.
254 indent_guides (bool, optional): Enable indentation guides. Defaults to False.
255 max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
256 Defaults to None.
257 max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to None.
258 max_depth (int, optional): Maximum depth of nested data structures, or None for no maximum. Defaults to None.
259 expand_all (bool, optional): Expand all containers. Defaults to False.
260 margin (int, optional): Subtrace a margin from width to force containers to expand earlier. Defaults to 0.
261 insert_line (bool, optional): Insert a new line if the output has multiple new lines. Defaults to False.
262 """
264 def __init__(
265 self,
266 _object: Any,
267 highlighter: Optional["HighlighterType"] = None,
268 *,
269 indent_size: int = 4,
270 justify: Optional["JustifyMethod"] = None,
271 overflow: Optional["OverflowMethod"] = None,
272 no_wrap: Optional[bool] = False,
273 indent_guides: bool = False,
274 max_length: Optional[int] = None,
275 max_string: Optional[int] = None,
276 max_depth: Optional[int] = None,
277 expand_all: bool = False,
278 margin: int = 0,
279 insert_line: bool = False,
280 ) -> None:
281 self._object = _object
282 self.highlighter = highlighter or ReprHighlighter()
283 self.indent_size = indent_size
284 self.justify: Optional["JustifyMethod"] = justify
285 self.overflow: Optional["OverflowMethod"] = overflow
286 self.no_wrap = no_wrap
287 self.indent_guides = indent_guides
288 self.max_length = max_length
289 self.max_string = max_string
290 self.max_depth = max_depth
291 self.expand_all = expand_all
292 self.margin = margin
293 self.insert_line = insert_line
295 def __rich_console__(
296 self, console: "Console", options: "ConsoleOptions"
297 ) -> "RenderResult":
298 pretty_str = pretty_repr(
299 self._object,
300 max_width=options.max_width - self.margin,
301 indent_size=self.indent_size,
302 max_length=self.max_length,
303 max_string=self.max_string,
304 max_depth=self.max_depth,
305 expand_all=self.expand_all,
306 )
307 pretty_text = Text.from_ansi(
308 pretty_str,
309 justify=self.justify or options.justify,
310 overflow=self.overflow or options.overflow,
311 no_wrap=pick_bool(self.no_wrap, options.no_wrap),
312 style="pretty",
313 )
314 pretty_text = (
315 self.highlighter(pretty_text)
316 if pretty_text
317 else Text(
318 f"{type(self._object)}.__repr__ returned empty string",
319 style="dim italic",
320 )
321 )
322 if self.indent_guides and not options.ascii_only:
323 pretty_text = pretty_text.with_indent_guides(
324 self.indent_size, style="repr.indent"
325 )
326 if self.insert_line and "\n" in pretty_text:
327 yield ""
328 yield pretty_text
330 def __rich_measure__(
331 self, console: "Console", options: "ConsoleOptions"
332 ) -> "Measurement":
333 pretty_str = pretty_repr(
334 self._object,
335 max_width=options.max_width,
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 text_width = (
343 max(cell_len(line) for line in pretty_str.splitlines()) if pretty_str else 0
344 )
345 return Measurement(text_width, text_width)
348def _get_braces_for_defaultdict(_object: DefaultDict[Any, Any]) -> Tuple[str, str, str]:
349 return (
350 f"defaultdict({_object.default_factory!r}, {{",
351 "})",
352 f"defaultdict({_object.default_factory!r}, {{}})",
353 )
356def _get_braces_for_array(_object: "array[Any]") -> Tuple[str, str, str]:
357 return (f"array({_object.typecode!r}, [", "])", f"array({_object.typecode!r})")
360_BRACES: Dict[type, Callable[[Any], Tuple[str, str, str]]] = {
361 os._Environ: lambda _object: ("environ({", "})", "environ({})"),
362 array: _get_braces_for_array,
363 defaultdict: _get_braces_for_defaultdict,
364 Counter: lambda _object: ("Counter({", "})", "Counter()"),
365 deque: lambda _object: ("deque([", "])", "deque()"),
366 dict: lambda _object: ("{", "}", "{}"),
367 UserDict: lambda _object: ("{", "}", "{}"),
368 frozenset: lambda _object: ("frozenset({", "})", "frozenset()"),
369 list: lambda _object: ("[", "]", "[]"),
370 UserList: lambda _object: ("[", "]", "[]"),
371 set: lambda _object: ("{", "}", "set()"),
372 tuple: lambda _object: ("(", ")", "()"),
373 MappingProxyType: lambda _object: ("mappingproxy({", "})", "mappingproxy({})"),
374}
375_CONTAINERS = tuple(_BRACES.keys())
376_MAPPING_CONTAINERS = (dict, os._Environ, MappingProxyType, UserDict)
379def is_expandable(obj: Any) -> bool:
380 """Check if an object may be expanded by pretty print."""
381 return (
382 _safe_isinstance(obj, _CONTAINERS)
383 or (is_dataclass(obj))
384 or (hasattr(obj, "__rich_repr__"))
385 or _is_attr_object(obj)
386 ) and not isclass(obj)
389@dataclass
390class Node:
391 """A node in a repr tree. May be atomic or a container."""
393 key_repr: str = ""
394 value_repr: str = ""
395 open_brace: str = ""
396 close_brace: str = ""
397 empty: str = ""
398 last: bool = False
399 is_tuple: bool = False
400 is_namedtuple: bool = False
401 children: Optional[List["Node"]] = None
402 key_separator: str = ": "
403 separator: str = ", "
405 def iter_tokens(self) -> Iterable[str]:
406 """Generate tokens for this node."""
407 if self.key_repr:
408 yield self.key_repr
409 yield self.key_separator
410 if self.value_repr:
411 yield self.value_repr
412 elif self.children is not None:
413 if self.children:
414 yield self.open_brace
415 if self.is_tuple and not self.is_namedtuple and len(self.children) == 1:
416 yield from self.children[0].iter_tokens()
417 yield ","
418 else:
419 for child in self.children:
420 yield from child.iter_tokens()
421 if not child.last:
422 yield self.separator
423 yield self.close_brace
424 else:
425 yield self.empty
427 def check_length(self, start_length: int, max_length: int) -> bool:
428 """Check the length fits within a limit.
430 Args:
431 start_length (int): Starting length of the line (indent, prefix, suffix).
432 max_length (int): Maximum length.
434 Returns:
435 bool: True if the node can be rendered within max length, otherwise False.
436 """
437 total_length = start_length
438 for token in self.iter_tokens():
439 total_length += cell_len(token)
440 if total_length > max_length:
441 return False
442 return True
444 def __str__(self) -> str:
445 repr_text = "".join(self.iter_tokens())
446 return repr_text
448 def render(
449 self, max_width: int = 80, indent_size: int = 4, expand_all: bool = False
450 ) -> str:
451 """Render the node to a pretty repr.
453 Args:
454 max_width (int, optional): Maximum width of the repr. Defaults to 80.
455 indent_size (int, optional): Size of indents. Defaults to 4.
456 expand_all (bool, optional): Expand all levels. Defaults to False.
458 Returns:
459 str: A repr string of the original object.
460 """
461 lines = [_Line(node=self, is_root=True)]
462 line_no = 0
463 while line_no < len(lines):
464 line = lines[line_no]
465 if line.expandable and not line.expanded:
466 if expand_all or not line.check_length(max_width):
467 lines[line_no : line_no + 1] = line.expand(indent_size)
468 line_no += 1
470 repr_str = "\n".join(str(line) for line in lines)
471 return repr_str
474@dataclass
475class _Line:
476 """A line in repr output."""
478 parent: Optional["_Line"] = None
479 is_root: bool = False
480 node: Optional[Node] = None
481 text: str = ""
482 suffix: str = ""
483 whitespace: str = ""
484 expanded: bool = False
485 last: bool = False
487 @property
488 def expandable(self) -> bool:
489 """Check if the line may be expanded."""
490 return bool(self.node is not None and self.node.children)
492 def check_length(self, max_length: int) -> bool:
493 """Check this line fits within a given number of cells."""
494 start_length = (
495 len(self.whitespace) + cell_len(self.text) + cell_len(self.suffix)
496 )
497 assert self.node is not None
498 return self.node.check_length(start_length, max_length)
500 def expand(self, indent_size: int) -> Iterable["_Line"]:
501 """Expand this line by adding children on their own line."""
502 node = self.node
503 assert node is not None
504 whitespace = self.whitespace
505 assert node.children
506 if node.key_repr:
507 new_line = yield _Line(
508 text=f"{node.key_repr}{node.key_separator}{node.open_brace}",
509 whitespace=whitespace,
510 )
511 else:
512 new_line = yield _Line(text=node.open_brace, whitespace=whitespace)
513 child_whitespace = self.whitespace + " " * indent_size
514 tuple_of_one = node.is_tuple and len(node.children) == 1
515 for last, child in loop_last(node.children):
516 separator = "," if tuple_of_one else node.separator
517 line = _Line(
518 parent=new_line,
519 node=child,
520 whitespace=child_whitespace,
521 suffix=separator,
522 last=last and not tuple_of_one,
523 )
524 yield line
526 yield _Line(
527 text=node.close_brace,
528 whitespace=whitespace,
529 suffix=self.suffix,
530 last=self.last,
531 )
533 def __str__(self) -> str:
534 if self.last:
535 return f"{self.whitespace}{self.text}{self.node or ''}"
536 else:
537 return (
538 f"{self.whitespace}{self.text}{self.node or ''}{self.suffix.rstrip()}"
539 )
542def _is_namedtuple(obj: Any) -> bool:
543 """Checks if an object is most likely a namedtuple. It is possible
544 to craft an object that passes this check and isn't a namedtuple, but
545 there is only a minuscule chance of this happening unintentionally.
547 Args:
548 obj (Any): The object to test
550 Returns:
551 bool: True if the object is a namedtuple. False otherwise.
552 """
553 try:
554 fields = getattr(obj, "_fields", None)
555 except Exception:
556 # Being very defensive - if we cannot get the attr then its not a namedtuple
557 return False
558 return isinstance(obj, tuple) and isinstance(fields, tuple)
561def traverse(
562 _object: Any,
563 max_length: Optional[int] = None,
564 max_string: Optional[int] = None,
565 max_depth: Optional[int] = None,
566) -> Node:
567 """Traverse object and generate a tree.
569 Args:
570 _object (Any): Object to be traversed.
571 max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
572 Defaults to None.
573 max_string (int, optional): Maximum length of string before truncating, or None to disable truncating.
574 Defaults to None.
575 max_depth (int, optional): Maximum depth of data structures, or None for no maximum.
576 Defaults to None.
578 Returns:
579 Node: The root of a tree structure which can be used to render a pretty repr.
580 """
582 def to_repr(obj: Any) -> str:
583 """Get repr string for an object, but catch errors."""
584 if (
585 max_string is not None
586 and _safe_isinstance(obj, (bytes, str))
587 and len(obj) > max_string
588 ):
589 truncated = len(obj) - max_string
590 obj_repr = f"{obj[:max_string]!r}+{truncated}"
591 else:
592 try:
593 obj_repr = repr(obj)
594 except Exception as error:
595 obj_repr = f"<repr-error {str(error)!r}>"
596 return obj_repr
598 visited_ids: Set[int] = set()
599 push_visited = visited_ids.add
600 pop_visited = visited_ids.remove
602 def _traverse(obj: Any, root: bool = False, depth: int = 0) -> Node:
603 """Walk the object depth first."""
605 obj_id = id(obj)
606 if obj_id in visited_ids:
607 # Recursion detected
608 return Node(value_repr="...")
610 obj_type = type(obj)
611 children: List[Node]
612 reached_max_depth = max_depth is not None and depth >= max_depth
614 def iter_rich_args(rich_args: Any) -> Iterable[Union[Any, Tuple[str, Any]]]:
615 for arg in rich_args:
616 if _safe_isinstance(arg, tuple):
617 if len(arg) == 3:
618 key, child, default = arg
619 if default == child:
620 continue
621 yield key, child
622 elif len(arg) == 2:
623 key, child = arg
624 yield key, child
625 elif len(arg) == 1:
626 yield arg[0]
627 else:
628 yield arg
630 try:
631 fake_attributes = hasattr(
632 obj, "awehoi234_wdfjwljet234_234wdfoijsdfmmnxpi492"
633 )
634 except Exception:
635 fake_attributes = False
637 rich_repr_result: Optional[RichReprResult] = None
638 if not fake_attributes:
639 try:
640 if hasattr(obj, "__rich_repr__") and not isclass(obj):
641 rich_repr_result = obj.__rich_repr__()
642 except Exception:
643 pass
645 if rich_repr_result is not None:
646 push_visited(obj_id)
647 angular = getattr(obj.__rich_repr__, "angular", False)
648 args = list(iter_rich_args(rich_repr_result))
649 class_name = obj.__class__.__name__
651 if args:
652 children = []
653 append = children.append
655 if reached_max_depth:
656 if angular:
657 node = Node(value_repr=f"<{class_name}...>")
658 else:
659 node = Node(value_repr=f"{class_name}(...)")
660 else:
661 if angular:
662 node = Node(
663 open_brace=f"<{class_name} ",
664 close_brace=">",
665 children=children,
666 last=root,
667 separator=" ",
668 )
669 else:
670 node = Node(
671 open_brace=f"{class_name}(",
672 close_brace=")",
673 children=children,
674 last=root,
675 )
676 for last, arg in loop_last(args):
677 if _safe_isinstance(arg, tuple):
678 key, child = arg
679 child_node = _traverse(child, depth=depth + 1)
680 child_node.last = last
681 child_node.key_repr = key
682 child_node.key_separator = "="
683 append(child_node)
684 else:
685 child_node = _traverse(arg, depth=depth + 1)
686 child_node.last = last
687 append(child_node)
688 else:
689 node = Node(
690 value_repr=f"<{class_name}>" if angular else f"{class_name}()",
691 children=[],
692 last=root,
693 )
694 pop_visited(obj_id)
695 elif _is_attr_object(obj) and not fake_attributes:
696 push_visited(obj_id)
697 children = []
698 append = children.append
700 attr_fields = _get_attr_fields(obj)
701 if attr_fields:
702 if reached_max_depth:
703 node = Node(value_repr=f"{obj.__class__.__name__}(...)")
704 else:
705 node = Node(
706 open_brace=f"{obj.__class__.__name__}(",
707 close_brace=")",
708 children=children,
709 last=root,
710 )
712 def iter_attrs() -> (
713 Iterable[Tuple[str, Any, Optional[Callable[[Any], str]]]]
714 ):
715 """Iterate over attr fields and values."""
716 for attr in attr_fields:
717 if attr.repr:
718 try:
719 value = getattr(obj, attr.name)
720 except Exception as error:
721 # Can happen, albeit rarely
722 yield (attr.name, error, None)
723 else:
724 yield (
725 attr.name,
726 value,
727 attr.repr if callable(attr.repr) else None,
728 )
730 for last, (name, value, repr_callable) in loop_last(iter_attrs()):
731 if repr_callable:
732 child_node = Node(value_repr=str(repr_callable(value)))
733 else:
734 child_node = _traverse(value, depth=depth + 1)
735 child_node.last = last
736 child_node.key_repr = name
737 child_node.key_separator = "="
738 append(child_node)
739 else:
740 node = Node(
741 value_repr=f"{obj.__class__.__name__}()", children=[], last=root
742 )
743 pop_visited(obj_id)
744 elif (
745 is_dataclass(obj)
746 and not _safe_isinstance(obj, type)
747 and not fake_attributes
748 and _is_dataclass_repr(obj)
749 ):
750 push_visited(obj_id)
751 children = []
752 append = children.append
753 if reached_max_depth:
754 node = Node(value_repr=f"{obj.__class__.__name__}(...)")
755 else:
756 node = Node(
757 open_brace=f"{obj.__class__.__name__}(",
758 close_brace=")",
759 children=children,
760 last=root,
761 empty=f"{obj.__class__.__name__}()",
762 )
764 for last, field in loop_last(
765 field for field in fields(obj) if field.repr
766 ):
767 child_node = _traverse(getattr(obj, field.name), depth=depth + 1)
768 child_node.key_repr = field.name
769 child_node.last = last
770 child_node.key_separator = "="
771 append(child_node)
773 pop_visited(obj_id)
774 elif _is_namedtuple(obj) and _has_default_namedtuple_repr(obj):
775 push_visited(obj_id)
776 class_name = obj.__class__.__name__
777 if reached_max_depth:
778 # If we've reached the max depth, we still show the class name, but not its contents
779 node = Node(
780 value_repr=f"{class_name}(...)",
781 )
782 else:
783 children = []
784 append = children.append
785 node = Node(
786 open_brace=f"{class_name}(",
787 close_brace=")",
788 children=children,
789 empty=f"{class_name}()",
790 )
791 for last, (key, value) in loop_last(obj._asdict().items()):
792 child_node = _traverse(value, depth=depth + 1)
793 child_node.key_repr = key
794 child_node.last = last
795 child_node.key_separator = "="
796 append(child_node)
797 pop_visited(obj_id)
798 elif _safe_isinstance(obj, _CONTAINERS):
799 for container_type in _CONTAINERS:
800 if _safe_isinstance(obj, container_type):
801 obj_type = container_type
802 break
804 push_visited(obj_id)
806 open_brace, close_brace, empty = _BRACES[obj_type](obj)
808 if reached_max_depth:
809 node = Node(value_repr=f"{open_brace}...{close_brace}")
810 elif obj_type.__repr__ != type(obj).__repr__:
811 node = Node(value_repr=to_repr(obj), last=root)
812 elif obj:
813 children = []
814 node = Node(
815 open_brace=open_brace,
816 close_brace=close_brace,
817 children=children,
818 last=root,
819 )
820 append = children.append
821 num_items = len(obj)
822 last_item_index = num_items - 1
824 if _safe_isinstance(obj, _MAPPING_CONTAINERS):
825 iter_items = iter(obj.items())
826 if max_length is not None:
827 iter_items = islice(iter_items, max_length)
828 for index, (key, child) in enumerate(iter_items):
829 child_node = _traverse(child, depth=depth + 1)
830 child_node.key_repr = to_repr(key)
831 child_node.last = index == last_item_index
832 append(child_node)
833 else:
834 iter_values = iter(obj)
835 if max_length is not None:
836 iter_values = islice(iter_values, max_length)
837 for index, child in enumerate(iter_values):
838 child_node = _traverse(child, depth=depth + 1)
839 child_node.last = index == last_item_index
840 append(child_node)
841 if max_length is not None and num_items > max_length:
842 append(Node(value_repr=f"... +{num_items - max_length}", last=True))
843 else:
844 node = Node(empty=empty, children=[], last=root)
846 pop_visited(obj_id)
847 else:
848 node = Node(value_repr=to_repr(obj), last=root)
849 node.is_tuple = _safe_isinstance(obj, tuple)
850 node.is_namedtuple = _is_namedtuple(obj)
851 return node
853 node = _traverse(_object, root=True)
854 return node
857def pretty_repr(
858 _object: Any,
859 *,
860 max_width: int = 80,
861 indent_size: int = 4,
862 max_length: Optional[int] = None,
863 max_string: Optional[int] = None,
864 max_depth: Optional[int] = None,
865 expand_all: bool = False,
866) -> str:
867 """Prettify repr string by expanding on to new lines to fit within a given width.
869 Args:
870 _object (Any): Object to repr.
871 max_width (int, optional): Desired maximum width of repr string. Defaults to 80.
872 indent_size (int, optional): Number of spaces to indent. Defaults to 4.
873 max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
874 Defaults to None.
875 max_string (int, optional): Maximum length of string before truncating, or None to disable truncating.
876 Defaults to None.
877 max_depth (int, optional): Maximum depth of nested data structure, or None for no depth.
878 Defaults to None.
879 expand_all (bool, optional): Expand all containers regardless of available width. Defaults to False.
881 Returns:
882 str: A possibly multi-line representation of the object.
883 """
885 if _safe_isinstance(_object, Node):
886 node = _object
887 else:
888 node = traverse(
889 _object, max_length=max_length, max_string=max_string, max_depth=max_depth
890 )
891 repr_str: str = node.render(
892 max_width=max_width, indent_size=indent_size, expand_all=expand_all
893 )
894 return repr_str
897def pprint(
898 _object: Any,
899 *,
900 console: Optional["Console"] = None,
901 indent_guides: bool = True,
902 max_length: Optional[int] = None,
903 max_string: Optional[int] = None,
904 max_depth: Optional[int] = None,
905 expand_all: bool = False,
906) -> None:
907 """A convenience function for pretty printing.
909 Args:
910 _object (Any): Object to pretty print.
911 console (Console, optional): Console instance, or None to use default. Defaults to None.
912 max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
913 Defaults to None.
914 max_string (int, optional): Maximum length of strings before truncating, or None to disable. Defaults to None.
915 max_depth (int, optional): Maximum depth for nested data structures, or None for unlimited depth. Defaults to None.
916 indent_guides (bool, optional): Enable indentation guides. Defaults to True.
917 expand_all (bool, optional): Expand all containers. Defaults to False.
918 """
919 _console = get_console() if console is None else console
920 _console.print(
921 Pretty(
922 _object,
923 max_length=max_length,
924 max_string=max_string,
925 max_depth=max_depth,
926 indent_guides=indent_guides,
927 expand_all=expand_all,
928 overflow="ignore",
929 ),
930 soft_wrap=True,
931 )
934if __name__ == "__main__": # pragma: no cover
936 class BrokenRepr:
937 def __repr__(self) -> str:
938 1 / 0
939 return "this will fail"
941 from typing import NamedTuple
943 class StockKeepingUnit(NamedTuple):
944 name: str
945 description: str
946 price: float
947 category: str
948 reviews: List[str]
950 d = defaultdict(int)
951 d["foo"] = 5
952 data = {
953 "foo": [
954 1,
955 "Hello World!",
956 100.123,
957 323.232,
958 432324.0,
959 {5, 6, 7, (1, 2, 3, 4), 8},
960 ],
961 "bar": frozenset({1, 2, 3}),
962 "defaultdict": defaultdict(
963 list, {"crumble": ["apple", "rhubarb", "butter", "sugar", "flour"]}
964 ),
965 "counter": Counter(
966 [
967 "apple",
968 "orange",
969 "pear",
970 "kumquat",
971 "kumquat",
972 "durian" * 100,
973 ]
974 ),
975 "atomic": (False, True, None),
976 "namedtuple": StockKeepingUnit(
977 "Sparkling British Spring Water",
978 "Carbonated spring water",
979 0.9,
980 "water",
981 ["its amazing!", "its terrible!"],
982 ),
983 "Broken": BrokenRepr(),
984 }
985 data["foo"].append(data) # type: ignore[attr-defined]
987 from rich import print
989 # print(Pretty(data, indent_guides=True, max_string=20))
991 class Thing:
992 def __repr__(self) -> str:
993 return "Hello\x1b[38;5;239m World!"
995 print(Pretty(Thing()))