Skip to content

Commit 759bb82

Browse files
committed
op_codes conversion to flat dicts
1 parent 54ebdb5 commit 759bb82

File tree

4 files changed

+128
-12
lines changed

4 files changed

+128
-12
lines changed

deepdiff/delta.py

Lines changed: 73 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111
strings, short_repr, numbers,
1212
np_ndarray, np_array_factory, numpy_dtypes, get_doc,
1313
not_found, numpy_dtype_string_to_type, dict_,
14-
Opcode, FlatDeltaRow, UnkownValueCode,
14+
Opcode, FlatDeltaRow, UnkownValueCode, FlatDataAction,
15+
OPCODE_TAG_TO_FLAT_DATA_ACTION,
16+
FLAT_DATA_ACTION_TO_OPCODE_TAG,
1517
)
1618
from deepdiff.path import (
1719
_path_to_elements, _get_nested_obj, _get_nested_obj_and_force,
@@ -877,6 +879,31 @@ def dumps(self):
877879
def to_dict(self):
878880
return dict(self.diff)
879881

882+
def _flatten_iterable_opcodes(self, _parse_path):
883+
"""
884+
Converts op_codes to FlatDeltaRows
885+
"""
886+
result = []
887+
for path, op_codes in self.diff['_iterable_opcodes'].items():
888+
for op_code in op_codes:
889+
result.append(
890+
FlatDeltaRow(
891+
path=_parse_path(path),
892+
action=OPCODE_TAG_TO_FLAT_DATA_ACTION[op_code.tag],
893+
value=op_code.new_values,
894+
old_value=op_code.old_values,
895+
type=type(op_code.new_values),
896+
old_type=type(op_code.old_values),
897+
new_path=None,
898+
t1_from_index=op_code.t1_from_index,
899+
t1_to_index=op_code.t1_to_index,
900+
t2_from_index=op_code.t2_from_index,
901+
t2_to_index=op_code.t2_to_index,
902+
903+
)
904+
)
905+
return result
906+
880907
@staticmethod
881908
def _get_flat_row(action, info, _parse_path, keys_and_funcs, report_type_changes=True):
882909
for path, details in info.items():
@@ -923,28 +950,44 @@ def _from_flat_dicts(flat_dict_list):
923950
if action in FLATTENING_NEW_ACTION_MAP:
924951
action = FLATTENING_NEW_ACTION_MAP[action]
925952
index = path.pop()
926-
if action in {'attribute_added', 'attribute_removed'}:
953+
if action in {
954+
FlatDataAction.attribute_added,
955+
FlatDataAction.attribute_removed,
956+
}:
927957
root_element = ('root', GETATTR)
928958
else:
929959
root_element = ('root', GET)
930-
path_str = stringify_path(path, root_element=root_element) # We need the string path
960+
if isinstance(path, str):
961+
path_str = path
962+
else:
963+
path_str = stringify_path(path, root_element=root_element) # We need the string path
931964
if new_path and new_path != path:
932965
new_path = stringify_path(new_path, root_element=root_element)
933966
else:
934967
new_path = None
935968
if action not in result:
936969
result[action] = {}
937-
if action in {'iterable_items_added_at_indexes', 'iterable_items_removed_at_indexes'}:
970+
if action in {
971+
'iterable_items_added_at_indexes',
972+
'iterable_items_removed_at_indexes',
973+
}:
938974
if path_str not in result[action]:
939975
result[action][path_str] = {}
940976
result[action][path_str][index] = value
941-
elif action in {'set_item_added', 'set_item_removed'}:
977+
elif action in {
978+
FlatDataAction.set_item_added,
979+
FlatDataAction.set_item_removed
980+
}:
942981
if path_str not in result[action]:
943982
result[action][path_str] = set()
944983
result[action][path_str].add(value)
945984
elif action in {
946-
'dictionary_item_added', 'dictionary_item_removed',
947-
'attribute_removed', 'attribute_added', 'iterable_item_added', 'iterable_item_removed',
985+
FlatDataAction.dictionary_item_added,
986+
FlatDataAction.dictionary_item_removed,
987+
FlatDataAction.attribute_removed,
988+
FlatDataAction.attribute_added,
989+
FlatDataAction.iterable_item_added,
990+
FlatDataAction.iterable_item_removed,
948991
}:
949992
result[action][path_str] = value
950993
elif action == 'values_changed':
@@ -964,8 +1007,29 @@ def _from_flat_dicts(flat_dict_list):
9641007
]:
9651008
if elem_value != UnkownValueCode:
9661009
result[action][path_str][elem] = elem_value
967-
elif action == 'iterable_item_moved':
1010+
elif action == FlatDataAction.iterable_item_moved:
9681011
result[action][path_str] = {'value': value}
1012+
elif action in {
1013+
FlatDataAction.iterable_items_inserted,
1014+
FlatDataAction.iterable_items_deleted,
1015+
FlatDataAction.iterable_items_replaced,
1016+
FlatDataAction.iterable_items_equal,
1017+
}:
1018+
if '_iterable_opcodes' not in result:
1019+
result['_iterable_opcodes'] = {}
1020+
if path_str not in result['_iterable_opcodes']:
1021+
result['_iterable_opcodes'][path_str] = []
1022+
result['_iterable_opcodes'][path_str].append(
1023+
Opcode(
1024+
tag=FLAT_DATA_ACTION_TO_OPCODE_TAG[action],
1025+
t1_from_index=flat_dict.get('t1_from_index'),
1026+
t1_to_index=flat_dict.get('t1_to_index'),
1027+
t2_from_index=flat_dict.get('t2_from_index'),
1028+
t2_to_index=flat_dict.get('t2_to_index'),
1029+
new_values=flat_dict.get('value'),
1030+
old_values=flat_dict.get('old_value'),
1031+
)
1032+
)
9691033
if new_path:
9701034
result[action][path_str]['new_path'] = new_path
9711035

@@ -1066,7 +1130,7 @@ def to_flat_rows(self, include_action_in_path=False, report_type_changes=True) -
10661130
}
10671131
for action, info in self.diff.items():
10681132
if action == '_iterable_opcodes':
1069-
result.extend(self._flatten_iterable_opcodes())
1133+
result.extend(self._flatten_iterable_opcodes(_parse_path=_parse_path))
10701134
continue
10711135
if action.startswith('_'):
10721136
continue

deepdiff/helper.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,17 @@ class pydantic_base_model_type:
110110
NUMERICS = frozenset(string.digits)
111111

112112

113+
class EnumBase(str, enum.Enum):
114+
def __repr__(self):
115+
"""
116+
We need to add a single quotes so we can easily copy the value when we do ipdb.
117+
"""
118+
return f"'{self.name}'"
119+
120+
def __str__(self):
121+
return self.name
122+
123+
113124
def _int_or_zero(value):
114125
"""
115126
Tries to extract some number from a string.
@@ -739,6 +750,13 @@ def named_tuple_repr(self):
739750
return f"{self.__class__.__name__}({', '.join(fields)})"
740751

741752

753+
class OpcodeTag(EnumBase):
754+
insert = 'insert'
755+
delete = 'delete'
756+
equal = 'equal'
757+
replace = 'replace'
758+
759+
742760
class Opcode(NamedTuple):
743761
tag: str
744762
t1_from_index: int
@@ -751,7 +769,7 @@ class Opcode(NamedTuple):
751769
__repr__ = __str__ = named_tuple_repr
752770

753771

754-
class FlatDataAction(str, enum.Enum):
772+
class FlatDataAction(EnumBase):
755773
values_changed = 'values_changed'
756774
type_changes = 'type_changes'
757775
set_item_added = 'set_item_added'
@@ -771,6 +789,16 @@ class FlatDataAction(str, enum.Enum):
771789
unordered_iterable_item_removed = 'unordered_iterable_item_removed'
772790

773791

792+
OPCODE_TAG_TO_FLAT_DATA_ACTION = {
793+
OpcodeTag.insert: FlatDataAction.iterable_items_inserted,
794+
OpcodeTag.delete: FlatDataAction.iterable_items_deleted,
795+
OpcodeTag.replace: FlatDataAction.iterable_items_replaced,
796+
OpcodeTag.equal: FlatDataAction.iterable_items_equal,
797+
}
798+
799+
FLAT_DATA_ACTION_TO_OPCODE_TAG = {v: i for i, v in OPCODE_TAG_TO_FLAT_DATA_ACTION.items()}
800+
801+
774802
UnkownValueCode = 'unknown___'
775803

776804

deepdiff/path.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,8 @@ def parse_path(path, root_element=DEFAULT_FIRST_ELEMENT, include_actions=False):
261261

262262
result = _path_to_elements(path, root_element=root_element)
263263
result = iter(result)
264-
next(result) # We don't want the root item
264+
if root_element:
265+
next(result) # We don't want the root item
265266
if include_actions is False:
266267
return [i[0] for i in result]
267268
return [{'element': i[0], 'action': i[1]} for i in result]

tests/test_delta.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from unittest import mock
1010
from ordered_set import OrderedSet
1111
from deepdiff import Delta, DeepDiff
12-
from deepdiff.helper import np, number_to_string, TEXT_VIEW, DELTA_VIEW, CannotCompare, FlatDeltaRow
12+
from deepdiff.helper import np, number_to_string, TEXT_VIEW, DELTA_VIEW, CannotCompare, FlatDeltaRow, FlatDataAction
1313
from deepdiff.path import GETATTR, GET
1414
from deepdiff.delta import (
1515
ELEM_NOT_FOUND_TO_ADD_MSG,
@@ -2397,6 +2397,29 @@ def test_list_of_alphabet_and_its_delta(self):
23972397
assert l2 == l1 + delta4
23982398
assert l1 == l2 - delta4
23992399

2400+
flat_rows = delta2.to_flat_rows()
2401+
2402+
expected_flat_rows = [
2403+
FlatDeltaRow(path=[3], action='values_changed', value='X', old_value='D', type=str, old_type=str, new_path=[2]),
2404+
FlatDeltaRow(path=[6], action='values_changed', value='Z', old_value='G', type=str, old_type=str),
2405+
FlatDeltaRow(path=[5], action='values_changed', value='Y', old_value='F', type=str, old_type=str),
2406+
FlatDeltaRow(path=[], action=FlatDataAction.iterable_items_deleted, value=[], old_value=['A'], type=list, old_type=list, t1_from_index=0, t1_to_index=1, t2_from_index=0, t2_to_index=0),
2407+
FlatDeltaRow(path=[], action=FlatDataAction.iterable_items_equal, value=None, old_value=None, type=type(None), old_type=type(None), t1_from_index=1, t1_to_index=3, t2_from_index=0, t2_to_index=2),
2408+
FlatDeltaRow(path=[], action=FlatDataAction.iterable_items_replaced, value=['X'], old_value=['D', 'E', 'F', 'G'], type=list, old_type=list, t1_from_index=3, t1_to_index=7, t2_from_index=2, t2_to_index=3),
2409+
FlatDeltaRow(path=[], action=FlatDataAction.iterable_items_equal, value=None, old_value=None, type=type(None), old_type=type(None), t1_from_index=7, t1_to_index=9, t2_from_index=3, t2_to_index=5),
2410+
FlatDeltaRow(path=[], action=FlatDataAction.iterable_items_inserted, value=['Y', 'Z'], old_value=[], type=list, old_type=list, t1_from_index=9, t1_to_index=9, t2_from_index=5, t2_to_index=7)
2411+
]
2412+
2413+
# The order of the first 3 items is not deterministic
2414+
assert not DeepDiff(expected_flat_rows[:3], flat_rows[:3], ignore_order=True)
2415+
assert expected_flat_rows[3:] == flat_rows[3:]
2416+
2417+
delta5 = Delta(flat_rows_list=flat_rows, bidirectional=True, force=True)
2418+
2419+
2420+
assert l2 == l1 + delta5
2421+
assert l1 == l2 - delta5
2422+
24002423
def test_delta_flat_rows(self):
24012424
t1 = {"key1": "value1"}
24022425
t2 = {"field2": {"key2": "value2"}}

0 commit comments

Comments
 (0)