@@ -221,10 +221,11 @@ def _from_tree_value_changed(self, tree):
221
221
222
222
def _from_tree_iterable_item_moved (self , tree ):
223
223
if 'iterable_item_moved' in tree and self .verbose_level > 1 :
224
+
224
225
for change in tree ['iterable_item_moved' ]:
225
- the_changed = {'new_path' : change .path (use_t2 = True ), 'value' : change .t2 }
226
+ the_changed = {'new_path' : change .path (use_t2 = True , reporting_move = True ), 'value' : change .t2 }
226
227
self ['iterable_item_moved' ][change .path (
227
- force = FORCE_DEFAULT )] = the_changed
228
+ force = FORCE_DEFAULT , use_t2 = False , reporting_move = True )] = the_changed
228
229
229
230
def _from_tree_unprocessed (self , tree ):
230
231
if 'unprocessed' in tree :
@@ -428,11 +429,11 @@ def _from_tree_iterable_item_moved(self, tree):
428
429
if 'iterable_item_moved' in tree :
429
430
for change in tree ['iterable_item_moved' ]:
430
431
if (
431
- change .up .path (force = FORCE_DEFAULT ) not in self ["_iterable_opcodes" ]
432
+ change .up .path (force = FORCE_DEFAULT , reporting_move = True ) not in self ["_iterable_opcodes" ]
432
433
):
433
- the_changed = {'new_path' : change .path (use_t2 = True ), 'value' : change .t2 }
434
+ the_changed = {'new_path' : change .path (use_t2 = True , reporting_move = True ), 'value' : change .t2 }
434
435
self ['iterable_item_moved' ][change .path (
435
- force = FORCE_DEFAULT )] = the_changed
436
+ force = FORCE_DEFAULT , reporting_move = True )] = the_changed
436
437
437
438
438
439
class DiffLevel :
@@ -673,7 +674,7 @@ def get_root_key(self, use_t2=False):
673
674
return next_rel .param
674
675
return notpresent
675
676
676
- def path (self , root = "root" , force = None , get_parent_too = False , use_t2 = False , output_format = 'str' ):
677
+ def path (self , root = "root" , force = None , get_parent_too = False , use_t2 = False , output_format = 'str' , reporting_move = False ):
677
678
"""
678
679
A python syntax string describing how to descend to this level, assuming the top level object is called root.
679
680
Returns None if the path is not representable as a string.
@@ -699,6 +700,9 @@ def path(self, root="root", force=None, get_parent_too=False, use_t2=False, outp
699
700
:param output_format: The format of the output. The options are 'str' which is the default and produces a
700
701
string representation of the path or 'list' to produce a list of keys and attributes
701
702
that produce the path.
703
+
704
+ :param reporting_move: This should be set to true if and only if we are reporting on iterable_item_moved.
705
+ All other cases should leave this set to False.
702
706
"""
703
707
# TODO: We could optimize this by building on top of self.up's path if it is cached there
704
708
cache_key = "{}{}{}{}" .format (force , get_parent_too , use_t2 , output_format )
@@ -720,7 +724,16 @@ def path(self, root="root", force=None, get_parent_too=False, use_t2=False, outp
720
724
# traverse all levels of this relationship
721
725
while level and level is not self :
722
726
# get this level's relationship object
723
- if use_t2 :
727
+ if level .additional .get ("moved" ) and not reporting_move :
728
+ # To ensure we can properly replay items such as values_changed in items that may have moved, we
729
+ # need to make sure that all paths are reported relative to t2 if a level has reported a move.
730
+ # If we are reporting a move, the path is already correct and does not need to be swapped.
731
+ # Additional context of "moved" is only ever set if using iterable_compare_func and a move has taken place.
732
+ level_use_t2 = not use_t2
733
+ else :
734
+ level_use_t2 = use_t2
735
+
736
+ if level_use_t2 :
724
737
next_rel = level .t2_child_rel or level .t1_child_rel
725
738
else :
726
739
next_rel = level .t1_child_rel or level .t2_child_rel # next relationship object to get a formatted param from
0 commit comments