File: customize_repr.md

package info (click to toggle)
python-inline-snapshot 0.32.5-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 3,900 kB
  • sloc: python: 11,339; makefile: 40; sh: 36
file content (149 lines) | stat: -rw-r--r-- 4,231 bytes parent folder | download
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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149

!!! warning "deprecated"
    `@customize_repr` will be removed in the future because [`@customize`](plugin.md#customize-examples) provides the same and even more features.
    You should use

    ``` python title="conftest.py"
    class InlineSnapshotPlugin:
        @customize
        def my_class_handler(value, builder):
            if isinstance(value, MyClass):
                return builder.create_code("my_class_repr")
    ```

    instead of

    ``` python title="conftest.py"
    @customize_repr
    def my_class_handler(value: MyClass):
        return "my_class_repr"
    ```

    `@customize` allows you not only to generate code but also imports and function calls which can be analysed by inline-snapshot.


That said, what is/was `@customize_repr` for?

`repr()` can be used to convert a Python object into a source code representation of the object, but this does not work for every type.
Here are some examples:

```pycon
>>> repr(int)
"<class 'int'>"

>>> from enum import Enum
>>> E = Enum("E", ["a", "b"])
>>> repr(E.a)
'<E.a: 1>'
```

`customize_repr` can be used to overwrite the default `repr()` behaviour.

The implementation for `MyClass` could look like this:

<!-- inline-snapshot-lib: my_class.py -->
``` python title="my_class.py"
class MyClass:
    def __init__(self, values):
        self.values = values.split()

    def __repr__(self):
        return repr(self.values)

    def __eq__(self, other):
        if not isinstance(other, MyClass):
            return NotImplemented
        return self.values == other.values
```

You can specify the `repr()` used by inline-snapshot in your *conftest.py*

<!-- inline-snapshot-lib: conftest.py -->
``` python title="conftest.py"
from my_class import MyClass
from inline_snapshot import customize_repr


@customize_repr
def _(value: MyClass):
    return f"{MyClass.__qualname__}({' '.join(value.values) !r})"
```

This implementation is then used by inline-snapshot if `repr()` is called during code generation, but not in normal code.

<!-- inline-snapshot: create fix first_block outcome-passed=1 -->
``` python
from my_class import MyClass
from inline_snapshot import snapshot


def test_my_class():
    e = MyClass("1 5 hello")

    # normal repr
    assert repr(e) == "['1', '5', 'hello']"

    # the special implementation to convert the Enum into code
    assert e == snapshot(MyClass("1 5 hello"))
```

!!! note
    The example above can be better handled with [`@customize`](plugin.md#customize-examples) as shown in the [plugin documentation](plugin.md).


## customize recursive repr

You can also use `repr()` inside `__repr__()` if you want to make your own type compatible with inline-snapshot.

<!-- inline-snapshot: create fix first_block outcome-passed=1 -->
``` python
from enum import Enum
from inline_snapshot import snapshot


class Pair:
    def __init__(self, a, b):
        self.a = a
        self.b = b

    def __repr__(self):
        # this would not work
        # return f"Pair({self.a!r}, {self.b!r})"

        # you have to use repr()
        return f"Pair({repr(self.a)}, {repr(self.b)})"

    def __eq__(self, other):
        if not isinstance(other, Pair):
            return NotImplemented
        return self.a == other.a and self.b == other.b


E = Enum("E", ["a", "b"])


def test_enum():

    # the special repr implementation is used recursively here
    # to convert every Enum to the correct representation
    assert Pair(E.a, [E.b]) == snapshot(Pair(E.a, [E.b]))
```

!!! note
    using `#!python f"{obj!r}"` or `#!c PyObject_Repr()` will not work, because inline-snapshot replaces `#!python builtins.repr` during the code generation. The only way to use the custom repr implementation is to use the `repr()` function.

!!! note
    This implementation allows inline-snapshot to use the custom `repr()` recursively, but it does not allow you to use [unmanaged](/eq_snapshot.md#unmanaged-snapshot-values) snapshot values like `#!python Pair(Is(some_var),5)`


You can also customize the representation of data types in other libraries:

``` python
from inline_snapshot import customize_repr
from other_lib import SomeType


@customize_repr
def _(value: SomeType):
    return f"SomeType(x={repr(value.x)})"
```