Skip to content

Fix for diffing using iterable_compare_func with nested objects. #333

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Aug 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
33 changes: 18 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# DeepDiff v 5.8.1
# DeepDiff v 5.8.2

![Downloads](https://img.shields.io/pypi/dm/deepdiff.svg?style=flat)
![Python Versions](https://img.shields.io/pypi/pyversions/deepdiff.svg?style=flat)
Expand All @@ -14,10 +14,13 @@

Tested on Python 3.6+ and PyPy3.

- **[Documentation](https://zepworks.com/deepdiff/5.8.1/)**
- **[Documentation](https://zepworks.com/deepdiff/5.8.2/)**

## What is new?

DeepDiff 5-8-2
Fixing dependency for Py3.6

DeepDiff 5-8-1 includes bug fixes:
- Fixed test suite for 32bit systems (https://github.com/seperman/deepdiff/issues/302) by [Louis-Philippe Véronneau](https://github.com/baldurmen)
- Fixed the issue when using `ignore_order=True` and `group_by` simultaneously
Expand Down Expand Up @@ -67,13 +70,13 @@ Note: if you want to use DeepDiff via commandline, make sure to run `pip install

DeepDiff gets the difference of 2 objects.

> - Please take a look at the [DeepDiff docs](https://zepworks.com/deepdiff/5.8.1/diff.html)
> - The full documentation of all modules can be found on <https://zepworks.com/deepdiff/5.8.1/>
> - Please take a look at the [DeepDiff docs](https://zepworks.com/deepdiff/5.8.2/diff.html)
> - The full documentation of all modules can be found on <https://zepworks.com/deepdiff/5.8.2/>
> - Tutorials and posts about DeepDiff can be found on <https://zepworks.com/tags/deepdiff/>

## A few Examples

> Note: This is just a brief overview of what DeepDiff can do. Please visit <https://zepworks.com/deepdiff/5.8.1/> for full documentation.
> Note: This is just a brief overview of what DeepDiff can do. Please visit <https://zepworks.com/deepdiff/5.8.2/> for full documentation.

### List difference ignoring order or duplicates

Expand Down Expand Up @@ -277,8 +280,8 @@ Example:
```


> - Please take a look at the [DeepDiff docs](https://zepworks.com/deepdiff/5.8.1/diff.html)
> - The full documentation can be found on <https://zepworks.com/deepdiff/5.8.1/>
> - Please take a look at the [DeepDiff docs](https://zepworks.com/deepdiff/5.8.2/diff.html)
> - The full documentation can be found on <https://zepworks.com/deepdiff/5.8.2/>


# Deep Search
Expand Down Expand Up @@ -310,17 +313,17 @@ And you can pass all the same kwargs as DeepSearch to grep too:
{'matched_paths': {"root['somewhere']": 'around'}, 'matched_values': {"root['long']": 'somewhere'}}
```

> - Please take a look at the [DeepSearch docs](https://zepworks.com/deepdiff/5.8.1/dsearch.html)
> - The full documentation can be found on <https://zepworks.com/deepdiff/5.8.1/>
> - Please take a look at the [DeepSearch docs](https://zepworks.com/deepdiff/5.8.2/dsearch.html)
> - The full documentation can be found on <https://zepworks.com/deepdiff/5.8.2/>

# Deep Hash
(New in v4-0-0)

DeepHash is designed to give you hash of ANY python object based on its contents even if the object is not considered hashable!
DeepHash is supposed to be deterministic in order to make sure 2 objects that contain the same data, produce the same hash.

> - Please take a look at the [DeepHash docs](https://zepworks.com/deepdiff/5.8.1/deephash.html)
> - The full documentation can be found on <https://zepworks.com/deepdiff/5.8.1/>
> - Please take a look at the [DeepHash docs](https://zepworks.com/deepdiff/5.8.2/deephash.html)
> - The full documentation can be found on <https://zepworks.com/deepdiff/5.8.2/>

Let's say you have a dictionary object.

Expand Down Expand Up @@ -368,8 +371,8 @@ Which you can write as:
At first it might seem weird why DeepHash(obj)[obj] but remember that DeepHash(obj) is a dictionary of hashes of all other objects that obj contains too.


> - Please take a look at the [DeepHash docs](https://zepworks.com/deepdiff/5.8.1/deephash.html)
> - The full documentation can be found on <https://zepworks.com/deepdiff/5.8.1/>
> - Please take a look at the [DeepHash docs](https://zepworks.com/deepdiff/5.8.2/deephash.html)
> - The full documentation can be found on <https://zepworks.com/deepdiff/5.8.2/>


# Using DeepDiff in unit tests
Expand Down Expand Up @@ -449,11 +452,11 @@ Thank you!

How to cite this library (APA style):

Dehpour, S. (2022). DeepDiff (Version 5.8.1) [Software]. Available from https://github.com/seperman/deepdiff.
Dehpour, S. (2022). DeepDiff (Version 5.8.2) [Software]. Available from https://github.com/seperman/deepdiff.

How to cite this library (Chicago style):

Dehpour, Sep. 2022. DeepDiff (version 5.8.1).
Dehpour, Sep. 2022. DeepDiff (version 5.8.2).

# Authors

Expand Down
2 changes: 1 addition & 1 deletion deepdiff/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""This module offers the DeepDiff, DeepSearch, grep, Delta and DeepHash classes."""
# flake8: noqa
__version__ = '5.8.1'
__version__ = '5.8.2'
import logging

if __name__ == '__main__':
Expand Down
10 changes: 9 additions & 1 deletion deepdiff/delta.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,13 +263,21 @@ def _del_elem(self, parent, parent_to_obj_elem, parent_to_obj_action,
def _do_iterable_item_added(self):
iterable_item_added = self.diff.get('iterable_item_added', {})
iterable_item_moved = self.diff.get('iterable_item_moved')

# First we need to create a placeholder for moved items.
# This will then get replaced below after we go through added items.
# Without this items can get double added because moved store the new_value and does not need item_added replayed
Comment on lines +267 to +269
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment pretty much explains why we made this change. But it has to do with the delta modifying data in place. If we didn't make this change, the nested items would get added twice, simply because the "moved" item has the new value that includes any additions. So moved items do not need to be replayed. This just sets the place holders so all the indexes are right when we go into adding items. Then we will actually replace the None with the real value after we replay the added items.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense!

if iterable_item_moved:
added_dict = {v["new_path"]: v["value"] for k, v in iterable_item_moved.items()}
added_dict = {v["new_path"]: None for k, v in iterable_item_moved.items()}
iterable_item_added.update(added_dict)

if iterable_item_added:
self._do_item_added(iterable_item_added, insert=True)

if iterable_item_moved:
added_dict = {v["new_path"]: v["value"] for k, v in iterable_item_moved.items()}
self._do_item_added(added_dict, insert=False)

def _do_dictionary_item_added(self):
dictionary_item_added = self.diff.get('dictionary_item_added')
if dictionary_item_added:
Expand Down
2 changes: 1 addition & 1 deletion deepdiff/diff.py
Original file line number Diff line number Diff line change
Expand Up @@ -709,7 +709,7 @@ def _diff_iterable_in_order(self, level, parents_ids=frozenset(), _original_type
x,
y,
child_relationship_class=child_relationship_class,
child_relationship_param=i)
child_relationship_param=j)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change ensures that all indexes in the resulting diff are relative the t2 which is consistent with items added. If you are ignoring order I don't think this changes anything since i should be equal to j. But if using a compare function and not ignoring order we want the output indexes to be relative to t2 to make them able to be replayed. Only items removed are relative to t1 (since they have to be) and removed items always get replayed first in the delta objects.

self._diff(next_level, parents_ids_added)

def _diff_str(self, level):
Expand Down
4 changes: 2 additions & 2 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,9 @@
# built documents.
#
# The short X.Y version.
version = '5.8.1'
version = '5.8.2'
# The full version, including alpha/beta/rc tags.
release = '5.8.1'
release = '5.8.2'

load_dotenv(override=True)
DOC_VERSION = os.environ.get('DOC_VERSION', version)
Expand Down
7 changes: 6 additions & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
contain the root `toctree` directive.


DeepDiff 5.8.1 documentation!
DeepDiff 5.8.2 documentation!
=============================

*****************
Expand All @@ -31,6 +31,11 @@ The DeepDiff library includes the following modules:
What is New
***********

DeepDiff 5-8-2
--------------

Fixing dependency for Py3.6

New In DeepDiff 5-8-1
---------------------

Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
ordered-set>=4.1.0,<4.2.0
ordered-set>=4.0.2,<4.2.0
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 5.8.1
current_version = 5.8.2
commit = True
tag = True
tag_name = {new_version}
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
if os.environ.get('USER', '') == 'vagrant':
del os.link

version = '5.8.1'
version = '5.8.2'


def get_reqs(filename):
Expand Down
10 changes: 5 additions & 5 deletions tests/fixtures/compare_func_result1.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
"root['Cars'][3]['production']"
],
"values_changed": {
"root['Cars'][0]['dealers'][1]['quantity']": {
"root['Cars'][2]['dealers'][0]['quantity']": {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changes to the existing unittests are because the output is now relative to t2 instead of t1 as mentioned in the above comment.

"new_value": 50,
"old_value": 20
},
"root['Cars'][2]['model_numbers'][2]": {
"root['Cars'][1]['model_numbers'][2]": {
"new_value": 3,
"old_value": 4
},
Expand All @@ -20,20 +20,20 @@
}
},
"iterable_item_added": {
"root['Cars'][0]['dealers'][1]": {
"root['Cars'][2]['dealers'][1]": {
"id": 200,
"address": "200 Fake St",
"quantity": 10
},
"root['Cars'][2]['model_numbers'][3]": 4,
"root['Cars'][1]['model_numbers'][3]": 4,
"root['Cars'][0]": {
"id": "7",
"make": "Toyota",
"model": "8Runner"
}
},
"iterable_item_removed": {
"root['Cars'][0]['dealers'][0]": {
"root['Cars'][2]['dealers'][0]": {
"id": 103,
"address": "103 Fake St",
"quantity": 50
Expand Down
73 changes: 71 additions & 2 deletions tests/test_delta.py
Original file line number Diff line number Diff line change
Expand Up @@ -1478,7 +1478,7 @@ def test_compare_func_with_duplicates_removed(self):
t2 = [{'id': 3, 'val': 3}, {'id': 2, 'val': 2}, {'id': 1, 'val': 3}]
ddiff = DeepDiff(t1, t2, iterable_compare_func=self.compare_func, verbose_level=2)
expected = {
'values_changed': {"root[0]['val']": {'new_value': 3, 'old_value': 1}},
'values_changed': {"root[2]['val']": {'new_value': 3, 'old_value': 1}},
'iterable_item_removed': {'root[2]': {'id': 1, 'val': 3}},
'iterable_item_moved': {
'root[0]': {'new_path': 'root[2]', 'value': {'id': 1, 'val': 3}},
Expand All @@ -1495,7 +1495,7 @@ def test_compare_func_with_duplicates_added(self):
t2 = [{'id': 1, 'val': 1}, {'id': 2, 'val': 2}, {'id': 1, 'val': 3}, {'id': 3, 'val': 3}]
ddiff = DeepDiff(t1, t2, iterable_compare_func=self.compare_func, verbose_level=2)
expected = {
'values_changed': {"root[2]['val']": {'new_value': 1, 'old_value': 3}},
'values_changed': {"root[0]['val']": {'new_value': 1, 'old_value': 3}},
'iterable_item_added': {'root[2]': {'id': 1, 'val': 3}},
'iterable_item_moved': {
'root[2]': {'new_path': 'root[0]', 'value': {'id': 1, 'val': 1}},
Expand Down Expand Up @@ -1526,3 +1526,72 @@ def test_compare_func_path_specific(self):
delta = Delta(ddiff)
recreated_t2 = t1 + delta
assert t2 == recreated_t2

def test_compare_func_nested_changes(self):
t1 = {
"TestTable": [
{
"id": "022fb580-800e-11ea-a361-39b3dada34b5",
"name": "Max",
"NestedTable": [
{
"id": "022fb580-800e-11ea-a361-39b3dada34a6",
"NestedField": "Test Field"
}
]
},
{
"id": "022fb580-800e-11ea-a361-12354656532",
"name": "Bob",
"NestedTable": [
{
"id": "022fb580-800e-11ea-a361-39b3dada34c7",
"NestedField": "Test Field 2"
},
]
},
]
}
t2 = {"TestTable": [
{
"id": "022fb580-800e-11ea-a361-12354656532",
"name": "Bob (Changed Name)",
"NestedTable": [
{
"id": "022fb580-800e-11ea-a361-39b3dada34c7",
"NestedField": "Test Field 2 (Changed Nested Field)"
},
{
"id": "new id",
"NestedField": "Test Field 3"
},
{
"id": "newer id",
"NestedField": "Test Field 4"
},
]
},
{
"id": "adding_some_random_id",
"name": "New Name",
"NestedTable": [
{
"id": "random_nested_id_added",
"NestedField": "New Nested Field"
},
{
"id": "random_nested_id_added2",
"NestedField": "New Nested Field2"
},
{
"id": "random_nested_id_added3",
"NestedField": "New Nested Field43"
},
]
}
]}

ddiff = DeepDiff(t1, t2, iterable_compare_func=self.compare_func, verbose_level=2)
delta = Delta(ddiff)
recreated_t2 = t1 + delta
assert t2 == recreated_t2