Skip to content

7.0.1 #459

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 4 commits into from
Apr 8, 2024
Merged

7.0.1 #459

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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# DeepDiff Change log


- v7-0-1
- Fixes the translation between Difflib opcodes and Delta flat rows.
- v7-0-0
- When verbose=2, return `new_path` when the `path` and `new_path` are different (for example when ignore_order=True and the index of items have changed).
- Dropping support for Python 3.7
Expand Down
2 changes: 1 addition & 1 deletion CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ authors:
given-names: "Sep"
orcid: "https://orcid.org/0009-0009-5828-4345"
title: "DeepDiff"
version: 7.0.0
version: 7.0.1
date-released: 2024
url: "https://github.com/seperman/deepdiff"
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# DeepDiff v 7.0.0
# DeepDiff v 7.0.1

![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 @@ -17,12 +17,16 @@

Tested on Python 3.8+ and PyPy3.

- **[Documentation](https://zepworks.com/deepdiff/7.0.0/)**
- **[Documentation](https://zepworks.com/deepdiff/7.0.1/)**

## What is new?

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

DeepDiff 7-0-1

- Fixes the translation between Difflib opcodes and Delta flat rows.

DeepDiff 7-0-0

- DeepDiff 7 comes with an improved delta object. [Delta to flat dictionaries](https://zepworks.com/deepdiff/current/serialization.html#delta-serialize-to-flat-dictionaries) have undergone a major change. We have also introduced [Delta serialize to flat rows](https://zepworks.com/deepdiff/current/serialization.html#delta-serialize-to-flat-rows).
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__ = '7.0.0'
__version__ = '7.0.1'
import logging

if __name__ == '__main__':
Expand Down
118 changes: 103 additions & 15 deletions deepdiff/delta.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
strings, short_repr, numbers,
np_ndarray, np_array_factory, numpy_dtypes, get_doc,
not_found, numpy_dtype_string_to_type, dict_,
Opcode, FlatDeltaRow, UnkownValueCode,
Opcode, FlatDeltaRow, UnkownValueCode, FlatDataAction,
OPCODE_TAG_TO_FLAT_DATA_ACTION,
FLAT_DATA_ACTION_TO_OPCODE_TAG,
)
from deepdiff.path import (
_path_to_elements, _get_nested_obj, _get_nested_obj_and_force,
Expand Down Expand Up @@ -877,8 +879,33 @@ def dumps(self):
def to_dict(self):
return dict(self.diff)

def _flatten_iterable_opcodes(self, _parse_path):
"""
Converts op_codes to FlatDeltaRows
"""
result = []
for path, op_codes in self.diff['_iterable_opcodes'].items():
for op_code in op_codes:
result.append(
FlatDeltaRow(
path=_parse_path(path),
action=OPCODE_TAG_TO_FLAT_DATA_ACTION[op_code.tag],
value=op_code.new_values,
old_value=op_code.old_values,
type=type(op_code.new_values),
old_type=type(op_code.old_values),
new_path=None,
t1_from_index=op_code.t1_from_index,
t1_to_index=op_code.t1_to_index,
t2_from_index=op_code.t2_from_index,
t2_to_index=op_code.t2_to_index,

)
)
return result

@staticmethod
def _get_flat_row(action, info, _parse_path, keys_and_funcs):
def _get_flat_row(action, info, _parse_path, keys_and_funcs, report_type_changes=True):
for path, details in info.items():
row = {'path': _parse_path(path), 'action': action}
for key, new_key, func in keys_and_funcs:
Expand All @@ -887,6 +914,11 @@ def _get_flat_row(action, info, _parse_path, keys_and_funcs):
row[new_key] = func(details[key])
else:
row[new_key] = details[key]
if report_type_changes:
if 'value' in row and 'type' not in row:
row['type'] = type(row['value'])
if 'old_value' in row and 'old_type' not in row:
row['old_type'] = type(row['old_value'])
yield FlatDeltaRow(**row)

@staticmethod
Expand Down Expand Up @@ -918,28 +950,44 @@ def _from_flat_dicts(flat_dict_list):
if action in FLATTENING_NEW_ACTION_MAP:
action = FLATTENING_NEW_ACTION_MAP[action]
index = path.pop()
if action in {'attribute_added', 'attribute_removed'}:
if action in {
FlatDataAction.attribute_added,
FlatDataAction.attribute_removed,
}:
root_element = ('root', GETATTR)
else:
root_element = ('root', GET)
path_str = stringify_path(path, root_element=root_element) # We need the string path
if isinstance(path, str):
path_str = path
else:
path_str = stringify_path(path, root_element=root_element) # We need the string path
if new_path and new_path != path:
new_path = stringify_path(new_path, root_element=root_element)
else:
new_path = None
if action not in result:
result[action] = {}
if action in {'iterable_items_added_at_indexes', 'iterable_items_removed_at_indexes'}:
if action in {
'iterable_items_added_at_indexes',
'iterable_items_removed_at_indexes',
}:
if path_str not in result[action]:
result[action][path_str] = {}
result[action][path_str][index] = value
elif action in {'set_item_added', 'set_item_removed'}:
elif action in {
FlatDataAction.set_item_added,
FlatDataAction.set_item_removed
}:
if path_str not in result[action]:
result[action][path_str] = set()
result[action][path_str].add(value)
elif action in {
'dictionary_item_added', 'dictionary_item_removed',
'attribute_removed', 'attribute_added', 'iterable_item_added', 'iterable_item_removed',
FlatDataAction.dictionary_item_added,
FlatDataAction.dictionary_item_removed,
FlatDataAction.attribute_removed,
FlatDataAction.attribute_added,
FlatDataAction.iterable_item_added,
FlatDataAction.iterable_item_removed,
}:
result[action][path_str] = value
elif action == 'values_changed':
Expand All @@ -959,8 +1007,29 @@ def _from_flat_dicts(flat_dict_list):
]:
if elem_value != UnkownValueCode:
result[action][path_str][elem] = elem_value
elif action == 'iterable_item_moved':
elif action == FlatDataAction.iterable_item_moved:
result[action][path_str] = {'value': value}
elif action in {
FlatDataAction.iterable_items_inserted,
FlatDataAction.iterable_items_deleted,
FlatDataAction.iterable_items_replaced,
FlatDataAction.iterable_items_equal,
}:
if '_iterable_opcodes' not in result:
result['_iterable_opcodes'] = {}
if path_str not in result['_iterable_opcodes']:
result['_iterable_opcodes'][path_str] = []
result['_iterable_opcodes'][path_str].append(
Opcode(
tag=FLAT_DATA_ACTION_TO_OPCODE_TAG[action],
t1_from_index=flat_dict.get('t1_from_index'),
t1_to_index=flat_dict.get('t1_to_index'),
t2_from_index=flat_dict.get('t2_from_index'),
t2_to_index=flat_dict.get('t2_to_index'),
new_values=flat_dict.get('value'),
old_values=flat_dict.get('old_value'),
)
)
if new_path:
result[action][path_str]['new_path'] = new_path

Expand Down Expand Up @@ -1060,6 +1129,9 @@ def to_flat_rows(self, include_action_in_path=False, report_type_changes=True) -
'iterable_items_removed_at_indexes': 'unordered_iterable_item_removed',
}
for action, info in self.diff.items():
if action == '_iterable_opcodes':
result.extend(self._flatten_iterable_opcodes(_parse_path=_parse_path))
continue
if action.startswith('_'):
continue
if action in FLATTENING_NEW_ACTION_MAP:
Expand All @@ -1072,12 +1144,20 @@ def to_flat_rows(self, include_action_in_path=False, report_type_changes=True) -
path2.append((index, 'GET'))
else:
path2.append(index)
result.append(FlatDeltaRow(path=path2, value=value, action=new_action))
if report_type_changes:
row = FlatDeltaRow(path=path2, value=value, action=new_action, type=type(value))
else:
row = FlatDeltaRow(path=path2, value=value, action=new_action)
result.append(row)
elif action in {'set_item_added', 'set_item_removed'}:
for path, values in info.items():
path = _parse_path(path)
for value in values:
result.append(FlatDeltaRow(path=path, value=value, action=action))
if report_type_changes:
row = FlatDeltaRow(path=path, value=value, action=action, type=type(value))
else:
row = FlatDeltaRow(path=path, value=value, action=action)
result.append(row)
elif action == 'dictionary_item_added':
for path, value in info.items():
path = _parse_path(path)
Expand All @@ -1092,14 +1172,22 @@ def to_flat_rows(self, include_action_in_path=False, report_type_changes=True) -
elif isinstance(value, set) and len(value) == 1:
value = value.pop()
action = 'set_item_added'
result.append(FlatDeltaRow(path=path, value=value, action=action))
if report_type_changes:
row = FlatDeltaRow(path=path, value=value, action=action, type=type(value))
else:
row = FlatDeltaRow(path=path, value=value, action=action)
result.append(row)
elif action in {
'dictionary_item_removed', 'iterable_item_added',
'iterable_item_removed', 'attribute_removed', 'attribute_added'
}:
for path, value in info.items():
path = _parse_path(path)
result.append(FlatDeltaRow(path=path, value=value, action=action))
if report_type_changes:
row = FlatDeltaRow(path=path, value=value, action=action, type=type(value))
else:
row = FlatDeltaRow(path=path, value=value, action=action)
result.append(row)
elif action == 'type_changes':
if not report_type_changes:
action = 'values_changed'
Expand All @@ -1109,16 +1197,16 @@ def to_flat_rows(self, include_action_in_path=False, report_type_changes=True) -
info=info,
_parse_path=_parse_path,
keys_and_funcs=keys_and_funcs,
report_type_changes=report_type_changes,
):
result.append(row)
elif action == '_iterable_opcodes':
result.extend(self._flatten_iterable_opcodes())
else:
for row in self._get_flat_row(
action=action,
info=info,
_parse_path=_parse_path,
keys_and_funcs=keys_and_funcs,
report_type_changes=report_type_changes,
):
result.append(row)
return result
Expand Down
32 changes: 30 additions & 2 deletions deepdiff/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,17 @@ class pydantic_base_model_type:
NUMERICS = frozenset(string.digits)


class EnumBase(str, enum.Enum):
def __repr__(self):
"""
We need to add a single quotes so we can easily copy the value when we do ipdb.
"""
return f"'{self.name}'"

def __str__(self):
return self.name


def _int_or_zero(value):
"""
Tries to extract some number from a string.
Expand Down Expand Up @@ -739,6 +750,13 @@ def named_tuple_repr(self):
return f"{self.__class__.__name__}({', '.join(fields)})"


class OpcodeTag(EnumBase):
insert = 'insert'
delete = 'delete'
equal = 'equal'
replace = 'replace'


class Opcode(NamedTuple):
tag: str
t1_from_index: int
Expand All @@ -751,7 +769,7 @@ class Opcode(NamedTuple):
__repr__ = __str__ = named_tuple_repr


class FlatDataAction(str, enum.Enum):
class FlatDataAction(EnumBase):
values_changed = 'values_changed'
type_changes = 'type_changes'
set_item_added = 'set_item_added'
Expand All @@ -771,7 +789,17 @@ class FlatDataAction(str, enum.Enum):
unordered_iterable_item_removed = 'unordered_iterable_item_removed'


UnkownValueCode = '*-UNKNOWN-*'
OPCODE_TAG_TO_FLAT_DATA_ACTION = {
OpcodeTag.insert: FlatDataAction.iterable_items_inserted,
OpcodeTag.delete: FlatDataAction.iterable_items_deleted,
OpcodeTag.replace: FlatDataAction.iterable_items_replaced,
OpcodeTag.equal: FlatDataAction.iterable_items_equal,
}

FLAT_DATA_ACTION_TO_OPCODE_TAG = {v: i for i, v in OPCODE_TAG_TO_FLAT_DATA_ACTION.items()}


UnkownValueCode = 'unknown___'


class FlatDeltaRow(NamedTuple):
Expand Down
3 changes: 2 additions & 1 deletion deepdiff/path.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,8 @@ def parse_path(path, root_element=DEFAULT_FIRST_ELEMENT, include_actions=False):

result = _path_to_elements(path, root_element=root_element)
result = iter(result)
next(result) # We don't want the root item
if root_element:
next(result) # We don't want the root item
if include_actions is False:
return [i[0] for i in result]
return [{'element': i[0], 'action': i[1]} for i in result]
Expand Down
5 changes: 5 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ Changelog

DeepDiff Changelog


- v7-0-1

- Fixes the translation between Difflib opcodes and Delta flat rows.

- v7-0-0

- When verbose=2, return ``new_path`` when the ``path`` and
Expand Down
4 changes: 2 additions & 2 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,9 @@
# built documents.
#
# The short X.Y version.
version = '7.0.0'
version = '7.0.1'
# The full version, including alpha/beta/rc tags.
release = '7.0.0'
release = '7.0.1'

load_dotenv(override=True)
DOC_VERSION = os.environ.get('DOC_VERSION', version)
Expand Down
2 changes: 1 addition & 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 7.0.0 documentation!
DeepDiff 7.0.1 documentation!
=============================

*******
Expand Down
Loading