File size: 5,081 Bytes
9c6594c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
"""Tools to provide pretty/human-readable display of objects."""

from __future__ import annotations as _annotations

import types
import typing
from typing import Any

import typing_extensions
from typing_inspection import typing_objects
from typing_inspection.introspection import is_union_origin

from . import _typing_extra

if typing.TYPE_CHECKING:
    ReprArgs: typing_extensions.TypeAlias = 'typing.Iterable[tuple[str | None, Any]]'
    RichReprResult: typing_extensions.TypeAlias = (
        'typing.Iterable[Any | tuple[Any] | tuple[str, Any] | tuple[str, Any, Any]]'
    )


class PlainRepr(str):
    """String class where repr doesn't include quotes. Useful with Representation when you want to return a string
    representation of something that is valid (or pseudo-valid) python.
    """

    def __repr__(self) -> str:
        return str(self)


class Representation:
    # Mixin to provide `__str__`, `__repr__`, and `__pretty__` and `__rich_repr__` methods.
    # `__pretty__` is used by [devtools](https://python-devtools.helpmanual.io/).
    # `__rich_repr__` is used by [rich](https://rich.readthedocs.io/en/stable/pretty.html).
    # (this is not a docstring to avoid adding a docstring to classes which inherit from Representation)

    # we don't want to use a type annotation here as it can break get_type_hints
    __slots__ = ()  # type: typing.Collection[str]

    def __repr_args__(self) -> ReprArgs:
        """Returns the attributes to show in __str__, __repr__, and __pretty__ this is generally overridden.

        Can either return:
        * name - value pairs, e.g.: `[('foo_name', 'foo'), ('bar_name', ['b', 'a', 'r'])]`
        * or, just values, e.g.: `[(None, 'foo'), (None, ['b', 'a', 'r'])]`
        """
        attrs_names = self.__slots__
        if not attrs_names and hasattr(self, '__dict__'):
            attrs_names = self.__dict__.keys()
        attrs = ((s, getattr(self, s)) for s in attrs_names)
        return [(a, v if v is not self else self.__repr_recursion__(v)) for a, v in attrs if v is not None]

    def __repr_name__(self) -> str:
        """Name of the instance's class, used in __repr__."""
        return self.__class__.__name__

    def __repr_recursion__(self, object: Any) -> str:
        """Returns the string representation of a recursive object."""
        # This is copied over from the stdlib `pprint` module:
        return f'<Recursion on {type(object).__name__} with id={id(object)}>'

    def __repr_str__(self, join_str: str) -> str:
        return join_str.join(repr(v) if a is None else f'{a}={v!r}' for a, v in self.__repr_args__())

    def __pretty__(self, fmt: typing.Callable[[Any], Any], **kwargs: Any) -> typing.Generator[Any, None, None]:
        """Used by devtools (https://python-devtools.helpmanual.io/) to pretty print objects."""
        yield self.__repr_name__() + '('
        yield 1
        for name, value in self.__repr_args__():
            if name is not None:
                yield name + '='
            yield fmt(value)
            yield ','
            yield 0
        yield -1
        yield ')'

    def __rich_repr__(self) -> RichReprResult:
        """Used by Rich (https://rich.readthedocs.io/en/stable/pretty.html) to pretty print objects."""
        for name, field_repr in self.__repr_args__():
            if name is None:
                yield field_repr
            else:
                yield name, field_repr

    def __str__(self) -> str:
        return self.__repr_str__(' ')

    def __repr__(self) -> str:
        return f'{self.__repr_name__()}({self.__repr_str__(", ")})'


def display_as_type(obj: Any) -> str:
    """Pretty representation of a type, should be as close as possible to the original type definition string.

    Takes some logic from `typing._type_repr`.
    """
    if isinstance(obj, (types.FunctionType, types.BuiltinFunctionType)):
        return obj.__name__
    elif obj is ...:
        return '...'
    elif isinstance(obj, Representation):
        return repr(obj)
    elif isinstance(obj, typing.ForwardRef) or typing_objects.is_typealiastype(obj):
        return str(obj)

    if not isinstance(obj, (_typing_extra.typing_base, _typing_extra.WithArgsTypes, type)):
        obj = obj.__class__

    if is_union_origin(typing_extensions.get_origin(obj)):
        args = ', '.join(map(display_as_type, typing_extensions.get_args(obj)))
        return f'Union[{args}]'
    elif isinstance(obj, _typing_extra.WithArgsTypes):
        if typing_objects.is_literal(typing_extensions.get_origin(obj)):
            args = ', '.join(map(repr, typing_extensions.get_args(obj)))
        else:
            args = ', '.join(map(display_as_type, typing_extensions.get_args(obj)))
        try:
            return f'{obj.__qualname__}[{args}]'
        except AttributeError:
            return str(obj).replace('typing.', '').replace('typing_extensions.', '')  # handles TypeAliasType in 3.12
    elif isinstance(obj, type):
        return obj.__qualname__
    else:
        return repr(obj).replace('typing.', '').replace('typing_extensions.', '')