Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/rich/pretty.py: 21%

396 statements  

« prev     ^ index     » next       coverage.py v7.0.1, created at 2022-12-25 06:11 +0000

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) 

27 

28from rich.repr import RichReprResult 

29 

30try: 

31 import attr as _attr_module 

32 

33 _has_attrs = True 

34except ImportError: # pragma: no cover 

35 _has_attrs = False 

36 

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 

46 

47if TYPE_CHECKING: 

48 from .console import ( 

49 Console, 

50 ConsoleOptions, 

51 HighlighterType, 

52 JustifyMethod, 

53 OverflowMethod, 

54 RenderResult, 

55 ) 

56 

57 

58JUPYTER_CLASSES_TO_NOT_RENDER = { 

59 # Matplotlib "Artists" manage their own rendering in a Jupyter notebook, and we should not try to render them too. 

60 # "Typically, all [Matplotlib] visible elements in a figure are subclasses of Artist." 

61 "matplotlib.artist.Artist", 

62} 

63 

64 

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

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

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

68 

69 

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

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

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

73 

74 

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

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

77 

78 Args: 

79 obj (object): A dataclass instance. 

80 

81 Returns: 

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

83 """ 

84 # Digging in to a lot of internals here 

85 # Catching all exceptions in case something is missing on a non CPython implementation 

86 try: 

87 return obj.__repr__.__code__.co_filename == dataclasses.__file__ 

88 except Exception: # pragma: no coverage 

89 return False 

90 

91 

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

93 

94 

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

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

97 

98 Args: 

99 obj (object): A namedtuple 

100 

101 Returns: 

102 bool: True if the default repr is used, False if there's a custom repr. 

103 """ 

104 obj_file = None 

105 try: 

106 obj_file = inspect.getfile(obj.__repr__) 

107 except (OSError, TypeError): 

108 # OSError handles case where object is defined in __main__ scope, e.g. REPL - no filename available. 

109 # TypeError trapped defensively, in case of object without filename slips through. 

110 pass 

111 default_repr_file = inspect.getfile(_dummy_namedtuple.__repr__) 

112 return obj_file == default_repr_file 

113 

114 

115def _ipy_display_hook( 

116 value: Any, 

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

118 overflow: "OverflowMethod" = "ignore", 

119 crop: bool = False, 

120 indent_guides: bool = False, 

121 max_length: Optional[int] = None, 

122 max_string: Optional[int] = None, 

123 max_depth: Optional[int] = None, 

124 expand_all: bool = False, 

125) -> None: 

126 # needed here to prevent circular import: 

127 from ._inspect import is_object_one_of_types 

128 from .console import ConsoleRenderable 

129 

130 # always skip rich generated jupyter renderables or None values 

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

132 return 

133 

134 console = console or get_console() 

135 if console.is_jupyter: 

136 # Delegate rendering to IPython if the object (and IPython) supports it 

137 # https://ipython.readthedocs.io/en/stable/config/integrating.html#rich-display 

138 ipython_repr_methods = [ 

139 "_repr_html_", 

140 "_repr_markdown_", 

141 "_repr_json_", 

142 "_repr_latex_", 

143 "_repr_jpeg_", 

144 "_repr_png_", 

145 "_repr_svg_", 

146 "_repr_mimebundle_", 

147 ] 

148 for repr_method in ipython_repr_methods: 

149 method = getattr(value, repr_method, None) 

150 if inspect.ismethod(method): 

151 # Calling the method ourselves isn't ideal. The interface for the `_repr_*_` methods 

152 # specifies that if they return None, then they should not be rendered 

153 # by the notebook. 

154 try: 

155 repr_result = method() 

156 except Exception: 

157 continue # If the method raises, treat it as if it doesn't exist, try any others 

158 if repr_result is not None: 

159 return # Delegate rendering to IPython 

160 

161 # When in a Jupyter notebook let's avoid the display of some specific classes, 

162 # as they result in the rendering of useless and noisy lines such as `<Figure size 432x288 with 1 Axes>`. 

163 # What does this do? 

164 # --> if the class has "matplotlib.artist.Artist" in its hierarchy for example, we don't render it. 

165 if is_object_one_of_types(value, JUPYTER_CLASSES_TO_NOT_RENDER): 

166 return 

167 

168 # certain renderables should start on a new line 

169 if _safe_isinstance(value, ConsoleRenderable): 

170 console.line() 

171 

172 console.print( 

173 value 

174 if _safe_isinstance(value, RichRenderable) 

175 else Pretty( 

176 value, 

177 overflow=overflow, 

178 indent_guides=indent_guides, 

179 max_length=max_length, 

180 max_string=max_string, 

181 max_depth=max_depth, 

182 expand_all=expand_all, 

183 margin=12, 

184 ), 

185 crop=crop, 

186 new_line_start=True, 

187 ) 

188 

189 

190def _safe_isinstance( 

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

192) -> bool: 

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

194 try: 

195 return isinstance(obj, class_or_tuple) 

196 except Exception: 

197 return False 

198 

199 

200def install( 

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

202 overflow: "OverflowMethod" = "ignore", 

203 crop: bool = False, 

204 indent_guides: bool = False, 

205 max_length: Optional[int] = None, 

206 max_string: Optional[int] = None, 

207 max_depth: Optional[int] = None, 

208 expand_all: bool = False, 

209) -> None: 

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

211 

212 Args: 

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

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

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

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

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

218 Defaults to None. 

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

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

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

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

223 """ 

224 from rich import get_console 

225 

226 console = console or get_console() 

227 assert console is not None 

228 

229 def display_hook(value: Any) -> None: 

230 """Replacement sys.displayhook which prettifies objects with Rich.""" 

231 if value is not None: 

232 assert console is not None 

233 builtins._ = None # type: ignore[attr-defined] 

234 console.print( 

235 value 

236 if _safe_isinstance(value, RichRenderable) 

237 else Pretty( 

238 value, 

239 overflow=overflow, 

240 indent_guides=indent_guides, 

241 max_length=max_length, 

242 max_string=max_string, 

243 max_depth=max_depth, 

244 expand_all=expand_all, 

245 ), 

246 crop=crop, 

247 ) 

248 builtins._ = value # type: ignore[attr-defined] 

249 

250 try: # pragma: no cover 

251 ip = get_ipython() # type: ignore[name-defined] 

252 from IPython.core.formatters import BaseFormatter 

253 

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

255 pprint: bool = True 

256 

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

258 if self.pprint: 

259 return _ipy_display_hook( 

260 value, 

261 console=get_console(), 

262 overflow=overflow, 

263 indent_guides=indent_guides, 

264 max_length=max_length, 

265 max_string=max_string, 

266 max_depth=max_depth, 

267 expand_all=expand_all, 

268 ) 

269 else: 

270 return repr(value) 

271 

272 # replace plain text formatter with rich formatter 

273 rich_formatter = RichFormatter() 

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

275 except Exception: 

276 sys.displayhook = display_hook 

277 

278 

279class Pretty(JupyterMixin): 

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

281 

282 Args: 

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

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

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

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

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

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

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

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

291 Defaults to None. 

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

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

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

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

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

297 """ 

298 

299 def __init__( 

300 self, 

301 _object: Any, 

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

303 *, 

304 indent_size: int = 4, 

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

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

307 no_wrap: Optional[bool] = False, 

308 indent_guides: bool = False, 

309 max_length: Optional[int] = None, 

310 max_string: Optional[int] = None, 

311 max_depth: Optional[int] = None, 

312 expand_all: bool = False, 

313 margin: int = 0, 

314 insert_line: bool = False, 

315 ) -> None: 

316 self._object = _object 

317 self.highlighter = highlighter or ReprHighlighter() 

318 self.indent_size = indent_size 

319 self.justify: Optional["JustifyMethod"] = justify 

320 self.overflow: Optional["OverflowMethod"] = overflow 

321 self.no_wrap = no_wrap 

322 self.indent_guides = indent_guides 

323 self.max_length = max_length 

324 self.max_string = max_string 

325 self.max_depth = max_depth 

326 self.expand_all = expand_all 

327 self.margin = margin 

328 self.insert_line = insert_line 

329 

330 def __rich_console__( 

331 self, console: "Console", options: "ConsoleOptions" 

332 ) -> "RenderResult": 

333 pretty_str = pretty_repr( 

334 self._object, 

335 max_width=options.max_width - self.margin, 

336 indent_size=self.indent_size, 

337 max_length=self.max_length, 

338 max_string=self.max_string, 

339 max_depth=self.max_depth, 

340 expand_all=self.expand_all, 

341 ) 

342 pretty_text = Text.from_ansi( 

343 pretty_str, 

344 justify=self.justify or options.justify, 

345 overflow=self.overflow or options.overflow, 

346 no_wrap=pick_bool(self.no_wrap, options.no_wrap), 

347 style="pretty", 

348 ) 

349 pretty_text = ( 

350 self.highlighter(pretty_text) 

351 if pretty_text 

352 else Text( 

353 f"{type(self._object)}.__repr__ returned empty string", 

354 style="dim italic", 

355 ) 

356 ) 

357 if self.indent_guides and not options.ascii_only: 

358 pretty_text = pretty_text.with_indent_guides( 

359 self.indent_size, style="repr.indent" 

360 ) 

361 if self.insert_line and "\n" in pretty_text: 

362 yield "" 

363 yield pretty_text 

364 

365 def __rich_measure__( 

366 self, console: "Console", options: "ConsoleOptions" 

367 ) -> "Measurement": 

368 pretty_str = pretty_repr( 

369 self._object, 

370 max_width=options.max_width, 

371 indent_size=self.indent_size, 

372 max_length=self.max_length, 

373 max_string=self.max_string, 

374 expand_all=self.expand_all, 

375 ) 

376 text_width = ( 

377 max(cell_len(line) for line in pretty_str.splitlines()) if pretty_str else 0 

378 ) 

379 return Measurement(text_width, text_width) 

380 

381 

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

383 return ( 

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

385 "})", 

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

387 ) 

388 

389 

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

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

392 

393 

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

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

396 array: _get_braces_for_array, 

397 defaultdict: _get_braces_for_defaultdict, 

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

399 deque: lambda _object: ("deque([", "])", "deque()"), 

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

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

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

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

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

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

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

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

408} 

409_CONTAINERS = tuple(_BRACES.keys()) 

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

411 

412 

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

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

415 return ( 

416 _safe_isinstance(obj, _CONTAINERS) 

417 or (is_dataclass(obj)) 

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

419 or _is_attr_object(obj) 

420 ) and not isclass(obj) 

421 

422 

423@dataclass 

424class Node: 

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

426 

427 key_repr: str = "" 

428 value_repr: str = "" 

429 open_brace: str = "" 

430 close_brace: str = "" 

431 empty: str = "" 

432 last: bool = False 

433 is_tuple: bool = False 

434 is_namedtuple: bool = False 

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

436 key_separator = ": " 

437 separator: str = ", " 

438 

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

440 """Generate tokens for this node.""" 

441 if self.key_repr: 

442 yield self.key_repr 

443 yield self.key_separator 

444 if self.value_repr: 

445 yield self.value_repr 

446 elif self.children is not None: 

447 if self.children: 

448 yield self.open_brace 

449 if self.is_tuple and not self.is_namedtuple and len(self.children) == 1: 

450 yield from self.children[0].iter_tokens() 

451 yield "," 

452 else: 

453 for child in self.children: 

454 yield from child.iter_tokens() 

455 if not child.last: 

456 yield self.separator 

457 yield self.close_brace 

458 else: 

459 yield self.empty 

460 

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

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

463 

464 Args: 

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

466 max_length (int): Maximum length. 

467 

468 Returns: 

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

470 """ 

471 total_length = start_length 

472 for token in self.iter_tokens(): 

473 total_length += cell_len(token) 

474 if total_length > max_length: 

475 return False 

476 return True 

477 

478 def __str__(self) -> str: 

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

480 return repr_text 

481 

482 def render( 

483 self, max_width: int = 80, indent_size: int = 4, expand_all: bool = False 

484 ) -> str: 

485 """Render the node to a pretty repr. 

486 

487 Args: 

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

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

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

491 

492 Returns: 

493 str: A repr string of the original object. 

494 """ 

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

496 line_no = 0 

497 while line_no < len(lines): 

498 line = lines[line_no] 

499 if line.expandable and not line.expanded: 

500 if expand_all or not line.check_length(max_width): 

501 lines[line_no : line_no + 1] = line.expand(indent_size) 

502 line_no += 1 

503 

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

505 return repr_str 

506 

507 

508@dataclass 

509class _Line: 

510 """A line in repr output.""" 

511 

512 parent: Optional["_Line"] = None 

513 is_root: bool = False 

514 node: Optional[Node] = None 

515 text: str = "" 

516 suffix: str = "" 

517 whitespace: str = "" 

518 expanded: bool = False 

519 last: bool = False 

520 

521 @property 

522 def expandable(self) -> bool: 

523 """Check if the line may be expanded.""" 

524 return bool(self.node is not None and self.node.children) 

525 

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

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

528 start_length = ( 

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

530 ) 

531 assert self.node is not None 

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

533 

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

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

536 node = self.node 

537 assert node is not None 

538 whitespace = self.whitespace 

539 assert node.children 

540 if node.key_repr: 

541 new_line = yield _Line( 

542 text=f"{node.key_repr}{node.key_separator}{node.open_brace}", 

543 whitespace=whitespace, 

544 ) 

545 else: 

546 new_line = yield _Line(text=node.open_brace, whitespace=whitespace) 

547 child_whitespace = self.whitespace + " " * indent_size 

548 tuple_of_one = node.is_tuple and len(node.children) == 1 

549 for last, child in loop_last(node.children): 

550 separator = "," if tuple_of_one else node.separator 

551 line = _Line( 

552 parent=new_line, 

553 node=child, 

554 whitespace=child_whitespace, 

555 suffix=separator, 

556 last=last and not tuple_of_one, 

557 ) 

558 yield line 

559 

560 yield _Line( 

561 text=node.close_brace, 

562 whitespace=whitespace, 

563 suffix=self.suffix, 

564 last=self.last, 

565 ) 

566 

567 def __str__(self) -> str: 

568 if self.last: 

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

570 else: 

571 return ( 

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

573 ) 

574 

575 

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

577 """Checks if an object is most likely a namedtuple. It is possible 

578 to craft an object that passes this check and isn't a namedtuple, but 

579 there is only a minuscule chance of this happening unintentionally. 

580 

581 Args: 

582 obj (Any): The object to test 

583 

584 Returns: 

585 bool: True if the object is a namedtuple. False otherwise. 

586 """ 

587 try: 

588 fields = getattr(obj, "_fields", None) 

589 except Exception: 

590 # Being very defensive - if we cannot get the attr then its not a namedtuple 

591 return False 

592 return isinstance(obj, tuple) and isinstance(fields, tuple) 

593 

594 

595def traverse( 

596 _object: Any, 

597 max_length: Optional[int] = None, 

598 max_string: Optional[int] = None, 

599 max_depth: Optional[int] = None, 

600) -> Node: 

601 """Traverse object and generate a tree. 

602 

603 Args: 

604 _object (Any): Object to be traversed. 

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

606 Defaults to None. 

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

608 Defaults to None. 

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

610 Defaults to None. 

611 

612 Returns: 

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

614 """ 

615 

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

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

618 if ( 

619 max_string is not None 

620 and _safe_isinstance(obj, (bytes, str)) 

621 and len(obj) > max_string 

622 ): 

623 truncated = len(obj) - max_string 

624 obj_repr = f"{obj[:max_string]!r}+{truncated}" 

625 else: 

626 try: 

627 obj_repr = repr(obj) 

628 except Exception as error: 

629 obj_repr = f"<repr-error {str(error)!r}>" 

630 return obj_repr 

631 

632 visited_ids: Set[int] = set() 

633 push_visited = visited_ids.add 

634 pop_visited = visited_ids.remove 

635 

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

637 """Walk the object depth first.""" 

638 

639 obj_id = id(obj) 

640 if obj_id in visited_ids: 

641 # Recursion detected 

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

643 

644 obj_type = type(obj) 

645 py_version = (sys.version_info.major, sys.version_info.minor) 

646 children: List[Node] 

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

648 

649 def iter_rich_args(rich_args: Any) -> Iterable[Union[Any, Tuple[str, Any]]]: 

650 for arg in rich_args: 

651 if _safe_isinstance(arg, tuple): 

652 if len(arg) == 3: 

653 key, child, default = arg 

654 if default == child: 

655 continue 

656 yield key, child 

657 elif len(arg) == 2: 

658 key, child = arg 

659 yield key, child 

660 elif len(arg) == 1: 

661 yield arg[0] 

662 else: 

663 yield arg 

664 

665 try: 

666 fake_attributes = hasattr( 

667 obj, "awehoi234_wdfjwljet234_234wdfoijsdfmmnxpi492" 

668 ) 

669 except Exception: 

670 fake_attributes = False 

671 

672 rich_repr_result: Optional[RichReprResult] = None 

673 if not fake_attributes: 

674 try: 

675 if hasattr(obj, "__rich_repr__") and not isclass(obj): 

676 rich_repr_result = obj.__rich_repr__() 

677 except Exception: 

678 pass 

679 

680 if rich_repr_result is not None: 

681 push_visited(obj_id) 

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

683 args = list(iter_rich_args(rich_repr_result)) 

684 class_name = obj.__class__.__name__ 

685 

686 if args: 

687 children = [] 

688 append = children.append 

689 

690 if reached_max_depth: 

691 if angular: 

692 node = Node(value_repr=f"<{class_name}...>") 

693 else: 

694 node = Node(value_repr=f"{class_name}(...)") 

695 else: 

696 if angular: 

697 node = Node( 

698 open_brace=f"<{class_name} ", 

699 close_brace=">", 

700 children=children, 

701 last=root, 

702 separator=" ", 

703 ) 

704 else: 

705 node = Node( 

706 open_brace=f"{class_name}(", 

707 close_brace=")", 

708 children=children, 

709 last=root, 

710 ) 

711 for last, arg in loop_last(args): 

712 if _safe_isinstance(arg, tuple): 

713 key, child = arg 

714 child_node = _traverse(child, depth=depth + 1) 

715 child_node.last = last 

716 child_node.key_repr = key 

717 child_node.key_separator = "=" 

718 append(child_node) 

719 else: 

720 child_node = _traverse(arg, depth=depth + 1) 

721 child_node.last = last 

722 append(child_node) 

723 else: 

724 node = Node( 

725 value_repr=f"<{class_name}>" if angular else f"{class_name}()", 

726 children=[], 

727 last=root, 

728 ) 

729 pop_visited(obj_id) 

730 elif _is_attr_object(obj) and not fake_attributes: 

731 push_visited(obj_id) 

732 children = [] 

733 append = children.append 

734 

735 attr_fields = _get_attr_fields(obj) 

736 if attr_fields: 

737 if reached_max_depth: 

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

739 else: 

740 node = Node( 

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

742 close_brace=")", 

743 children=children, 

744 last=root, 

745 ) 

746 

747 def iter_attrs() -> Iterable[ 

748 Tuple[str, Any, Optional[Callable[[Any], str]]] 

749 ]: 

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

751 for attr in attr_fields: 

752 if attr.repr: 

753 try: 

754 value = getattr(obj, attr.name) 

755 except Exception as error: 

756 # Can happen, albeit rarely 

757 yield (attr.name, error, None) 

758 else: 

759 yield ( 

760 attr.name, 

761 value, 

762 attr.repr if callable(attr.repr) else None, 

763 ) 

764 

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

766 if repr_callable: 

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

768 else: 

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

770 child_node.last = last 

771 child_node.key_repr = name 

772 child_node.key_separator = "=" 

773 append(child_node) 

774 else: 

775 node = Node( 

776 value_repr=f"{obj.__class__.__name__}()", children=[], last=root 

777 ) 

778 pop_visited(obj_id) 

779 elif ( 

780 is_dataclass(obj) 

781 and not _safe_isinstance(obj, type) 

782 and not fake_attributes 

783 and (_is_dataclass_repr(obj) or py_version == (3, 6)) 

784 ): 

785 push_visited(obj_id) 

786 children = [] 

787 append = children.append 

788 if reached_max_depth: 

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

790 else: 

791 node = Node( 

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

793 close_brace=")", 

794 children=children, 

795 last=root, 

796 ) 

797 

798 for last, field in loop_last( 

799 field for field in fields(obj) if field.repr 

800 ): 

801 child_node = _traverse(getattr(obj, field.name), depth=depth + 1) 

802 child_node.key_repr = field.name 

803 child_node.last = last 

804 child_node.key_separator = "=" 

805 append(child_node) 

806 

807 pop_visited(obj_id) 

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

809 push_visited(obj_id) 

810 class_name = obj.__class__.__name__ 

811 if reached_max_depth: 

812 # If we've reached the max depth, we still show the class name, but not its contents 

813 node = Node( 

814 value_repr=f"{class_name}(...)", 

815 ) 

816 else: 

817 children = [] 

818 append = children.append 

819 node = Node( 

820 open_brace=f"{class_name}(", 

821 close_brace=")", 

822 children=children, 

823 empty=f"{class_name}()", 

824 ) 

825 for last, (key, value) in loop_last(obj._asdict().items()): 

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

827 child_node.key_repr = key 

828 child_node.last = last 

829 child_node.key_separator = "=" 

830 append(child_node) 

831 pop_visited(obj_id) 

832 elif _safe_isinstance(obj, _CONTAINERS): 

833 for container_type in _CONTAINERS: 

834 if _safe_isinstance(obj, container_type): 

835 obj_type = container_type 

836 break 

837 

838 push_visited(obj_id) 

839 

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

841 

842 if reached_max_depth: 

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

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

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

846 elif obj: 

847 children = [] 

848 node = Node( 

849 open_brace=open_brace, 

850 close_brace=close_brace, 

851 children=children, 

852 last=root, 

853 ) 

854 append = children.append 

855 num_items = len(obj) 

856 last_item_index = num_items - 1 

857 

858 if _safe_isinstance(obj, _MAPPING_CONTAINERS): 

859 iter_items = iter(obj.items()) 

860 if max_length is not None: 

861 iter_items = islice(iter_items, max_length) 

862 for index, (key, child) in enumerate(iter_items): 

863 child_node = _traverse(child, depth=depth + 1) 

864 child_node.key_repr = to_repr(key) 

865 child_node.last = index == last_item_index 

866 append(child_node) 

867 else: 

868 iter_values = iter(obj) 

869 if max_length is not None: 

870 iter_values = islice(iter_values, max_length) 

871 for index, child in enumerate(iter_values): 

872 child_node = _traverse(child, depth=depth + 1) 

873 child_node.last = index == last_item_index 

874 append(child_node) 

875 if max_length is not None and num_items > max_length: 

876 append(Node(value_repr=f"... +{num_items - max_length}", last=True)) 

877 else: 

878 node = Node(empty=empty, children=[], last=root) 

879 

880 pop_visited(obj_id) 

881 else: 

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

883 node.is_tuple = _safe_isinstance(obj, tuple) 

884 node.is_namedtuple = _is_namedtuple(obj) 

885 return node 

886 

887 node = _traverse(_object, root=True) 

888 return node 

889 

890 

891def pretty_repr( 

892 _object: Any, 

893 *, 

894 max_width: int = 80, 

895 indent_size: int = 4, 

896 max_length: Optional[int] = None, 

897 max_string: Optional[int] = None, 

898 max_depth: Optional[int] = None, 

899 expand_all: bool = False, 

900) -> str: 

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

902 

903 Args: 

904 _object (Any): Object to repr. 

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

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

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

908 Defaults to None. 

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

910 Defaults to None. 

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

912 Defaults to None. 

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

914 

915 Returns: 

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

917 """ 

918 

919 if _safe_isinstance(_object, Node): 

920 node = _object 

921 else: 

922 node = traverse( 

923 _object, max_length=max_length, max_string=max_string, max_depth=max_depth 

924 ) 

925 repr_str: str = node.render( 

926 max_width=max_width, indent_size=indent_size, expand_all=expand_all 

927 ) 

928 return repr_str 

929 

930 

931def pprint( 

932 _object: Any, 

933 *, 

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

935 indent_guides: bool = True, 

936 max_length: Optional[int] = None, 

937 max_string: Optional[int] = None, 

938 max_depth: Optional[int] = None, 

939 expand_all: bool = False, 

940) -> None: 

941 """A convenience function for pretty printing. 

942 

943 Args: 

944 _object (Any): Object to pretty print. 

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

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

947 Defaults to None. 

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

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

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

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

952 """ 

953 _console = get_console() if console is None else console 

954 _console.print( 

955 Pretty( 

956 _object, 

957 max_length=max_length, 

958 max_string=max_string, 

959 max_depth=max_depth, 

960 indent_guides=indent_guides, 

961 expand_all=expand_all, 

962 overflow="ignore", 

963 ), 

964 soft_wrap=True, 

965 ) 

966 

967 

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

969 

970 class BrokenRepr: 

971 def __repr__(self) -> str: 

972 1 / 0 

973 return "this will fail" 

974 

975 from typing import NamedTuple 

976 

977 class StockKeepingUnit(NamedTuple): 

978 name: str 

979 description: str 

980 price: float 

981 category: str 

982 reviews: List[str] 

983 

984 d = defaultdict(int) 

985 d["foo"] = 5 

986 data = { 

987 "foo": [ 

988 1, 

989 "Hello World!", 

990 100.123, 

991 323.232, 

992 432324.0, 

993 {5, 6, 7, (1, 2, 3, 4), 8}, 

994 ], 

995 "bar": frozenset({1, 2, 3}), 

996 "defaultdict": defaultdict( 

997 list, {"crumble": ["apple", "rhubarb", "butter", "sugar", "flour"]} 

998 ), 

999 "counter": Counter( 

1000 [ 

1001 "apple", 

1002 "orange", 

1003 "pear", 

1004 "kumquat", 

1005 "kumquat", 

1006 "durian" * 100, 

1007 ] 

1008 ), 

1009 "atomic": (False, True, None), 

1010 "namedtuple": StockKeepingUnit( 

1011 "Sparkling British Spring Water", 

1012 "Carbonated spring water", 

1013 0.9, 

1014 "water", 

1015 ["its amazing!", "its terrible!"], 

1016 ), 

1017 "Broken": BrokenRepr(), 

1018 } 

1019 data["foo"].append(data) # type: ignore[attr-defined] 

1020 

1021 from rich import print 

1022 

1023 # print(Pretty(data, indent_guides=True, max_string=20)) 

1024 

1025 class Thing: 

1026 def __repr__(self) -> str: 

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

1028 

1029 print(Pretty(Thing()))