deepdiff-rs 0.9.0__tar.gz → 0.9.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/Cargo.lock +8 -8
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/Cargo.toml +1 -1
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/PKG-INFO +6 -5
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/README.md +5 -4
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-core/src/diff/array.rs +32 -11
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-core/src/diff/dispatch.rs +60 -17
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-core/src/diff/mod.rs +2 -2
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-core/src/diff/scalar.rs +23 -10
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-core/src/diff/tests.rs +251 -59
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-core/src/ignore_order/distance.rs +4 -2
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-core/src/ignore_order/fxhash.rs +8 -0
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-core/src/ignore_order/hash.rs +44 -12
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-core/src/ignore_order/memo.rs +8 -6
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-core/src/ignore_order/tests.rs +99 -91
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-core/src/lcs.rs +34 -1
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-core/src/lcs_tests.rs +61 -0
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-core/src/path.rs +49 -30
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-core/src/report.rs +4 -1
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-core/src/test_support.rs +28 -6
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-core/src/value.rs +349 -54
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-core/src/value_tests.rs +99 -26
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-core/tests/golden.rs +9 -6
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-core/tests/ignore_order_memory.rs +5 -4
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-py/deepdiff_rs.pyi +9 -2
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-py/src/convert.rs +320 -133
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-py/src/deepdiff.rs +21 -16
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-py/src/guard.rs +107 -6
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-py/src/lib.rs +1 -0
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-py/tests/test_conversions.py +156 -49
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-py/tests/test_datetimes.py +76 -0
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-py/tests/test_differential_fuzz.py +152 -9
- deepdiff_rs-0.9.2/crates/onix-py/tests/test_non_finite.py +318 -0
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-py/tests/test_sets.py +49 -11
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-py/tests/test_stub_signatures.py +20 -2
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-py/tests/test_tuples.py +41 -22
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/pyproject.toml +9 -6
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-arrow/Cargo.toml +0 -0
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-arrow/examples/row_diff_rss.rs +0 -0
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-arrow/examples/type_stack_cost.rs +0 -0
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-arrow/src/error.rs +0 -0
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-arrow/src/json_rows.rs +0 -0
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-arrow/src/lib.rs +0 -0
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-arrow/src/options.rs +0 -0
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-arrow/src/row_diff.rs +0 -0
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-arrow/src/schema.rs +0 -0
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-arrow/src/table_diff.rs +0 -0
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-core/Cargo.toml +0 -0
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-core/examples/stack_frame_cost.rs +0 -0
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-core/src/datetime.rs +0 -0
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-core/src/datetime_tests.rs +0 -0
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-core/src/diff/object.rs +0 -0
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-core/src/diff/options.rs +0 -0
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-core/src/diff/set.rs +0 -0
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-core/src/error.rs +0 -0
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-core/src/ignore_order/mod.rs +0 -0
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-core/src/ignore_order/pairing.rs +0 -0
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-core/src/lib.rs +0 -0
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-core/src/report_tests.rs +0 -0
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-core/src/unified_diff.rs +0 -0
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-core/src/unified_diff_tests.rs +0 -0
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-core/tests/memory_footprint.rs +0 -0
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-core/tests/proptest_diff.rs +0 -0
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-core/tests/proptest_ignore_order.rs +0 -0
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-py/.python-version +0 -0
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-py/Cargo.toml +0 -0
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-py/benchmarks/bench_bindings.py +0 -0
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-py/src/arrow.rs +0 -0
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-py/src/errors.rs +0 -0
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-py/src/fast_path.rs +0 -0
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-py/tests/conftest.py +0 -0
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-py/tests/test_bindings_memory.py +0 -0
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-py/tests/test_depth_guard.py +0 -0
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-py/tests/test_golden_parity.py +0 -0
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-py/tests/test_signed_zero.py +0 -0
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-py/tests/test_smoke.py +0 -0
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-py/tests/test_stub_mypy.py +0 -0
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-py/tests/test_suite_hygiene.py +0 -0
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-py/tests/test_table_diff.py +0 -0
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-py/tests/test_table_row_diff.py +0 -0
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-py/tests/test_timedeltas.py +0 -0
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-py/tests/test_times.py +0 -0
- {deepdiff_rs-0.9.0 → deepdiff_rs-0.9.2}/crates/onix-py/tests/test_wheel_contents.py +0 -0
|
@@ -629,7 +629,7 @@ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
|
|
629
629
|
|
|
630
630
|
[[package]]
|
|
631
631
|
name = "onix-arrow"
|
|
632
|
-
version = "0.9.
|
|
632
|
+
version = "0.9.2"
|
|
633
633
|
dependencies = [
|
|
634
634
|
"arrow-array",
|
|
635
635
|
"arrow-buffer",
|
|
@@ -646,7 +646,7 @@ dependencies = [
|
|
|
646
646
|
|
|
647
647
|
[[package]]
|
|
648
648
|
name = "onix-cli"
|
|
649
|
-
version = "0.9.
|
|
649
|
+
version = "0.9.2"
|
|
650
650
|
dependencies = [
|
|
651
651
|
"onix-core",
|
|
652
652
|
"serde_json",
|
|
@@ -654,7 +654,7 @@ dependencies = [
|
|
|
654
654
|
|
|
655
655
|
[[package]]
|
|
656
656
|
name = "onix-core"
|
|
657
|
-
version = "0.9.
|
|
657
|
+
version = "0.9.2"
|
|
658
658
|
dependencies = [
|
|
659
659
|
"proptest",
|
|
660
660
|
"serde",
|
|
@@ -665,7 +665,7 @@ dependencies = [
|
|
|
665
665
|
|
|
666
666
|
[[package]]
|
|
667
667
|
name = "onix-py"
|
|
668
|
-
version = "0.9.
|
|
668
|
+
version = "0.9.2"
|
|
669
669
|
dependencies = [
|
|
670
670
|
"arrow-array",
|
|
671
671
|
"arrow-ipc",
|
|
@@ -997,7 +997,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
|
|
|
997
997
|
dependencies = [
|
|
998
998
|
"proc-macro2",
|
|
999
999
|
"quote",
|
|
1000
|
-
"syn 3.0.
|
|
1000
|
+
"syn 3.0.5",
|
|
1001
1001
|
]
|
|
1002
1002
|
|
|
1003
1003
|
[[package]]
|
|
@@ -1050,9 +1050,9 @@ dependencies = [
|
|
|
1050
1050
|
|
|
1051
1051
|
[[package]]
|
|
1052
1052
|
name = "syn"
|
|
1053
|
-
version = "3.0.
|
|
1053
|
+
version = "3.0.5"
|
|
1054
1054
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1055
|
-
checksum = "
|
|
1055
|
+
checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9"
|
|
1056
1056
|
dependencies = [
|
|
1057
1057
|
"proc-macro2",
|
|
1058
1058
|
"quote",
|
|
@@ -1199,7 +1199,7 @@ dependencies = [
|
|
|
1199
1199
|
"bumpalo",
|
|
1200
1200
|
"proc-macro2",
|
|
1201
1201
|
"quote",
|
|
1202
|
-
"syn 3.0.
|
|
1202
|
+
"syn 3.0.5",
|
|
1203
1203
|
"wasm-bindgen-shared",
|
|
1204
1204
|
]
|
|
1205
1205
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: deepdiff-rs
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.2
|
|
4
4
|
Classifier: Programming Language :: Python :: 3
|
|
5
5
|
Classifier: Programming Language :: Rust
|
|
6
6
|
Classifier: License :: OSI Approved :: MIT License
|
|
@@ -195,11 +195,11 @@ The engine's own diff-only time and peak resident memory against pinned `deepdif
|
|
|
195
195
|
| `ignore_order_10k` | 73.475 ms (71.672 ms-73.940 ms) | 12.976 s (12.900 s-13.005 s) | 176.60x | 60.11 MB | 345.19 MB | 5.74x | ✅ |
|
|
196
196
|
| `identical_1m` | 9.254 ms (6.726 ms-9.875 ms) | 15.790 s (15.660 s-15.989 s) | 1706.35x | 315.41 MB | 503.19 MB | 1.60x | ✅ |
|
|
197
197
|
|
|
198
|
-
Both reports carry their full methodology, fairness rules, and the reproduce command. `perf/RESULTS.md` is an upper bound (JSON parsed straight into the engine, no Python-object conversion); the bindings table is the product-surface number. Regenerate them with `perf/run_bench.sh` and `crates/onix-py/benchmarks/bench_bindings.py` (see [CONTRIBUTING.md](CONTRIBUTING.md)).
|
|
198
|
+
Both reports carry their full methodology, fairness rules, and the reproduce command. `perf/RESULTS.md` is an upper bound (JSON parsed straight into the engine, no Python-object conversion); the bindings table is the product-surface number. Regenerate them with `perf/run_bench.sh` and `crates/onix-py/benchmarks/bench_bindings.py` (see [CONTRIBUTING.md](CONTRIBUTING.md)). The Arrow table diff's cost against two hand-rolled baselines — a DuckDB SQL join and a polars anti-join/inner-join diff, on a seeded 5 GB parquet pair and its 1M-row subset — is in [`perf/arrow/RESULTS.md`](perf/arrow/RESULTS.md): onix's single-threaded row diff trails both parallelized baselines at both sizes; see the linked results for the figures; regenerated with `perf/arrow/bench_tables.py`.
|
|
199
199
|
|
|
200
200
|
## Reference
|
|
201
201
|
|
|
202
|
-
**Python API.** The public surface is `DeepDiff`, `diff_json`, `diff_tables` (returning a `TableDiff`), `MaxDepthError`, and `
|
|
202
|
+
**Python API.** The public surface is `DeepDiff`, `diff_json`, `diff_tables` (returning a `TableDiff`), `MaxDepthError`, `MAX_DEPTH_CEILING`, and `__version__` (a `str` matching the installed `deepdiff-rs` distribution version).
|
|
203
203
|
|
|
204
204
|
- `DeepDiff(t1, t2, ignore_order=False, max_depth=None)`: diffs two live Python objects of supported value types — `None`, `bool`, `int`, `float`, `str`, `dict`, `list`, `tuple`, `set`, `frozenset`, `datetime.datetime`, `datetime.date`, `datetime.time`, and `datetime.timedelta` (see [Known limitations](#known-limitations) for the exact restrictions and exclusions, including which types a `dict` key may be); `.to_json()` returns the DeepDiff-compatible JSON string, `.to_dict()` the same report as a dict — with Python types preserved, so a value the diff found in a `tuple`, `set` or `frozenset` comes back as one and a `datetime`/`date`/`time`/`timedelta` comes back as a real one of those — and the instance is falsy when there is no difference. The `set_item_added`/`set_item_removed` categories are lists of path strings, each ending in the item itself (`root['a'][2]`, `root['x']`, `root[(1, 2)]`).
|
|
205
205
|
- `diff_json(a, b, ignore_order=False, max_depth=None) -> str`: diffs two JSON strings entirely in Rust and returns the report as a JSON string.
|
|
@@ -236,10 +236,11 @@ perf/ # cross-language benchmark harness and RESULTS.md
|
|
|
236
236
|
## Known limitations
|
|
237
237
|
|
|
238
238
|
- Only the core diff is implemented: `exclude_paths`, `significant_digits`, custom operators, `verbose_level != 2`, and delta/patch are not (yet) supported.
|
|
239
|
-
- Supported value types are `None`, `bool`, `int`, `float
|
|
240
|
-
- A subclass of a supported
|
|
239
|
+
- Supported value types are `None`, `bool`, `int`, `float` (`NaN`/`Infinity`/`-Infinity` included), `str`, `dict`, `list`, `tuple`, `set`, `frozenset`, `datetime.datetime`, `datetime.date`, `datetime.time`, and `datetime.timedelta`; a `set`/`frozenset` member may be any of these except a `list`, `dict` or `set`, matching Python's own hashability rule, transitively through whatever the member nests, and a `dict` key may be `str`, `None`, `bool`, `int`, `float`, `datetime.datetime`, `datetime.date`, or a `tuple` of those (never nested), or a `tuple`/`datetime`/`date` subclass key (`namedtuple` included), accepted and matched against its base-type value the same way — see [Known limitations](#known-limitations)'s subclass bullet. `int`s must fit in `i64`/`u64`, and anything else — a custom object or an arbitrary-precision `int` — raises `TypeError`/`ValueError` naming the exact path it was found at. A non-`str` key's path renders via Python's own `repr()`, except a `tuple` key, which splits into one bracket group per element (`root[1][2]` for `(1, 2)`, never `root[(1, 2)]`, with one deliberate exception for a real DeepDiff bug on the empty tuple, and another on a non-finite-float key — see [`tests/golden/README.md`](tests/golden/README.md)). A nested dict value's own `bool`/`None`/`int`/`float` key stringifies the way `json.dumps` does, but its `datetime`/`date`/`tuple` key renders that same `repr()` text where DeepDiff's own `to_json()` raises `TypeError` on one — a superset, not a difference in the findings (see [`tests/golden/README.md`](tests/golden/README.md)). `1`/`1.0`/`True` match as the same key between two dicts (Python `dict`/`set` equality). The **Datetimes**, **Sets** and **Non-finite floats** bullets below cover the deliberate divergences for those types. See [`crates/onix-py/src/convert.rs`](crates/onix-py/src/convert.rs) and [`tests/golden/README.md`](tests/golden/README.md).
|
|
240
|
+
- A subclass of a supported `dict`, `list`, `tuple`, `set`, `frozenset`, `datetime.datetime`, `datetime.date`, `datetime.time`, or `datetime.timedelta` (including `namedtuple` and pandas' `Timestamp`) converts and compares as its base type but carries its own class name into a `type_changes` entry, matching DeepDiff's `type(obj).__name__` reporting, with three exceptions: a calendar-type (`datetime`/`date`/`time`/`timedelta`) subclass held as a `set`/`frozenset` member compares as its base with no `type_changes`, matching DeepDiff, since set membership has no pairwise comparison to report one against; a `tuple`/`frozenset` subclass, including a `namedtuple`, is not accepted as a `set`/`frozenset` member at all and raises `TypeError` where DeepDiff accepts and compares it by value (a documented divergence); and `namedtuple` diffs positionally rather than by field (also documented). A `type_changes` entry's `old_type`/`new_type` are type *names* in `to_dict()`, where DeepDiff returns the type objects. See [`tests/golden/README.md`](tests/golden/README.md).
|
|
241
241
|
- **Datetimes** compare by instant, with a naive value read as UTC, matching DeepDiff. A changed pair is reported normalized to UTC (`to_json()` renders `...+00:00`, `to_dict()` returns UTC-aware `datetime`s); everywhere else a datetime keeps its raw value. Three deliberate departures: `to_json()` renders a `date` as `YYYY-MM-DD` where DeepDiff's own `to_json()` raises `TypeError` (a documented superset); a `zoneinfo`/`pytz` tzinfo comes back from `to_dict()` as a fixed-offset `datetime.timezone` carrying the offset it was in force at, not the original zone object; and a set holding both a naive and an aware value at one instant reports both as members, where DeepDiff's own digest cache can report only one (see [`crates/onix-py/src/convert.rs`](crates/onix-py/src/convert.rs)). Comparing two datetimes whose UTC form would leave year 1..=9999 raises `ValueError` naming the path, where DeepDiff raises `OverflowError`; under `ignore_order` DeepDiff's hasher normalizes every datetime and so raises for such a value even when it is only added, removed, or shuffled, where onix hashes by instant and reports it normally (see [`tests/golden/README.md`](tests/golden/README.md)). `truncate_datetime` is not supported; the normalized-versus-raw split is documented there too. A `time`/`timedelta`, unlike a datetime, is never normalized for report (DeepDiff compares `time`/`date`/`timedelta` with a plain `!=`), and a naive `time` is never equal to an aware one; `to_json()` renders a `time` as `time.isoformat()`'s bytes and a `timedelta` as `str(timedelta)`'s, both supersets. Under `ignore_order`, `DeepHash` hashes a `time` by whole seconds-of-day only — dropping the microsecond and any offset, a confirmed upstream quirk — while a `timedelta` hashes exactly; see [`tests/golden/README.md`](tests/golden/README.md)'s "Known DeepDiff quirks" section.
|
|
242
242
|
- **Sets** are diffed deterministically, where DeepDiff's own answers depend on the order the running process happens to iterate a set in (hash order, and `PYTHONHASHSEED`-dependent for `str` members) or on how its digest cache/computation handles a tuple, frozenset, or calendar member independently of Python's own `==`. Each consequence — entry order, which member of an equality class is reported, set-versus-sequence coercion, and a tuple/frozenset member's own (positional, not order-/repetition-insensitive) matching rule — is shown with both tools' output in [`tests/golden/README.md`](tests/golden/README.md)'s "Set iteration order" section. A report holding a `frozenset` value also serializes to JSON here, where DeepDiff's own `to_json()` raises `TypeError` — a superset, not a difference in the findings.
|
|
243
|
+
- **Non-finite floats** (`NaN`, `Infinity`, `-Infinity`) compare and hash like real Python: two `NaN`s are never equal, `Infinity == Infinity`, `to_json()` renders the same bare `NaN`/`Infinity`/`-Infinity` tokens Python's `json.dumps` does, and `ignore_order` matching treats every `NaN` as one shared item, matching `DeepHash`. The one divergence, always deterministic: this crate's value model carries no Python object identity, so two independently-obtained `NaN`s always compare unequal here, where DeepDiff sometimes reports no difference (`t1 is t2`) or lets one collapse into another (a `set` member, an ordered-list match) when the two objects, or their containers, happen to be the same one. See [`tests/golden/README.md`](tests/golden/README.md)'s "Non-finite floats" section.
|
|
243
244
|
- A `str` containing a lone (unpaired) surrogate code point (e.g. `'\udc80'`, legal in Python but not encodable as UTF-8) raises `ValueError` naming the exact path on either side, before the two values are ever compared — including a pair DeepDiff would call equal and report as no change, since DeepDiff's scalar equality is plain Python `==` and never hits the encoding problem; DeepDiff does report a plain change for a *differing* pair, and crashes with an unhandled `UnicodeEncodeError` if such a string is ever hashed (a `set`/`frozenset` member). See [`tests/golden/README.md`](tests/golden/README.md)'s "Known DeepDiff quirks" section.
|
|
244
245
|
- A `str` inside a `tuple` or `frozenset` set item is escaped exactly as Python's `repr()` escapes it, against Unicode 16.0.0; on a Python older than 3.14 (an older `unicodedata` table), a code point assigned to Unicode after that Python's own version is escaped by DeepDiff and rendered literally by onix. See [`tests/golden/README.md`](tests/golden/README.md)'s "Pinned versions" section.
|
|
245
246
|
- Adversarially deep input raises `MaxDepthError` instead of crashing: the default `max_depth` is 512 and the hard ceiling is `MAX_DEPTH_CEILING` (20,000). See [`crates/onix-py/src/guard.rs`](crates/onix-py/src/guard.rs).
|
|
@@ -177,11 +177,11 @@ The engine's own diff-only time and peak resident memory against pinned `deepdif
|
|
|
177
177
|
| `ignore_order_10k` | 73.475 ms (71.672 ms-73.940 ms) | 12.976 s (12.900 s-13.005 s) | 176.60x | 60.11 MB | 345.19 MB | 5.74x | ✅ |
|
|
178
178
|
| `identical_1m` | 9.254 ms (6.726 ms-9.875 ms) | 15.790 s (15.660 s-15.989 s) | 1706.35x | 315.41 MB | 503.19 MB | 1.60x | ✅ |
|
|
179
179
|
|
|
180
|
-
Both reports carry their full methodology, fairness rules, and the reproduce command. `perf/RESULTS.md` is an upper bound (JSON parsed straight into the engine, no Python-object conversion); the bindings table is the product-surface number. Regenerate them with `perf/run_bench.sh` and `crates/onix-py/benchmarks/bench_bindings.py` (see [CONTRIBUTING.md](CONTRIBUTING.md)).
|
|
180
|
+
Both reports carry their full methodology, fairness rules, and the reproduce command. `perf/RESULTS.md` is an upper bound (JSON parsed straight into the engine, no Python-object conversion); the bindings table is the product-surface number. Regenerate them with `perf/run_bench.sh` and `crates/onix-py/benchmarks/bench_bindings.py` (see [CONTRIBUTING.md](CONTRIBUTING.md)). The Arrow table diff's cost against two hand-rolled baselines — a DuckDB SQL join and a polars anti-join/inner-join diff, on a seeded 5 GB parquet pair and its 1M-row subset — is in [`perf/arrow/RESULTS.md`](perf/arrow/RESULTS.md): onix's single-threaded row diff trails both parallelized baselines at both sizes; see the linked results for the figures; regenerated with `perf/arrow/bench_tables.py`.
|
|
181
181
|
|
|
182
182
|
## Reference
|
|
183
183
|
|
|
184
|
-
**Python API.** The public surface is `DeepDiff`, `diff_json`, `diff_tables` (returning a `TableDiff`), `MaxDepthError`, and `
|
|
184
|
+
**Python API.** The public surface is `DeepDiff`, `diff_json`, `diff_tables` (returning a `TableDiff`), `MaxDepthError`, `MAX_DEPTH_CEILING`, and `__version__` (a `str` matching the installed `deepdiff-rs` distribution version).
|
|
185
185
|
|
|
186
186
|
- `DeepDiff(t1, t2, ignore_order=False, max_depth=None)`: diffs two live Python objects of supported value types — `None`, `bool`, `int`, `float`, `str`, `dict`, `list`, `tuple`, `set`, `frozenset`, `datetime.datetime`, `datetime.date`, `datetime.time`, and `datetime.timedelta` (see [Known limitations](#known-limitations) for the exact restrictions and exclusions, including which types a `dict` key may be); `.to_json()` returns the DeepDiff-compatible JSON string, `.to_dict()` the same report as a dict — with Python types preserved, so a value the diff found in a `tuple`, `set` or `frozenset` comes back as one and a `datetime`/`date`/`time`/`timedelta` comes back as a real one of those — and the instance is falsy when there is no difference. The `set_item_added`/`set_item_removed` categories are lists of path strings, each ending in the item itself (`root['a'][2]`, `root['x']`, `root[(1, 2)]`).
|
|
187
187
|
- `diff_json(a, b, ignore_order=False, max_depth=None) -> str`: diffs two JSON strings entirely in Rust and returns the report as a JSON string.
|
|
@@ -218,10 +218,11 @@ perf/ # cross-language benchmark harness and RESULTS.md
|
|
|
218
218
|
## Known limitations
|
|
219
219
|
|
|
220
220
|
- Only the core diff is implemented: `exclude_paths`, `significant_digits`, custom operators, `verbose_level != 2`, and delta/patch are not (yet) supported.
|
|
221
|
-
- Supported value types are `None`, `bool`, `int`, `float
|
|
222
|
-
- A subclass of a supported
|
|
221
|
+
- Supported value types are `None`, `bool`, `int`, `float` (`NaN`/`Infinity`/`-Infinity` included), `str`, `dict`, `list`, `tuple`, `set`, `frozenset`, `datetime.datetime`, `datetime.date`, `datetime.time`, and `datetime.timedelta`; a `set`/`frozenset` member may be any of these except a `list`, `dict` or `set`, matching Python's own hashability rule, transitively through whatever the member nests, and a `dict` key may be `str`, `None`, `bool`, `int`, `float`, `datetime.datetime`, `datetime.date`, or a `tuple` of those (never nested), or a `tuple`/`datetime`/`date` subclass key (`namedtuple` included), accepted and matched against its base-type value the same way — see [Known limitations](#known-limitations)'s subclass bullet. `int`s must fit in `i64`/`u64`, and anything else — a custom object or an arbitrary-precision `int` — raises `TypeError`/`ValueError` naming the exact path it was found at. A non-`str` key's path renders via Python's own `repr()`, except a `tuple` key, which splits into one bracket group per element (`root[1][2]` for `(1, 2)`, never `root[(1, 2)]`, with one deliberate exception for a real DeepDiff bug on the empty tuple, and another on a non-finite-float key — see [`tests/golden/README.md`](tests/golden/README.md)). A nested dict value's own `bool`/`None`/`int`/`float` key stringifies the way `json.dumps` does, but its `datetime`/`date`/`tuple` key renders that same `repr()` text where DeepDiff's own `to_json()` raises `TypeError` on one — a superset, not a difference in the findings (see [`tests/golden/README.md`](tests/golden/README.md)). `1`/`1.0`/`True` match as the same key between two dicts (Python `dict`/`set` equality). The **Datetimes**, **Sets** and **Non-finite floats** bullets below cover the deliberate divergences for those types. See [`crates/onix-py/src/convert.rs`](crates/onix-py/src/convert.rs) and [`tests/golden/README.md`](tests/golden/README.md).
|
|
222
|
+
- A subclass of a supported `dict`, `list`, `tuple`, `set`, `frozenset`, `datetime.datetime`, `datetime.date`, `datetime.time`, or `datetime.timedelta` (including `namedtuple` and pandas' `Timestamp`) converts and compares as its base type but carries its own class name into a `type_changes` entry, matching DeepDiff's `type(obj).__name__` reporting, with three exceptions: a calendar-type (`datetime`/`date`/`time`/`timedelta`) subclass held as a `set`/`frozenset` member compares as its base with no `type_changes`, matching DeepDiff, since set membership has no pairwise comparison to report one against; a `tuple`/`frozenset` subclass, including a `namedtuple`, is not accepted as a `set`/`frozenset` member at all and raises `TypeError` where DeepDiff accepts and compares it by value (a documented divergence); and `namedtuple` diffs positionally rather than by field (also documented). A `type_changes` entry's `old_type`/`new_type` are type *names* in `to_dict()`, where DeepDiff returns the type objects. See [`tests/golden/README.md`](tests/golden/README.md).
|
|
223
223
|
- **Datetimes** compare by instant, with a naive value read as UTC, matching DeepDiff. A changed pair is reported normalized to UTC (`to_json()` renders `...+00:00`, `to_dict()` returns UTC-aware `datetime`s); everywhere else a datetime keeps its raw value. Three deliberate departures: `to_json()` renders a `date` as `YYYY-MM-DD` where DeepDiff's own `to_json()` raises `TypeError` (a documented superset); a `zoneinfo`/`pytz` tzinfo comes back from `to_dict()` as a fixed-offset `datetime.timezone` carrying the offset it was in force at, not the original zone object; and a set holding both a naive and an aware value at one instant reports both as members, where DeepDiff's own digest cache can report only one (see [`crates/onix-py/src/convert.rs`](crates/onix-py/src/convert.rs)). Comparing two datetimes whose UTC form would leave year 1..=9999 raises `ValueError` naming the path, where DeepDiff raises `OverflowError`; under `ignore_order` DeepDiff's hasher normalizes every datetime and so raises for such a value even when it is only added, removed, or shuffled, where onix hashes by instant and reports it normally (see [`tests/golden/README.md`](tests/golden/README.md)). `truncate_datetime` is not supported; the normalized-versus-raw split is documented there too. A `time`/`timedelta`, unlike a datetime, is never normalized for report (DeepDiff compares `time`/`date`/`timedelta` with a plain `!=`), and a naive `time` is never equal to an aware one; `to_json()` renders a `time` as `time.isoformat()`'s bytes and a `timedelta` as `str(timedelta)`'s, both supersets. Under `ignore_order`, `DeepHash` hashes a `time` by whole seconds-of-day only — dropping the microsecond and any offset, a confirmed upstream quirk — while a `timedelta` hashes exactly; see [`tests/golden/README.md`](tests/golden/README.md)'s "Known DeepDiff quirks" section.
|
|
224
224
|
- **Sets** are diffed deterministically, where DeepDiff's own answers depend on the order the running process happens to iterate a set in (hash order, and `PYTHONHASHSEED`-dependent for `str` members) or on how its digest cache/computation handles a tuple, frozenset, or calendar member independently of Python's own `==`. Each consequence — entry order, which member of an equality class is reported, set-versus-sequence coercion, and a tuple/frozenset member's own (positional, not order-/repetition-insensitive) matching rule — is shown with both tools' output in [`tests/golden/README.md`](tests/golden/README.md)'s "Set iteration order" section. A report holding a `frozenset` value also serializes to JSON here, where DeepDiff's own `to_json()` raises `TypeError` — a superset, not a difference in the findings.
|
|
225
|
+
- **Non-finite floats** (`NaN`, `Infinity`, `-Infinity`) compare and hash like real Python: two `NaN`s are never equal, `Infinity == Infinity`, `to_json()` renders the same bare `NaN`/`Infinity`/`-Infinity` tokens Python's `json.dumps` does, and `ignore_order` matching treats every `NaN` as one shared item, matching `DeepHash`. The one divergence, always deterministic: this crate's value model carries no Python object identity, so two independently-obtained `NaN`s always compare unequal here, where DeepDiff sometimes reports no difference (`t1 is t2`) or lets one collapse into another (a `set` member, an ordered-list match) when the two objects, or their containers, happen to be the same one. See [`tests/golden/README.md`](tests/golden/README.md)'s "Non-finite floats" section.
|
|
225
226
|
- A `str` containing a lone (unpaired) surrogate code point (e.g. `'\udc80'`, legal in Python but not encodable as UTF-8) raises `ValueError` naming the exact path on either side, before the two values are ever compared — including a pair DeepDiff would call equal and report as no change, since DeepDiff's scalar equality is plain Python `==` and never hits the encoding problem; DeepDiff does report a plain change for a *differing* pair, and crashes with an unhandled `UnicodeEncodeError` if such a string is ever hashed (a `set`/`frozenset` member). See [`tests/golden/README.md`](tests/golden/README.md)'s "Known DeepDiff quirks" section.
|
|
226
227
|
- A `str` inside a `tuple` or `frozenset` set item is escaped exactly as Python's `repr()` escapes it, against Unicode 16.0.0; on a Python older than 3.14 (an older `unicodedata` table), a code point assigned to Unicode after that Python's own version is escaped by DeepDiff and rendered literally by onix. See [`tests/golden/README.md`](tests/golden/README.md)'s "Pinned versions" section.
|
|
227
228
|
- Adversarially deep input raises `MaxDepthError` instead of crashing: the default `max_depth` is 512 and the hard ceiling is `MAX_DEPTH_CEILING` (20,000). See [`crates/onix-py/src/guard.rs`](crates/onix-py/src/guard.rs).
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
//! the parent `diff` module's "List diffing" doc section for the full,
|
|
4
4
|
//! empirically-verified spec this implements.
|
|
5
5
|
|
|
6
|
-
use crate::value::Value;
|
|
6
|
+
use crate::value::{Value, class_name};
|
|
7
7
|
|
|
8
8
|
use crate::error::Error;
|
|
9
9
|
use crate::ignore_order::{self, IgnoreOrderMemo};
|
|
@@ -12,8 +12,8 @@ use crate::path::PathSegment;
|
|
|
12
12
|
use crate::report::{Report, TypeChangeEntry, ValuesChangedEntry};
|
|
13
13
|
|
|
14
14
|
use super::{
|
|
15
|
-
DiffOptions, check_traversal_depth, check_value_depth, diff_at,
|
|
16
|
-
python_type_name, scoped,
|
|
15
|
+
DiffOptions, check_traversal_depth, check_value_depth, diff_at, effective_type_name,
|
|
16
|
+
normalized_pair, python_type_name, scoped,
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
/// Diffs two lists (JSON arrays) at `path`, `depth` levels deep.
|
|
@@ -178,17 +178,36 @@ fn insert_lcs_pair_finding(
|
|
|
178
178
|
new_segments
|
|
179
179
|
});
|
|
180
180
|
|
|
181
|
-
if let (Value::DateTime(
|
|
182
|
-
|
|
181
|
+
if let (Value::DateTime(old_typed), Value::DateTime(new_typed)) = (old_value, new_value) {
|
|
182
|
+
// A `datetime` subclass (e.g. pandas `Timestamp`) still reports
|
|
183
|
+
// `type_changes` against a base `datetime` (or a differently
|
|
184
|
+
// named subclass) even at equal value — see `Typed`'s doc — so
|
|
185
|
+
// this check must run before the normalize-and-compare path
|
|
186
|
+
// below, which only ever produces `values_changed`.
|
|
187
|
+
if old_typed.class_name() != new_typed.class_name() {
|
|
188
|
+
report.insert_type_change(
|
|
189
|
+
path.clone(),
|
|
190
|
+
TypeChangeEntry {
|
|
191
|
+
old_type: effective_type_name(old_value),
|
|
192
|
+
new_type: effective_type_name(new_value),
|
|
193
|
+
old_value: old_value.clone(),
|
|
194
|
+
new_value: new_value.clone(),
|
|
195
|
+
new_path,
|
|
196
|
+
},
|
|
197
|
+
);
|
|
198
|
+
return Ok(());
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
let (old_norm, new_norm) = normalized_pair(path, old_typed.value(), new_typed.value())?;
|
|
183
202
|
|
|
184
|
-
if
|
|
203
|
+
if old_norm != new_norm {
|
|
185
204
|
report.insert_values_changed(
|
|
186
205
|
path.clone(),
|
|
187
206
|
ValuesChangedEntry {
|
|
188
207
|
// Datetimes are never strings, so `_diff_str` never runs.
|
|
189
208
|
diff: None,
|
|
190
|
-
old_value: Value::DateTime(
|
|
191
|
-
new_value: Value::DateTime(
|
|
209
|
+
old_value: Value::DateTime(old_norm.into()),
|
|
210
|
+
new_value: Value::DateTime(new_norm.into()),
|
|
192
211
|
new_path,
|
|
193
212
|
},
|
|
194
213
|
);
|
|
@@ -197,7 +216,9 @@ fn insert_lcs_pair_finding(
|
|
|
197
216
|
return Ok(());
|
|
198
217
|
}
|
|
199
218
|
|
|
200
|
-
if python_type_name(old_value) == python_type_name(new_value)
|
|
219
|
+
if python_type_name(old_value) == python_type_name(new_value)
|
|
220
|
+
&& class_name(old_value) == class_name(new_value)
|
|
221
|
+
{
|
|
201
222
|
report.insert_values_changed(
|
|
202
223
|
path.clone(),
|
|
203
224
|
ValuesChangedEntry {
|
|
@@ -211,8 +232,8 @@ fn insert_lcs_pair_finding(
|
|
|
211
232
|
report.insert_type_change(
|
|
212
233
|
path.clone(),
|
|
213
234
|
TypeChangeEntry {
|
|
214
|
-
old_type:
|
|
215
|
-
new_type:
|
|
235
|
+
old_type: effective_type_name(old_value),
|
|
236
|
+
new_type: effective_type_name(new_value),
|
|
216
237
|
old_value: old_value.clone(),
|
|
217
238
|
new_value: new_value.clone(),
|
|
218
239
|
new_path,
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
//! See the parent `diff` module's doc for the full recursion-depth hardening
|
|
7
7
|
//! (its "Hardening" section) this file implements.
|
|
8
8
|
|
|
9
|
-
use crate::value::{Object, Value};
|
|
9
|
+
use crate::value::{Object, Value, class_name};
|
|
10
10
|
|
|
11
11
|
use crate::error::Error;
|
|
12
12
|
use crate::ignore_order::IgnoreOrderMemo;
|
|
@@ -18,6 +18,18 @@ use super::{
|
|
|
18
18
|
type_change_report,
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
+
/// Whether `a` and `b` — already known to be the same [`Value`] variant —
|
|
22
|
+
/// carry different subclass names, per [`class_name`]. `diff_at` checks
|
|
23
|
+
/// this before recursing into any of the eight variants that can carry one
|
|
24
|
+
/// (`DateTime`, `Date`, `Time`, `TimeDelta`, `Array`, `Tuple`,
|
|
25
|
+
/// `Set`/`FrozenSet`, `Object`): a mismatch means `DeepDiff` would report
|
|
26
|
+
/// `type_changes` here even though the values are the same JSON-ish shape —
|
|
27
|
+
/// see [`crate::value::Typed`]'s doc for why (and why every other matching
|
|
28
|
+
/// identity in the crate stays class-agnostic instead).
|
|
29
|
+
fn same_class(a: &Value, b: &Value) -> bool {
|
|
30
|
+
class_name(a) == class_name(b)
|
|
31
|
+
}
|
|
32
|
+
|
|
21
33
|
/// The recursive core of [`diff_with_max_depth()`](super::diff_with_max_depth): identical dispatch, but
|
|
22
34
|
/// carrying the path and depth accumulated so far, so that nested findings
|
|
23
35
|
/// get their full deep path and the recursion-depth bound can be enforced.
|
|
@@ -55,33 +67,64 @@ pub(crate) fn diff_at(
|
|
|
55
67
|
scalar_diff(path, old == new, a, b, depth, opts.max_depth)
|
|
56
68
|
}
|
|
57
69
|
(Value::DateTime(old), Value::DateTime(new)) => {
|
|
58
|
-
|
|
70
|
+
if same_class(a, b) {
|
|
71
|
+
datetime_diff(path, old.value(), new.value(), depth, opts.max_depth)
|
|
72
|
+
} else {
|
|
73
|
+
type_change_report(path, a, b, depth, opts.max_depth)
|
|
74
|
+
}
|
|
59
75
|
}
|
|
60
76
|
(Value::Date(old), Value::Date(new)) => {
|
|
61
|
-
|
|
77
|
+
if same_class(a, b) {
|
|
78
|
+
scalar_diff(path, old == new, a, b, depth, opts.max_depth)
|
|
79
|
+
} else {
|
|
80
|
+
type_change_report(path, a, b, depth, opts.max_depth)
|
|
81
|
+
}
|
|
62
82
|
}
|
|
63
83
|
(Value::Time(old), Value::Time(new)) => {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
84
|
+
if same_class(a, b) {
|
|
85
|
+
// Plain `_diff_time` equality — no normalization step,
|
|
86
|
+
// unlike `datetime_diff` (see `crate::datetime`'s module
|
|
87
|
+
// doc).
|
|
88
|
+
scalar_diff(
|
|
89
|
+
path,
|
|
90
|
+
crate::datetime::times_equal(old.value(), new.value()),
|
|
91
|
+
a,
|
|
92
|
+
b,
|
|
93
|
+
depth,
|
|
94
|
+
opts.max_depth,
|
|
95
|
+
)
|
|
96
|
+
} else {
|
|
97
|
+
type_change_report(path, a, b, depth, opts.max_depth)
|
|
98
|
+
}
|
|
74
99
|
}
|
|
75
100
|
(Value::TimeDelta(old), Value::TimeDelta(new)) => {
|
|
76
|
-
|
|
101
|
+
if same_class(a, b) {
|
|
102
|
+
scalar_diff(path, old == new, a, b, depth, opts.max_depth)
|
|
103
|
+
} else {
|
|
104
|
+
type_change_report(path, a, b, depth, opts.max_depth)
|
|
105
|
+
}
|
|
77
106
|
}
|
|
78
107
|
(Value::Array(old), Value::Array(new)) | (Value::Tuple(old), Value::Tuple(new)) => {
|
|
79
|
-
|
|
108
|
+
if same_class(a, b) {
|
|
109
|
+
array_diff(path, old, new, depth, opts, memo)
|
|
110
|
+
} else {
|
|
111
|
+
type_change_report(path, a, b, depth, opts.max_depth)
|
|
112
|
+
}
|
|
80
113
|
}
|
|
81
114
|
(Value::Set(old), Value::Set(new)) | (Value::FrozenSet(old), Value::FrozenSet(new)) => {
|
|
82
|
-
|
|
115
|
+
if same_class(a, b) {
|
|
116
|
+
set_diff(path, old, new, depth, opts, memo)
|
|
117
|
+
} else {
|
|
118
|
+
type_change_report(path, a, b, depth, opts.max_depth)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
(Value::Object(old), Value::Object(new)) => {
|
|
122
|
+
if same_class(a, b) {
|
|
123
|
+
object_diff(path, old, new, depth, opts, memo)
|
|
124
|
+
} else {
|
|
125
|
+
type_change_report(path, a, b, depth, opts.max_depth)
|
|
126
|
+
}
|
|
83
127
|
}
|
|
84
|
-
(Value::Object(old), Value::Object(new)) => object_diff(path, old, new, depth, opts, memo),
|
|
85
128
|
_ => type_change_report(path, a, b, depth, opts.max_depth),
|
|
86
129
|
}
|
|
87
130
|
}
|
|
@@ -228,7 +228,7 @@ pub(crate) use object::object_diff;
|
|
|
228
228
|
#[cfg(test)]
|
|
229
229
|
pub(crate) use options::diff_with_options_memo;
|
|
230
230
|
pub(crate) use scalar::{
|
|
231
|
-
datetime_diff, normalized_pair, numbers_equal, numeric_diff,
|
|
232
|
-
type_change_report,
|
|
231
|
+
datetime_diff, effective_type_name, normalized_pair, numbers_equal, numeric_diff,
|
|
232
|
+
python_type_name, scalar_diff, type_change_report,
|
|
233
233
|
};
|
|
234
234
|
pub(crate) use set::set_diff;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
//! arrays.
|
|
5
5
|
|
|
6
6
|
use crate::datetime::DateTime;
|
|
7
|
-
use crate::value::{Number, Value};
|
|
7
|
+
use crate::value::{Number, Value, class_name};
|
|
8
8
|
|
|
9
9
|
use crate::error::Error;
|
|
10
10
|
use crate::path::{PathSegment, render_path};
|
|
@@ -12,7 +12,9 @@ use crate::report::{Report, TypeChangeEntry, ValuesChangedEntry};
|
|
|
12
12
|
|
|
13
13
|
use super::check_value_depth;
|
|
14
14
|
|
|
15
|
-
/// The
|
|
15
|
+
/// The base-type name for a given [`Value`], ignoring any subclass name it
|
|
16
|
+
/// carries; see [`effective_type_name`] for the name `DeepDiff` actually
|
|
17
|
+
/// reports.
|
|
16
18
|
///
|
|
17
19
|
/// Numbers are split into `"int"` and `"float"` by the compact [`Number`]'s
|
|
18
20
|
/// preserved representation (which carries `serde_json`'s original parse: a
|
|
@@ -37,6 +39,14 @@ pub(crate) fn python_type_name(value: &Value) -> &'static str {
|
|
|
37
39
|
Value::Object(_) => "dict",
|
|
38
40
|
}
|
|
39
41
|
}
|
|
42
|
+
|
|
43
|
+
/// [`python_type_name`], overridden by [`class_name`] when `value` carries a
|
|
44
|
+
/// subclass name — the name `DeepDiff` actually reports for `old_type`/
|
|
45
|
+
/// `new_type` (`type(obj).__name__`, not the base type it structurally
|
|
46
|
+
/// compares as). See [`crate::value::Typed`]'s doc.
|
|
47
|
+
pub(crate) fn effective_type_name(value: &Value) -> String {
|
|
48
|
+
class_name(value).map_or_else(|| python_type_name(value).to_string(), str::to_string)
|
|
49
|
+
}
|
|
40
50
|
/// Builds a single-entry `type_changes` report at `path`, `depth` levels
|
|
41
51
|
/// deep.
|
|
42
52
|
///
|
|
@@ -60,8 +70,8 @@ pub(crate) fn type_change_report(
|
|
|
60
70
|
report.insert_type_change(
|
|
61
71
|
path.to_vec(),
|
|
62
72
|
TypeChangeEntry {
|
|
63
|
-
old_type:
|
|
64
|
-
new_type:
|
|
73
|
+
old_type: effective_type_name(a),
|
|
74
|
+
new_type: effective_type_name(b),
|
|
65
75
|
old_value: a.clone(),
|
|
66
76
|
new_value: b.clone(),
|
|
67
77
|
new_path: None,
|
|
@@ -128,8 +138,8 @@ pub(crate) fn datetime_diff(
|
|
|
128
138
|
scalar_diff(
|
|
129
139
|
path,
|
|
130
140
|
old == new,
|
|
131
|
-
&Value::DateTime(old),
|
|
132
|
-
&Value::DateTime(new),
|
|
141
|
+
&Value::DateTime(old.into()),
|
|
142
|
+
&Value::DateTime(new.into()),
|
|
133
143
|
depth,
|
|
134
144
|
max_depth,
|
|
135
145
|
)
|
|
@@ -207,14 +217,17 @@ pub(crate) fn numbers_equal(old: &Number, new: &Number) -> bool {
|
|
|
207
217
|
}
|
|
208
218
|
/// Compares two floats for exact equality.
|
|
209
219
|
///
|
|
210
|
-
/// This mirrors Python's `==` semantics for floats
|
|
211
|
-
///
|
|
212
|
-
/// for
|
|
220
|
+
/// This mirrors Python's `==` semantics for floats: `0.0 == -0.0`, and —
|
|
221
|
+
/// with no special-casing needed — `NaN != NaN` (matching real `DeepDiff`
|
|
222
|
+
/// for two independently-obtained `NaN` values; see `tests/golden/README.md`'s
|
|
223
|
+
/// "Non-finite floats" section for the one case this crate cannot
|
|
224
|
+
/// reproduce, where `DeepDiff` short-circuits on `t1 is t2`), no implicit
|
|
225
|
+
/// epsilon, and `Infinity == Infinity`. Exact IEEE-754 `==` is the correct
|
|
213
226
|
/// (and only) rule here.
|
|
214
227
|
fn floats_equal(a: f64, b: f64) -> bool {
|
|
215
228
|
#[allow(
|
|
216
229
|
clippy::float_cmp,
|
|
217
|
-
reason = "exact IEEE-754 equality is the intended rule (Python == semantics, including 0.0 == -0.0
|
|
230
|
+
reason = "exact IEEE-754 equality is the intended rule (Python == semantics, including 0.0 == -0.0 and NaN != NaN)"
|
|
218
231
|
)]
|
|
219
232
|
{
|
|
220
233
|
a == b
|