text: import builtins
  import collections
  import dataclasses
  import inspect
  import os
  import sys
  from array import array
  from collections import Counter, UserDict, UserList, defaultdict, deque
  from dataclasses import dataclass, fields, is_dataclass
  from inspect import isclass
  from itertools import islice
  from types import MappingProxyType
  from typing import (
      TYPE_CHECKING,
      Any,
      Callable,
      DefaultDict,
      Dict,
      Iterable,
      List,
      Optional,
      Set,
      Tuple,
      Union,
  )
  
  from rich.repr import RichReprResult
  
  try:
      import attr as _attr_module
  except ImportError:  # pragma: no cover
      _attr_module = None  # type: ignore
  
  from . import get_console
  from ._loop import loop_last
  from ._pick import pick_bool
  from .abc import RichRenderable
  from .cells import cell_len
  from .highlighter import ReprHighlighter
  from .jupyter import JupyterMixin, JupyterRenderable
  from .measure import Measurement
  from .text import Text
  
  if TYPE_CHECKING:
      from .console import (
          Console,
          ConsoleOptions,
          HighlighterType,
          JustifyMethod,
          OverflowMethod,
          RenderResult,
      )
  
  
  def _is_attr_object(obj: Any) -> bool:
      """Check if an object was created with attrs module."""
      return _attr_module is not None and _attr_module.has(type(obj))
  
  
  def _get_attr_fields(obj: Any) -> Iterable["_attr_module.Attribute[Any]"]:
      """Get fields for an attrs object."""
      return _attr_module.fields(type(obj)) if _attr_module is not None else []
  
  
  def _is_dataclass_repr(obj: object) -> bool:
      """Check if an instance of a dataclass contains the default repr.
  
      Args:
          obj (object): A dataclass instance.
  
      Returns:
          bool: True if the default repr is used, False if there is a custom repr.
      """
      # Digging in to a lot of internals here
      # Catching all exceptions in case something is missing on a non CPython implementation
      try:
          return obj.__repr__.__code__.co_filename == dataclasses.__file__
      except Exception:  # pragma: no coverage
          return False
  
  
  _dummy_namedtuple = collections.namedtuple("_dummy_namedtuple", [])
  
  
  def _has_default_namedtuple_repr(obj: object) -> bool:
      """Check if an instance of namedtuple contains the default repr
  
      Args:
          obj (object): A namedtuple
  
      Returns:
          bool: True if the default repr is used, False if there's a custom repr.
      """
      obj_file = None
      try:
          obj_file = inspect.getfile(obj.__repr__)
      except (OSError, TypeError):
          # OSError handles case where object is defined in __main__ scope, e.g. REPL - no filename available.
          # TypeError trapped defensively, in case of object without filename slips through.
          pass
      default_repr_file = inspect.getfile(_dummy_namedtuple.__repr__)
      return obj_file == default_repr_file
  
  
  def _ipy_display_hook(
      value: Any,
      console: Optional["Console"] = None,
      overflow: "OverflowMethod" = "ignore",
      crop: bool = False,
      indent_guides: bool = False,
      max_length: Optional[int] = None,
      max_string: Optional[int] = None,
      expand_all: bool = False,
  ) -> None:
      from .console import ConsoleRenderable  # needed here to prevent circular import
  
      # always skip rich generated jupyter renderables or None values
      if _safe_isinstance(value, JupyterRenderable) or value is None:
          return
  
      console = console or get_console()
      if console.is_jupyter:
          # Delegate rendering to IPython if the object (and IPython) supports it
          #  https://ipython.readthedocs.io/en/stable/config/integrating.html#rich-display
          ipython_repr_methods = [
              "_repr_html_",
              "_repr_markdown_",
              "_repr_json_",
              "_repr_latex_",
              "_repr_jpeg_",
              "_repr_png_",
              "_repr_svg_",
              "_repr_mimebundle_",
          ]
          for repr_method in ipython_repr_methods:
              method = getattr(value, repr_method, None)
              if inspect.ismethod(method):
                  # Calling the method ourselves isn't ideal. The interface for the `_repr_*_` methods
                  #  specifies that if they return None, then they should not be rendered
                  #  by the notebook.
                  try:
                      repr_result = method()
                  except Exception:
                      continue  # If the method raises, treat it as if it doesn't exist, try any others
                  if repr_result is not None:
                      return  # Delegate rendering to IPython
  
      # certain renderables should start on a new line
      if _safe_isinstance(value, ConsoleRenderable):
          console.line()
  
      console.print(
          value
          if _safe_isinstance(value, RichRenderable)
          else Pretty(
              value,
              overflow=overflow,
              indent_guides=indent_guides,
              max_length=max_length,
              max_string=max_string,
              expand_all=expand_all,
              margin=12,
          ),
          crop=crop,
          new_line_start=True,
      )
  
  
  def _safe_isinstance(
      obj: object, class_or_tuple: Union[type, Tuple[type, ...]]
  ) -> bool:
      """isinstance can fail in rare cases, for example types with no __class__"""
      try:
          return isinstance(obj, class_or_tuple)
      except Exception:
          return False
  
  
  def install(
      console: Optional["Console"] = None,
      overflow: "OverflowMethod" = "ignore",
      crop: bool = False,
      indent_guides: bool = False,
      max_length: Optional[int] = None,
      max_string: Optional[int] = None,
      expand_all: bool = False,
  ) -> None:
      """Install automatic pretty printing in the Python REPL.
  
      Args:
          console (Console, optional): Console instance or ``None`` to use global console. Defaults to None.
          overflow (Optional[OverflowMethod], optional): Overflow method. Defaults to "ignore".
          crop (Optional[bool], optional): Enable cropping of long lines. Defaults to False.
          indent_guides (bool, optional): Enable indentation guides. Defaults to False.
          max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
              Defaults to None.
          max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to None.
          expand_all (bool, optional): Expand all containers. Defaults to False.
          max_frames (int): Maximum number of frames to show in a traceback, 0 for no maximum. Defaults to 100.
      """
      from rich import get_console
  
      console = console or get_console()
      assert console is not None
  
      def display_hook(value: Any) -> None:
          """Replacement sys.displayhook which prettifies objects with Rich."""
          if value is not None:
              assert console is not None
              builtins._ = None  # type: ignore
              console.print(
                  value
                  if _safe_isinstance(value, RichRenderable)
                  else Pretty(
                      value,
                      overflow=overflow,
                      indent_guides=indent_guides,
                      max_length=max_length,
                      max_string=max_string,
                      expand_all=expand_all,
                  ),
                  crop=crop,
              )
              builtins._ = value  # type: ignore
  
      try:  # pragma: no cover
          ip = get_ipython()  # type: ignore
          from IPython.core.formatters import BaseFormatter
  
          class RichFormatter(BaseFormatter):  # type: ignore
              pprint: bool = True
  
              def __call__(self, value: Any) -> Any:
                  if self.pprint:
                      return _ipy_display_hook(
                          value,
                          console=get_console(),
                          overflow=overflow,
                          indent_guides=indent_guides,
                          max_length=max_length,
                          max_string=max_string,
                          expand_all=expand_all,
                      )
                  else:
                      return repr(value)
  
          # replace plain text formatter with rich formatter
          rich_formatter = RichFormatter()
          ip.display_formatter.formatters["text/plain"] = rich_formatter
      except Exception:
          sys.displayhook = display_hook
  
  
  class Pretty(JupyterMixin):
      """A rich renderable that pretty prints an object.
  
      Args:
          _object (Any): An object to pretty print.
          highlighter (HighlighterType, optional): Highlighter object to apply to result, or None for ReprHighlighter. Defaults to None.
          indent_size (int, optional): Number of spaces in indent. Defaults to 4.
          justify (JustifyMethod, optional): Justify method, or None for default. Defaults to None.
          overflow (OverflowMethod, optional): Overflow method, or None for default. Defaults to None.
          no_wrap (Optional[bool], optional): Disable word wrapping. Defaults to False.
          indent_guides (bool, optional): Enable indentation guides. Defaults to False.
          max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
              Defaults to None.
          max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to None.
          max_depth (int, optional): Maximum depth of nested data structures, or None for no maximum. Defaults to None.
          expand_all (bool, optional): Expand all containers. Defaults to False.
          margin (int, optional): Subtrace a margin from width to force containers to expand earlier. Defaults to 0.
          insert_line (bool, optional): Insert a new line if the output has multiple new lines. Defaults to False.
      """
  
      def __init__(
          self,
          _object: Any,
          highlighter: Optional["HighlighterType"] = None,
          *,
          indent_size: int = 4,
          justify: Optional["JustifyMethod"] = None,
          overflow: Optional["OverflowMethod"] = None,
          no_wrap: Optional[bool] = False,
          indent_guides: bool = False,
          max_length: Optional[int] = None,
          max_string: Optional[int] = None,
          max_depth: Optional[int] = None,
          expand_all: bool = False,
          margin: int = 0,
          insert_line: bool = False,
      ) -> None:
          self._object = _object
          self.highlighter = highlighter or ReprHighlighter()
          self.indent_size = indent_size
          self.justify: Optional["JustifyMethod"] = justify
          self.overflow: Optional["OverflowMethod"] = overflow
          self.no_wrap = no_wrap
          self.indent_guides = indent_guides
          self.max_length = max_length
          self.max_string = max_string
          self.max_depth = max_depth
          self.expand_all = expand_all
          self.margin = margin
          self.insert_line = insert_line
  
      def __rich_console__(
          self, console: "Console", options: "ConsoleOptions"
      ) -> "RenderResult":
          pretty_str = pretty_repr(
              self._object,
              max_width=options.max_width - self.margin,
              indent_size=self.indent_size,
              max_length=self.max_length,
              max_string=self.max_string,
              max_depth=self.max_depth,
              expand_all=self.expand_all,
          )
          pretty_text = Text(
              pretty_str,
              justify=self.justify or options.justify,
              overflow=self.overflow or options.overflow,
              no_wrap=pick_bool(self.no_wrap, options.no_wrap),
              style="pretty",
          )
          pretty_text = (
              self.highlighter(pretty_text)
              if pretty_text
              else Text(
                  f"{type(self._object)}.__repr__ returned empty string",
                  style="dim italic",
              )
          )
          if self.indent_guides and not options.ascii_only:
              pretty_text = pretty_text.with_indent_guides(
                  self.indent_size, style="repr.indent"
              )
          if self.insert_line and "\n" in pretty_text:
              yield ""
          yield pretty_text
  
      def __rich_measure__(
          self, console: "Console", options: "ConsoleOptions"
      ) -> "Measurement":
          pretty_str = pretty_repr(
              self._object,
              max_width=options.max_width,
              indent_size=self.indent_size,
              max_length=self.max_length,
              max_string=self.max_string,
              expand_all=self.expand_all,
          )
          text_width = (
              max(cell_len(line) for line in pretty_str.splitlines()) if pretty_str else 0
          )
          return Measurement(text_width, text_width)
  
  
  def _get_braces_for_defaultdict(_object: DefaultDict[Any, Any]) -> Tuple[str, str, str]:
      return (
          f"defaultdict({_object.default_factory!r}, {{",
          "})",
          f"defaultdict({_object.default_factory!r}, {{}})",
      )
  
  
  def _get_braces_for_array(_object: "array[Any]") -> Tuple[str, str, str]:
      return (f"array({_object.typecode!r}, [", "])", "array({_object.typecode!r})")
  
  
  _BRACES: Dict[type, Callable[[Any], Tuple[str, str, str]]] = {
      os._Environ: lambda _object: ("environ({", "})", "environ({})"),
      array: _get_braces_for_array,
      defaultdict: _get_braces_for_defaultdict,
      Counter: lambda _object: ("Counter({", "})", "Counter()"),
      deque: lambda _object: ("deque([", "])", "deque()"),
      dict: lambda _object: ("{", "}", "{}"),
      UserDict: lambda _object: ("{", "}", "{}"),
      frozenset: lambda _object: ("frozenset({", "})", "frozenset()"),
      list: lambda _object: ("[", "]", "[]"),
      UserList: lambda _object: ("[", "]", "[]"),
      set: lambda _object: ("{", "}", "set()"),
      tuple: lambda _object: ("(", ")", "()"),
      MappingProxyType: lambda _object: ("mappingproxy({", "})", "mappingproxy({})"),
  }
  _CONTAINERS = tuple(_BRACES.keys())
  _MAPPING_CONTAINERS = (dict, os._Environ, MappingProxyType, UserDict)
  
  
  def is_expandable(obj: Any) -> bool:
      """Check if an object may be expanded by pretty print."""
      return (
          _safe_isinstance(obj, _CONTAINERS)
          or (is_dataclass(obj))
          or (hasattr(obj, "__rich_repr__"))
          or _is_attr_object(obj)
      ) and not isclass(obj)
  
  
  @dataclass
  class Node:
      """A node in a repr tree. May be atomic or a container."""
  
      key_repr: str = ""
      value_repr: str = ""
      open_brace: str = ""
      close_brace: str = ""
      empty: str = ""
      last: bool = False
      is_tuple: bool = False
      is_namedtuple: bool = False
      children: Optional[List["Node"]] = None
      key_separator = ": "
      separator: str = ", "
  
      def iter_tokens(self) -> Iterable[str]:
          """Generate tokens for this node."""
          if self.key_repr:
              yield self.key_repr
              yield self.key_separator
          if self.value_repr:
              yield self.value_repr
          elif self.children is not None:
              if self.children:
                  yield self.open_brace
                  if self.is_tuple and not self.is_namedtuple and len(self.children) == 1:
                      yield from self.children[0].iter_tokens()
                      yield ","
                  else:
                      for child in self.children:
                          yield from child.iter_tokens()
                          if not child.last:
                              yield self.separator
                  yield self.close_brace
              else:
                  yield self.empty
  
      def check_length(self, start_length: int, max_length: int) -> bool:
          """Check the length fits within a limit.
  
          Args:
              start_length (int): Starting length of the line (indent, prefix, suffix).
              max_length (int): Maximum length.
  
          Returns:
              bool: True if the node can be rendered within max length, otherwise False.
          """
          total_length = start_length
          for token in self.iter_tokens():
              total_length += cell_len(token)
              if total_length > max_length:
                  return False
          return True
  
      def __str__(self) -> str:
          repr_text = "".join(self.iter_tokens())
          return repr_text
  
      def render(
          self, max_width: int = 80, indent_size: int = 4, expand_all: bool = False
      ) -> str:
          """Render the node to a pretty repr.
  
          Args:
              max_width (int, optional): Maximum width of the repr. Defaults to 80.
              indent_size (int, optional): Size of indents. Defaults to 4.
              expand_all (bool, optional): Expand all levels. Defaults to False.
  
          Returns:
              str: A repr string of the original object.
          """
          lines = [_Line(node=self, is_root=True)]
          line_no = 0
          while line_no < len(lines):
              line = lines[line_no]
              if line.expandable and not line.expanded:
                  if expand_all or not line.check_length(max_width):
                      lines[line_no : line_no + 1] = line.expand(indent_size)
              line_no += 1
  
          repr_str = "\n".join(str(line) for line in lines)
          return repr_str
  
  
  @dataclass
  class _Line:
      """A line in repr output."""
  
      parent: Optional["_Line"] = None
      is_root: bool = False
      node: Optional[Node] = None
      text: str = ""
      suffix: str = ""
      whitespace: str = ""
      expanded: bool = False
      last: bool = False
  
      @property
      def expandable(self) -> bool:
          """Check if the line may be expanded."""
          return bool(self.node is not None and self.node.children)
  
      def check_length(self, max_length: int) -> bool:
          """Check this line fits within a given number of cells."""
          start_length = (
              len(self.whitespace) + cell_len(self.text) + cell_len(self.suffix)
          )
          assert self.node is not None
          return self.node.check_length(start_length, max_length)
  
      def expand(self, indent_size: int) -> Iterable["_Line"]:
          """Expand this line by adding children on their own line."""
          node = self.node
          assert node is not None
          whitespace = self.whitespace
          assert node.children
          if node.key_repr:
              new_line = yield _Line(
                  text=f"{node.key_repr}{node.key_separator}{node.open_brace}",
                  whitespace=whitespace,
              )
          else:
              new_line = yield _Line(text=node.open_brace, whitespace=whitespace)
          child_whitespace = self.whitespace + " " * indent_size
          tuple_of_one = node.is_tuple and len(node.children) == 1
          for last, child in loop_last(node.children):
              separator = "," if tuple_of_one else node.separator
              line = _Line(
                  parent=new_line,
                  node=child,
                  whitespace=child_whitespace,
                  suffix=separator,
                  last=last and not tuple_of_one,
              )
              yield line
  
          yield _Line(
              text=node.close_brace,
              whitespace=whitespace,
              suffix=self.suffix,
              last=self.last,
          )
  
      def __str__(self) -> str:
          if self.last:
              return f"{self.whitespace}{self.text}{self.node or ''}"
          else:
              return (
                  f"{self.whitespace}{self.text}{self.node or ''}{self.suffix.rstrip()}"
              )
  
  
  def _is_namedtuple(obj: Any) -> bool:
      """Checks if an object is most likely a namedtuple. It is possible
      to craft an object that passes this check and isn't a namedtuple, but
      there is only a minuscule chance of this happening unintentionally.
  
      Args:
          obj (Any): The object to test
  
      Returns:
          bool: True if the object is a namedtuple. False otherwise.
      """
      try:
          fields = getattr(obj, "_fields", None)
      except Exception:
          # Being very defensive - if we cannot get the attr then its not a namedtuple
          return False
      return isinstance(obj, tuple) and isinstance(fields, tuple)
  
  
  def traverse(
      _object: Any,
      max_length: Optional[int] = None,
      max_string: Optional[int] = None,
      max_depth: Optional[int] = None,
  ) -> Node:
      """Traverse object and generate a tree.
  
      Args:
          _object (Any): Object to be traversed.
          max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
              Defaults to None.
          max_string (int, optional): Maximum length of string before truncating, or None to disable truncating.
              Defaults to None.
          max_depth (int, optional): Maximum depth of data structures, or None for no maximum.
              Defaults to None.
  
      Returns:
          Node: The root of a tree structure which can be used to render a pretty repr.
      """
  
      def to_repr(obj: Any) -> str:
          """Get repr string for an object, but catch errors."""
          if (
              max_string is not None
              and _safe_isinstance(obj, (bytes, str))
              and len(obj) > max_string
          ):
              truncated = len(obj) - max_string
              obj_repr = f"{obj[:max_string]!r}+{truncated}"
          else:
              try:
                  obj_repr = repr(obj)
              except Exception as error:
                  obj_repr = f"<repr-error {str(error)!r}>"
          return obj_repr
  
      visited_ids: Set[int] = set()
      push_visited = visited_ids.add
      pop_visited = visited_ids.remove
  
      def _traverse(obj: Any, root: bool = False, depth: int = 0) -> Node:
          """Walk the object depth first."""
  
          obj_type = type(obj)
          py_version = (sys.version_info.major, sys.version_info.minor)
          children: List[Node]
          reached_max_depth = max_depth is not None and depth >= max_depth
  
          def iter_rich_args(rich_args: Any) -> Iterable[Union[Any, Tuple[str, Any]]]:
              for arg in rich_args:
                  if _safe_isinstance(arg, tuple):
                      if len(arg) == 3:
                          key, child, default = arg
                          if default == child:
                              continue
                          yield key, child
                      elif len(arg) == 2:
                          key, child = arg
                          yield key, child
                      elif len(arg) == 1:
                          yield arg[0]
                  else:
                      yield arg
  
          try:
              fake_attributes = hasattr(
                  obj, "awehoi234_wdfjwljet234_234wdfoijsdfmmnxpi492"
              )
          except Exception:
              fake_attributes = False
  
          rich_repr_result: Optional[RichReprResult] = None
          if not fake_attributes:
              try:
                  if hasattr(obj, "__rich_repr__") and not isclass(obj):
                      rich_repr_result = obj.__rich_repr__()
              except Exception:
                  pass
  
          if rich_repr_result is not None:
              angular = getattr(obj.__rich_repr__, "angular", False)
              args = list(iter_rich_args(rich_repr_result))
              class_name = obj.__class__.__name__
  
              if args:
                  children = []
                  append = children.append
  
                  if reached_max_depth:
                      node = Node(value_repr=f"...")
                  else:
                      if angular:
                          node = Node(
                              open_brace=f"<{class_name} ",
                              close_brace=">",
                              children=children,
                              last=root,
                              separator=" ",
                          )
                      else:
                          node = Node(
                              open_brace=f"{class_name}(",
                              close_brace=")",
                              children=children,
                              last=root,
                          )
                      for last, arg in loop_last(args):
                          if _safe_isinstance(arg, tuple):
                              key, child = arg
                              child_node = _traverse(child, depth=depth + 1)
                              child_node.last = last
                              child_node.key_repr = key
                              child_node.key_separator = "="
                              append(child_node)
                          else:
                              child_node = _traverse(arg, depth=depth + 1)
                              child_node.last = last
                              append(child_node)
              else:
                  node = Node(
                      value_repr=f"<{class_name}>" if angular else f"{class_name}()",
                      children=[],
                      last=root,
                  )
          elif _is_attr_object(obj) and not fake_attributes:
              children = []
              append = children.append
  
              attr_fields = _get_attr_fields(obj)
              if attr_fields:
                  if reached_max_depth:
                      node = Node(value_repr=f"...")
                  else:
                      node = Node(
                          open_brace=f"{obj.__class__.__name__}(",
                          close_brace=")",
                          children=children,
                          last=root,
                      )
  
                      def iter_attrs() -> Iterable[
                          Tuple[str, Any, Optional[Callable[[Any], str]]]
                      ]:
                          """Iterate over attr fields and values."""
                          for attr in attr_fields:
                              if attr.repr:
                                  try:
                                      value = getattr(obj, attr.name)
                                  except Exception as error:
                                      # Can happen, albeit rarely
                                      yield (attr.name, error, None)
                                  else:
                                      yield (
                                          attr.name,
                                          value,
                                          attr.repr if callable(attr.repr) else None,
                                      )
  
                      for last, (name, value, repr_callable) in loop_last(iter_attrs()):
                          if repr_callable:
                              child_node = Node(value_repr=str(repr_callable(value)))
                          else:
                              child_node = _traverse(value, depth=depth + 1)
                          child_node.last = last
                          child_node.key_repr = name
                          child_node.key_separator = "="
                          append(child_node)
              else:
                  node = Node(
                      value_repr=f"{obj.__class__.__name__}()", children=[], last=root
                  )
  
          elif (
              is_dataclass(obj)
              and not _safe_isinstance(obj, type)
              and not fake_attributes
              and (_is_dataclass_repr(obj) or py_version == (3, 6))
          ):
              obj_id = id(obj)
              if obj_id in visited_ids:
                  # Recursion detected
                  return Node(value_repr="...")
              push_visited(obj_id)
  
              children = []
              append = children.append
              if reached_max_depth:
                  node = Node(value_repr=f"...")
              else:
                  node = Node(
                      open_brace=f"{obj.__class__.__name__}(",
                      close_brace=")",
                      children=children,
                      last=root,
                  )
  
                  for last, field in loop_last(
                      field for field in fields(obj) if field.repr
                  ):
                      child_node = _traverse(getattr(obj, field.name), depth=depth + 1)
                      child_node.key_repr = field.name
                      child_node.last = last
                      child_node.key_separator = "="
                      append(child_node)
  
                  pop_visited(obj_id)
          elif _is_namedtuple(obj) and _has_default_namedtuple_repr(obj):
              if reached_max_depth:
                  node = Node(value_repr="...")
              else:
                  children = []
                  class_name = obj.__class__.__name__
                  node = Node(
                      open_brace=f"{class_name}(",
                      close_brace=")",
                      children=children,
                      empty=f"{class_name}()",
                  )
                  append = children.append
                  for last, (key, value) in loop_last(obj._asdict().items()):
                      child_node = _traverse(value, depth=depth + 1)
                      child_node.key_repr = key
                      child_node.last = last
                      child_node.key_separator = "="
                      append(child_node)
          elif _safe_isinstance(obj, _CONTAINERS):
              for container_type in _CONTAINERS:
                  if _safe_isinstance(obj, container_type):
                      obj_type = container_type
                      break
  
              obj_id = id(obj)
              if obj_id in visited_ids:
                  # Recursion detected
                  return Node(value_repr="...")
              push_visited(obj_id)
  
              open_brace, close_brace, empty = _BRACES[obj_type](obj)
  
              if reached_max_depth:
                  node = Node(value_repr=f"...", last=root)
              elif obj_type.__repr__ != type(obj).__repr__:
                  node = Node(value_repr=to_repr(obj), last=root)
              elif obj:
                  children = []
                  node = Node(
                      open_brace=open_brace,
                      close_brace=close_brace,
                      children=children,
                      last=root,
                  )
                  append = children.append
                  num_items = len(obj)
                  last_item_index = num_items - 1
  
                  if _safe_isinstance(obj, _MAPPING_CONTAINERS):
                      iter_items = iter(obj.items())
                      if max_length is not None:
                          iter_items = islice(iter_items, max_length)
                      for index, (key, child) in enumerate(iter_items):
                          child_node = _traverse(child, depth=depth + 1)
                          child_node.key_repr = to_repr(key)
                          child_node.last = index == last_item_index
                          append(child_node)
                  else:
                      iter_values = iter(obj)
                      if max_length is not None:
                          iter_values = islice(iter_values, max_length)
                      for index, child in enumerate(iter_values):
                          child_node = _traverse(child, depth=depth + 1)
                          child_node.last = index == last_item_index
                          append(child_node)
                  if max_length is not None and num_items > max_length:
                      append(Node(value_repr=f"... +{num_items - max_length}", last=True))
              else:
                  node = Node(empty=empty, children=[], last=root)
  
              pop_visited(obj_id)
          else:
              node = Node(value_repr=to_repr(obj), last=root)
          node.is_tuple = _safe_isinstance(obj, tuple)
          node.is_namedtuple = _is_namedtuple(obj)
          return node
  
      node = _traverse(_object, root=True)
      return node
  
  
  def pretty_repr(
      _object: Any,
      *,
      max_width: int = 80,
      indent_size: int = 4,
      max_length: Optional[int] = None,
      max_string: Optional[int] = None,
      max_depth: Optional[int] = None,
      expand_all: bool = False,
  ) -> str:
      """Prettify repr string by expanding on to new lines to fit within a given width.
  
      Args:
          _object (Any): Object to repr.
          max_width (int, optional): Desired maximum width of repr string. Defaults to 80.
          indent_size (int, optional): Number of spaces to indent. Defaults to 4.
          max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
              Defaults to None.
          max_string (int, optional): Maximum length of string before truncating, or None to disable truncating.
              Defaults to None.
          max_depth (int, optional): Maximum depth of nested data structure, or None for no depth.
              Defaults to None.
          expand_all (bool, optional): Expand all containers regardless of available width. Defaults to False.
  
      Returns:
          str: A possibly multi-line representation of the object.
      """
  
      if _safe_isinstance(_object, Node):
          node = _object
      else:
          node = traverse(
              _object, max_length=max_length, max_string=max_string, max_depth=max_depth
          )
      repr_str: str = node.render(
          max_width=max_width, indent_size=indent_size, expand_all=expand_all
      )
      return repr_str
  
  
  def pprint(
      _object: Any,
      *,
      console: Optional["Console"] = None,
      indent_guides: bool = True,
      max_length: Optional[int] = None,
      max_string: Optional[int] = None,
      max_depth: Optional[int] = None,
      expand_all: bool = False,
  ) -> None:
      """A convenience function for pretty printing.
  
      Args:
          _object (Any): Object to pretty print.
          console (Console, optional): Console instance, or None to use default. Defaults to None.
          max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
              Defaults to None.
          max_string (int, optional): Maximum length of strings before truncating, or None to disable. Defaults to None.
          max_depth (int, optional): Maximum depth for nested data structures, or None for unlimited depth. Defaults to None.
          indent_guides (bool, optional): Enable indentation guides. Defaults to True.
          expand_all (bool, optional): Expand all containers. Defaults to False.
      """
      _console = get_console() if console is None else console
      _console.print(
          Pretty(
              _object,
              max_length=max_length,
              max_string=max_string,
              max_depth=max_depth,
              indent_guides=indent_guides,
              expand_all=expand_all,
              overflow="ignore",
          ),
          soft_wrap=True,
      )
  
  
  if __name__ == "__main__":  # pragma: no cover
  
      class BrokenRepr:
          def __repr__(self) -> str:
              1 / 0
              return "this will fail"
  
      from typing import NamedTuple
  
      class StockKeepingUnit(NamedTuple):
          name: str
          description: str
          price: float
          category: str
          reviews: List[str]
  
      d = defaultdict(int)
      d["foo"] = 5
      data = {
          "foo": [
              1,
              "Hello World!",
              100.123,
              323.232,
              432324.0,
              {5, 6, 7, (1, 2, 3, 4), 8},
          ],
          "bar": frozenset({1, 2, 3}),
          "defaultdict": defaultdict(
              list, {"crumble": ["apple", "rhubarb", "butter", "sugar", "flour"]}
          ),
          "counter": Counter(
              [
                  "apple",
                  "orange",
                  "pear",
                  "kumquat",
                  "kumquat",
                  "durian" * 100,
              ]
          ),
          "atomic": (False, True, None),
          "namedtuple": StockKeepingUnit(
              "Sparkling British Spring Water",
              "Carbonated spring water",
              0.9,
              "water",
              ["its amazing!", "its terrible!"],
          ),
          "Broken": BrokenRepr(),
      }
      data["foo"].append(data)  # type: ignore
  
      from rich import print
  
      print(Pretty(data, indent_guides=True, max_string=20))
  