-
-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #85 from 15r10nk/code_repr
feat: customize repr (#73)
- Loading branch information
Showing
23 changed files
with
1,162 additions
and
145 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
|
||
|
||
You can use almost any python datatype and also complex values like `datatime.date`, because `repr()` is used to convert the values to source code. | ||
The default `__repr__()` behaviour can be [customized](customize_repr.md). | ||
It might be necessary to import the right modules to match the `repr()` output. | ||
|
||
=== "original code" | ||
<!-- inline-snapshot: outcome-passed=1 outcome-errors=1 --> | ||
```python | ||
from inline_snapshot import snapshot | ||
import datetime | ||
|
||
|
||
def something(): | ||
return { | ||
"name": "hello", | ||
"one number": 5, | ||
"numbers": list(range(10)), | ||
"sets": {1, 2, 15}, | ||
"datetime": datetime.date(1, 2, 22), | ||
"complex stuff": 5j + 3, | ||
"bytes": b"byte abc\n\x16", | ||
} | ||
|
||
|
||
def test_something(): | ||
assert something() == snapshot() | ||
``` | ||
=== "--inline-snapshot=create" | ||
<!-- inline-snapshot: create outcome-passed=1 --> | ||
```python | ||
from inline_snapshot import snapshot | ||
import datetime | ||
|
||
|
||
def something(): | ||
return { | ||
"name": "hello", | ||
"one number": 5, | ||
"numbers": list(range(10)), | ||
"sets": {1, 2, 15}, | ||
"datetime": datetime.date(1, 2, 22), | ||
"complex stuff": 5j + 3, | ||
"bytes": b"byte abc\n\x16", | ||
} | ||
|
||
|
||
def test_something(): | ||
assert something() == snapshot( | ||
{ | ||
"name": "hello", | ||
"one number": 5, | ||
"numbers": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], | ||
"sets": {1, 2, 15}, | ||
"datetime": datetime.date(1, 2, 22), | ||
"complex stuff": (3 + 5j), | ||
"bytes": b"byte abc\n\x16", | ||
} | ||
) | ||
``` | ||
|
||
The code is generated in the following way: | ||
|
||
1. The value is copied with `value = copy.deepcopy(value)` and it is checked if the copied value is equal to the original value. | ||
2. The code is generated with `repr(value)` (which can be [customized](customize_repr.md)) | ||
3. Strings which contain newlines are converted to triple quoted strings. | ||
|
||
!!! note | ||
Missing newlines at start or end are escaped (since 0.4.0). | ||
|
||
=== "original code" | ||
<!-- inline-snapshot: outcome-passed=1 --> | ||
``` python | ||
def test_something(): | ||
assert "first line\nsecond line" == snapshot( | ||
"""first line | ||
second line""" | ||
) | ||
``` | ||
|
||
=== "--inline-snapshot=update" | ||
<!-- inline-snapshot: update outcome-passed=1 --> | ||
``` python | ||
def test_something(): | ||
assert "first line\nsecond line" == snapshot( | ||
"""\ | ||
first line | ||
second line\ | ||
""" | ||
) | ||
``` | ||
|
||
|
||
4. The code is formatted with black. | ||
|
||
|
||
5. The whole file is formatted with black if it was formatted before. | ||
|
||
!!! note | ||
The black formatting of the whole file could not work for the following reasons: | ||
|
||
1. black is configured with cli arguments and not in a configuration file.<br> | ||
**Solution:** configure black in a [configuration file](https://black.readthedocs.io/en/stable/usage_and_configuration/the_basics.html#configuration-via-a-file) | ||
2. inline-snapshot uses a different black version.<br> | ||
**Solution:** specify which black version inline-snapshot should use by adding black with a specific version to your dependencies. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
|
||
|
||
|
||
`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 `Enum` looks like this: | ||
|
||
```python exec="1" result="python" | ||
print('--8<-- "inline_snapshot/_code_repr.py:Enum"') | ||
``` | ||
|
||
This implementation is then used by inline-snapshot if `repr()` is called during the code generation, but not in normal code. | ||
|
||
<!-- inline-snapshot: create fix this outcome-passed=1 --> | ||
```python | ||
from enum import Enum | ||
|
||
|
||
def test_enum(): | ||
E = Enum("E", ["a", "b"]) | ||
|
||
# normal repr | ||
assert repr(E.a) == "<E.a: 1>" | ||
|
||
# the special implementation to convert the Enum into a code | ||
assert E.a == snapshot(E.a) | ||
``` | ||
|
||
## builtin datatypes | ||
|
||
inline-snapshot comes with a special implementation for the following types: | ||
```python exec="1" | ||
from inline_snapshot._code_repr import code_repr_dispatch, code_repr | ||
|
||
for name, obj in sorted( | ||
( | ||
getattr( | ||
obj, "_inline_snapshot_name", f"{obj.__module__}.{obj.__qualname__}" | ||
), | ||
obj, | ||
) | ||
for obj in code_repr_dispatch.registry.keys() | ||
): | ||
if obj is not object: | ||
print(f"- `{name}`") | ||
``` | ||
|
||
Container types like `dict` or `dataclass` need a special implementation because it is necessary that the implementation uses `repr()` for the child elements. | ||
|
||
```python exec="1" result="python" | ||
print('--8<-- "inline_snapshot/_code_repr.py:list"') | ||
``` | ||
|
||
!!! note | ||
using `#!python f"{obj!r}"` or `#!c PyObject_Repr()` will not work, because inline-snapshot replaces `#!python builtins.repr` during the code generation. | ||
|
||
## customize | ||
|
||
You can also use `repr()` inside `__repr__()`, if you want to make your own type compatible with inline-snapshot. | ||
|
||
<!-- inline-snapshot: create fix this outcome-passed=1 --> | ||
```python | ||
from enum import Enum | ||
|
||
|
||
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 | ||
|
||
|
||
def test_enum(): | ||
E = Enum("E", ["a", "b"]) | ||
|
||
# the special repr implementation is used recursive here | ||
# to convert every Enum to the correct representation | ||
assert Pair(E.a, [E.b]) == snapshot(Pair(E.a, [E.b])) | ||
``` | ||
|
||
you can also customize the representation of datatypes 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)})" | ||
``` |
Oops, something went wrong.