_repr.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. """Tools to provide pretty/human-readable display of objects."""
  2. from __future__ import annotations as _annotations
  3. import types
  4. import typing
  5. from typing import Any
  6. import typing_extensions
  7. from . import _typing_extra
  8. if typing.TYPE_CHECKING:
  9. ReprArgs: typing_extensions.TypeAlias = 'typing.Iterable[tuple[str | None, Any]]'
  10. RichReprResult: typing_extensions.TypeAlias = (
  11. 'typing.Iterable[Any | tuple[Any] | tuple[str, Any] | tuple[str, Any, Any]]'
  12. )
  13. class PlainRepr(str):
  14. """String class where repr doesn't include quotes. Useful with Representation when you want to return a string
  15. representation of something that is valid (or pseudo-valid) python.
  16. """
  17. def __repr__(self) -> str:
  18. return str(self)
  19. class Representation:
  20. # Mixin to provide `__str__`, `__repr__`, and `__pretty__` and `__rich_repr__` methods.
  21. # `__pretty__` is used by [devtools](https://python-devtools.helpmanual.io/).
  22. # `__rich_repr__` is used by [rich](https://rich.readthedocs.io/en/stable/pretty.html).
  23. # (this is not a docstring to avoid adding a docstring to classes which inherit from Representation)
  24. # we don't want to use a type annotation here as it can break get_type_hints
  25. __slots__ = tuple() # type: typing.Collection[str]
  26. def __repr_args__(self) -> ReprArgs:
  27. """Returns the attributes to show in __str__, __repr__, and __pretty__ this is generally overridden.
  28. Can either return:
  29. * name - value pairs, e.g.: `[('foo_name', 'foo'), ('bar_name', ['b', 'a', 'r'])]`
  30. * or, just values, e.g.: `[(None, 'foo'), (None, ['b', 'a', 'r'])]`
  31. """
  32. attrs_names = self.__slots__
  33. if not attrs_names and hasattr(self, '__dict__'):
  34. attrs_names = self.__dict__.keys()
  35. attrs = ((s, getattr(self, s)) for s in attrs_names)
  36. return [(a, v) for a, v in attrs if v is not None]
  37. def __repr_name__(self) -> str:
  38. """Name of the instance's class, used in __repr__."""
  39. return self.__class__.__name__
  40. def __repr_str__(self, join_str: str) -> str:
  41. return join_str.join(repr(v) if a is None else f'{a}={v!r}' for a, v in self.__repr_args__())
  42. def __pretty__(self, fmt: typing.Callable[[Any], Any], **kwargs: Any) -> typing.Generator[Any, None, None]:
  43. """Used by devtools (https://python-devtools.helpmanual.io/) to pretty print objects."""
  44. yield self.__repr_name__() + '('
  45. yield 1
  46. for name, value in self.__repr_args__():
  47. if name is not None:
  48. yield name + '='
  49. yield fmt(value)
  50. yield ','
  51. yield 0
  52. yield -1
  53. yield ')'
  54. def __rich_repr__(self) -> RichReprResult:
  55. """Used by Rich (https://rich.readthedocs.io/en/stable/pretty.html) to pretty print objects."""
  56. for name, field_repr in self.__repr_args__():
  57. if name is None:
  58. yield field_repr
  59. else:
  60. yield name, field_repr
  61. def __str__(self) -> str:
  62. return self.__repr_str__(' ')
  63. def __repr__(self) -> str:
  64. return f'{self.__repr_name__()}({self.__repr_str__(", ")})'
  65. def display_as_type(obj: Any) -> str:
  66. """Pretty representation of a type, should be as close as possible to the original type definition string.
  67. Takes some logic from `typing._type_repr`.
  68. """
  69. if isinstance(obj, types.FunctionType):
  70. return obj.__name__
  71. elif obj is ...:
  72. return '...'
  73. elif isinstance(obj, Representation):
  74. return repr(obj)
  75. if not isinstance(obj, (_typing_extra.typing_base, _typing_extra.WithArgsTypes, type)):
  76. obj = obj.__class__
  77. if _typing_extra.origin_is_union(typing_extensions.get_origin(obj)):
  78. args = ', '.join(map(display_as_type, typing_extensions.get_args(obj)))
  79. return f'Union[{args}]'
  80. elif isinstance(obj, _typing_extra.WithArgsTypes):
  81. if typing_extensions.get_origin(obj) == typing_extensions.Literal:
  82. args = ', '.join(map(repr, typing_extensions.get_args(obj)))
  83. else:
  84. args = ', '.join(map(display_as_type, typing_extensions.get_args(obj)))
  85. try:
  86. return f'{obj.__qualname__}[{args}]'
  87. except AttributeError:
  88. return str(obj) # handles TypeAliasType in 3.12
  89. elif isinstance(obj, type):
  90. return obj.__qualname__
  91. else:
  92. return repr(obj).replace('typing.', '').replace('typing_extensions.', '')