Coverage for /run/media/veganeco/water/status600.com/pypi/reptilian_climates/modules_series_2/ships/fields/gardens_pip/rich/pretty.py: 21%

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 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 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 try:

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

216 except NameError:

217 sys.displayhook = display_hook

218 else:

219 from IPython.core.formatters import BaseFormatter

220

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

222 pprint: bool = True

223

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

225 if self.pprint:

226 return _ipy_display_hook(

227 value,

228 console=get_console(),

229 overflow=overflow,

230 indent_guides=indent_guides,

231 max_length=max_length,

232 max_string=max_string,

233 max_depth=max_depth,

234 expand_all=expand_all,

235 )

236 else:

237 return repr(value)

238

239 # replace plain text formatter with rich formatter

240 rich_formatter = RichFormatter()

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

242

243

244 class Pretty(JupyterMixin):

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

246

247 Args:

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

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

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

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

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

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

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

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

256 Defaults to None.

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

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

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

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

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

262 """

263

264 def __init__(

265 self,

266 _object: Any,

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

268 *,

269 indent_size: int = 4,

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

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

272 no_wrap: Optional[bool] = False,

273 indent_guides: bool = False,

274 max_length: Optional[int] = None,

275 max_string: Optional[int] = None,

276 max_depth: Optional[int] = None,

277 expand_all: bool = False,

278 margin: int = 0,

279 insert_line: bool = False,

280 ) -> None:

281 self._object = _object

282 self.highlighter = highlighter or ReprHighlighter()

283 self.indent_size = indent_size

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

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

286 self.no_wrap = no_wrap

287 self.indent_guides = indent_guides

288 self.max_length = max_length

289 self.max_string = max_string

290 self.max_depth = max_depth

291 self.expand_all = expand_all

292 self.margin = margin

293 self.insert_line = insert_line

294

295 def __rich_console__(

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

297 ) -> "RenderResult":

298 pretty_str = pretty_repr(

299 self._object,

300 max_width=options.max_width - self.margin,

301 indent_size=self.indent_size,

302 max_length=self.max_length,

303 max_string=self.max_string,

304 max_depth=self.max_depth,

305 expand_all=self.expand_all,

306 )

307 pretty_text = Text.from_ansi(

308 pretty_str,

309 justify=self.justify or options.justify,

310 overflow=self.overflow or options.overflow,

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

312 style="pretty",

313 )

314 pretty_text = (

315 self.highlighter(pretty_text)

316 if pretty_text

317 else Text(

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

319 style="dim italic",

320 )

321 )

322 if self.indent_guides and not options.ascii_only:

323 pretty_text = pretty_text.with_indent_guides(

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

325 )

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

327 yield ""

328 yield pretty_text

329

330 def __rich_measure__(

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

332 ) -> "Measurement":

333 pretty_str = pretty_repr(

334 self._object,

335 max_width=options.max_width,

336 indent_size=self.indent_size,

337 max_length=self.max_length,

338 max_string=self.max_string,

339 max_depth=self.max_depth,

340 expand_all=self.expand_all,

341 )

342 text_width = (

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

344 )

345 return Measurement(text_width, text_width)

346

347

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

349 return (

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

351 "})",

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

353 )

354

355

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

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

358

359

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

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

362 array: _get_braces_for_array,

363 defaultdict: _get_braces_for_defaultdict,

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

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

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

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

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

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

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

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

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

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

374}

375 _CONTAINERS = tuple(_BRACES.keys())

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

377

378

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

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

381 return (

382 _safe_isinstance(obj, _CONTAINERS)

383 or (is_dataclass(obj))

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

385 or _is_attr_object(obj)

386 ) and not isclass(obj)

387

388

389@dataclass

390 class Node:

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

392

393 key_repr: str = ""

394 value_repr: str = ""

395 open_brace: str = ""

396 close_brace: str = ""

397 empty: str = ""

398 last: bool = False

399 is_tuple: bool = False

400 is_namedtuple: bool = False

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

402 key_separator: str = ": "

403 separator: str = ", "

404

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

406 """Generate tokens for this node."""

407 if self.key_repr:

408 yield self.key_repr

409 yield self.key_separator

410 if self.value_repr:

411 yield self.value_repr

412 elif self.children is not None:

413 if self.children:

414 yield self.open_brace

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

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

417 yield ","

418 else:

419 for child in self.children:

420 yield from child.iter_tokens()

421 if not child.last:

422 yield self.separator

423 yield self.close_brace

424 else:

425 yield self.empty

426

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

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

429

430 Args:

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

432 max_length (int): Maximum length.

433

434 Returns:

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

436 """

437 total_length = start_length

438 for token in self.iter_tokens():

439 total_length += cell_len(token)

440 if total_length > max_length:

441 return False

442 return True

443

444 def __str__(self) -> str:

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

446 return repr_text

447

448 def render(

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

450 ) -> str:

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

452

453 Args:

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

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

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

457

458 Returns:

459 str: A repr string of the original object.

460 """

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

462 line_no = 0

463 while line_no < len(lines):

464 line = lines[line_no]

465 if line.expandable and not line.expanded:

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

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

468 line_no += 1

469

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

471 return repr_str

472

473

474@dataclass

475 class _Line:

476 """A line in repr output."""

477

478 parent: Optional["_Line"] = None

479 is_root: bool = False

480 node: Optional[Node] = None

481 text: str = ""

482 suffix: str = ""

483 whitespace: str = ""

484 expanded: bool = False

485 last: bool = False

486

487 @property

488 def expandable(self) -> bool:

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

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

491

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

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

494 start_length = (

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

496 )

497 assert self.node is not None

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

499

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

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

502 node = self.node

503 assert node is not None

504 whitespace = self.whitespace

505 assert node.children

506 if node.key_repr:

507 new_line = yield _Line(

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

509 whitespace=whitespace,

510 )

511 else:

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

513 child_whitespace = self.whitespace + " " * indent_size

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

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

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

517 line = _Line(

518 parent=new_line,

519 node=child,

520 whitespace=child_whitespace,

521 suffix=separator,

522 last=last and not tuple_of_one,

523 )

524 yield line

525

526 yield _Line(

527 text=node.close_brace,

528 whitespace=whitespace,

529 suffix=self.suffix,

530 last=self.last,

531 )

532

533 def __str__(self) -> str:

534 if self.last:

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

536 else:

537 return (

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

539 )

540

541

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

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

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

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

546

547 Args:

548 obj (Any): The object to test

549

550 Returns:

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

552 """

553 try:

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

555 except Exception:

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

557 return False

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

559

560

561 def traverse(

562 _object: Any,

563 max_length: Optional[int] = None,

564 max_string: Optional[int] = None,

565 max_depth: Optional[int] = None,

566) -> Node:

567 """Traverse object and generate a tree.

568

569 Args:

570 _object (Any): Object to be traversed.

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

572 Defaults to None.

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

574 Defaults to None.

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

576 Defaults to None.

577

578 Returns:

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

580 """

581

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

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

584 if (

585 max_string is not None

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

587 and len(obj) > max_string

588 ):

589 truncated = len(obj) - max_string

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

591 else:

592 try:

593 obj_repr = repr(obj)

594 except Exception as error:

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

596 return obj_repr

597

598 visited_ids: Set[int] = set()

599 push_visited = visited_ids.add

600 pop_visited = visited_ids.remove

601

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

603 """Walk the object depth first."""

604

605 obj_id = id(obj)

606 if obj_id in visited_ids:

607 # Recursion detected

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

609

610 obj_type = type(obj)

611 children: List[Node]

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

613

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

615 for arg in rich_args:

616 if _safe_isinstance(arg, tuple):

617 if len(arg) == 3:

618 key, child, default = arg

619 if default == child:

620 continue

621 yield key, child

622 elif len(arg) == 2:

623 key, child = arg

624 yield key, child

625 elif len(arg) == 1:

626 yield arg[0]

627 else:

628 yield arg

629

630 try:

631 fake_attributes = hasattr(

632 obj, "awehoi234_wdfjwljet234_234wdfoijsdfmmnxpi492"

633 )

634 except Exception:

635 fake_attributes = False

636

637 rich_repr_result: Optional[RichReprResult] = None

638 if not fake_attributes:

639 try:

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

641 rich_repr_result = obj.__rich_repr__()

642 except Exception:

643 pass

644

645 if rich_repr_result is not None:

646 push_visited(obj_id)

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

648 args = list(iter_rich_args(rich_repr_result))

649 class_name = obj.__class__.__name__

650

651 if args:

652 children = []

653 append = children.append

654

655 if reached_max_depth:

656 if angular:

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

658 else:

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

660 else:

661 if angular:

662 node = Node(

663 open_brace=f"<{class_name} ",

664 close_brace=">",

665 children=children,

666 last=root,

667 separator=" ",

668 )

669 else:

670 node = Node(

671 open_brace=f"{class_name}(",

672 close_brace=")",

673 children=children,

674 last=root,

675 )

676 for last, arg in loop_last(args):

677 if _safe_isinstance(arg, tuple):

678 key, child = arg

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

680 child_node.last = last

681 child_node.key_repr = key

682 child_node.key_separator = "="

683 append(child_node)

684 else:

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

686 child_node.last = last

687 append(child_node)

688 else:

689 node = Node(

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

691 children=[],

692 last=root,

693 )

694 pop_visited(obj_id)

695 elif _is_attr_object(obj) and not fake_attributes:

696 push_visited(obj_id)

697 children = []

698 append = children.append

699

700 attr_fields = _get_attr_fields(obj)

701 if attr_fields:

702 if reached_max_depth:

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

704 else:

705 node = Node(

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

707 close_brace=")",

708 children=children,

709 last=root,

710 )

711

712 def iter_attrs() -> (

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

714 ):

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

716 for attr in attr_fields:

717 if attr.repr:

718 try:

719 value = getattr(obj, attr.name)

720 except Exception as error:

721 # Can happen, albeit rarely

722 yield (attr.name, error, None)

723 else:

724 yield (

725 attr.name,

726 value,

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

728 )

729

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

731 if repr_callable:

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

733 else:

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

735 child_node.last = last

736 child_node.key_repr = name

737 child_node.key_separator = "="

738 append(child_node)

739 else:

740 node = Node(

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

742 )

743 pop_visited(obj_id)

744 elif (

745 is_dataclass(obj)

746 and not _safe_isinstance(obj, type)

747 and not fake_attributes

748 and _is_dataclass_repr(obj)

749 ):

750 push_visited(obj_id)

751 children = []

752 append = children.append

753 if reached_max_depth:

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

755 else:

756 node = Node(

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

758 close_brace=")",

759 children=children,

760 last=root,

761 empty=f"{obj.__class__.__name__}()",

762 )

763

764 for last, field in loop_last(

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

766 ):

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

768 child_node.key_repr = field.name

769 child_node.last = last

770 child_node.key_separator = "="

771 append(child_node)

772

773 pop_visited(obj_id)

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

775 push_visited(obj_id)

776 class_name = obj.__class__.__name__

777 if reached_max_depth:

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

779 node = Node(

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

781 )

782 else:

783 children = []

784 append = children.append

785 node = Node(

786 open_brace=f"{class_name}(",

787 close_brace=")",

788 children=children,

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

790 )

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

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

793 child_node.key_repr = key

794 child_node.last = last

795 child_node.key_separator = "="

796 append(child_node)

797 pop_visited(obj_id)

798 elif _safe_isinstance(obj, _CONTAINERS):

799 for container_type in _CONTAINERS:

800 if _safe_isinstance(obj, container_type):

801 obj_type = container_type

802 break

803

804 push_visited(obj_id)

805

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

807

808 if reached_max_depth:

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

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

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

812 elif obj:

813 children = []

814 node = Node(

815 open_brace=open_brace,

816 close_brace=close_brace,

817 children=children,

818 last=root,

819 )

820 append = children.append

821 num_items = len(obj)

822 last_item_index = num_items - 1

823

824 if _safe_isinstance(obj, _MAPPING_CONTAINERS):

825 iter_items = iter(obj.items())

826 if max_length is not None:

827 iter_items = islice(iter_items, max_length)

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

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

830 child_node.key_repr = to_repr(key)

831 child_node.last = index == last_item_index

832 append(child_node)

833 else:

834 iter_values = iter(obj)

835 if max_length is not None:

836 iter_values = islice(iter_values, max_length)

837 for index, child in enumerate(iter_values):

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

839 child_node.last = index == last_item_index

840 append(child_node)

841 if max_length is not None and num_items > max_length:

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

843 else:

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

845

846 pop_visited(obj_id)

847 else:

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

849 node.is_tuple = _safe_isinstance(obj, tuple)

850 node.is_namedtuple = _is_namedtuple(obj)

851 return node

852

853 node = _traverse(_object, root=True)

854 return node

855

856

857 def pretty_repr(

858 _object: Any,

859 *,

860 max_width: int = 80,

861 indent_size: int = 4,

862 max_length: Optional[int] = None,

863 max_string: Optional[int] = None,

864 max_depth: Optional[int] = None,

865 expand_all: bool = False,

866) -> str:

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

868

869 Args:

870 _object (Any): Object to repr.

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

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

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

874 Defaults to None.

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

876 Defaults to None.

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

878 Defaults to None.

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

880

881 Returns:

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

883 """

884

885 if _safe_isinstance(_object, Node):

886 node = _object

887 else:

888 node = traverse(

889 _object, max_length=max_length, max_string=max_string, max_depth=max_depth

890 )

891 repr_str: str = node.render(

892 max_width=max_width, indent_size=indent_size, expand_all=expand_all

893 )

894 return repr_str

895

896

897 def pprint(

898 _object: Any,

899 *,

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

901 indent_guides: bool = True,

902 max_length: Optional[int] = None,

903 max_string: Optional[int] = None,

904 max_depth: Optional[int] = None,

905 expand_all: bool = False,

906) -> None:

907 """A convenience function for pretty printing.

908

909 Args:

910 _object (Any): Object to pretty print.

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

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

913 Defaults to None.

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

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

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

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

918 """

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

920 _console.print(

921 Pretty(

922 _object,

923 max_length=max_length,

924 max_string=max_string,

925 max_depth=max_depth,

926 indent_guides=indent_guides,

927 expand_all=expand_all,

928 overflow="ignore",

929 ),

930 soft_wrap=True,

931 )

932

933

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

935

936 class BrokenRepr:

937 def __repr__(self) -> str:

938 1 / 0

939 return "this will fail"

940

941 from typing import NamedTuple

942

943 class StockKeepingUnit(NamedTuple):

944 name: str

945 description: str

946 price: float

947 category: str

948 reviews: List[str]

949

950 d = defaultdict(int)

951 d["foo"] = 5

952 data = {

953 "foo": [

954 1,

955 "Hello World!",

956 100.123,

957 323.232,

958 432324.0,

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

960 ],

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

962 "defaultdict": defaultdict(

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

964 ),

965 "counter": Counter(

966 [

967 "apple",

968 "orange",

969 "pear",

970 "kumquat",

971 "kumquat",

972 "durian" * 100,

973 ]

974 ),

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

976 "namedtuple": StockKeepingUnit(

977 "Sparkling British Spring Water",

978 "Carbonated spring water",

979 0.9,

980 "water",

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

982 ),

983 "Broken": BrokenRepr(),

984 }

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

986

987 from rich import print

988

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

990

991 class Thing:

992 def __repr__(self) -> str:

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

994

995 print(Pretty(Thing()))