DeepDiff diffing of pydantic v2 model dumps with ignore_order: working versions (tested 2026-10-15)
Compare pydantic v2 model dumps with DeepDiff, normalizing python vs json dump modes and taming list order, datetime and float gotchas.
passed6 passed · 0 failed2026-10-15 21:56 UTC- Status
- passed
- Last passed
- 2026-10-15 21:56 UTC
- Tests
- 6 / 6
- Language
- Python
- Runtime
- Python 3.13.16
uv venv -q .venv && uv pip install -q --python .venv/bin/python -r requirements.txt .venv/bin/python -m pytest -q
Python 3.13.16 linux 6.17.0-1022-azure x64 runner: github-actions 38813530570
The code
Exactly the files that passed. Your agent can pull them with get_recipe("deepdiff-pydantic-compare") (install), or see them on GitHub.
deepdiff==9.1.0 pydantic==2.14.1 pytest==9.1.1
from typing import Any
from deepdiff import DeepDiff
from pydantic import BaseModel
def dump(
model: BaseModel,
*,
json_mode: bool = False,
exclude_none: bool = False,
) -> dict[str, Any]:
return model.model_dump(
mode="json" if json_mode else "python",
exclude_none=exclude_none,
)
def _as_dict(value: BaseModel | dict[str, Any], json_mode: bool, exclude_none: bool) -> dict[str, Any]:
if isinstance(value, BaseModel):
return dump(value, json_mode=json_mode, exclude_none=exclude_none)
return value
def diff_models(
left: BaseModel | dict[str, Any],
right: BaseModel | dict[str, Any],
*,
ignore_order: bool = False,
significant_digits: int | None = None,
exclude_paths: set[str] | None = None,
json_mode: bool = False,
exclude_none: bool = False,
) -> DeepDiff:
return DeepDiff(
_as_dict(left, json_mode, exclude_none),
_as_dict(right, json_mode, exclude_none),
ignore_order=ignore_order,
significant_digits=significant_digits,
exclude_paths=exclude_paths or set(),
)
import json
from datetime import datetime, timedelta, timezone
from pydantic import BaseModel
from compare import diff_models
NOW = datetime(2024, 1, 1, tzinfo=timezone.utc)
class Event(BaseModel):
name: str
tags: set[str] = set()
steps: list[str] = []
meta: dict[str, int] = {}
at: datetime
score: float = 0.0
def test_dict_key_order_is_ignored():
a = Event(name="x", meta={"a": 1, "b": 2}, at=NOW)
b = Event(name="x", meta={"b": 2, "a": 1}, at=NOW)
assert diff_models(a, b) == {}
def test_list_order_is_significant_until_ignored():
a = Event(name="x", steps=["build", "test"], at=NOW)
b = Event(name="x", steps=["test", "build"], at=NOW)
assert "values_changed" in diff_models(a, b)
assert diff_models(a, b, ignore_order=True) == {}
def test_python_dump_vs_json_payload_types():
a = Event(name="x", tags={"a", "b"}, at=NOW)
wire = json.loads(a.model_dump_json())
assert "type_changes" in diff_models(a, wire)
assert diff_models(a, wire, json_mode=True) == {}
def test_aware_datetimes_same_instant_but_different_json():
a = Event(name="x", at=datetime.fromisoformat("2024-01-01T00:00:00+00:00"))
b = Event(name="x", at=datetime.fromisoformat("2023-12-31T19:00:00-05:00"))
assert diff_models(a, b) == {}
assert "values_changed" in diff_models(a, b, json_mode=True)
def test_float_noise_needs_significant_digits():
a = Event(name="x", score=0.1 + 0.2, at=NOW)
b = Event(name="x", score=0.3, at=NOW)
assert "values_changed" in diff_models(a, b)
assert diff_models(a, b, significant_digits=6) == {}
def test_exclude_paths_skips_volatile_fields():
a = Event(name="x", at=NOW)
b = Event(name="x", at=NOW + timedelta(seconds=1))
assert "values_changed" in diff_models(a, b)
assert diff_models(a, b, exclude_paths={"root['at']"}) == {}
[pytest] pythonpath = .
Connect to ProvenPin
Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp
{ "mcpServers": { "provenpin": { "url": "https://mcp.provenpin.com/mcp" } } }{ "servers": { "provenpin": { "type": "http", "url": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "serverUrl": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "type": "streamableHttp", "url": "https://mcp.provenpin.com/mcp" } } }Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.
Related stacks
beartype conf + pydantic model_validate runtime checks
Pair beartype boundary checks with pydantic model_validate, using BeartypeConf to align int/float coercion and route violations, and pin down the gotchas where the two guards disagree.
Faker and polyfactory seeded Pydantic model factories offline
Define Pydantic models and generate fixtures offline with polyfactory, binding one Faker instance so Use() providers and seed_random stay reproducible.
FastAPI + Pydantic v2 validators, discriminated unions and serializers
Pydantic v2 patterns in FastAPI that agents often get wrong: field_validator, model_validator, discriminated unions, computed_field and response_model filtering.
FastAPI background tasks + dependency overrides
FastAPI BackgroundTasks for post-response work, with dependency_overrides to swap services in tests.