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

398 statements  

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) 

29 

30from rich.repr import RichReprResult 

31 

32try: 

33 import attr as _attr_module 

34 

35 _has_attrs = hasattr(_attr_module, "ib") 

36except ImportError: # pragma: no cover 

37 _has_attrs = False 

38 

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 

48 

49if TYPE_CHECKING: 

50 from .console import ( 

51 Console, 

52 ConsoleOptions, 

53 HighlighterType, 

54 JustifyMethod, 

55 OverflowMethod, 

56 RenderResult, 

57 ) 

58 

59 

60def _is_attr_object(obj: Any) -> bool: 

61 """Check if an object was created with attrs module.""" 

62 return _has_attrs and _attr_module.has(type(obj)) 

63 

64 

65def _get_attr_fields(obj: Any) -> Sequence["_attr_module.Attribute[Any]"]: 

66 """Get fields for an attrs object.""" 

67 return _attr_module.fields(type(obj)) if _has_attrs else [] 

68 

69 

70def _is_dataclass_repr(obj: object) -> bool: 

71 """Check if an instance of a dataclass contains the default repr. 

72 

73 Args: 

74 obj (object): A dataclass instance. 

75 

76 Returns: 

77 bool: True if the default repr is used, False if there is a custom repr. 

78 """ 

79 # Digging in to a lot of internals 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 

88 

89 

90_dummy_namedtuple = collections.namedtuple("_dummy_namedtuple", []) 

91 

92 

93def _has_default_namedtuple_repr(obj: object) -> bool: 

94 """Check if an instance of namedtuple contains the default repr 

95 

96 Args: 

97 obj (object): A namedtuple 

98 

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 

111 

112 

113def _ipy_display_hook( 

114 value: Any, 

115 console: Optional["Console"] = None, 

116 overflow: "OverflowMethod" = "ignore", 

117 crop: bool = False, 

118 indent_guides: bool = False, 

119 max_length: Optional[int] = None, 

120 max_string: Optional[int] = None, 

121 max_depth: Optional[int] = None, 

122 expand_all: bool = False, 

123) -> Union[str, None]: 

124 # needed here to prevent circular import: 

125 from .console import ConsoleRenderable 

126 

127 # always skip rich generated jupyter renderables or None values 

128 if _safe_isinstance(value, JupyterRenderable) or value is None: 

129 return None 

130 

131 console = console or get_console() 

132 

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") 

159 

160 

161def _safe_isinstance( 

162 obj: object, class_or_tuple: Union[type, Tuple[type, ...]] 

163) -> bool: 

164 """isinstance can fail in rare cases, for example types with no __class__""" 

165 try: 

166 return isinstance(obj, class_or_tuple) 

167 except Exception: 

168 return False 

169 

170 

171def install( 

172 console: Optional["Console"] = None, 

173 overflow: "OverflowMethod" = "ignore", 

174 crop: bool = False, 

175 indent_guides: bool = False, 

176 max_length: Optional[int] = None, 

177 max_string: Optional[int] = None, 

178 max_depth: Optional[int] = None, 

179 expand_all: bool = False, 

180) -> None: 

181 """Install automatic pretty printing in the Python REPL. 

182 

183 Args: 

184 console (Console, optional): Console instance or ``None`` to use global console. Defaults to None. 

185 overflow (Optional[OverflowMethod], optional): Overflow method. Defaults to "ignore". 

186 crop (Optional[bool], optional): Enable cropping of long lines. Defaults to False. 

187 indent_guides (bool, optional): Enable indentation guides. Defaults to False. 

188 max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation. 

189 Defaults to None. 

190 max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to None. 

191 max_depth (int, optional): Maximum depth of nested data structures, or None for no maximum. Defaults to None. 

192 expand_all (bool, optional): Expand all containers. Defaults to False. 

193 max_frames (int): Maximum number of frames to show in a traceback, 0 for no maximum. Defaults to 100. 

194 """ 

195 from rich import get_console 

196 

197 console = console or get_console() 

198 assert console is not None 

199 

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] 

222 

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 

229 

230 class RichFormatter(BaseFormatter): # type: ignore[misc] 

231 pprint: bool = True 

232 

233 def __call__(self, value: Any) -> Any: 

234 if self.pprint: 

235 return _ipy_display_hook( 

236 value, 

237 console=get_console(), 

238 overflow=overflow, 

239 indent_guides=indent_guides, 

240 max_length=max_length, 

241 max_string=max_string, 

242 max_depth=max_depth, 

243 expand_all=expand_all, 

244 ) 

245 else: 

246 return repr(value) 

247 

248 # replace plain text formatter with rich formatter 

249 rich_formatter = RichFormatter() 

250 ip.display_formatter.formatters["text/plain"] = rich_formatter 

251 

252 

253class Pretty(JupyterMixin): 

254 """A rich renderable that pretty prints an object. 

255 

256 Args: 

257 _object (Any): An object to pretty print. 

258 highlighter (HighlighterType, optional): Highlighter object to apply to result, or None for ReprHighlighter. Defaults to None. 

259 indent_size (int, optional): Number of spaces in indent. Defaults to 4. 

260 justify (JustifyMethod, optional): Justify method, or None for default. Defaults to None. 

261 overflow (OverflowMethod, optional): Overflow method, or None for default. Defaults to None. 

262 no_wrap (Optional[bool], optional): Disable word wrapping. Defaults to False. 

263 indent_guides (bool, optional): Enable indentation guides. Defaults to False. 

264 max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation. 

265 Defaults to None. 

266 max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to None. 

267 max_depth (int, optional): Maximum depth of nested data structures, or None for no maximum. Defaults to None. 

268 expand_all (bool, optional): Expand all containers. Defaults to False. 

269 margin (int, optional): Subtrace a margin from width to force containers to expand earlier. Defaults to 0. 

270 insert_line (bool, optional): Insert a new line if the output has multiple new lines. Defaults to False. 

271 """ 

272 

273 def __init__( 

274 self, 

275 _object: Any, 

276 highlighter: Optional["HighlighterType"] = None, 

277 *, 

278 indent_size: int = 4, 

279 justify: Optional["JustifyMethod"] = None, 

280 overflow: Optional["OverflowMethod"] = None, 

281 no_wrap: Optional[bool] = False, 

282 indent_guides: bool = False, 

283 max_length: Optional[int] = None, 

284 max_string: Optional[int] = None, 

285 max_depth: Optional[int] = None, 

286 expand_all: bool = False, 

287 margin: int = 0, 

288 insert_line: bool = False, 

289 ) -> None: 

290 self._object = _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 

303 

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 

338 

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) 

355 

356 

357def _get_braces_for_defaultdict(_object: DefaultDict[Any, Any]) -> Tuple[str, str, str]: 

358 return ( 

359 f"defaultdict({_object.default_factory!r}, {{", 

360 "})", 

361 f"defaultdict({_object.default_factory!r}, {{}})", 

362 ) 

363 

364 

365def _get_braces_for_deque(_object: Deque[Any]) -> Tuple[str, str, str]: 

366 if _object.maxlen is None: 

367 return ("deque([", "])", "deque()") 

368 return ( 

369 "deque([", 

370 f"], maxlen={_object.maxlen})", 

371 f"deque(maxlen={_object.maxlen})", 

372 ) 

373 

374 

375def _get_braces_for_array(_object: "array[Any]") -> Tuple[str, str, str]: 

376 return (f"array({_object.typecode!r}, [", "])", f"array({_object.typecode!r})") 

377 

378 

379_BRACES: Dict[type, Callable[[Any], Tuple[str, str, str]]] = { 

380 os._Environ: lambda _object: ("environ({", "})", "environ({})"), 

381 array: _get_braces_for_array, 

382 defaultdict: _get_braces_for_defaultdict, 

383 Counter: lambda _object: ("Counter({", "})", "Counter()"), 

384 deque: _get_braces_for_deque, 

385 dict: lambda _object: ("{", "}", "{}"), 

386 UserDict: lambda _object: ("{", "}", "{}"), 

387 frozenset: lambda _object: ("frozenset({", "})", "frozenset()"), 

388 list: lambda _object: ("[", "]", "[]"), 

389 UserList: lambda _object: ("[", "]", "[]"), 

390 set: lambda _object: ("{", "}", "set()"), 

391 tuple: lambda _object: ("(", ")", "()"), 

392 MappingProxyType: lambda _object: ("mappingproxy({", "})", "mappingproxy({})"), 

393} 

394_CONTAINERS = tuple(_BRACES.keys()) 

395_MAPPING_CONTAINERS = (dict, os._Environ, MappingProxyType, UserDict) 

396 

397 

398def is_expandable(obj: Any) -> bool: 

399 """Check if an object may be expanded by pretty print.""" 

400 return ( 

401 _safe_isinstance(obj, _CONTAINERS) 

402 or (is_dataclass(obj)) 

403 or (hasattr(obj, "__rich_repr__")) 

404 or _is_attr_object(obj) 

405 ) and not isclass(obj) 

406 

407 

408@dataclass 

409class Node: 

410 """A node in a repr tree. May be atomic or a container.""" 

411 

412 key_repr: str = "" 

413 value_repr: str = "" 

414 open_brace: str = "" 

415 close_brace: str = "" 

416 empty: str = "" 

417 last: bool = False 

418 is_tuple: bool = False 

419 is_namedtuple: bool = False 

420 children: Optional[List["Node"]] = None 

421 key_separator: str = ": " 

422 separator: str = ", " 

423 

424 def iter_tokens(self) -> Iterable[str]: 

425 """Generate tokens for this node.""" 

426 if self.key_repr: 

427 yield self.key_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 

445 

446 def check_length(self, start_length: int, max_length: int) -> bool: 

447 """Check the length fits within a limit. 

448 

449 Args: 

450 start_length (int): Starting length of the line (indent, prefix, suffix). 

451 max_length (int): Maximum length. 

452 

453 Returns: 

454 bool: True if the node can be rendered within max length, otherwise False. 

455 """ 

456 total_length = start_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 

462 

463 def __str__(self) -> str: 

464 repr_text = "".join(self.iter_tokens()) 

465 return repr_text 

466 

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. 

471 

472 Args: 

473 max_width (int, optional): Maximum width of the repr. Defaults to 80. 

474 indent_size (int, optional): Size of indents. Defaults to 4. 

475 expand_all (bool, optional): Expand all levels. Defaults to False. 

476 

477 Returns: 

478 str: A repr string of the original object. 

479 """ 

480 lines = [_Line(node=self, is_root=True)] 

481 line_no = 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 

488 

489 repr_str = "\n".join(str(line) for line in lines) 

490 return repr_str 

491 

492 

493@dataclass 

494class _Line: 

495 """A line in repr output.""" 

496 

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 

505 

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) 

510 

511 def check_length(self, max_length: int) -> bool: 

512 """Check this line fits within a given number of cells.""" 

513 start_length = ( 

514 len(self.whitespace) + cell_len(self.text) + cell_len(self.suffix) 

515 ) 

516 assert self.node is not None 

517 return self.node.check_length(start_length, max_length) 

518 

519 def expand(self, indent_size: int) -> Iterable["_Line"]: 

520 """Expand this line by adding children on their own line.""" 

521 node = self.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 

544 

545 yield _Line( 

546 text=node.close_brace, 

547 whitespace=whitespace, 

548 suffix=self.suffix, 

549 last=self.last, 

550 ) 

551 

552 def __str__(self) -> str: 

553 if self.last: 

554 return f"{self.whitespace}{self.text}{self.node or ''}" 

555 else: 

556 return ( 

557 f"{self.whitespace}{self.text}{self.node or ''}{self.suffix.rstrip()}" 

558 ) 

559 

560 

561def _is_namedtuple(obj: Any) -> bool: 

562 """Checks if an object is most likely a namedtuple. It is 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. 

565 

566 Args: 

567 obj (Any): The object to test 

568 

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) 

578 

579 

580def traverse( 

581 _object: Any, 

582 max_length: Optional[int] = None, 

583 max_string: Optional[int] = None, 

584 max_depth: Optional[int] = None, 

585) -> Node: 

586 """Traverse object and generate a tree. 

587 

588 Args: 

589 _object (Any): Object to be traversed. 

590 max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation. 

591 Defaults to None. 

592 max_string (int, optional): Maximum length of string before truncating, or None to disable truncating. 

593 Defaults to None. 

594 max_depth (int, optional): Maximum depth of data structures, or None for no maximum. 

595 Defaults to None. 

596 

597 Returns: 

598 Node: The root of a tree structure which can be used to render a pretty repr. 

599 """ 

600 

601 def to_repr(obj: Any) -> str: 

602 """Get repr string for an object, but catch errors.""" 

603 if ( 

604 max_string is not 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 

616 

617 visited_ids: Set[int] = set() 

618 push_visited = visited_ids.add 

619 pop_visited = visited_ids.remove 

620 

621 def _traverse(obj: Any, root: bool = False, depth: int = 0) -> Node: 

622 """Walk the object depth first.""" 

623 

624 obj_id = id(obj) 

625 if obj_id in visited_ids: 

626 # Recursion detected 

627 return Node(value_repr="...") 

628 

629 obj_type = type(obj) 

630 children: List[Node] 

631 reached_max_depth = max_depth is not None and depth >= max_depth 

632 

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 

648 

649 try: 

650 fake_attributes = hasattr( 

651 obj, "awehoi234_wdfjwljet234_234wdfoijsdfmmnxpi492" 

652 ) 

653 except Exception: 

654 fake_attributes = False 

655 

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 

663 

664 if rich_repr_result is not None: 

665 push_visited(obj_id) 

666 angular = getattr(obj.__rich_repr__, "angular", False) 

667 args = list(iter_rich_args(rich_repr_result)) 

668 class_name = obj.__class__.__name__ 

669 

670 if args: 

671 children = [] 

672 append = children.append 

673 

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 

718 

719 attr_fields = _get_attr_fields(obj) 

720 if attr_fields: 

721 if reached_max_depth: 

722 node = Node(value_repr=f"{obj.__class__.__name__}(...)") 

723 else: 

724 node = Node( 

725 open_brace=f"{obj.__class__.__name__}(", 

726 close_brace=")", 

727 children=children, 

728 last=root, 

729 ) 

730 

731 def iter_attrs() -> ( 

732 Iterable[Tuple[str, Any, Optional[Callable[[Any], str]]]] 

733 ): 

734 """Iterate over attr fields and values.""" 

735 for attr in attr_fields: 

736 if attr.repr: 

737 try: 

738 value = getattr(obj, attr.name) 

739 except Exception as error: 

740 # Can happen, albeit 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 ) 

748 

749 for last, (name, value, repr_callable) in loop_last(iter_attrs()): 

750 if repr_callable: 

751 child_node = Node(value_repr=str(repr_callable(value))) 

752 else: 

753 child_node = _traverse(value, depth=depth + 1) 

754 child_node.last = 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 ) 

782 

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) 

793 

794 pop_visited(obj_id) 

795 elif _is_namedtuple(obj) and _has_default_namedtuple_repr(obj): 

796 push_visited(obj_id) 

797 class_name = obj.__class__.__name__ 

798 if reached_max_depth: 

799 # If we've reached the max depth, we still show the class name, but not its 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 

824 

825 push_visited(obj_id) 

826 

827 open_brace, close_brace, empty = _BRACES[obj_type](obj) 

828 

829 if reached_max_depth: 

830 node = Node(value_repr=f"{open_brace}...{close_brace}") 

831 elif obj_type.__repr__ != type(obj).__repr__: 

832 node = Node(value_repr=to_repr(obj), last=root) 

833 elif obj: 

834 children = [] 

835 node = Node( 

836 open_brace=open_brace, 

837 close_brace=close_brace, 

838 children=children, 

839 last=root, 

840 ) 

841 append = children.append 

842 num_items = len(obj) 

843 last_item_index = num_items - 1 

844 

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) 

866 

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 

873 

874 node = _traverse(_object, root=True) 

875 return node 

876 

877 

878def pretty_repr( 

879 _object: Any, 

880 *, 

881 max_width: int = 80, 

882 indent_size: int = 4, 

883 max_length: Optional[int] = None, 

884 max_string: Optional[int] = None, 

885 max_depth: Optional[int] = None, 

886 expand_all: bool = False, 

887) -> str: 

888 """Prettify repr string by expanding on to new lines to fit within a given width. 

889 

890 Args: 

891 _object (Any): Object to repr. 

892 max_width (int, optional): Desired maximum width of repr string. Defaults to 80. 

893 indent_size (int, optional): Number of spaces to indent. Defaults to 4. 

894 max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation. 

895 Defaults to None. 

896 max_string (int, optional): Maximum length of string before truncating, or None to disable truncating. 

897 Defaults to None. 

898 max_depth (int, optional): Maximum depth of nested data structure, or None for no depth. 

899 Defaults to None. 

900 expand_all (bool, optional): Expand all containers regardless of available width. Defaults to False. 

901 

902 Returns: 

903 str: A possibly multi-line representation of the object. 

904 """ 

905 

906 if _safe_isinstance(_object, Node): 

907 node = _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 

916 

917 

918def pprint( 

919 _object: Any, 

920 *, 

921 console: Optional["Console"] = None, 

922 indent_guides: bool = True, 

923 max_length: Optional[int] = None, 

924 max_string: Optional[int] = None, 

925 max_depth: Optional[int] = None, 

926 expand_all: bool = False, 

927) -> None: 

928 """A convenience function for pretty printing. 

929 

930 Args: 

931 _object (Any): Object to pretty print. 

932 console (Console, optional): Console instance, or None to use default. Defaults to None. 

933 max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation. 

934 Defaults to None. 

935 max_string (int, optional): Maximum length of strings before truncating, or None to disable. Defaults to None. 

936 max_depth (int, optional): Maximum depth for nested data structures, or None for unlimited depth. Defaults to None. 

937 indent_guides (bool, optional): Enable indentation guides. Defaults to True. 

938 expand_all (bool, optional): Expand all containers. Defaults to False. 

939 """ 

940 _console = get_console() if console is None else 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 ) 

953 

954 

955if __name__ == "__main__": # pragma: no cover 

956 

957 class BrokenRepr: 

958 def __repr__(self) -> str: 

959 1 / 0 

960 return "this will fail" 

961 

962 from typing import NamedTuple 

963 

964 class StockKeepingUnit(NamedTuple): 

965 name: str 

966 description: str 

967 price: float 

968 category: str 

969 reviews: List[str] 

970 

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] 

1007 

1008 from rich import print 

1009 

1010 print(Pretty(data, indent_guides=True, max_string=20)) 

1011 

1012 class Thing: 

1013 def __repr__(self) -> str: 

1014 return "Hello\x1b[38;5;239m World!" 

1015 

1016 print(Pretty(Thing()))