Skip to content
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

8.1.0 #483

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open

8.1.0 #483

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
2 changes: 2 additions & 0 deletions AUTHORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,5 @@ Authors in order of the timeline of their contributions:
- [sf-tcalhoun](https://github.com/sf-tcalhoun) for fixing "Instantiating a Delta with a flat_dict_list unexpectedly mutates the flat_dict_list"
- [dtorres-sf](https://github.com/dtorres-sf) for fixing iterable moved items when iterable_compare_func is used.
- [Florian Finkernagel](https://github.com/TyberiusPrime) for pandas and polars support.
- Mathis Chenuet [artemisart](https://github.com/artemisart) for fixing slots classes comparison.
- [Aaron D. Marasco](https://github.com/AaronDMarasco) added `prefix` option to `pretty()`
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

- v8-0-1
- Bugfix. Numpy should be optional.
- Added `prefix` option to `pretty()`
Comment on lines 4 to +6

Choose a reason for hiding this comment

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

Hello, is the release still planned? Also got a small patch for the changelog:

Suggested change
- v8-0-1
- Bugfix. Numpy should be optional.
- Added `prefix` option to `pretty()`
- v8-1-0
- Fixed slots-classes comparison.
- Added `prefix` option to `pretty()`
- Relax `orderly-set` dependency.
- Fixes hashing of numpy boolean values.
- v8-0-1
- Bugfix. Numpy should be optional.


- v8-0-0

Expand Down
7 changes: 6 additions & 1 deletion deepdiff/deephash.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@
import polars
except ImportError:
polars = False
try:
import numpy as np
booleanTypes = (bool, np.bool_)
except ImportError:
booleanTypes = bool

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -492,7 +497,7 @@ def _hash(self, obj, parent, parents_ids=EMPTY_FROZENSET):
"""The main hash method"""
counts = 1

if isinstance(obj, bool):
if isinstance(obj, booleanTypes):
obj = self._prep_bool(obj)
result = None
elif self.use_enum_value and isinstance(obj, Enum):
Expand Down
36 changes: 31 additions & 5 deletions deepdiff/diff.py
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,7 @@ def unmangle(attribute):
else:
all_slots.extend(slots)

return {i: getattr(object, unmangle(i)) for i in all_slots}
return {i: getattr(object, key) for i in all_slots if hasattr(object, key := unmangle(i))}

def _diff_enum(self, level, parents_ids=frozenset(), local_tree=None):
t1 = detailed__dict__(level.t1, include_keys=ENUM_INCLUDE_KEYS)
Expand Down Expand Up @@ -510,6 +510,32 @@ def _skip_this(self, level):

return skip

def _skip_this_key(self, level, key):
# if include_paths is not set, than treet every path as included
if self.include_paths is None:
return False
if "{}['{}']".format(level.path(), key) in self.include_paths:
return False
if level.path() in self.include_paths:
# matches e.g. level+key root['foo']['bar']['veg'] include_paths ["root['foo']['bar']"]
return False
for prefix in self.include_paths:
if "{}['{}']".format(level.path(), key) in prefix:
# matches as long the prefix is longer than this object key
# eg.: level+key root['foo']['bar'] matches prefix root['foo']['bar'] from include paths
# level+key root['foo'] matches prefix root['foo']['bar'] from include_paths
# level+key root['foo']['bar'] DOES NOT match root['foo'] from include_paths This needs to be handled afterwards
return False
# check if a higher level is included as a whole (=without any sublevels specified)
# matches e.g. level+key root['foo']['bar']['veg'] include_paths ["root['foo']"]
# but does not match, if it is level+key root['foo']['bar']['veg'] include_paths ["root['foo']['bar']['fruits']"]
up = level.up
while up is not None:
if up.path() in self.include_paths:
return False
up = up.up
return True

def _get_clean_to_keys_mapping(self, keys, level):
"""
Get a dictionary of cleaned value of keys to the keys themselves.
Expand Down Expand Up @@ -570,11 +596,11 @@ def _diff_dict(
rel_class = DictRelationship

if self.ignore_private_variables:
t1_keys = SetOrdered([key for key in t1 if not(isinstance(key, str) and key.startswith('__'))])
t2_keys = SetOrdered([key for key in t2 if not(isinstance(key, str) and key.startswith('__'))])
t1_keys = SetOrdered([key for key in t1 if not(isinstance(key, str) and key.startswith('__')) and not self._skip_this_key(level, key)])
t2_keys = SetOrdered([key for key in t2 if not(isinstance(key, str) and key.startswith('__')) and not self._skip_this_key(level, key)])
else:
t1_keys = SetOrdered(t1.keys())
t2_keys = SetOrdered(t2.keys())
t1_keys = SetOrdered([key for key in t1 if not self._skip_this_key(level, key)])
t2_keys = SetOrdered([key for key in t2 if not self._skip_this_key(level, key)])
if self.ignore_string_type_changes or self.ignore_numeric_type_changes or self.ignore_string_case:
t1_clean_to_keys = self._get_clean_to_keys_mapping(keys=t1_keys, level=level)
t2_clean_to_keys = self._get_clean_to_keys_mapping(keys=t2_keys, level=level)
Expand Down
8 changes: 6 additions & 2 deletions deepdiff/serialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ def _to_delta_dict(self, directed=True, report_repetition_required=True, always_

return deepcopy(dict(result))

def pretty(self):
def pretty(self, prefix=None):
"""
The pretty human readable string output for the diff object
regardless of what view was used to generate the diff.
Expand All @@ -310,12 +310,16 @@ def pretty(self):
Item root[1] removed from set.
"""
result = []
if prefix is None:
prefix = ''
keys = sorted(self.tree.keys()) # sorting keys to guarantee constant order across python versions.
for key in keys:
for item_key in self.tree[key]:
result += [pretty_print_diff(item_key)]

return '\n'.join(result)
if callable(prefix):
return "\n".join(f"{prefix(diff=self)}{r}" for r in result)
return "\n".join(f"{prefix}{r}" for r in result)


class _RestrictedUnpickler(pickle.Unpickler):
Expand Down
23 changes: 23 additions & 0 deletions docs/view.rst
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,29 @@ Use the pretty method for human readable output. This is regardless of what view
Item root[4] removed from set.
Item root[1] removed from set.

The pretty method has an optional parameter ``prefix`` that allows a prefix string before every output line (*e.g.* for logging):
>>> from deepdiff import DeepDiff
>>> t1={1,2,4}
>>> t2={2,3}
>>> print(DeepDiff(t1, t2).pretty(prefix='Diff: '))
Diff: Item root[3] added to set.
Diff: Item root[4] removed from set.
Diff: Item root[1] removed from set.

The ``prefix`` may also be a callable function. This function must accept ``**kwargs``; as of this version, the only parameter is ``diff`` but the signature allows for future expansion.
The ``diff`` given will be the ``DeepDiff`` that ``pretty`` was called on; this allows interesting capabilities such as:
>>> from deepdiff import DeepDiff
>>> t1={1,2,4}
>>> t2={2,3}
>>> def callback(**kwargs):
... """Helper function using a hidden variable on the diff that tracks which count prints next"""
... kwargs['diff']._diff_count = 1 + getattr(kwargs['diff'], '_diff_count', 0)
... return f"Diff #{kwargs['diff']._diff_count}: "
...
>>> print(DeepDiff(t1, t2).pretty(prefix=callback))
Diff #1: Item root[3] added to set.
Diff #2: Item root[4] removed from set.
Diff #3: Item root[1] removed from set.


Text view vs. Tree view vs. vs. pretty() method
Expand Down
2 changes: 1 addition & 1 deletion requirements-cli.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
click==8.1.7
pyyaml==6.0.1
pyyaml==6.0.2
30 changes: 15 additions & 15 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
-r requirements.txt
-r requirements-cli.txt
bump2version==1.0.1
jsonpickle==3.2.1
coverage==7.5.3
jsonpickle==4.0.0
coverage==7.6.4
ipdb==0.13.13
numpy==2.0.0
pytest==8.2.2
pytest-cov==5.0.0
numpy==2.1.3
pytest==8.3.3
pytest-cov==6.0.0
python-dotenv==1.0.1
Sphinx==6.2.1 # We use the html style that is not supported in Sphinx 7 anymore.
sphinx-sitemap==2.6.0
sphinxemoji==0.2.0
flake8==7.1.0
sphinxemoji==0.3.1
flake8==7.1.1
python-dateutil==2.9.0.post0
orjson==3.10.5
wheel==0.43.0
tomli==2.0.1
tomli-w==1.0.0
pydantic==2.7.4
pytest-benchmark==4.0.0
pandas==2.2.2
polars==1.0.0
orjson==3.10.11
wheel==0.45.0
tomli==2.1.0
tomli-w==1.1.0
pydantic==2.9.2
pytest-benchmark==5.1.0
pandas==2.2.3
polars==1.13.1
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
orderly-set==5.2.2
orderly-set>=5.2.3,<6
2 changes: 0 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,7 @@ def get_reqs(filename):
license='MIT',
packages=['deepdiff'],
zip_safe=True,
test_suite="tests",
include_package_data=True,
tests_require=['mock'],
long_description=long_description,
long_description_content_type='text/markdown',
install_requires=reqs,
Expand Down
2 changes: 1 addition & 1 deletion tests/test_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class TestCommands:

@pytest.mark.parametrize('name1, name2, expected_in_stdout, expected_exit_code', [
('t1.json', 't2.json', """dictionary_item_added": [\n "root[0][\'key3\']""", 0),
('t1_corrupt.json', 't2.json', "Expecting property name enclosed in double quotes", 1),
('t1_corrupt.json', 't2.json', "Error when loading t1: Illegal trailing comma before end of object: line 3 column 21 (char 45)\n", 1),
('t1.json', 't2_json.csv', '"old_value": "value2"', 0),
('t2_json.csv', 't1.json', '"old_value": "value3"', 0),
('t1.csv', 't2.csv', '"new_value": "James"', 0),
Expand Down
81 changes: 0 additions & 81 deletions tests/test_diff_include_paths

This file was deleted.

Loading
Loading