python (3.11.7)
       1  import inspect
       2  from functools import partial
       3  from typing import (
       4      Any,
       5      Callable,
       6      Iterable,
       7      List,
       8      Optional,
       9      Tuple,
      10      Type,
      11      TypeVar,
      12      Union,
      13      overload,
      14  )
      15  
      16  T = TypeVar("T")
      17  
      18  
      19  Result = Iterable[Union[Any, Tuple[Any], Tuple[str, Any], Tuple[str, Any, Any]]]
      20  RichReprResult = Result
      21  
      22  
      23  class ESC[4;38;5;81mReprError(ESC[4;38;5;149mException):
      24      """An error occurred when attempting to build a repr."""
      25  
      26  
      27  @overload
      28  def auto(cls: Optional[Type[T]]) -> Type[T]:
      29      ...
      30  
      31  
      32  @overload
      33  def auto(*, angular: bool = False) -> Callable[[Type[T]], Type[T]]:
      34      ...
      35  
      36  
      37  def auto(
      38      cls: Optional[Type[T]] = None, *, angular: Optional[bool] = None
      39  ) -> Union[Type[T], Callable[[Type[T]], Type[T]]]:
      40      """Class decorator to create __repr__ from __rich_repr__"""
      41  
      42      def do_replace(cls: Type[T], angular: Optional[bool] = None) -> Type[T]:
      43          def auto_repr(self: T) -> str:
      44              """Create repr string from __rich_repr__"""
      45              repr_str: List[str] = []
      46              append = repr_str.append
      47  
      48              angular: bool = getattr(self.__rich_repr__, "angular", False)  # type: ignore[attr-defined]
      49              for arg in self.__rich_repr__():  # type: ignore[attr-defined]
      50                  if isinstance(arg, tuple):
      51                      if len(arg) == 1:
      52                          append(repr(arg[0]))
      53                      else:
      54                          key, value, *default = arg
      55                          if key is None:
      56                              append(repr(value))
      57                          else:
      58                              if default and default[0] == value:
      59                                  continue
      60                              append(f"{key}={value!r}")
      61                  else:
      62                      append(repr(arg))
      63              if angular:
      64                  return f"<{self.__class__.__name__} {' '.join(repr_str)}>"
      65              else:
      66                  return f"{self.__class__.__name__}({', '.join(repr_str)})"
      67  
      68          def auto_rich_repr(self: Type[T]) -> Result:
      69              """Auto generate __rich_rep__ from signature of __init__"""
      70              try:
      71                  signature = inspect.signature(self.__init__)
      72                  for name, param in signature.parameters.items():
      73                      if param.kind == param.POSITIONAL_ONLY:
      74                          yield getattr(self, name)
      75                      elif param.kind in (
      76                          param.POSITIONAL_OR_KEYWORD,
      77                          param.KEYWORD_ONLY,
      78                      ):
      79                          if param.default == param.empty:
      80                              yield getattr(self, param.name)
      81                          else:
      82                              yield param.name, getattr(self, param.name), param.default
      83              except Exception as error:
      84                  raise ReprError(
      85                      f"Failed to auto generate __rich_repr__; {error}"
      86                  ) from None
      87  
      88          if not hasattr(cls, "__rich_repr__"):
      89              auto_rich_repr.__doc__ = "Build a rich repr"
      90              cls.__rich_repr__ = auto_rich_repr  # type: ignore[attr-defined]
      91  
      92          auto_repr.__doc__ = "Return repr(self)"
      93          cls.__repr__ = auto_repr  # type: ignore[assignment]
      94          if angular is not None:
      95              cls.__rich_repr__.angular = angular  # type: ignore[attr-defined]
      96          return cls
      97  
      98      if cls is None:
      99          return partial(do_replace, angular=angular)
     100      else:
     101          return do_replace(cls, angular=angular)
     102  
     103  
     104  @overload
     105  def rich_repr(cls: Optional[Type[T]]) -> Type[T]:
     106      ...
     107  
     108  
     109  @overload
     110  def rich_repr(*, angular: bool = False) -> Callable[[Type[T]], Type[T]]:
     111      ...
     112  
     113  
     114  def rich_repr(
     115      cls: Optional[Type[T]] = None, *, angular: bool = False
     116  ) -> Union[Type[T], Callable[[Type[T]], Type[T]]]:
     117      if cls is None:
     118          return auto(angular=angular)
     119      else:
     120          return auto(cls)
     121  
     122  
     123  if __name__ == "__main__":
     124  
     125      @auto
     126      class ESC[4;38;5;81mFoo:
     127          def __rich_repr__(self) -> Result:
     128              yield "foo"
     129              yield "bar", {"shopping": ["eggs", "ham", "pineapple"]}
     130              yield "buy", "hand sanitizer"
     131  
     132      foo = Foo()
     133      from pip._vendor.rich.console import Console
     134  
     135      console = Console()
     136  
     137      console.rule("Standard repr")
     138      console.print(foo)
     139  
     140      console.print(foo, width=60)
     141      console.print(foo, width=30)
     142  
     143      console.rule("Angular repr")
     144      Foo.__rich_repr__.angular = True  # type: ignore[attr-defined]
     145  
     146      console.print(foo)
     147  
     148      console.print(foo, width=60)
     149      console.print(foo, width=30)