From d1ede4493740830f4ec53143f6779962e7b4ca1c Mon Sep 17 00:00:00 2001 From: Frank Hoffmann <15r10nk-git@polarbit.de> Date: Sat, 11 Jan 2025 14:23:27 +0100 Subject: [PATCH 1/4] fix: implemented Is() as a noop during type checking --- src/inline_snapshot/_is.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/inline_snapshot/_is.py b/src/inline_snapshot/_is.py index 1f695397..4f344de1 100644 --- a/src/inline_snapshot/_is.py +++ b/src/inline_snapshot/_is.py @@ -1,6 +1,18 @@ -class Is: - def __init__(self, value): - self.value = value +import typing +from typing import TYPE_CHECKING - def __eq__(self, other): - return self.value == other +if TYPE_CHECKING: + + T = typing.TypeVar("T") + + def Is(v: T) -> T: + return v + +else: + + class Is: + def __init__(self, value): + self.value = value + + def __eq__(self, other): + return self.value == other From 22865dce5e713b1be4da680944060d432d677382 Mon Sep 17 00:00:00 2001 From: Frank Hoffmann <15r10nk-git@polarbit.de> Date: Sun, 12 Jan 2025 00:12:42 +0100 Subject: [PATCH 2/4] perf: fixing lists should be faster now --- docs/outsource.md | 4 ++-- src/inline_snapshot/_align.py | 26 ++++++++++++++++++++++++++ src/inline_snapshot/_unmanaged.py | 3 +++ 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/docs/outsource.md b/docs/outsource.md index 57ce7cef..d81ba110 100644 --- a/docs/outsource.md +++ b/docs/outsource.md @@ -27,7 +27,7 @@ Example: === "--inline-snapshot=create" - ``` python hl_lines="2 3 7 8 9" + ``` python hl_lines="3 4 7 8 9" from inline_snapshot import snapshot, outsource from inline_snapshot import external @@ -55,7 +55,7 @@ The `external` object can be used inside other data structures. === "--inline-snapshot=create" - ``` python hl_lines="2 3 9 10 11 12 13 14 15" + ``` python hl_lines="3 4 9 10 11 12 13 14 15" from inline_snapshot import snapshot, outsource from inline_snapshot import external diff --git a/src/inline_snapshot/_align.py b/src/inline_snapshot/_align.py index ef708888..143804a1 100644 --- a/src/inline_snapshot/_align.py +++ b/src/inline_snapshot/_align.py @@ -3,6 +3,32 @@ def align(seq_a, seq_b) -> str: + start = 0 + + for a, b in zip(seq_a, seq_b): + if a == b: + start += 1 + else: + break + + if start == len(seq_a) == len(seq_b): + return "m" * start + + end = 0 + + for a, b in zip(reversed(seq_a[start:]), reversed(seq_b[start:])): + if a == b: + end += 1 + else: + break + + diff = nw_align(seq_a[start : len(seq_a) - end], seq_b[start : len(seq_b) - end]) + + return "m" * start + diff + "m" * end + + +def nw_align(seq_a, seq_b) -> str: + matrix: list = [[(0, "e")] + [(0, "i")] * len(seq_b)] for a in seq_a: diff --git a/src/inline_snapshot/_unmanaged.py b/src/inline_snapshot/_unmanaged.py index 111d86aa..e1b33697 100644 --- a/src/inline_snapshot/_unmanaged.py +++ b/src/inline_snapshot/_unmanaged.py @@ -43,6 +43,9 @@ def __eq__(self, other): return self.value == other + def __repr__(self): + return repr(self.value) + def map_unmanaged(value): if is_unmanaged(value): From 793ad2f70be7b3508781f702a6b4afcd2d17dfdc Mon Sep 17 00:00:00 2001 From: Frank Hoffmann <15r10nk-git@polarbit.de> Date: Sun, 12 Jan 2025 12:25:26 +0100 Subject: [PATCH 3/4] fix: snapshots with pydantic models can now be compared multiple times --- ...12_121000_15r10nk-git_pydantic_ai_fixes.md | 13 +++++++++++ .../_adapter/generic_call_adapter.py | 22 ++++++++++++++----- tests/test_pydantic.py | 18 +++++++++++++++ 3 files changed, 47 insertions(+), 6 deletions(-) create mode 100644 changelog.d/20250112_121000_15r10nk-git_pydantic_ai_fixes.md diff --git a/changelog.d/20250112_121000_15r10nk-git_pydantic_ai_fixes.md b/changelog.d/20250112_121000_15r10nk-git_pydantic_ai_fixes.md new file mode 100644 index 00000000..92e99aa1 --- /dev/null +++ b/changelog.d/20250112_121000_15r10nk-git_pydantic_ai_fixes.md @@ -0,0 +1,13 @@ +### Fixed + +- snapshots with pydantic models can now be compared multiple times + + ``` python + class A(BaseModel): + a: int + + + def test_something(): + for _ in [1, 2]: + assert A(a=1) == snapshot(A(a=1)) + ``` diff --git a/src/inline_snapshot/_adapter/generic_call_adapter.py b/src/inline_snapshot/_adapter/generic_call_adapter.py index ac3ab837..17e4a908 100644 --- a/src/inline_snapshot/_adapter/generic_call_adapter.py +++ b/src/inline_snapshot/_adapter/generic_call_adapter.py @@ -76,14 +76,24 @@ def map(cls, value, map_function): ) def items(self, value, node): - assert isinstance(node, ast.Call) - assert not node.args - assert all(kw.arg for kw in node.keywords) + new_args, new_kwargs = self.arguments(value) + + if node is not None: + assert isinstance(node, ast.Call) + assert not node.args + assert all(kw.arg for kw in node.keywords) + kw_arg_node = {kw.arg: kw.value for kw in node.keywords if kw.arg}.get + pos_arg_node = lambda pos: node.args[pos] + else: + kw_arg_node = lambda _: None + pos_arg_node = lambda _: None return [ - Item(value=self.argument(value, kw.arg), node=kw.value) - for kw in node.keywords - if kw.arg + Item(value=arg.value, node=pos_arg_node(i)) + for i, arg in enumerate(new_args) + ] + [ + Item(value=kw.value, node=kw_arg_node(name)) + for name, kw in new_kwargs.items() ] def assign(self, old_value, old_node, new_value): diff --git a/tests/test_pydantic.py b/tests/test_pydantic.py index fd96e9c2..abaea7a8 100644 --- a/tests/test_pydantic.py +++ b/tests/test_pydantic.py @@ -117,3 +117,21 @@ def test_something(): } ), ) + + +def test_pydantic_evaluate_twice(): + Example( + """\ +from inline_snapshot import snapshot +from pydantic import BaseModel + +class A(BaseModel): + a:int + +def test_something(): + for _ in [1,2]: + assert A(a=1) == snapshot(A(a=1)) +""" + ).run_pytest( + changed_files=snapshot({}), + ) From 5eec756dfda55e5c0c4151b34d8d5032d661bcfc Mon Sep 17 00:00:00 2001 From: Frank Hoffmann <15r10nk-git@polarbit.de> Date: Sun, 12 Jan 2025 20:29:55 +0100 Subject: [PATCH 4/4] fix: raised required dirty-equal to 0.9.0 --- .github/workflows/ci.yml | 2 +- .../20250112_213028_15r10nk-git_pydantic_ai_fixes.md | 3 +++ docs/eq_snapshot.md | 7 +++++++ pyproject.toml | 10 ++++++---- src/inline_snapshot/_code_repr.py | 3 ++- tests/test_dirty_equals.py | 2 ++ 6 files changed, 21 insertions(+), 6 deletions(-) create mode 100644 changelog.d/20250112_213028_15r10nk-git_pydantic_ai_fixes.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index da22c03c..cb61dd38 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,7 +36,7 @@ jobs: python-version: ${{matrix.python-version}} - run: | - uv run ${{matrix.extra_deps}} --extra black -m ${{ matrix.os == 'ubuntu-latest' && 'coverage run -m' || '' }} pytest -n=auto -vv + uv run ${{matrix.extra_deps}} --extra black --extra dirty-equals -m ${{ matrix.os == 'ubuntu-latest' && 'coverage run -m' || '' }} pytest -n=auto -vv - run: | uv run -m coverage combine mv .coverage .coverage.${{ matrix.python-version }}-${{matrix.os}}-${{strategy.job-index}} diff --git a/changelog.d/20250112_213028_15r10nk-git_pydantic_ai_fixes.md b/changelog.d/20250112_213028_15r10nk-git_pydantic_ai_fixes.md new file mode 100644 index 00000000..fdad544f --- /dev/null +++ b/changelog.d/20250112_213028_15r10nk-git_pydantic_ai_fixes.md @@ -0,0 +1,3 @@ +### Added + +- added the optional `inline-snapshot[dirty-equals]` dependency to depend on the dirty-equals version which works in combination with inline-snapshot. diff --git a/docs/eq_snapshot.md b/docs/eq_snapshot.md index c05bdff5..b9ca92f0 100644 --- a/docs/eq_snapshot.md +++ b/docs/eq_snapshot.md @@ -120,6 +120,7 @@ Example: The date can be replaced with the [dirty-equals](https://dirty-equals.helpmanual.io/latest/) expression `IsDatetime()`. + Example: === "using IsDatetime()" @@ -195,6 +196,12 @@ Example: ) ``` +!!! note + Use the optional *dirty-equals* dependency to install the version that works best in combination with inline-snapshot. + ``` sh + pip install inline-snapshot[dirty-equals] + ``` + ### Is(...) `Is()` can be used to put runtime values inside snapshots. diff --git a/pyproject.toml b/pyproject.toml index 9959c505..ad41fee3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,6 +41,9 @@ black = [ "black>=23.3.0", "click>=8.1.4" ] +dirty-equals =[ + "dirty-equals>=0.9.0", +] [dependency-groups] dev = [ @@ -54,7 +57,6 @@ dev = [ "coverage[toml]>=7.6.1", "coverage-enable-subprocess>=1.0", "pytest>=8", - "dirty-equals>=0.7.0", "attrs>=24.3.0", "pydantic>=1", ] @@ -134,8 +136,8 @@ matrix.extra-deps.dependencies = [ [tool.hatch.envs.hatch-test] extra-dependencies = [ - "inline-snapshot[black]", - "dirty-equals>=0.7.0", + "inline-snapshot[black,dirty-equals]", + "dirty-equals>=0.9.0", "hypothesis>=6.75.5", "mypy>=1.2.0", "pyright>=1.1.359", @@ -153,7 +155,7 @@ cov-report=["coverage report","coverage html"] [tool.hatch.envs.types] extra-dependencies = [ - "inline-snapshot[black]", + "inline-snapshot[black,dirty-equals]", "mypy>=1.0.0", "pytest", "hypothesis>=6.75.5", diff --git a/src/inline_snapshot/_code_repr.py b/src/inline_snapshot/_code_repr.py index cf94a953..3b5252bc 100644 --- a/src/inline_snapshot/_code_repr.py +++ b/src/inline_snapshot/_code_repr.py @@ -88,7 +88,8 @@ def mocked_code_repr(obj): def value_code_repr(obj): - if not type(obj) == type(obj): + if not type(obj) == type(obj): # pragma: no cover + # this was caused by https://github.com/samuelcolvin/dirty-equals/issues/104 # dispatch will not work in cases like this return ( f"HasRepr({repr(type(obj))}, '< type(obj) can not be compared with == >')" diff --git a/tests/test_dirty_equals.py b/tests/test_dirty_equals.py index 1bc897a3..a5e7b4fc 100644 --- a/tests/test_dirty_equals.py +++ b/tests/test_dirty_equals.py @@ -1,7 +1,9 @@ +import pytest from inline_snapshot._inline_snapshot import snapshot from inline_snapshot.testing._example import Example +@pytest.mark.xfail def test_dirty_equals_repr(): Example( """\