Skip to content
Merged
Show file tree
Hide file tree
Changes from 66 commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
3cef0cf
feat: customize
15r10nk Apr 26, 2025
825f2b0
refactor: cleanup
15r10nk Sep 30, 2025
c35d8df
refactor: wip
15r10nk Oct 1, 2025
05598f0
refactoring
15r10nk Nov 28, 2025
5d03e4d
refactoring
15r10nk Nov 29, 2025
5bb4c49
refactoring
15r10nk Nov 29, 2025
bf111b6
fix: ci
15r10nk Nov 29, 2025
9d87492
refactor: remove old adapter
15r10nk Nov 30, 2025
a2d0f90
refactor: coverage
15r10nk Dec 1, 2025
8363e8e
refactor: coverage
15r10nk Dec 1, 2025
341ed47
refactor: coverage
15r10nk Dec 1, 2025
1bb6747
refactor: coverage
15r10nk Dec 4, 2025
c39758b
fix: fixed some issues
15r10nk Dec 12, 2025
4bfb262
refactor: convert original ast to custom object
15r10nk Dec 16, 2025
8b34536
fix: update works now with customize
15r10nk Dec 17, 2025
c89f8d1
feat: implemented import support for created snapshots
15r10nk Dec 21, 2025
b031d57
refactor: removed special import generation for HasRepr and external
15r10nk Dec 21, 2025
cc2f1c4
feat: support for local_vars and global_vars in customize
15r10nk Dec 23, 2025
8be6a49
fix: support dirty-equals expressions with arguments
15r10nk Dec 23, 2025
adfb4ec
refactor: reimplemented outsource with @customize
15r10nk Dec 28, 2025
35e24db
refactor: use customize to format strings
15r10nk Dec 30, 2025
1d76d8b
refactor: simplify code generation
15r10nk Dec 30, 2025
cb918b0
refactor: removed _value_to_code
15r10nk Dec 30, 2025
1364b7e
refactor: moved format_expression into code generation
15r10nk Dec 30, 2025
fa659a7
test: coverage
15r10nk Jan 1, 2026
88e6f3e
feat: use pluggy to manage plugins and extensions
15r10nk Jan 4, 2026
81f7d44
refactor: moved customization classes
15r10nk Jan 4, 2026
512652e
refactor: changed _needed_imports signature
15r10nk Jan 4, 2026
931ed34
fix: fixed type errors
15r10nk Jan 4, 2026
4ede15c
fix: fixed type errors
15r10nk Jan 4, 2026
2774c61
test: coverage
15r10nk Jan 5, 2026
ba3c7c6
docs: added docs for customize
15r10nk Jan 5, 2026
b9fe8a2
fix: removed customize function from my conftest.py
15r10nk Jan 6, 2026
b71a4ea
feat!: deprecated @customize_repr and import only InlineSnapshot* cla…
15r10nk Jan 8, 2026
0b9ccb6
docs: support for title="..." in code-blocks
15r10nk Jan 8, 2026
c12ea75
docs: added docs for customize
15r10nk Jan 8, 2026
caaa7b6
docs: moved some docs
15r10nk Jan 10, 2026
299e909
docs: moved some docs
15r10nk Jan 11, 2026
2ba9900
docs: improved docs
15r10nk Jan 11, 2026
3852bc8
docs: sort imports
15r10nk Jan 11, 2026
e4908c6
docs: builder documentation
15r10nk Jan 11, 2026
832580b
refactor: renamed map and made it private
15r10nk Jan 11, 2026
d8d0d1c
refactor: renamed eval to _eval
15r10nk Jan 11, 2026
85cb3d4
refactor: renamed repr to _code_repr
15r10nk Jan 11, 2026
7a20636
feat: support for defaults in named tuples
15r10nk Jan 11, 2026
9b2593a
docs: fixed some documentation errors
15r10nk Jan 11, 2026
ceee21d
fix: several bugfixes which I found while I tested pydantic-ai
15r10nk Jan 14, 2026
e3fc57c
refactor: used to with_default and removed create_default
15r10nk Jan 14, 2026
9da49c0
refactor: local/global_vars are now a dict
15r10nk Jan 14, 2026
2fd6c20
feat: typing, custom __file__
15r10nk Jan 14, 2026
6f7b7e6
refactor: moved _custom_value.py
15r10nk Jan 14, 2026
7a46027
feat: with_import('module')
15r10nk Jan 17, 2026
5754bd3
refactor: removed _needed_imports
15r10nk Jan 17, 2026
545af71
refactor: simplified RequiredImport
15r10nk Jan 17, 2026
ba7726a
fix: typeing fixes
15r10nk Jan 18, 2026
ae3ebd0
test: fixed docs
15r10nk Jan 18, 2026
d1092a0
test: coverage
15r10nk Jan 19, 2026
3838909
test: coverage
15r10nk Jan 20, 2026
29351b2
test: coverage
15r10nk Jan 20, 2026
b590ce5
refactor: removed value argument of builder.create_code(...)
15r10nk Jan 21, 2026
d6eac42
test: coverage
15r10nk Jan 21, 2026
4215c8b
test: coverage
15r10nk Jan 22, 2026
11b26e5
feat: removed kw_only_args from CustomCall
15r10nk Jan 22, 2026
a4daaf5
feat: changed plugin implementation
15r10nk Jan 24, 2026
0df95c8
feat: datetime customization
15r10nk Jan 24, 2026
a537c4c
refactor: coverage
15r10nk Jan 25, 2026
8a3d363
refactor: review changes and changelog
15r10nk Jan 26, 2026
93dd721
fix: fixes for pydantic-ai
15r10nk Jan 30, 2026
d93a470
fix: coverage
15r10nk Feb 3, 2026
0ded4c7
fix: fix corner case for 3.10
15r10nk Feb 8, 2026
c3953c9
fix: original_value in custom child
15r10nk Feb 9, 2026
6aeb712
fix: coverage
15r10nk Feb 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@ jobs:
uvx coverage html --skip-covered --skip-empty

# Report and write to summary.
uvx coverage report --format=markdown >> $GITHUB_STEP_SUMMARY
uvx coverage report --format=markdown --skip-covered --skip-empty -m >> $GITHUB_STEP_SUMMARY
uvx coverage report --skip-covered --skip-empty -m > htmlcov/report.txt

# Report again and fail if under 100%.
uvx coverage report --fail-under=100
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,9 @@ def test_something():
The following examples show how you can use inline-snapshot in your tests. Take a look at the
[documentation](https://15r10nk.github.io/inline-snapshot/latest) if you want to know more.

<!-- inline-snapshot: create fix trim first_block outcome-passed=1 -->
<!-- inline-snapshot: create fix trim first_block outcome-passed=1 outcome-errors=1 -->
``` python
from inline_snapshot import snapshot, outsource, external
from inline_snapshot import external, outsource, snapshot


def test_something():
Expand Down Expand Up @@ -135,9 +135,9 @@ strings\

<!-- inline-snapshot: create fix trim first_block outcome-passed=1 -->
``` python
from inline_snapshot import snapshot
import subprocess as sp
import sys
from inline_snapshot import snapshot


def run_python(cmd, stdout=None, stderr=None):
Expand Down
3 changes: 3 additions & 0 deletions changelog.d/20251201_200505_15r10nk-git_customize.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### Added

- `pathlib.Path/PurePath` values are now never stored as `Posix/WindowsPath` or their Pure variants, which improves the writing of platform independent tests.
12 changes: 7 additions & 5 deletions docs/categories.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,10 @@ It is recommended to use trim only if you run your complete test suite.
### Update

Changes in the update category do not change the value of the snapshot, just its representation.
These updates are not shown by default in your reports and can be enabled with [show-updates](configuration.md/#show-updates).
The reason might be that `#!python repr()` of the object has changed or that inline-snapshot provides some new logic which changes the representation. Like with the strings in the following example:
These updates are not shown by default in your reports, because it can be confusing for users who uses inline-snapshot the first time or want to change the snapshot values manual.
Updates can be enabled with [show-updates](configuration.md/#show-updates).

The reason for updates might be that `#!python repr()` of the object has changed or that inline-snapshot provides some new logic which changes the representation. Like with the strings in the following example:


=== "original"
Expand Down Expand Up @@ -254,7 +256,7 @@ The reason might be that `#!python repr()` of the object has changed or that inl
```


The approval of this type of changes is easier, because inline-snapshot assures that the value has not changed.
The approval of this type of changes is easier, because the update category assures that the value has not changed.

The goal of inline-snapshot is to generate the values for you in the correct format so that no manual editing is required.
This improves your productivity and saves time.
Expand All @@ -270,5 +272,5 @@ You can agree with inline-snapshot and accept the changes or you can use one of

4. you can also open an [issue](https://github.com/15r10nk/inline-snapshot/issues?q=is%3Aissue%20state%3Aopen%20label%3Aupdate_related) if you have a specific problem with the way inline-snapshot generates the code.

!!! note:
[#177](https://github.com/15r10nk/inline-snapshot/issues/177) will give the developer more control about how snapshots are created. *update* will them become much more useful.
!!! note
[#177](https://github.com/15r10nk/inline-snapshot/issues/177) will give the developer more control about how snapshots are created. *update* will then become much more useful.
4 changes: 2 additions & 2 deletions docs/code_generation.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ It might be necessary to import the right modules to match the `repr()` output.
=== "original code"
<!-- inline-snapshot: first_block outcome-passed=1 outcome-errors=1 -->
``` python
from inline_snapshot import snapshot
import datetime
from inline_snapshot import snapshot


def something():
Expand All @@ -29,8 +29,8 @@ It might be necessary to import the right modules to match the `repr()` output.
=== "--inline-snapshot=create"
<!-- inline-snapshot: create outcome-passed=1 outcome-errors=1 -->
``` python hl_lines="18 19 20 21 22 23 24 25 26 27 28"
from inline_snapshot import snapshot
import datetime
from inline_snapshot import snapshot


def something():
Expand Down
93 changes: 60 additions & 33 deletions docs/customize_repr.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,28 @@

!!! warning "deprecated"
`@customize_repr` will be removed in the future because `@customize` 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:
Expand All @@ -16,54 +39,56 @@ Here are some examples:

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

The implementation for `Enum` looks like this:
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)

``` python exec="1" result="python"
print('--8<-- "src/inline_snapshot/_code_repr.py:Enum"')
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 the 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
from enum import Enum


def test_enum():
E = Enum("E", ["a", "b"])
def test_my_class():
e = MyClass("1 5 hello")

# normal repr
assert repr(E.a) == "<E.a: 1>"
assert repr(e) == "['1', '5', 'hello']"

# the special implementation to convert the Enum into a code
assert E.a == snapshot(E.a)
```

## built-in data types

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}`")
assert e == snapshot(MyClass("1 5 hello"))
```

Please open an [issue](https://github.com/15r10nk/inline-snapshot/issues) if you found a built-in type which is not supported by inline-snapshot.

!!! note
Container types like `dict`, `list`, `tuple` or `dataclass` are handled in a different way, because inline-snapshot also needs to inspect these types to implement [unmanaged](/eq_snapshot.md#unmanaged-snapshot-values) snapshot values.
The example above can be better handled with `@customize` as shown in the documentation there.


## customize recursive repr
Expand All @@ -72,8 +97,8 @@ You can also use `repr()` inside `__repr__()`, if you want to make your own type

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


class Pair:
Expand All @@ -94,8 +119,10 @@ class Pair:
return self.a == other.a and self.b == other.b


E = Enum("E", ["a", "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
Expand Down
66 changes: 21 additions & 45 deletions docs/eq_snapshot.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,8 @@ You could initialize a test like this:

<!-- inline-snapshot: first_block outcome-passed=1 outcome-errors=1 -->
``` python
from inline_snapshot import snapshot
import datetime
from inline_snapshot import snapshot


def get_data():
Expand All @@ -135,31 +135,11 @@ def test_function():
If you use `--inline-snapshot=create`, inline-snapshot will record the current `datetime` in the snapshot:

<!-- inline-snapshot: create outcome-passed=1 outcome-errors=1 -->
``` python hl_lines="13 14 15"
from inline_snapshot import snapshot
``` python hl_lines="4 5 15"
import datetime


def get_data():
return {
"date": datetime.datetime.utcnow(),
"payload": "some data",
}


def test_function():
assert get_data() == snapshot(
{"date": datetime.datetime(2024, 3, 14, 0, 0), "payload": "some data"}
)
```

To avoid the test failing in future runs, replace the `datetime` with [dirty-equals' `IsDatetime()`](https://dirty-equals.helpmanual.io/latest/types/datetime/#dirty_equals.IsDatetime):

<!-- inline-snapshot: first_block outcome-passed=1 -->
``` python
from inline_snapshot import snapshot
from dirty_equals import IsDatetime
import datetime

from dirty_equals import IsNow


def get_data():
Expand All @@ -170,21 +150,17 @@ def get_data():


def test_function():
assert get_data() == snapshot(
{
"date": IsDatetime(),
"payload": "some data",
}
)
assert get_data() == snapshot({"date": IsNow(), "payload": "some data"})
```
Inline-snapshot uses [dirty-equals `IsNow()`](https://dirty-equals.helpmanual.io/latest/types/datetime/#dirty_equals.IsDatetime) by default when the value is equal to the current time to avoid the test failing in future runs.

Say a different part of the return data changes, such as the `payload` value:

<!-- inline-snapshot: outcome-failed=1 outcome-errors=1 -->
``` python hl_lines="9"
from inline_snapshot import snapshot
from dirty_equals import IsDatetime
``` python hl_lines="3 9 14 15 16 17 18 19"
import datetime
from dirty_equals import IsNow
from inline_snapshot import snapshot


def get_data():
Expand All @@ -197,7 +173,7 @@ def get_data():
def test_function():
assert get_data() == snapshot(
{
"date": IsDatetime(),
"date": IsNow(),
"payload": "some data",
}
)
Expand All @@ -207,9 +183,9 @@ Re-running the test with `--inline-snapshot=fix` will update the snapshot to mat

<!-- inline-snapshot: fix outcome-passed=1 outcome-errors=1 -->
``` python hl_lines="17"
from inline_snapshot import snapshot
from dirty_equals import IsDatetime
import datetime
from dirty_equals import IsNow
from inline_snapshot import snapshot


def get_data():
Expand All @@ -222,7 +198,7 @@ def get_data():
def test_function():
assert get_data() == snapshot(
{
"date": IsDatetime(),
"date": IsNow(),
"payload": "data changed for some good reason",
}
)
Expand Down Expand Up @@ -270,7 +246,7 @@ It tells inline-snapshot that the developer wants control over some part of the

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

current_version = "1.5"

Expand All @@ -292,7 +268,7 @@ The snapshot does not need to be fixed when `current_version` changes in the fut
=== "original code"
<!-- inline-snapshot: first_block outcome-failed=1 outcome-errors=1 -->
``` python
from inline_snapshot import snapshot, Is
from inline_snapshot import Is, snapshot


def test_function():
Expand All @@ -303,7 +279,7 @@ The snapshot does not need to be fixed when `current_version` changes in the fut
=== "--inline-snapshot=fix"
<!-- inline-snapshot: fix outcome-passed=1 outcome-errors=1 -->
``` python hl_lines="6"
from inline_snapshot import snapshot, Is
from inline_snapshot import Is, snapshot


def test_function():
Expand All @@ -326,7 +302,7 @@ The following example shows how this can be used to run a tests with two differe
=== "my_lib.py v1"

<!-- inline-snapshot-lib: my_lib.py -->
``` python
``` python title="my_lib.py"
version = 1


Expand All @@ -337,7 +313,7 @@ The following example shows how this can be used to run a tests with two differe
=== "my_lib.py v2"

<!-- inline-snapshot-lib: my_lib.py -->
``` python
``` python title="my_lib.py"
version = 2


Expand All @@ -348,8 +324,8 @@ The following example shows how this can be used to run a tests with two differe

<!-- inline-snapshot: create fix first_block outcome-passed=1 -->
``` python
from my_lib import get_schema, version
from inline_snapshot import snapshot
from my_lib import version, get_schema


def test_function():
Expand All @@ -368,8 +344,8 @@ The advantage of this approach is that the test uses always the correct values f
You can also extract the version logic into its own function.
<!-- inline-snapshot: create fix first_block outcome-passed=1 -->
``` python
from inline_snapshot import snapshot, Snapshot
from my_lib import version, get_schema
from my_lib import get_schema, version
from inline_snapshot import Snapshot, snapshot


def version_snapshot(v1: Snapshot, v2: Snapshot):
Expand Down
Loading
Loading