Skip to content

Commit 80102a6

Browse files
authored
Merge pull request #360 from seperman/dev
6.2.2
2 parents 093949f + 2a2bd73 commit 80102a6

File tree

13 files changed

+141
-48
lines changed

13 files changed

+141
-48
lines changed

.github/workflows/main.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
runs-on: ubuntu-latest
1313
strategy:
1414
matrix:
15-
python-version: [3.7, 3.8, 3.9, "3.10"]
15+
python-version: [3.7, 3.8, 3.9, "3.10", "3.11"]
1616
architecture: ["x64"]
1717
steps:
1818
- uses: actions/checkout@v2

AUTHORS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,4 @@ Authors in order of the timeline of their contributions:
4747
- Mikhail Khviyuzov [mskhviyu](https://github.com/mskhviyu) for Exclude obj callback strict.
4848
- [dtorres-sf](https://github.com/dtorres-sf) for the fix for diffing using iterable_compare_func with nested objects.
4949
- [Enric Pou](https://github.com/epou) for bug fix of ValueError when using Decimal 0.x
50+
- [Uwe Fladrich](https://github.com/uwefladrich) for fixing bug when diff'ing non-sequence iterables

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# DeepDiff Change log
22

3+
- v6-2-2
4+
- Enum test fix for python 3.11
5+
- Adding support for dateutils rrules
36
- v6-2-1
47
- Removed the print statements.
58
- v6-2-0

deepdiff/diff.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from enum import Enum
1212
from copy import deepcopy
1313
from math import isclose as is_close
14-
from collections.abc import Mapping, Iterable
14+
from collections.abc import Mapping, Iterable, Sequence
1515
from collections import defaultdict
1616
from itertools import zip_longest
1717
from ordered_set import OrderedSet
@@ -23,7 +23,7 @@
2323
number_to_string, datetime_normalize, KEY_TO_VAL_STR, booleans,
2424
np_ndarray, get_numpy_ndarray_rows, OrderedSetPlus, RepeatedTimer,
2525
TEXT_VIEW, TREE_VIEW, DELTA_VIEW, detailed__dict__, add_root_to_paths,
26-
np, get_truncate_datetime, dict_, CannotCompare, ENUM_IGNORE_KEYS)
26+
np, get_truncate_datetime, dict_, CannotCompare, ENUM_INCLUDE_KEYS)
2727
from deepdiff.serialization import SerializationMixin
2828
from deepdiff.distance import DistanceMixin
2929
from deepdiff.model import (
@@ -395,8 +395,8 @@ def unmangle(attribute):
395395
return {i: getattr(object, unmangle(i)) for i in all_slots}
396396

397397
def _diff_enum(self, level, parents_ids=frozenset(), local_tree=None):
398-
t1 = detailed__dict__(level.t1, ignore_private_variables=self.ignore_private_variables, ignore_keys=ENUM_IGNORE_KEYS)
399-
t2 = detailed__dict__(level.t2, ignore_private_variables=self.ignore_private_variables, ignore_keys=ENUM_IGNORE_KEYS)
398+
t1 = detailed__dict__(level.t1, include_keys=ENUM_INCLUDE_KEYS)
399+
t2 = detailed__dict__(level.t2, include_keys=ENUM_INCLUDE_KEYS)
400400

401401
self._diff_dict(
402402
level,
@@ -727,7 +727,13 @@ def _diff_iterable_in_order(self, level, parents_ids=frozenset(), _original_type
727727
else:
728728
child_relationship_class = NonSubscriptableIterableRelationship
729729

730-
if self._all_values_basic_hashable(level.t1) and self._all_values_basic_hashable(level.t2) and self.iterable_compare_func is None:
730+
if (
731+
isinstance(level.t1, Sequence)
732+
and isinstance(level.t2, Sequence)
733+
and self._all_values_basic_hashable(level.t1)
734+
and self._all_values_basic_hashable(level.t2)
735+
and self.iterable_compare_func is None
736+
):
731737
local_tree_pass = TreeResult()
732738
self._diff_ordered_iterable_by_difflib(
733739
level,

deepdiff/helper.py

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ class np_type:
133133
TEXT_VIEW = 'text'
134134
DELTA_VIEW = '_delta'
135135

136-
ENUM_IGNORE_KEYS = frozenset(['_name_', '_value_', '_sort_order_'])
136+
ENUM_INCLUDE_KEYS = ['__objclass__', 'name', 'value']
137137

138138

139139
def short_repr(item, max_length=15):
@@ -630,26 +630,37 @@ def get_homogeneous_numpy_compatible_type_of_seq(seq):
630630
return False
631631

632632

633-
def detailed__dict__(obj, ignore_private_variables=True, ignore_keys=frozenset()):
633+
def detailed__dict__(obj, ignore_private_variables=True, ignore_keys=frozenset(), include_keys=None):
634634
"""
635635
Get the detailed dictionary of an object.
636636
637637
This is used so we retrieve object properties too.
638638
"""
639-
result = obj.__dict__.copy() # A shallow copy
640-
private_var_prefix = f"_{obj.__class__.__name__}__" # The semi private variables in Python get this prefix
641-
for key in ignore_keys:
642-
if key in result or (
643-
ignore_private_variables and key.startswith('__') and not key.startswith(private_var_prefix)
644-
):
645-
del result[key]
646-
for key in dir(obj):
647-
if key not in result and key not in ignore_keys and (
648-
not ignore_private_variables or (
649-
ignore_private_variables and not key.startswith('__') and not key.startswith(private_var_prefix)
650-
)
651-
):
652-
value = getattr(obj, key)
653-
if not callable(value):
654-
result[key] = value
639+
if include_keys:
640+
result = {}
641+
for key in include_keys:
642+
try:
643+
value = getattr(obj, key)
644+
except Exception:
645+
pass
646+
else:
647+
if not callable(value) or key == '__objclass__': # We don't want to compare functions, however for backward compatibility, __objclass__ needs to be reported.
648+
result[key] = value
649+
else:
650+
result = obj.__dict__.copy() # A shallow copy
651+
private_var_prefix = f"_{obj.__class__.__name__}__" # The semi private variables in Python get this prefix
652+
for key in ignore_keys:
653+
if key in result or (
654+
ignore_private_variables and key.startswith('__') and not key.startswith(private_var_prefix)
655+
):
656+
del result[key]
657+
for key in dir(obj):
658+
if key not in result and key not in ignore_keys and (
659+
not ignore_private_variables or (
660+
ignore_private_variables and not key.startswith('__') and not key.startswith(private_var_prefix)
661+
)
662+
):
663+
value = getattr(obj, key)
664+
if not callable(value):
665+
result[key] = value
655666
return result

deepdiff/serialization.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@
2121
toml = None # pragma: no cover.
2222
try:
2323
import clevercsv
24+
csv = None
2425
except ImportError: # pragma: no cover.
26+
import csv
2527
clevercsv = None # pragma: no cover.
2628
from copy import deepcopy
2729
from functools import partial
@@ -424,9 +426,11 @@ def load_path_content(path, file_type=None):
424426
content = the_file.read()
425427
content = pickle_load(content)
426428
elif file_type in {'csv', 'tsv'}:
427-
if clevercsv is None: # pragma: no cover.
428-
raise ImportError('CleverCSV needs to be installed.') # pragma: no cover.
429-
content = clevercsv.read_dicts(path)
429+
if clevercsv: # pragma: no cover.
430+
content = clevercsv.read_dicts(path)
431+
else:
432+
with open(path, 'r') as the_file:
433+
content = list(csv.DictReader(the_file))
430434
logger.info(f"NOTE: CSV content was empty in {path}")
431435

432436
# Everything in csv is string but we try to automatically convert any numbers we find
@@ -485,11 +489,13 @@ def _save_content(content, path, file_type, keep_backup=True):
485489
with open(path, 'wb') as the_file:
486490
content = pickle_dump(content, file_obj=the_file)
487491
elif file_type in {'csv', 'tsv'}:
488-
if clevercsv is None: # pragma: no cover.
489-
raise ImportError('CleverCSV needs to be installed.') # pragma: no cover.
492+
if clevercsv: # pragma: no cover.
493+
dict_writer = clevercsv.DictWriter
494+
else:
495+
dict_writer = csv.DictWriter
490496
with open(path, 'w', newline='') as csvfile:
491497
fieldnames = list(content[0].keys())
492-
writer = clevercsv.DictWriter(csvfile, fieldnames=fieldnames)
498+
writer = dict_writer(csvfile, fieldnames=fieldnames)
493499
writer.writeheader()
494500
writer.writerows(content)
495501
else:

docs/authors.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ Authors in order of the timeline of their contributions:
6060
- `dtorres-sf`_ for the fix for diffing using iterable_compare_func with nested objects.
6161
- `Enric Pou <https://github.com/epou>`__ for bug fix of ValueError
6262
when using Decimal 0.x
63+
- `Uwe Fladrich <https://github.com/uwefladrich>`__ for fixing bug when diff'ing non-sequence iterables
64+
6365

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

docs/changelog.rst

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,39 @@ Changelog
55

66
DeepDiff Changelog
77

8+
- v6-2-2
9+
10+
- Enum test fix for python 3.11
11+
- Adding support for dateutils rrules
12+
13+
- v6-2-1
14+
15+
- Removed the print statements.
16+
17+
- v6-2-0
18+
19+
- Major improvement in the diff report for lists when items are all
20+
hashable and the order of items is important.
21+
22+
- v6-1-0
23+
24+
- DeepDiff.affected_paths can be used to get the list of all paths
25+
where a change, addition, or deletion was reported for.
26+
- DeepDiff.affected_root_keys can be used to get the list of all
27+
paths where a change, addition, or deletion was reported for.
28+
- Bugfix: ValueError when using Decimal 0.x #339 by `Enric
29+
Pou <https://github.com/epou>`__
30+
- Serialization of UUID
31+
32+
- v6-0-0
33+
34+
- `Exclude obj callback
35+
strict <https://github.com/seperman/deepdiff/pull/320/files>`__
36+
parameter is added to DeepDiff by Mikhail Khviyuzov
37+
`mskhviyu <https://github.com/mskhviyu>`__.
38+
- A fix for diffing using ``iterable_compare_func`` with nested
39+
objects by `dtorres-sf <https://github.com/dtorres-sf>`__ who
40+
originally contributed this feature.
841
- v5-7-0:
942

1043
- https://github.com/seperman/deepdiff/pull/284 Bug-Fix: TypeError

requirements-cli.txt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,2 @@
11
click==8.1.3
22
pyyaml==6.0
3-
toml==0.10.2
4-
clevercsv==0.7.4

requirements-dev-3.7.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ ipdb==0.13.9
77
numpy==1.21.6
88
pytest==7.1.2
99
python-dotenv==0.20.0
10+
python-dateutil==2.8.2

requirements-dev.txt

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
1-
wheel==0.37.0
1+
wheel==0.38.4
22
-r requirements.txt
33
-r requirements-cli.txt
44
bump2version==1.0.1
5-
jsonpickle==2.2.0
6-
coverage==6.4.3
5+
jsonpickle==3.0.0
6+
coverage==6.5.0
77
ipdb==0.13.9
8-
numpy==1.23.1
9-
pytest==7.1.2
10-
pytest-cov==3.0.0
11-
python-dotenv==0.20.0
12-
watchdog==2.1.9
13-
Sphinx==5.1.1
14-
sphinx-sitemap==2.2.0
15-
flake8==5.0.4
8+
numpy==1.23.5
9+
pytest==7.2.0
10+
pytest-cov==4.0.0
11+
python-dotenv==0.21.0
12+
watchdog==2.2.0
13+
Sphinx==5.3.0
14+
sphinx-sitemap==2.2.1
15+
flake8==6.0.0
16+
python-dateutil==2.8.2

tests/test_diff_text.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1713,3 +1713,34 @@ def __thing2(self):
17131713
}
17141714

17151715
assert expected2 == diff2
1716+
1717+
def test_diffs_rrules(self):
1718+
1719+
from dateutil.rrule import MONTHLY, rrule
1720+
1721+
d = DeepDiff(
1722+
rrule(freq=MONTHLY, count=5, dtstart=datetime.datetime(2014, 12, 31)),
1723+
rrule(freq=MONTHLY, count=4, dtstart=datetime.datetime(2011, 12, 31)),
1724+
)
1725+
1726+
assert d == {
1727+
"values_changed": {
1728+
"root[0]": {
1729+
"new_value": datetime.datetime(2011, 12, 31, 0, 0),
1730+
"old_value": datetime.datetime(2014, 12, 31, 0, 0),
1731+
},
1732+
"root[1]": {
1733+
"new_value": datetime.datetime(2012, 1, 31, 0, 0),
1734+
"old_value": datetime.datetime(2015, 1, 31, 0, 0),
1735+
},
1736+
"root[2]": {
1737+
"new_value": datetime.datetime(2012, 3, 31, 0, 0),
1738+
"old_value": datetime.datetime(2015, 3, 31, 0, 0),
1739+
},
1740+
"root[3]": {
1741+
"new_value": datetime.datetime(2012, 5, 31, 0, 0),
1742+
"old_value": datetime.datetime(2015, 5, 31, 0, 0),
1743+
},
1744+
},
1745+
"iterable_item_removed": {"root[4]": datetime.datetime(2015, 7, 31, 0, 0)},
1746+
}

tests/test_helper.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
cartesian_product_of_shape, literal_eval_extended,
1010
not_found, OrderedSetPlus, diff_numpy_array, cartesian_product_numpy,
1111
get_truncate_datetime, datetime_normalize,
12-
detailed__dict__, ENUM_IGNORE_KEYS, add_root_to_paths,
12+
detailed__dict__, ENUM_INCLUDE_KEYS, add_root_to_paths,
1313
)
1414

1515

@@ -278,15 +278,15 @@ def test_datetime_normalize(self, truncate_datetime, obj, expected):
278278
result = datetime_normalize(truncate_datetime, obj)
279279
assert expected == result
280280

281-
@pytest.mark.parametrize('obj, ignore_keys, expected', [
281+
@pytest.mark.parametrize('obj, include_keys, expected', [
282282
(
283283
MyEnum.A,
284-
ENUM_IGNORE_KEYS,
284+
ENUM_INCLUDE_KEYS,
285285
{'__objclass__': MyEnum, 'name': 'A', 'value': 1},
286286
)
287287
])
288-
def test_detailed__dict__(self, obj, ignore_keys, expected):
289-
result = detailed__dict__(obj, ignore_private_variables=True, ignore_keys=ignore_keys)
288+
def test_detailed__dict__(self, obj, include_keys, expected):
289+
result = detailed__dict__(obj, ignore_private_variables=True, include_keys=include_keys)
290290
assert expected == result, f"test_detailed__dict__ failed for {obj}"
291291

292292
@pytest.mark.parametrize('test_num, value, expected', [

0 commit comments

Comments
 (0)