python (3.12.0)
1 import builtins
2 import collections
3 import dataclasses
4 import inspect
5 import os
6 import sys
7 from array import array
8 from collections import Counter, UserDict, UserList, defaultdict, deque
9 from dataclasses import dataclass, fields, is_dataclass
10 from inspect import isclass
11 from itertools import islice
12 from types import MappingProxyType
13 from 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 )
27
28 from pip._vendor.rich.repr import RichReprResult
29
30 try:
31 import attr as _attr_module
32
33 _has_attrs = hasattr(_attr_module, "ib")
34 except ImportError: # pragma: no cover
35 _has_attrs = False
36
37 from . import get_console
38 from ._loop import loop_last
39 from ._pick import pick_bool
40 from .abc import RichRenderable
41 from .cells import cell_len
42 from .highlighter import ReprHighlighter
43 from .jupyter import JupyterMixin, JupyterRenderable
44 from .measure import Measurement
45 from .text import Text
46
47 if TYPE_CHECKING:
48 from .console import (
49 Console,
50 ConsoleOptions,
51 HighlighterType,
52 JustifyMethod,
53 OverflowMethod,
54 RenderResult,
55 )
56
57
58 def _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))
61
62
63 def _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 []
66
67
68 def _is_dataclass_repr(obj: object) -> bool:
69 """Check if an instance of a dataclass contains the default repr.
70
71 Args:
72 obj (object): A dataclass instance.
73
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
83
84
85 _dummy_namedtuple = collections.namedtuple("_dummy_namedtuple", [])
86
87
88 def _has_default_namedtuple_repr(obj: object) -> bool:
89 """Check if an instance of namedtuple contains the default repr
90
91 Args:
92 obj (object): A namedtuple
93
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
106
107
108 def _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
121
122 # always skip rich generated jupyter renderables or None values
123 if _safe_isinstance(value, JupyterRenderable) or value is None:
124 return None
125
126 console = console or get_console()
127
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")
152
153
154 def _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
162
163
164 def 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.
175
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 pip._vendor.rich import get_console
189
190 console = console or get_console()
191 assert console is not None
192
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]
213
214 if "get_ipython" in globals():
215 ip = get_ipython() # type: ignore[name-defined]
216 from IPython.core.formatters import BaseFormatter
217
218 class ESC[4;38;5;81mRichFormatter(ESC[4;38;5;149mBaseFormatter): # type: ignore[misc]
219 pprint: bool = True
220
221 def __call__(self, value: Any) -> Any:
222 if self.pprint:
223 return _ipy_display_hook(
224 value,
225 console=get_console(),
226 overflow=overflow,
227 indent_guides=indent_guides,
228 max_length=max_length,
229 max_string=max_string,
230 max_depth=max_depth,
231 expand_all=expand_all,
232 )
233 else:
234 return repr(value)
235
236 # replace plain text formatter with rich formatter
237 rich_formatter = RichFormatter()
238 ip.display_formatter.formatters["text/plain"] = rich_formatter
239 else:
240 sys.displayhook = display_hook
241
242
243 class ESC[4;38;5;81mPretty(ESC[4;38;5;149mJupyterMixin):
244 """A rich renderable that pretty prints an object.
245
246 Args:
247 _object (Any): An object to pretty print.
248 highlighter (HighlighterType, optional): Highlighter object to apply to result, or None for ReprHighlighter. Defaults to None.
249 indent_size (int, optional): Number of spaces in indent. Defaults to 4.
250 justify (JustifyMethod, optional): Justify method, or None for default. Defaults to None.
251 overflow (OverflowMethod, optional): Overflow method, or None for default. Defaults to None.
252 no_wrap (Optional[bool], optional): Disable word wrapping. Defaults to False.
253 indent_guides (bool, optional): Enable indentation guides. Defaults to False.
254 max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
255 Defaults to None.
256 max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to None.
257 max_depth (int, optional): Maximum depth of nested data structures, or None for no maximum. Defaults to None.
258 expand_all (bool, optional): Expand all containers. Defaults to False.
259 margin (int, optional): Subtrace a margin from width to force containers to expand earlier. Defaults to 0.
260 insert_line (bool, optional): Insert a new line if the output has multiple new lines. Defaults to False.
261 """
262
263 def __init__(
264 self,
265 _object: Any,
266 highlighter: Optional["HighlighterType"] = None,
267 *,
268 indent_size: int = 4,
269 justify: Optional["JustifyMethod"] = None,
270 overflow: Optional["OverflowMethod"] = None,
271 no_wrap: Optional[bool] = False,
272 indent_guides: bool = False,
273 max_length: Optional[int] = None,
274 max_string: Optional[int] = None,
275 max_depth: Optional[int] = None,
276 expand_all: bool = False,
277 margin: int = 0,
278 insert_line: bool = False,
279 ) -> None:
280 self._object = _object
281 self.highlighter = highlighter or ReprHighlighter()
282 self.indent_size = indent_size
283 self.justify: Optional["JustifyMethod"] = justify
284 self.overflow: Optional["OverflowMethod"] = overflow
285 self.no_wrap = no_wrap
286 self.indent_guides = indent_guides
287 self.max_length = max_length
288 self.max_string = max_string
289 self.max_depth = max_depth
290 self.expand_all = expand_all
291 self.margin = margin
292 self.insert_line = insert_line
293
294 def __rich_console__(
295 self, console: "Console", options: "ConsoleOptions"
296 ) -> "RenderResult":
297 pretty_str = pretty_repr(
298 self._object,
299 max_width=options.max_width - self.margin,
300 indent_size=self.indent_size,
301 max_length=self.max_length,
302 max_string=self.max_string,
303 max_depth=self.max_depth,
304 expand_all=self.expand_all,
305 )
306 pretty_text = Text.from_ansi(
307 pretty_str,
308 justify=self.justify or options.justify,
309 overflow=self.overflow or options.overflow,
310 no_wrap=pick_bool(self.no_wrap, options.no_wrap),
311 style="pretty",
312 )
313 pretty_text = (
314 self.highlighter(pretty_text)
315 if pretty_text
316 else Text(
317 f"{type(self._object)}.__repr__ returned empty string",
318 style="dim italic",
319 )
320 )
321 if self.indent_guides and not options.ascii_only:
322 pretty_text = pretty_text.with_indent_guides(
323 self.indent_size, style="repr.indent"
324 )
325 if self.insert_line and "\n" in pretty_text:
326 yield ""
327 yield pretty_text
328
329 def __rich_measure__(
330 self, console: "Console", options: "ConsoleOptions"
331 ) -> "Measurement":
332 pretty_str = pretty_repr(
333 self._object,
334 max_width=options.max_width,
335 indent_size=self.indent_size,
336 max_length=self.max_length,
337 max_string=self.max_string,
338 max_depth=self.max_depth,
339 expand_all=self.expand_all,
340 )
341 text_width = (
342 max(cell_len(line) for line in pretty_str.splitlines()) if pretty_str else 0
343 )
344 return Measurement(text_width, text_width)
345
346
347 def _get_braces_for_defaultdict(_object: DefaultDict[Any, Any]) -> Tuple[str, str, str]:
348 return (
349 f"defaultdict({_object.default_factory!r}, {{",
350 "})",
351 f"defaultdict({_object.default_factory!r}, {{}})",
352 )
353
354
355 def _get_braces_for_array(_object: "array[Any]") -> Tuple[str, str, str]:
356 return (f"array({_object.typecode!r}, [", "])", f"array({_object.typecode!r})")
357
358
359 _BRACES: Dict[type, Callable[[Any], Tuple[str, str, str]]] = {
360 os._Environ: lambda _object: ("environ({", "})", "environ({})"),
361 array: _get_braces_for_array,
362 defaultdict: _get_braces_for_defaultdict,
363 Counter: lambda _object: ("Counter({", "})", "Counter()"),
364 deque: lambda _object: ("deque([", "])", "deque()"),
365 dict: lambda _object: ("{", "}", "{}"),
366 UserDict: lambda _object: ("{", "}", "{}"),
367 frozenset: lambda _object: ("frozenset({", "})", "frozenset()"),
368 list: lambda _object: ("[", "]", "[]"),
369 UserList: lambda _object: ("[", "]", "[]"),
370 set: lambda _object: ("{", "}", "set()"),
371 tuple: lambda _object: ("(", ")", "()"),
372 MappingProxyType: lambda _object: ("mappingproxy({", "})", "mappingproxy({})"),
373 }
374 _CONTAINERS = tuple(_BRACES.keys())
375 _MAPPING_CONTAINERS = (dict, os._Environ, MappingProxyType, UserDict)
376
377
378 def is_expandable(obj: Any) -> bool:
379 """Check if an object may be expanded by pretty print."""
380 return (
381 _safe_isinstance(obj, _CONTAINERS)
382 or (is_dataclass(obj))
383 or (hasattr(obj, "__rich_repr__"))
384 or _is_attr_object(obj)
385 ) and not isclass(obj)
386
387
388 @dataclass
389 class ESC[4;38;5;81mNode:
390 """A node in a repr tree. May be atomic or a container."""
391
392 key_repr: str = ""
393 value_repr: str = ""
394 open_brace: str = ""
395 close_brace: str = ""
396 empty: str = ""
397 last: bool = False
398 is_tuple: bool = False
399 is_namedtuple: bool = False
400 children: Optional[List["Node"]] = None
401 key_separator: str = ": "
402 separator: str = ", "
403
404 def iter_tokens(self) -> Iterable[str]:
405 """Generate tokens for this node."""
406 if self.key_repr:
407 yield self.key_repr
408 yield self.key_separator
409 if self.value_repr:
410 yield self.value_repr
411 elif self.children is not None:
412 if self.children:
413 yield self.open_brace
414 if self.is_tuple and not self.is_namedtuple and len(self.children) == 1:
415 yield from self.children[0].iter_tokens()
416 yield ","
417 else:
418 for child in self.children:
419 yield from child.iter_tokens()
420 if not child.last:
421 yield self.separator
422 yield self.close_brace
423 else:
424 yield self.empty
425
426 def check_length(self, start_length: int, max_length: int) -> bool:
427 """Check the length fits within a limit.
428
429 Args:
430 start_length (int): Starting length of the line (indent, prefix, suffix).
431 max_length (int): Maximum length.
432
433 Returns:
434 bool: True if the node can be rendered within max length, otherwise False.
435 """
436 total_length = start_length
437 for token in self.iter_tokens():
438 total_length += cell_len(token)
439 if total_length > max_length:
440 return False
441 return True
442
443 def __str__(self) -> str:
444 repr_text = "".join(self.iter_tokens())
445 return repr_text
446
447 def render(
448 self, max_width: int = 80, indent_size: int = 4, expand_all: bool = False
449 ) -> str:
450 """Render the node to a pretty repr.
451
452 Args:
453 max_width (int, optional): Maximum width of the repr. Defaults to 80.
454 indent_size (int, optional): Size of indents. Defaults to 4.
455 expand_all (bool, optional): Expand all levels. Defaults to False.
456
457 Returns:
458 str: A repr string of the original object.
459 """
460 lines = [_Line(node=self, is_root=True)]
461 line_no = 0
462 while line_no < len(lines):
463 line = lines[line_no]
464 if line.expandable and not line.expanded:
465 if expand_all or not line.check_length(max_width):
466 lines[line_no : line_no + 1] = line.expand(indent_size)
467 line_no += 1
468
469 repr_str = "\n".join(str(line) for line in lines)
470 return repr_str
471
472
473 @dataclass
474 class ESC[4;38;5;81m_Line:
475 """A line in repr output."""
476
477 parent: Optional["_Line"] = None
478 is_root: bool = False
479 node: Optional[Node] = None
480 text: str = ""
481 suffix: str = ""
482 whitespace: str = ""
483 expanded: bool = False
484 last: bool = False
485
486 @property
487 def expandable(self) -> bool:
488 """Check if the line may be expanded."""
489 return bool(self.node is not None and self.node.children)
490
491 def check_length(self, max_length: int) -> bool:
492 """Check this line fits within a given number of cells."""
493 start_length = (
494 len(self.whitespace) + cell_len(self.text) + cell_len(self.suffix)
495 )
496 assert self.node is not None
497 return self.node.check_length(start_length, max_length)
498
499 def expand(self, indent_size: int) -> Iterable["_Line"]:
500 """Expand this line by adding children on their own line."""
501 node = self.node
502 assert node is not None
503 whitespace = self.whitespace
504 assert node.children
505 if node.key_repr:
506 new_line = yield _Line(
507 text=f"{node.key_repr}{node.key_separator}{node.open_brace}",
508 whitespace=whitespace,
509 )
510 else:
511 new_line = yield _Line(text=node.open_brace, whitespace=whitespace)
512 child_whitespace = self.whitespace + " " * indent_size
513 tuple_of_one = node.is_tuple and len(node.children) == 1
514 for last, child in loop_last(node.children):
515 separator = "," if tuple_of_one else node.separator
516 line = _Line(
517 parent=new_line,
518 node=child,
519 whitespace=child_whitespace,
520 suffix=separator,
521 last=last and not tuple_of_one,
522 )
523 yield line
524
525 yield _Line(
526 text=node.close_brace,
527 whitespace=whitespace,
528 suffix=self.suffix,
529 last=self.last,
530 )
531
532 def __str__(self) -> str:
533 if self.last:
534 return f"{self.whitespace}{self.text}{self.node or ''}"
535 else:
536 return (
537 f"{self.whitespace}{self.text}{self.node or ''}{self.suffix.rstrip()}"
538 )
539
540
541 def _is_namedtuple(obj: Any) -> bool:
542 """Checks if an object is most likely a namedtuple. It is possible
543 to craft an object that passes this check and isn't a namedtuple, but
544 there is only a minuscule chance of this happening unintentionally.
545
546 Args:
547 obj (Any): The object to test
548
549 Returns:
550 bool: True if the object is a namedtuple. False otherwise.
551 """
552 try:
553 fields = getattr(obj, "_fields", None)
554 except Exception:
555 # Being very defensive - if we cannot get the attr then its not a namedtuple
556 return False
557 return isinstance(obj, tuple) and isinstance(fields, tuple)
558
559
560 def traverse(
561 _object: Any,
562 max_length: Optional[int] = None,
563 max_string: Optional[int] = None,
564 max_depth: Optional[int] = None,
565 ) -> Node:
566 """Traverse object and generate a tree.
567
568 Args:
569 _object (Any): Object to be traversed.
570 max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
571 Defaults to None.
572 max_string (int, optional): Maximum length of string before truncating, or None to disable truncating.
573 Defaults to None.
574 max_depth (int, optional): Maximum depth of data structures, or None for no maximum.
575 Defaults to None.
576
577 Returns:
578 Node: The root of a tree structure which can be used to render a pretty repr.
579 """
580
581 def to_repr(obj: Any) -> str:
582 """Get repr string for an object, but catch errors."""
583 if (
584 max_string is not None
585 and _safe_isinstance(obj, (bytes, str))
586 and len(obj) > max_string
587 ):
588 truncated = len(obj) - max_string
589 obj_repr = f"{obj[:max_string]!r}+{truncated}"
590 else:
591 try:
592 obj_repr = repr(obj)
593 except Exception as error:
594 obj_repr = f"<repr-error {str(error)!r}>"
595 return obj_repr
596
597 visited_ids: Set[int] = set()
598 push_visited = visited_ids.add
599 pop_visited = visited_ids.remove
600
601 def _traverse(obj: Any, root: bool = False, depth: int = 0) -> Node:
602 """Walk the object depth first."""
603
604 obj_id = id(obj)
605 if obj_id in visited_ids:
606 # Recursion detected
607 return Node(value_repr="...")
608
609 obj_type = type(obj)
610 children: List[Node]
611 reached_max_depth = max_depth is not None and depth >= max_depth
612
613 def iter_rich_args(rich_args: Any) -> Iterable[Union[Any, Tuple[str, Any]]]:
614 for arg in rich_args:
615 if _safe_isinstance(arg, tuple):
616 if len(arg) == 3:
617 key, child, default = arg
618 if default == child:
619 continue
620 yield key, child
621 elif len(arg) == 2:
622 key, child = arg
623 yield key, child
624 elif len(arg) == 1:
625 yield arg[0]
626 else:
627 yield arg
628
629 try:
630 fake_attributes = hasattr(
631 obj, "awehoi234_wdfjwljet234_234wdfoijsdfmmnxpi492"
632 )
633 except Exception:
634 fake_attributes = False
635
636 rich_repr_result: Optional[RichReprResult] = None
637 if not fake_attributes:
638 try:
639 if hasattr(obj, "__rich_repr__") and not isclass(obj):
640 rich_repr_result = obj.__rich_repr__()
641 except Exception:
642 pass
643
644 if rich_repr_result is not None:
645 push_visited(obj_id)
646 angular = getattr(obj.__rich_repr__, "angular", False)
647 args = list(iter_rich_args(rich_repr_result))
648 class_name = obj.__class__.__name__
649
650 if args:
651 children = []
652 append = children.append
653
654 if reached_max_depth:
655 if angular:
656 node = Node(value_repr=f"<{class_name}...>")
657 else:
658 node = Node(value_repr=f"{class_name}(...)")
659 else:
660 if angular:
661 node = Node(
662 open_brace=f"<{class_name} ",
663 close_brace=">",
664 children=children,
665 last=root,
666 separator=" ",
667 )
668 else:
669 node = Node(
670 open_brace=f"{class_name}(",
671 close_brace=")",
672 children=children,
673 last=root,
674 )
675 for last, arg in loop_last(args):
676 if _safe_isinstance(arg, tuple):
677 key, child = arg
678 child_node = _traverse(child, depth=depth + 1)
679 child_node.last = last
680 child_node.key_repr = key
681 child_node.key_separator = "="
682 append(child_node)
683 else:
684 child_node = _traverse(arg, depth=depth + 1)
685 child_node.last = last
686 append(child_node)
687 else:
688 node = Node(
689 value_repr=f"<{class_name}>" if angular else f"{class_name}()",
690 children=[],
691 last=root,
692 )
693 pop_visited(obj_id)
694 elif _is_attr_object(obj) and not fake_attributes:
695 push_visited(obj_id)
696 children = []
697 append = children.append
698
699 attr_fields = _get_attr_fields(obj)
700 if attr_fields:
701 if reached_max_depth:
702 node = Node(value_repr=f"{obj.__class__.__name__}(...)")
703 else:
704 node = Node(
705 open_brace=f"{obj.__class__.__name__}(",
706 close_brace=")",
707 children=children,
708 last=root,
709 )
710
711 def iter_attrs() -> Iterable[
712 Tuple[str, Any, Optional[Callable[[Any], str]]]
713 ]:
714 """Iterate over attr fields and values."""
715 for attr in attr_fields:
716 if attr.repr:
717 try:
718 value = getattr(obj, attr.name)
719 except Exception as error:
720 # Can happen, albeit rarely
721 yield (attr.name, error, None)
722 else:
723 yield (
724 attr.name,
725 value,
726 attr.repr if callable(attr.repr) else None,
727 )
728
729 for last, (name, value, repr_callable) in loop_last(iter_attrs()):
730 if repr_callable:
731 child_node = Node(value_repr=str(repr_callable(value)))
732 else:
733 child_node = _traverse(value, depth=depth + 1)
734 child_node.last = last
735 child_node.key_repr = name
736 child_node.key_separator = "="
737 append(child_node)
738 else:
739 node = Node(
740 value_repr=f"{obj.__class__.__name__}()", children=[], last=root
741 )
742 pop_visited(obj_id)
743 elif (
744 is_dataclass(obj)
745 and not _safe_isinstance(obj, type)
746 and not fake_attributes
747 and _is_dataclass_repr(obj)
748 ):
749 push_visited(obj_id)
750 children = []
751 append = children.append
752 if reached_max_depth:
753 node = Node(value_repr=f"{obj.__class__.__name__}(...)")
754 else:
755 node = Node(
756 open_brace=f"{obj.__class__.__name__}(",
757 close_brace=")",
758 children=children,
759 last=root,
760 empty=f"{obj.__class__.__name__}()",
761 )
762
763 for last, field in loop_last(
764 field for field in fields(obj) if field.repr
765 ):
766 child_node = _traverse(getattr(obj, field.name), depth=depth + 1)
767 child_node.key_repr = field.name
768 child_node.last = last
769 child_node.key_separator = "="
770 append(child_node)
771
772 pop_visited(obj_id)
773 elif _is_namedtuple(obj) and _has_default_namedtuple_repr(obj):
774 push_visited(obj_id)
775 class_name = obj.__class__.__name__
776 if reached_max_depth:
777 # If we've reached the max depth, we still show the class name, but not its contents
778 node = Node(
779 value_repr=f"{class_name}(...)",
780 )
781 else:
782 children = []
783 append = children.append
784 node = Node(
785 open_brace=f"{class_name}(",
786 close_brace=")",
787 children=children,
788 empty=f"{class_name}()",
789 )
790 for last, (key, value) in loop_last(obj._asdict().items()):
791 child_node = _traverse(value, depth=depth + 1)
792 child_node.key_repr = key
793 child_node.last = last
794 child_node.key_separator = "="
795 append(child_node)
796 pop_visited(obj_id)
797 elif _safe_isinstance(obj, _CONTAINERS):
798 for container_type in _CONTAINERS:
799 if _safe_isinstance(obj, container_type):
800 obj_type = container_type
801 break
802
803 push_visited(obj_id)
804
805 open_brace, close_brace, empty = _BRACES[obj_type](obj)
806
807 if reached_max_depth:
808 node = Node(value_repr=f"{open_brace}...{close_brace}")
809 elif obj_type.__repr__ != type(obj).__repr__:
810 node = Node(value_repr=to_repr(obj), last=root)
811 elif obj:
812 children = []
813 node = Node(
814 open_brace=open_brace,
815 close_brace=close_brace,
816 children=children,
817 last=root,
818 )
819 append = children.append
820 num_items = len(obj)
821 last_item_index = num_items - 1
822
823 if _safe_isinstance(obj, _MAPPING_CONTAINERS):
824 iter_items = iter(obj.items())
825 if max_length is not None:
826 iter_items = islice(iter_items, max_length)
827 for index, (key, child) in enumerate(iter_items):
828 child_node = _traverse(child, depth=depth + 1)
829 child_node.key_repr = to_repr(key)
830 child_node.last = index == last_item_index
831 append(child_node)
832 else:
833 iter_values = iter(obj)
834 if max_length is not None:
835 iter_values = islice(iter_values, max_length)
836 for index, child in enumerate(iter_values):
837 child_node = _traverse(child, depth=depth + 1)
838 child_node.last = index == last_item_index
839 append(child_node)
840 if max_length is not None and num_items > max_length:
841 append(Node(value_repr=f"... +{num_items - max_length}", last=True))
842 else:
843 node = Node(empty=empty, children=[], last=root)
844
845 pop_visited(obj_id)
846 else:
847 node = Node(value_repr=to_repr(obj), last=root)
848 node.is_tuple = _safe_isinstance(obj, tuple)
849 node.is_namedtuple = _is_namedtuple(obj)
850 return node
851
852 node = _traverse(_object, root=True)
853 return node
854
855
856 def pretty_repr(
857 _object: Any,
858 *,
859 max_width: int = 80,
860 indent_size: int = 4,
861 max_length: Optional[int] = None,
862 max_string: Optional[int] = None,
863 max_depth: Optional[int] = None,
864 expand_all: bool = False,
865 ) -> str:
866 """Prettify repr string by expanding on to new lines to fit within a given width.
867
868 Args:
869 _object (Any): Object to repr.
870 max_width (int, optional): Desired maximum width of repr string. Defaults to 80.
871 indent_size (int, optional): Number of spaces to indent. Defaults to 4.
872 max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
873 Defaults to None.
874 max_string (int, optional): Maximum length of string before truncating, or None to disable truncating.
875 Defaults to None.
876 max_depth (int, optional): Maximum depth of nested data structure, or None for no depth.
877 Defaults to None.
878 expand_all (bool, optional): Expand all containers regardless of available width. Defaults to False.
879
880 Returns:
881 str: A possibly multi-line representation of the object.
882 """
883
884 if _safe_isinstance(_object, Node):
885 node = _object
886 else:
887 node = traverse(
888 _object, max_length=max_length, max_string=max_string, max_depth=max_depth
889 )
890 repr_str: str = node.render(
891 max_width=max_width, indent_size=indent_size, expand_all=expand_all
892 )
893 return repr_str
894
895
896 def pprint(
897 _object: Any,
898 *,
899 console: Optional["Console"] = None,
900 indent_guides: bool = True,
901 max_length: Optional[int] = None,
902 max_string: Optional[int] = None,
903 max_depth: Optional[int] = None,
904 expand_all: bool = False,
905 ) -> None:
906 """A convenience function for pretty printing.
907
908 Args:
909 _object (Any): Object to pretty print.
910 console (Console, optional): Console instance, or None to use default. Defaults to None.
911 max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
912 Defaults to None.
913 max_string (int, optional): Maximum length of strings before truncating, or None to disable. Defaults to None.
914 max_depth (int, optional): Maximum depth for nested data structures, or None for unlimited depth. Defaults to None.
915 indent_guides (bool, optional): Enable indentation guides. Defaults to True.
916 expand_all (bool, optional): Expand all containers. Defaults to False.
917 """
918 _console = get_console() if console is None else console
919 _console.print(
920 Pretty(
921 _object,
922 max_length=max_length,
923 max_string=max_string,
924 max_depth=max_depth,
925 indent_guides=indent_guides,
926 expand_all=expand_all,
927 overflow="ignore",
928 ),
929 soft_wrap=True,
930 )
931
932
933 if __name__ == "__main__": # pragma: no cover
934
935 class ESC[4;38;5;81mBrokenRepr:
936 def __repr__(self) -> str:
937 1 / 0
938 return "this will fail"
939
940 from typing import NamedTuple
941
942 class ESC[4;38;5;81mStockKeepingUnit(ESC[4;38;5;149mNamedTuple):
943 name: str
944 description: str
945 price: float
946 category: str
947 reviews: List[str]
948
949 d = defaultdict(int)
950 d["foo"] = 5
951 data = {
952 "foo": [
953 1,
954 "Hello World!",
955 100.123,
956 323.232,
957 432324.0,
958 {5, 6, 7, (1, 2, 3, 4), 8},
959 ],
960 "bar": frozenset({1, 2, 3}),
961 "defaultdict": defaultdict(
962 list, {"crumble": ["apple", "rhubarb", "butter", "sugar", "flour"]}
963 ),
964 "counter": Counter(
965 [
966 "apple",
967 "orange",
968 "pear",
969 "kumquat",
970 "kumquat",
971 "durian" * 100,
972 ]
973 ),
974 "atomic": (False, True, None),
975 "namedtuple": StockKeepingUnit(
976 "Sparkling British Spring Water",
977 "Carbonated spring water",
978 0.9,
979 "water",
980 ["its amazing!", "its terrible!"],
981 ),
982 "Broken": BrokenRepr(),
983 }
984 data["foo"].append(data) # type: ignore[attr-defined]
985
986 from pip._vendor.rich import print
987
988 # print(Pretty(data, indent_guides=True, max_string=20))
989
990 class ESC[4;38;5;81mThing:
991 def __repr__(self) -> str:
992 return "Hello\x1b[38;5;239m World!"
993
994 print(Pretty(Thing()))