Skip to content

Commit

Permalink
docs: documentation for register_repr
Browse files Browse the repository at this point in the history
  • Loading branch information
15r10nk committed May 29, 2024
1 parent a57a96f commit 680f80e
Show file tree
Hide file tree
Showing 5 changed files with 206 additions and 103 deletions.
106 changes: 106 additions & 0 deletions docs/code_generation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@

## Code generation

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 overwritten with [register_repr](register_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"fglecg\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"fglecg\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"fglecg\n\x16",
}
)
```

The code is generated in the following way:

1. The value is copied with `value = copy.deepcopy(value)`
2. The code is generated with `repr(value)` (which can be [customized](register_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.
103 changes: 0 additions & 103 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,108 +201,5 @@ or passed as an argument to a function:



## Code generation

You can use almost any python datatype and also complex values like `datatime.date`, because `repr()` is used to convert the values to a source code.
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"fglecg\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"fglecg\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"fglecg\n\x16",
}
)
```

The code is generated in the following way:

1. The value is copied with `value = copy.deepcopy(value)`
2. The code is generated with `repr(value)`
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.

!!! 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.

5. The whole file is formatted with black if it was formatted before.

--8<-- "README.md:Feedback"
89 changes: 89 additions & 0 deletions docs/register_repr.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@



`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>'
```

`register_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"])

assert repr(E.a) == "<E.a: 1>"
assert E.a == snapshot(E.a)
```

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 k in sorted(
f"{k.__module__}.{k.__qualname__}"
for k in code_repr_dispatch.registry.keys()
):
print(f"- `{k}`")
```

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.

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]))
```
8 changes: 8 additions & 0 deletions inline_snapshot/_code_repr.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,22 +76,30 @@ def code_repr(obj):
return result


# -8<- [start:Enum]
@register_repr
def _(v: Enum):
return f"{type(v).__qualname__}.{v.name}"


# -8<- [end:Enum]


@register_repr
def _(v: Flag):
name = type(v).__qualname__
return " | ".join(f"{name}.{flag.name}" for flag in type(v) if flag in v)


# -8<- [start:list]
@register_repr
def _(v: list):
return "[" + ", ".join(map(repr, v)) + "]"


# -8<- [end:list]


@register_repr
def _(v: set):
if len(v) == 0:
Expand Down
3 changes: 3 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,12 @@ nav:
- x in snapshot(): in_snapshot.md
- snapshot()[key]: getitem_snapshot.md
- outsource(data): outsource.md
- '@register_repr': register_repr.md
- pytest integration: pytest.md
- Categories: categories.md
- Configuration: configuration.md
- Internals:
- Code generation: code_generation.md
- Contributing: contributing.md
- Changelog: changelog.md

Expand Down

0 comments on commit 680f80e

Please sign in to comment.