1010 np_ndarray , np_array_factory , numpy_dtypes , get_doc ,
1111 not_found , numpy_dtype_string_to_type , dict_ ,
1212)
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+ )
1417from deepdiff .anyset import AnySet
1518
1619
@@ -55,6 +58,10 @@ class DeltaNumpyOperatorOverrideError(ValueError):
5558 pass
5659
5760
61+ class _ObjDoesNotExist :
62+ pass
63+
64+
5865class Delta :
5966
6067 __doc__ = doc
@@ -64,6 +71,7 @@ def __init__(
6471 diff = None ,
6572 delta_path = None ,
6673 delta_file = None ,
74+ flat_dict_list = None ,
6775 deserializer = pickle_load ,
6876 log_errors = True ,
6977 mutate = False ,
@@ -79,6 +87,8 @@ def __init__(
7987 def _deserializer (obj , safe_to_import = None ):
8088 return deserializer (obj )
8189
90+ self ._reversed_diff = None
91+
8292 if diff is not None :
8393 if isinstance (diff , DeepDiff ):
8494 self .diff = diff ._to_delta_dict (directed = not verify_symmetry )
@@ -96,6 +106,8 @@ def _deserializer(obj, safe_to_import=None):
96106 except UnicodeDecodeError as e :
97107 raise ValueError (BINIARY_MODE_NEEDED_MSG .format (e )) from None
98108 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 )
99111 else :
100112 raise ValueError (DELTA_AT_LEAST_ONE_ARG_NEEDED )
101113
@@ -161,7 +173,7 @@ def _do_verify_changes(self, path, expected_old_value, current_old_value):
161173 self ._raise_or_log (VERIFICATION_MSG .format (
162174 path , expected_old_value , current_old_value , VERIFY_SYMMETRY_MSG ))
163175
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 ):
165177 try :
166178 if action == GET :
167179 current_old_value = obj [elem ]
@@ -171,12 +183,12 @@ def _get_elem_and_compare_to_old_value(self, obj, path_for_err_reporting, expect
171183 raise DeltaError (INVALID_ACTION_WHEN_CALLING_GET_ELEM .format (action ))
172184 except (KeyError , IndexError , AttributeError , TypeError ) as e :
173185 if self .force :
174- forced_old_value = {}
186+ _forced_old_value = {} if forced_old_value is None else forced_old_value
175187 if action == GET :
176- obj [elem ] = forced_old_value
188+ obj [elem ] = _forced_old_value
177189 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
180192 current_old_value = not_found
181193 if isinstance (path_for_err_reporting , (list , tuple )):
182194 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):
475487 parent = self .get_nested_obj (obj = self , elements = elements [:- 1 ])
476488 elem , action = elements [- 1 ]
477489 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 () )
479491 new_value = getattr (obj , func )(value )
480492 self ._simple_set_elem_value (parent , path_for_err_reporting = path , elem = elem , value = new_value , action = action )
481493
@@ -568,6 +580,9 @@ def _do_ignore_order(self):
568580 self ._simple_set_elem_value (obj = parent , path_for_err_reporting = path , elem = parent_to_obj_elem ,
569581 value = new_obj , action = parent_to_obj_action )
570582
583+ def _reverse_diff (self ):
584+ pass
585+
571586 def dump (self , file ):
572587 """
573588 Dump into file object
@@ -604,6 +619,78 @@ def _get_flat_row(action, info, _parse_path, keys_and_funcs):
604619 row [new_key ] = details [key ]
605620 yield row
606621
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+
607694 def to_flat_dicts (self , include_action_in_path = False , report_type_changes = True ):
608695 """
609696 Returns a flat list of actions that is easily machine readable.
0 commit comments