10
10
np_ndarray , np_array_factory , numpy_dtypes , get_doc ,
11
11
not_found , numpy_dtype_string_to_type , dict_ ,
12
12
)
13
- from deepdiff .path import _path_to_elements , _get_nested_obj , _get_nested_obj_and_force , GET , GETATTR , parse_path
13
+ from deepdiff .path import (
14
+ _path_to_elements , _get_nested_obj , _get_nested_obj_and_force ,
15
+ GET , GETATTR , parse_path , stringify_path , DEFAULT_FIRST_ELEMENT
16
+ )
14
17
from deepdiff .anyset import AnySet
15
18
16
19
@@ -55,6 +58,10 @@ class DeltaNumpyOperatorOverrideError(ValueError):
55
58
pass
56
59
57
60
61
+ class _ObjDoesNotExist :
62
+ pass
63
+
64
+
58
65
class Delta :
59
66
60
67
__doc__ = doc
@@ -64,6 +71,7 @@ def __init__(
64
71
diff = None ,
65
72
delta_path = None ,
66
73
delta_file = None ,
74
+ flat_dict_list = None ,
67
75
deserializer = pickle_load ,
68
76
log_errors = True ,
69
77
mutate = False ,
@@ -79,6 +87,8 @@ def __init__(
79
87
def _deserializer (obj , safe_to_import = None ):
80
88
return deserializer (obj )
81
89
90
+ self ._reversed_diff = None
91
+
82
92
if diff is not None :
83
93
if isinstance (diff , DeepDiff ):
84
94
self .diff = diff ._to_delta_dict (directed = not verify_symmetry )
@@ -96,6 +106,8 @@ def _deserializer(obj, safe_to_import=None):
96
106
except UnicodeDecodeError as e :
97
107
raise ValueError (BINIARY_MODE_NEEDED_MSG .format (e )) from None
98
108
self .diff = _deserializer (content , safe_to_import = safe_to_import )
109
+ elif flat_dict_list :
110
+ self .diff = self ._from_flat_dicts (flat_dict_list )
99
111
else :
100
112
raise ValueError (DELTA_AT_LEAST_ONE_ARG_NEEDED )
101
113
@@ -161,7 +173,7 @@ def _do_verify_changes(self, path, expected_old_value, current_old_value):
161
173
self ._raise_or_log (VERIFICATION_MSG .format (
162
174
path , expected_old_value , current_old_value , VERIFY_SYMMETRY_MSG ))
163
175
164
- def _get_elem_and_compare_to_old_value (self , obj , path_for_err_reporting , expected_old_value , elem = None , action = None ):
176
+ def _get_elem_and_compare_to_old_value (self , obj , path_for_err_reporting , expected_old_value , elem = None , action = None , forced_old_value = None ):
165
177
try :
166
178
if action == GET :
167
179
current_old_value = obj [elem ]
@@ -171,12 +183,12 @@ def _get_elem_and_compare_to_old_value(self, obj, path_for_err_reporting, expect
171
183
raise DeltaError (INVALID_ACTION_WHEN_CALLING_GET_ELEM .format (action ))
172
184
except (KeyError , IndexError , AttributeError , TypeError ) as e :
173
185
if self .force :
174
- forced_old_value = {}
186
+ _forced_old_value = {} if forced_old_value is None else forced_old_value
175
187
if action == GET :
176
- obj [elem ] = forced_old_value
188
+ obj [elem ] = _forced_old_value
177
189
elif action == GETATTR :
178
- setattr (obj , elem , forced_old_value )
179
- return forced_old_value
190
+ setattr (obj , elem , _forced_old_value )
191
+ return _forced_old_value
180
192
current_old_value = not_found
181
193
if isinstance (path_for_err_reporting , (list , tuple )):
182
194
path_for_err_reporting = '.' .join ([i [0 ] for i in path_for_err_reporting ])
@@ -475,7 +487,7 @@ def _do_set_or_frozenset_item(self, items, func):
475
487
parent = self .get_nested_obj (obj = self , elements = elements [:- 1 ])
476
488
elem , action = elements [- 1 ]
477
489
obj = self ._get_elem_and_compare_to_old_value (
478
- parent , path_for_err_reporting = path , expected_old_value = None , elem = elem , action = action )
490
+ parent , path_for_err_reporting = path , expected_old_value = None , elem = elem , action = action , forced_old_value = set () )
479
491
new_value = getattr (obj , func )(value )
480
492
self ._simple_set_elem_value (parent , path_for_err_reporting = path , elem = elem , value = new_value , action = action )
481
493
@@ -568,6 +580,9 @@ def _do_ignore_order(self):
568
580
self ._simple_set_elem_value (obj = parent , path_for_err_reporting = path , elem = parent_to_obj_elem ,
569
581
value = new_obj , action = parent_to_obj_action )
570
582
583
+ def _reverse_diff (self ):
584
+ pass
585
+
571
586
def dump (self , file ):
572
587
"""
573
588
Dump into file object
@@ -604,6 +619,78 @@ def _get_flat_row(action, info, _parse_path, keys_and_funcs):
604
619
row [new_key ] = details [key ]
605
620
yield row
606
621
622
+ @staticmethod
623
+ def _from_flat_dicts (flat_dict_list ):
624
+ """
625
+ Create the delta's diff object from the flat_dict_list
626
+ """
627
+ result = {}
628
+
629
+ DEFLATTENING_NEW_ACTION_MAP = {
630
+ 'iterable_item_added' : 'iterable_items_added_at_indexes' ,
631
+ 'iterable_item_removed' : 'iterable_items_removed_at_indexes' ,
632
+ }
633
+ for flat_dict in flat_dict_list :
634
+ index = None
635
+ action = flat_dict .get ("action" )
636
+ path = flat_dict .get ("path" )
637
+ value = flat_dict .get ('value' )
638
+ old_value = flat_dict .get ('old_value' , _ObjDoesNotExist )
639
+ if not action :
640
+ raise ValueError ("Flat dict need to include the 'action'." )
641
+ if path is None :
642
+ raise ValueError ("Flat dict need to include the 'path'." )
643
+ if action in DEFLATTENING_NEW_ACTION_MAP :
644
+ action = DEFLATTENING_NEW_ACTION_MAP [action ]
645
+ index = path .pop ()
646
+ if action in {'attribute_added' , 'attribute_removed' }:
647
+ root_element = ('root' , GETATTR )
648
+ else :
649
+ root_element = ('root' , GET )
650
+ path_str = stringify_path (path , root_element = root_element ) # We need the string path
651
+ if action not in result :
652
+ result [action ] = {}
653
+ if action in {'iterable_items_added_at_indexes' , 'iterable_items_removed_at_indexes' }:
654
+ if path_str not in result [action ]:
655
+ result [action ][path_str ] = {}
656
+ result [action ][path_str ][index ] = value
657
+ elif action in {'set_item_added' , 'set_item_removed' }:
658
+ if path_str not in result [action ]:
659
+ result [action ][path_str ] = set ()
660
+ result [action ][path_str ].add (value )
661
+ elif action in {
662
+ 'dictionary_item_added' , 'dictionary_item_removed' , 'iterable_item_added' ,
663
+ 'iterable_item_removed' , 'attribute_removed' , 'attribute_added'
664
+ }:
665
+ result [action ][path_str ] = value
666
+ elif action == 'values_changed' :
667
+ if old_value is _ObjDoesNotExist :
668
+ result [action ][path_str ] = {'new_value' : value }
669
+ else :
670
+ result [action ][path_str ] = {'new_value' : value , 'old_value' : old_value }
671
+ elif action == 'type_changes' :
672
+ type_ = flat_dict .get ('type' , _ObjDoesNotExist )
673
+ old_type = flat_dict .get ('old_type' , _ObjDoesNotExist )
674
+
675
+ result [action ][path_str ] = {'new_value' : value }
676
+ for elem , elem_value in [
677
+ ('new_type' , type_ ),
678
+ ('old_type' , old_type ),
679
+ ('old_value' , old_value ),
680
+ ]:
681
+ if elem_value is not _ObjDoesNotExist :
682
+ result [action ][path_str ][elem ] = elem_value
683
+ elif action == 'iterable_item_moved' :
684
+ result [action ][path_str ] = {
685
+ 'new_path' : stringify_path (
686
+ flat_dict .get ('new_path' , '' ),
687
+ root_element = ('root' , GET )
688
+ ),
689
+ 'value' : value ,
690
+ }
691
+
692
+ return result
693
+
607
694
def to_flat_dicts (self , include_action_in_path = False , report_type_changes = True ):
608
695
"""
609
696
Returns a flat list of actions that is easily machine readable.
0 commit comments