Skip to content

Commit a3e97fd

Browse files
authoredOct 4, 2023
Merge pull request #423 from seperman/dev
6.6.0
2 parents 450634a + a928661 commit a3e97fd

21 files changed

+610
-50
lines changed
 

‎.github/workflows/main.yaml

+6
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ jobs:
1414
matrix:
1515
python-version: [3.7, 3.8, 3.9, "3.10", "3.11"]
1616
architecture: ["x64"]
17+
include:
18+
- python-version: "3.10"
19+
numpy-version: "2.0.dev"
1720
steps:
1821
- uses: actions/checkout@v2
1922
- name: Setup Python ${{ matrix.python-version }} on ${{ matrix.architecture }}
@@ -37,6 +40,9 @@ jobs:
3740
- name: Install dependencies
3841
if: matrix.python-version != 3.7
3942
run: pip install -r requirements-dev.txt
43+
- name: Install Numpy Dev
44+
if: ${{ matrix.numpy-version }}
45+
run: pip install -I --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple "numpy>=0.0.dev0"
4046
- name: Lint with flake8
4147
if: matrix.python-version == 3.11
4248
run: |

‎AUTHORS.md

+1
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,4 @@ Authors in order of the timeline of their contributions:
5858
- [kor4ik](https://github.com/kor4ik) for the bugfix for `include_paths` for nested dictionaries.
5959
- [martin-kokos](https://github.com/martin-kokos) for using tomli and tomli-w for dealing with tomli files.
6060
- [Alex Sauer-Budge](https://github.com/amsb) for the bugfix for `datetime.date`.
61+
- [William Jamieson](https://github.com/WilliamJamieson) for [NumPy 2.0 compatibility](https://github.com/seperman/deepdiff/pull/422)

‎README.md

+16-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# DeepDiff v 6.5.0
1+
# DeepDiff v 6.6.0
22

33
![Downloads](https://img.shields.io/pypi/dm/deepdiff.svg?style=flat)
44
![Python Versions](https://img.shields.io/pypi/pyversions/deepdiff.svg?style=flat)
@@ -17,12 +17,17 @@
1717

1818
Tested on Python 3.7+ and PyPy3.
1919

20-
- **[Documentation](https://zepworks.com/deepdiff/6.5.0/)**
20+
- **[Documentation](https://zepworks.com/deepdiff/6.6.0/)**
2121

2222
## What is new?
2323

2424
Please check the [ChangeLog](CHANGELOG.md) file for the detailed information.
2525

26+
DeepDiff 6-6-0
27+
28+
- [Serialize To Flat Dicts]()
29+
- [NumPy 2.0 compatibility](https://github.com/seperman/deepdiff/pull/422) by [William Jamieson](https://github.com/WilliamJamieson)
30+
2631
DeepDiff 6-5-0
2732

2833
- [parse_path](https://zepworks.com/deepdiff/current/faq.html#q-how-do-i-parse-deepdiff-result-paths)
@@ -62,6 +67,13 @@ Install optional packages:
6267

6368
<https://zepworks.com/deepdiff/current/>
6469

70+
### A message from Sep, the creator of DeepDiff
71+
72+
> 👋 Hi there,
73+
> If you find value in DeepDiff, you might be interested in another tool I've crafted: [Qluster](https://qluster.ai/solution). <br/>
74+
> As an engineer, I understand the frustration of wrestling with **unruly data** in pipelines. <br/>
75+
> I developed **Qluster** to empower product managers and ops teams to control and resolve data issues autonomously and **stop bugging the engineers**! 🛠️
76+
6577
# ChangeLog
6678

6779
Please take a look at the [CHANGELOG](CHANGELOG.md) file.
@@ -70,7 +82,6 @@ Please take a look at the [CHANGELOG](CHANGELOG.md) file.
7082

7183
:mega: **Please fill out our [fast 5-question survey](https://forms.gle/E6qXexcgjoKnSzjB8)** so that we can learn how & why you use DeepDiff, and what improvements we should make. Thank you! :dancers:
7284

73-
7485
# Contribute
7586

7687
1. Please make your PR against the dev branch
@@ -86,11 +97,11 @@ Thank you!
8697

8798
How to cite this library (APA style):
8899

89-
Dehpour, S. (2023). DeepDiff (Version 6.5.0) [Software]. Available from https://github.com/seperman/deepdiff.
100+
Dehpour, S. (2023). DeepDiff (Version 6.6.0) [Software]. Available from https://github.com/seperman/deepdiff.
90101

91102
How to cite this library (Chicago style):
92103

93-
Dehpour, Sep. 2023. DeepDiff (version 6.5.0).
104+
Dehpour, Sep. 2023. DeepDiff (version 6.6.0).
94105

95106
# Authors
96107

‎deepdiff/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""This module offers the DeepDiff, DeepSearch, grep, Delta and DeepHash classes."""
22
# flake8: noqa
3-
__version__ = '6.5.0'
3+
__version__ = '6.6.0'
44
import logging
55

66
if __name__ == '__main__':

‎deepdiff/delta.py

+151-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import logging
2+
from functools import partial
23
from collections.abc import Mapping
34
from copy import deepcopy
45
from ordered_set import OrderedSet
@@ -9,7 +10,7 @@
910
np_ndarray, np_array_factory, numpy_dtypes, get_doc,
1011
not_found, numpy_dtype_string_to_type, dict_,
1112
)
12-
from deepdiff.path import _path_to_elements, _get_nested_obj, _get_nested_obj_and_force, GET, GETATTR
13+
from deepdiff.path import _path_to_elements, _get_nested_obj, _get_nested_obj_and_force, GET, GETATTR, parse_path
1314
from deepdiff.anyset import AnySet
1415

1516

@@ -591,6 +592,155 @@ def dumps(self):
591592
def to_dict(self):
592593
return dict(self.diff)
593594

595+
@staticmethod
596+
def _get_flat_row(action, info, _parse_path, keys_and_funcs):
597+
for path, details in info.items():
598+
row = {'path': _parse_path(path), 'action': action}
599+
for key, new_key, func in keys_and_funcs:
600+
if key in details:
601+
if func:
602+
row[new_key] = func(details[key])
603+
else:
604+
row[new_key] = details[key]
605+
yield row
606+
607+
def to_flat_dicts(self, include_action_in_path=False, report_type_changes=True):
608+
"""
609+
Returns a flat list of actions that is easily machine readable.
610+
611+
For example:
612+
{'iterable_item_added': {'root[3]': 5, 'root[2]': 3}}
613+
614+
Becomes:
615+
[
616+
{'path': [3], 'value': 5, 'action': 'iterable_item_added'},
617+
{'path': [2], 'value': 3, 'action': 'iterable_item_added'},
618+
]
619+
620+
621+
**Parameters**
622+
623+
include_action_in_path : Boolean, default=False
624+
When False, we translate DeepDiff's paths like root[3].attribute1 into a [3, 'attribute1'].
625+
When True, we include the action to retrieve the item in the path: [(3, 'GET'), ('attribute1', 'GETATTR')]
626+
627+
report_type_changes : Boolean, default=True
628+
If False, we don't report the type change. Instead we report the value change.
629+
630+
Example:
631+
t1 = {"a": None}
632+
t2 = {"a": 1}
633+
634+
dump = Delta(DeepDiff(t1, t2)).dumps()
635+
delta = Delta(dump)
636+
assert t2 == delta + t1
637+
638+
flat_result = delta.to_flat_dicts()
639+
flat_expected = [{'path': ['a'], 'action': 'type_changes', 'value': 1, 'new_type': int, 'old_type': type(None)}]
640+
assert flat_expected == flat_result
641+
642+
flat_result2 = delta.to_flat_dicts(report_type_changes=False)
643+
flat_expected2 = [{'path': ['a'], 'action': 'values_changed', 'value': 1}]
644+
645+
**List of actions**
646+
647+
Here are the list of actions that the flat dictionary can return.
648+
iterable_item_added
649+
iterable_item_removed
650+
values_changed
651+
type_changes
652+
set_item_added
653+
set_item_removed
654+
dictionary_item_added
655+
dictionary_item_removed
656+
attribute_added
657+
attribute_removed
658+
"""
659+
result = []
660+
if include_action_in_path:
661+
_parse_path = partial(parse_path, include_actions=True)
662+
else:
663+
_parse_path = parse_path
664+
if report_type_changes:
665+
keys_and_funcs = [
666+
('value', 'value', None),
667+
('new_value', 'value', None),
668+
('old_value', 'old_value', None),
669+
('new_type', 'type', None),
670+
('old_type', 'old_type', None),
671+
('new_path', 'new_path', _parse_path),
672+
]
673+
action_mapping = {}
674+
else:
675+
keys_and_funcs = [
676+
('value', 'value', None),
677+
('new_value', 'value', None),
678+
('old_value', 'old_value', None),
679+
('new_path', 'new_path', _parse_path),
680+
]
681+
action_mapping = {'type_changes': 'values_changed'}
682+
683+
FLATTENING_NEW_ACTION_MAP = {
684+
'iterable_items_added_at_indexes': 'iterable_item_added',
685+
'iterable_items_removed_at_indexes': 'iterable_item_removed',
686+
}
687+
for action, info in self.diff.items():
688+
if action in FLATTENING_NEW_ACTION_MAP:
689+
new_action = FLATTENING_NEW_ACTION_MAP[action]
690+
for path, index_to_value in info.items():
691+
path = _parse_path(path)
692+
for index, value in index_to_value.items():
693+
path2 = path.copy()
694+
if include_action_in_path:
695+
path2.append((index, 'GET'))
696+
else:
697+
path2.append(index)
698+
result.append(
699+
{'path': path2, 'value': value, 'action': new_action}
700+
)
701+
elif action in {'set_item_added', 'set_item_removed'}:
702+
for path, values in info.items():
703+
path = _parse_path(path)
704+
for value in values:
705+
result.append(
706+
{'path': path, 'value': value, 'action': action}
707+
)
708+
elif action == 'dictionary_item_added':
709+
for path, value in info.items():
710+
path = _parse_path(path)
711+
if isinstance(value, dict) and len(value) == 1:
712+
new_key = next(iter(value))
713+
path.append(new_key)
714+
value = value[new_key]
715+
elif isinstance(value, (list, tuple)) and len(value) == 1:
716+
value = value[0]
717+
path.append(0)
718+
action = 'iterable_item_added'
719+
elif isinstance(value, set) and len(value) == 1:
720+
value = value.pop()
721+
action = 'set_item_added'
722+
result.append(
723+
{'path': path, 'value': value, 'action': action}
724+
)
725+
elif action in {
726+
'dictionary_item_removed', 'iterable_item_added',
727+
'iterable_item_removed', 'attribute_removed', 'attribute_added'
728+
}:
729+
for path, value in info.items():
730+
path = _parse_path(path)
731+
result.append(
732+
{'path': path, 'value': value, 'action': action}
733+
)
734+
else:
735+
for row in self._get_flat_row(
736+
action=action_mapping.get(action, action),
737+
info=info,
738+
_parse_path=_parse_path,
739+
keys_and_funcs=keys_and_funcs,
740+
):
741+
result.append(row)
742+
return result
743+
594744

595745
if __name__ == "__main__": # pragma: no cover
596746
import doctest

‎deepdiff/diff.py

+7-5
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ def __init__(self,
181181
self.custom_operators = custom_operators or []
182182
self.ignore_order = ignore_order
183183

184-
self.ignore_order_func = ignore_order_func or (lambda *_args, **_kwargs: ignore_order)
184+
self.ignore_order_func = ignore_order_func
185185

186186
ignore_type_in_groups = ignore_type_in_groups or []
187187
if numbers == ignore_type_in_groups or numbers in ignore_type_in_groups:
@@ -649,7 +649,7 @@ def _iterables_subscriptable(t1, t2):
649649

650650
def _diff_iterable(self, level, parents_ids=frozenset(), _original_type=None, local_tree=None):
651651
"""Difference of iterables"""
652-
if self.ignore_order_func(level):
652+
if (self.ignore_order_func and self.ignore_order_func(level)) or self.ignore_order:
653653
self._diff_iterable_with_deephash(level, parents_ids, _original_type=_original_type, local_tree=local_tree)
654654
else:
655655
self._diff_iterable_in_order(level, parents_ids, _original_type=_original_type, local_tree=local_tree)
@@ -1103,7 +1103,9 @@ def _get_most_in_common_pairs_in_iterables(
11031103
# And the objects with the same distances are grouped together in an ordered set.
11041104
# It also includes a "max" key that is just the value of the biggest current distance in the
11051105
# most_in_common_pairs dictionary.
1106-
most_in_common_pairs = defaultdict(lambda: defaultdict(OrderedSetPlus))
1106+
def defaultdict_orderedset():
1107+
return defaultdict(OrderedSetPlus)
1108+
most_in_common_pairs = defaultdict(defaultdict_orderedset)
11071109
pairs = dict_()
11081110

11091111
pre_calced_distances = None
@@ -1390,7 +1392,7 @@ def _diff_numpy_array(self, level, parents_ids=frozenset(), local_tree=None):
13901392
# which means numpy module needs to be available. So np can't be None.
13911393
raise ImportError(CANT_FIND_NUMPY_MSG) # pragma: no cover
13921394

1393-
if not self.ignore_order_func(level):
1395+
if (self.ignore_order_func and not self.ignore_order_func(level)) or not self.ignore_order:
13941396
# fast checks
13951397
if self.significant_digits is None:
13961398
if np.array_equal(level.t1, level.t2, equal_nan=self.ignore_nan_inequality):
@@ -1416,7 +1418,7 @@ def _diff_numpy_array(self, level, parents_ids=frozenset(), local_tree=None):
14161418
dimensions = len(shape)
14171419
if dimensions == 1:
14181420
self._diff_iterable(level, parents_ids, _original_type=_original_type, local_tree=local_tree)
1419-
elif self.ignore_order_func(level):
1421+
elif (self.ignore_order_func and self.ignore_order_func(level)) or self.ignore_order:
14201422
# arrays are converted to python lists so that certain features of DeepDiff can apply on them easier.
14211423
# They will be converted back to Numpy at their final dimension.
14221424
level.t1 = level.t1.tolist()

‎deepdiff/helper.py

+8-8
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,11 @@ class pydantic_base_model_type:
4242
np_uintp = np_type # pragma: no cover.
4343
np_float32 = np_type # pragma: no cover.
4444
np_float64 = np_type # pragma: no cover.
45-
np_float_ = np_type # pragma: no cover.
45+
np_double = np_type # pragma: no cover.
4646
np_floating = np_type # pragma: no cover.
4747
np_complex64 = np_type # pragma: no cover.
4848
np_complex128 = np_type # pragma: no cover.
49-
np_complex_ = np_type # pragma: no cover.
49+
np_cdouble = np_type # pragma: no cover.
5050
np_complexfloating = np_type # pragma: no cover.
5151
else:
5252
np_array_factory = np.array
@@ -64,21 +64,21 @@ class pydantic_base_model_type:
6464
np_uintp = np.uintp
6565
np_float32 = np.float32
6666
np_float64 = np.float64
67-
np_float_ = np.float_
67+
np_double = np.double # np.float_ is an alias for np.double and is being removed by NumPy 2.0
6868
np_floating = np.floating
6969
np_complex64 = np.complex64
7070
np_complex128 = np.complex128
71-
np_complex_ = np.complex_
71+
np_cdouble = np.cdouble # np.complex_ is an alias for np.cdouble and is being removed by NumPy 2.0
7272
np_complexfloating = np.complexfloating
7373

7474
numpy_numbers = (
7575
np_int8, np_int16, np_int32, np_int64, np_uint8,
7676
np_uint16, np_uint32, np_uint64, np_intp, np_uintp,
77-
np_float32, np_float64, np_float_, np_floating, np_complex64,
78-
np_complex128, np_complex_,)
77+
np_float32, np_float64, np_double, np_floating, np_complex64,
78+
np_complex128, np_cdouble,)
7979

8080
numpy_complex_numbers = (
81-
np_complexfloating, np_complex64, np_complex128, np_complex_,
81+
np_complexfloating, np_complex64, np_complex128, np_cdouble,
8282
)
8383

8484
numpy_dtypes = set(numpy_numbers)
@@ -655,7 +655,7 @@ def diff_numpy_array(A, B):
655655
By Divakar
656656
https://stackoverflow.com/a/52417967/1497443
657657
"""
658-
return A[~np.in1d(A, B)]
658+
return A[~np.isin(A, B)]
659659

660660

661661
PYTHON_TYPE_TO_NUMPY_TYPE = {

‎deepdiff/serialization.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@
4444
from copy import deepcopy
4545
from functools import partial
4646
from collections.abc import Mapping
47-
from deepdiff.helper import (strings, get_type, TEXT_VIEW)
47+
from deepdiff.helper import (
48+
strings, get_type, TEXT_VIEW, np_float32, np_float64, np_int32, np_int64
49+
)
4850
from deepdiff.model import DeltaResult
4951

5052
logger = logging.getLogger(__name__)
@@ -539,6 +541,10 @@ def _serialize_decimal(value):
539541
bytes: lambda x: x.decode('utf-8'),
540542
datetime.datetime: lambda x: x.isoformat(),
541543
uuid.UUID: lambda x: str(x),
544+
np_float32: float,
545+
np_float64: float,
546+
np_int32: int,
547+
np_int64: int
542548
}
543549

544550
if PydanticBaseModel:

‎docs/authors.rst

+1
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ Authors in order of the timeline of their contributions:
8383
and tomli-w for dealing with tomli files.
8484
- `Alex Sauer-Budge <https://github.com/amsb>`__ for the bugfix for
8585
``datetime.date``.
86+
- `William Jamieson <https://github.com/WilliamJamieson>`__ for `NumPy 2.0 compatibility <https://github.com/seperman/deepdiff/pull/422>`__
8687

8788
.. _Sep Dehpour (Seperman): http://www.zepworks.com
8889
.. _Victor Hahn Castell: http://hahncastell.de

0 commit comments

Comments
 (0)
Please sign in to comment.