1import logging
2from collections.abc import Mapping
3from copy import copy
4from ordered_set import OrderedSet
5from deepdiff.helper import (
6    RemapDict, strings, short_repr, notpresent, get_type, numpy_numbers, np, literal_eval_extended,
7    dict_)
8
9logger = logging.getLogger(__name__)
10
11FORCE_DEFAULT = 'fake'
12UP_DOWN = {'up': 'down', 'down': 'up'}
13
14REPORT_KEYS = {
15    "type_changes",
16    "dictionary_item_added",
17    "dictionary_item_removed",
18    "values_changed",
19    "unprocessed",
20    "iterable_item_added",
21    "iterable_item_removed",
22    "iterable_item_moved",
23    "attribute_added",
24    "attribute_removed",
25    "set_item_removed",
26    "set_item_added",
27    "repetition_change",
28}
29
30CUSTOM_FIELD = "__internal:custom:extra_info"
31
32
33class DoesNotExist(Exception):
34    pass
35
36
37class ResultDict(RemapDict):
38
39    def remove_empty_keys(self):
40        """
41        Remove empty keys from this object. Should always be called after the result is final.
42        :return:
43        """
44        empty_keys = [k for k, v in self.items() if not v]
45
46        for k in empty_keys:
47            del self[k]
48
49
50class PrettyOrderedSet(OrderedSet):
51    """
52    From the perspective of the users of the library, they are dealing with lists.
53    Behind the scene, we have ordered sets.
54    """
55
56    def __repr__(self):
57        return '[{}]'.format(", ".join(map(str, self)))
58
59
60class TreeResult(ResultDict):
61    def __init__(self):
62        for key in REPORT_KEYS:
63            self[key] = PrettyOrderedSet()
64
65    def mutual_add_removes_to_become_value_changes(self):
66        """
67        There might be the same paths reported in the results as removed and added.
68        In such cases they should be reported as value_changes.
69
70        Note that this function mutates the tree in ways that causes issues when report_repetition=True
71        and should be avoided in that case.
72
73        This function should only be run on the Tree Result.
74        """
75        if self.get('iterable_item_added') and self.get('iterable_item_removed'):
76            added_paths = {i.path(): i for i in self['iterable_item_added']}
77            removed_paths = {i.path(): i for i in self['iterable_item_removed']}
78            mutual_paths = set(added_paths) & set(removed_paths)
79
80            if mutual_paths and 'values_changed' not in self:
81                self['values_changed'] = PrettyOrderedSet()
82            for path in mutual_paths:
83                level_before = removed_paths[path]
84                self['iterable_item_removed'].remove(level_before)
85                level_after = added_paths[path]
86                self['iterable_item_added'].remove(level_after)
87                level_before.t2 = level_after.t2
88                self['values_changed'].add(level_before)
89        if 'iterable_item_removed' in self and not self['iterable_item_removed']:
90            del self['iterable_item_removed']
91        if 'iterable_item_added' in self and not self['iterable_item_added']:
92            del self['iterable_item_added']
93
94    def __getitem__(self, item):
95        if item not in self:
96            self[item] = PrettyOrderedSet()
97        return self.get(item)
98
99
100class TextResult(ResultDict):
101    ADD_QUOTES_TO_STRINGS = True
102
103    def __init__(self, tree_results=None, verbose_level=1):
104        self.verbose_level = verbose_level
105        # TODO: centralize keys
106        self.update({
107            "type_changes": dict_(),
108            "dictionary_item_added": self.__set_or_dict(),
109            "dictionary_item_removed": self.__set_or_dict(),
110            "values_changed": dict_(),
111            "unprocessed": [],
112            "iterable_item_added": dict_(),
113            "iterable_item_removed": dict_(),
114            "iterable_item_moved": dict_(),
115            "attribute_added": self.__set_or_dict(),
116            "attribute_removed": self.__set_or_dict(),
117            "set_item_removed": PrettyOrderedSet(),
118            "set_item_added": PrettyOrderedSet(),
119            "repetition_change": dict_()
120        })
121
122        if tree_results:
123            self._from_tree_results(tree_results)
124
125    def __set_or_dict(self):
126        return {} if self.verbose_level >= 2 else PrettyOrderedSet()
127
128    def _from_tree_results(self, tree):
129        """
130        Populate this object by parsing an existing reference-style result dictionary.
131        :param tree: A TreeResult
132        :return:
133        """
134        self._from_tree_type_changes(tree)
135        self._from_tree_default(tree, 'dictionary_item_added')
136        self._from_tree_default(tree, 'dictionary_item_removed')
137        self._from_tree_value_changed(tree)
138        self._from_tree_unprocessed(tree)
139        self._from_tree_default(tree, 'iterable_item_added')
140        self._from_tree_default(tree, 'iterable_item_removed')
141        self._from_tree_iterable_item_moved(tree)
142        self._from_tree_default(tree, 'attribute_added')
143        self._from_tree_default(tree, 'attribute_removed')
144        self._from_tree_set_item_removed(tree)
145        self._from_tree_set_item_added(tree)
146        self._from_tree_repetition_change(tree)
147        self._from_tree_deep_distance(tree)
148        self._from_tree_custom_results(tree)
149
150    def _from_tree_default(self, tree, report_type):
151        if report_type in tree:
152            for change in tree[report_type]:  # report each change
153                # determine change direction (added or removed)
154                # Report t2 (the new one) whenever possible.
155                # In cases where t2 doesn't exist (i.e. stuff removed), report t1.
156                if change.t2 is not notpresent:
157                    item = change.t2
158                else:
159                    item = change.t1
160
161                # do the reporting
162                report = self[report_type]
163                if isinstance(report, PrettyOrderedSet):
164                    report.add(change.path(force=FORCE_DEFAULT))
165                elif isinstance(report, dict):
166                    report[change.path(force=FORCE_DEFAULT)] = item
167                elif isinstance(report, list):  # pragma: no cover
168                    # we don't actually have any of those right now, but just in case
169                    report.append(change.path(force=FORCE_DEFAULT))
170                else:  # pragma: no cover
171                    # should never happen
172                    raise TypeError("Cannot handle {} report container type.".
173                                    format(report))
174
175    def _from_tree_type_changes(self, tree):
176        if 'type_changes' in tree:
177            for change in tree['type_changes']:
178                if type(change.t1) is type:
179                    include_values = False
180                    old_type = change.t1
181                    new_type = change.t2
182                else:
183                    include_values = True
184                    old_type = get_type(change.t1)
185                    new_type = get_type(change.t2)
186                remap_dict = RemapDict({
187                    'old_type': old_type,
188                    'new_type': new_type
189                })
190                self['type_changes'][change.path(
191                    force=FORCE_DEFAULT)] = remap_dict
192                if self.verbose_level and include_values:
193                    remap_dict.update(old_value=change.t1, new_value=change.t2)
194
195    def _from_tree_value_changed(self, tree):
196        if 'values_changed' in tree and self.verbose_level > 0:
197            for change in tree['values_changed']:
198                the_changed = {'new_value': change.t2, 'old_value': change.t1}
199                self['values_changed'][change.path(
200                    force=FORCE_DEFAULT)] = the_changed
201                if 'diff' in change.additional:
202                    the_changed.update({'diff': change.additional['diff']})
203
204    def _from_tree_iterable_item_moved(self, tree):
205        if 'iterable_item_moved' in tree and self.verbose_level > 1:
206            for change in tree['iterable_item_moved']:
207                the_changed = {'new_path': change.path(use_t2=True), 'value': change.t2}
208                self['iterable_item_moved'][change.path(
209                    force=FORCE_DEFAULT)] = the_changed
210
211    def _from_tree_unprocessed(self, tree):
212        if 'unprocessed' in tree:
213            for change in tree['unprocessed']:
214                self['unprocessed'].append("{}: {} and {}".format(change.path(
215                    force=FORCE_DEFAULT), change.t1, change.t2))
216
217    def _from_tree_set_item_added_or_removed(self, tree, key):
218        if key in tree:
219            set_item_info = self[key]
220            is_dict = isinstance(set_item_info, Mapping)
221            for change in tree[key]:
222                path = change.up.path(
223                )  # we want't the set's path, the added item is not directly accessible
224                item = change.t2 if key == 'set_item_added' else change.t1
225                if self.ADD_QUOTES_TO_STRINGS and isinstance(item, strings):
226                    item = "'%s'" % item
227                if is_dict:
228                    if path not in set_item_info:
229                        set_item_info[path] = set()
230                    set_item_info[path].add(item)
231                else:
232                    set_item_info.add("{}[{}]".format(path, str(item)))
233                    # this syntax is rather peculiar, but it's DeepDiff 2.x compatible)
234
235    def _from_tree_set_item_added(self, tree):
236        self._from_tree_set_item_added_or_removed(tree, key='set_item_added')
237
238    def _from_tree_set_item_removed(self, tree):
239        self._from_tree_set_item_added_or_removed(tree, key='set_item_removed')
240
241    def _from_tree_repetition_change(self, tree):
242        if 'repetition_change' in tree:
243            for change in tree['repetition_change']:
244                path = change.path(force=FORCE_DEFAULT)
245                self['repetition_change'][path] = RemapDict(
246                    change.additional['repetition']
247                )
248                self['repetition_change'][path]['value'] = change.t1
249
250    def _from_tree_deep_distance(self, tree):
251        if 'deep_distance' in tree:
252            self['deep_distance'] = tree['deep_distance']
253
254    def _from_tree_custom_results(self, tree):
255        for k, _level_list in tree.items():
256            if k not in REPORT_KEYS:
257                if not isinstance(_level_list, PrettyOrderedSet):
258                    continue
259
260                # if len(_level_list) == 0:
261                #     continue
262                #
263                # if not isinstance(_level_list[0], DiffLevel):
264                #     continue
265
266                # _level_list is a list of DiffLevel
267                _custom_dict = {}
268                for _level in _level_list:
269                    _custom_dict[_level.path(
270                        force=FORCE_DEFAULT)] = _level.additional.get(CUSTOM_FIELD, {})
271                self[k] = _custom_dict
272
273
274class DeltaResult(TextResult):
275    ADD_QUOTES_TO_STRINGS = False
276
277    def __init__(self, tree_results=None, ignore_order=None):
278        self.ignore_order = ignore_order
279
280        self.update({
281            "type_changes": dict_(),
282            "dictionary_item_added": dict_(),
283            "dictionary_item_removed": dict_(),
284            "values_changed": dict_(),
285            "iterable_item_added": dict_(),
286            "iterable_item_removed": dict_(),
287            "iterable_item_moved": dict_(),
288            "attribute_added": dict_(),
289            "attribute_removed": dict_(),
290            "set_item_removed": dict_(),
291            "set_item_added": dict_(),
292            "iterable_items_added_at_indexes": dict_(),
293            "iterable_items_removed_at_indexes": dict_(),
294        })
295
296        if tree_results:
297            self._from_tree_results(tree_results)
298
299    def _from_tree_results(self, tree):
300        """
301        Populate this object by parsing an existing reference-style result dictionary.
302        :param tree: A TreeResult
303        :return:
304        """
305        self._from_tree_type_changes(tree)
306        self._from_tree_default(tree, 'dictionary_item_added')
307        self._from_tree_default(tree, 'dictionary_item_removed')
308        self._from_tree_value_changed(tree)
309        if self.ignore_order:
310            self._from_tree_iterable_item_added_or_removed(
311                tree, 'iterable_item_added', delta_report_key='iterable_items_added_at_indexes')
312            self._from_tree_iterable_item_added_or_removed(
313                tree, 'iterable_item_removed', delta_report_key='iterable_items_removed_at_indexes')
314        else:
315            self._from_tree_default(tree, 'iterable_item_added')
316            self._from_tree_default(tree, 'iterable_item_removed')
317            self._from_tree_iterable_item_moved(tree)
318        self._from_tree_default(tree, 'attribute_added')
319        self._from_tree_default(tree, 'attribute_removed')
320        self._from_tree_set_item_removed(tree)
321        self._from_tree_set_item_added(tree)
322        self._from_tree_repetition_change(tree)
323
324    def _from_tree_iterable_item_added_or_removed(self, tree, report_type, delta_report_key):
325        if report_type in tree:
326            for change in tree[report_type]:  # report each change
327                # determine change direction (added or removed)
328                # Report t2 (the new one) whenever possible.
329                # In cases where t2 doesn't exist (i.e. stuff removed), report t1.
330                if change.t2 is not notpresent:
331                    item = change.t2
332                else:
333                    item = change.t1
334
335                # do the reporting
336                path, param, _ = change.path(force=FORCE_DEFAULT, get_parent_too=True)
337                try:
338                    iterable_items_added_at_indexes = self[delta_report_key][path]
339                except KeyError:
340                    iterable_items_added_at_indexes = self[delta_report_key][path] = dict_()
341                iterable_items_added_at_indexes[param] = item
342
343    def _from_tree_type_changes(self, tree):
344        if 'type_changes' in tree:
345            for change in tree['type_changes']:
346                include_values = None
347                if type(change.t1) is type:
348                    include_values = False
349                    old_type = change.t1
350                    new_type = change.t2
351                else:
352                    old_type = get_type(change.t1)
353                    new_type = get_type(change.t2)
354                    include_values = True
355                    try:
356                        if new_type in numpy_numbers:
357                            new_t1 = change.t1.astype(new_type)
358                            include_values = not np.array_equal(new_t1, change.t2)
359                        else:
360                            new_t1 = new_type(change.t1)
361                            # If simply applying the type from one value converts it to the other value,
362                            # there is no need to include the actual values in the delta.
363                            include_values = new_t1 != change.t2
364                    except Exception:
365                        pass
366
367                remap_dict = RemapDict({
368                    'old_type': old_type,
369                    'new_type': new_type
370                })
371                self['type_changes'][change.path(
372                    force=FORCE_DEFAULT)] = remap_dict
373                if include_values:
374                    remap_dict.update(old_value=change.t1, new_value=change.t2)
375
376    def _from_tree_value_changed(self, tree):
377        if 'values_changed' in tree:
378            for change in tree['values_changed']:
379                the_changed = {'new_value': change.t2, 'old_value': change.t1}
380                self['values_changed'][change.path(
381                    force=FORCE_DEFAULT)] = the_changed
382                # If we ever want to store the difflib results instead of the new_value
383                # these lines need to be uncommented and the Delta object needs to be able
384                # to use them.
385                # if 'diff' in change.additional:
386                #     the_changed.update({'diff': change.additional['diff']})
387
388    def _from_tree_repetition_change(self, tree):
389        if 'repetition_change' in tree:
390            for change in tree['repetition_change']:
391                path, _, _ = change.path(get_parent_too=True)
392                repetition = RemapDict(change.additional['repetition'])
393                value = change.t1
394                try:
395                    iterable_items_added_at_indexes = self['iterable_items_added_at_indexes'][path]
396                except KeyError:
397                    iterable_items_added_at_indexes = self['iterable_items_added_at_indexes'][path] = dict_()
398                for index in repetition['new_indexes']:
399                    iterable_items_added_at_indexes[index] = value
400
401    def _from_tree_iterable_item_moved(self, tree):
402        if 'iterable_item_moved' in tree:
403            for change in tree['iterable_item_moved']:
404                the_changed = {'new_path': change.path(use_t2=True), 'value': change.t2}
405                self['iterable_item_moved'][change.path(
406                    force=FORCE_DEFAULT)] = the_changed
407
408
409class DiffLevel:
410    """
411    An object of this class represents a single object-tree-level in a reported change.
412    A double-linked list of these object describes a single change on all of its levels.
413    Looking at the tree of all changes, a list of those objects represents a single path through the tree
414    (which is just fancy for "a change").
415    This is the result object class for object reference style reports.
416
417    Example:
418
419    >>> t1 = {2: 2, 4: 44}
420    >>> t2 = {2: "b", 5: 55}
421    >>> ddiff = DeepDiff(t1, t2, view='tree')
422    >>> ddiff
423    {'dictionary_item_added': {<DiffLevel id:4560126096, t1:None, t2:55>},
424     'dictionary_item_removed': {<DiffLevel id:4560126416, t1:44, t2:None>},
425     'type_changes': {<DiffLevel id:4560126608, t1:2, t2:b>}}
426
427    Graph:
428
429    <DiffLevel id:123, original t1,t2>          <DiffLevel id:200, original t1,t2>
430                    ↑up                                         ↑up
431                    |                                           |
432                    | ChildRelationship                         | ChildRelationship
433                    |                                           |
434                    ↓down                                       ↓down
435    <DiffLevel id:13, t1:None, t2:55>            <DiffLevel id:421, t1:44, t2:None>
436    .path() = 'root[5]'                         .path() = 'root[4]'
437
438    Note that the 2 top level DiffLevel objects are 2 different objects even though
439    they are essentially talking about the same diff operation.
440
441
442    A ChildRelationship object describing the relationship between t1 and it's child object,
443    where t1's child object equals down.t1.
444
445    Think about it like a graph:
446
447    +---------------------------------------------------------------+
448    |                                                               |
449    |    parent                 difflevel                 parent    |
450    |      +                          ^                     +       |
451    +------|--------------------------|---------------------|-------+
452           |                      |   | up                  |
453           | Child                |   |                     | ChildRelationship
454           | Relationship         |   |                     |
455           |                 down |   |                     |
456    +------|----------------------|-------------------------|-------+
457    |      v                      v                         v       |
458    |    child                  difflevel                 child     |
459    |                                                               |
460    +---------------------------------------------------------------+
461
462
463    The child_rel example:
464
465    # dictionary_item_removed is a set so in order to get an item from it:
466    >>> (difflevel,) = ddiff['dictionary_item_removed'])
467    >>> difflevel.up.t1_child_rel
468    <DictRelationship id:456, parent:{2: 2, 4: 44}, child:44, param:4>
469
470    >>> (difflevel,) = ddiff['dictionary_item_added'])
471    >>> difflevel
472    <DiffLevel id:4560126096, t1:None, t2:55>
473
474    >>> difflevel.up
475    >>> <DiffLevel id:4560154512, t1:{2: 2, 4: 44}, t2:{2: 'b', 5: 55}>
476
477    >>> difflevel.up
478    <DiffLevel id:4560154512, t1:{2: 2, 4: 44}, t2:{2: 'b', 5: 55}>
479
480    # t1 didn't exist
481    >>> difflevel.up.t1_child_rel
482
483    # t2 is added
484    >>> difflevel.up.t2_child_rel
485    <DictRelationship id:4560154384, parent:{2: 'b', 5: 55}, child:55, param:5>
486
487    """
488
489    def __init__(self,
490                 t1,
491                 t2,
492                 down=None,
493                 up=None,
494                 report_type=None,
495                 child_rel1=None,
496                 child_rel2=None,
497                 additional=None,
498                 verbose_level=1):
499        """
500        :param child_rel1: Either:
501                            - An existing ChildRelationship object describing the "down" relationship for t1; or
502                            - A ChildRelationship subclass. In this case, we will create the ChildRelationship objects
503                              for both t1 and t2.
504                            Alternatives for child_rel1 and child_rel2 must be used consistently.
505        :param child_rel2: Either:
506                            - An existing ChildRelationship object describing the "down" relationship for t2; or
507                            - The param argument for a ChildRelationship class we shall create.
508                           Alternatives for child_rel1 and child_rel2 must be used consistently.
509        """
510
511        # The current-level object in the left hand tree
512        self.t1 = t1
513
514        # The current-level object in the right hand tree
515        self.t2 = t2
516
517        # Another DiffLevel object describing this change one level deeper down the object tree
518        self.down = down
519
520        # Another DiffLevel object describing this change one level further up the object tree
521        self.up = up
522
523        self.report_type = report_type
524
525        # If this object is this change's deepest level, this contains a string describing the type of change.
526        # Examples: "set_item_added", "values_changed"
527
528        # Note: don't use {} as additional's default value - this would turn out to be always the same dict object
529        self.additional = dict_() if additional is None else additional
530
531        # For some types of changes we store some additional information.
532        # This is a dict containing this information.
533        # Currently, this is used for:
534        # - values_changed: In case the changes data is a multi-line string,
535        #                   we include a textual diff as additional['diff'].
536        # - repetition_change: additional['repetition']:
537        #                      e.g. {'old_repeat': 2, 'new_repeat': 1, 'old_indexes': [0, 2], 'new_indexes': [2]}
538        # the user supplied ChildRelationship objects for t1 and t2
539
540        # A ChildRelationship object describing the relationship between t1 and it's child object,
541        # where t1's child object equals down.t1.
542        # If this relationship is representable as a string, str(self.t1_child_rel) returns a formatted param parsable python string,
543        # e.g. "[2]", ".my_attribute"
544        self.t1_child_rel = child_rel1
545
546        # Another ChildRelationship object describing the relationship between t2 and it's child object.
547        self.t2_child_rel = child_rel2
548
549        # Will cache result of .path() per 'force' as key for performance
550        self._path = dict_()
551
552        self.verbose_level = verbose_level
553
554    def __repr__(self):
555        if self.verbose_level:
556            if self.additional:
557                additional_repr = short_repr(self.additional, max_length=35)
558                result = "<{} {}>".format(self.path(), additional_repr)
559            else:
560                t1_repr = short_repr(self.t1)
561                t2_repr = short_repr(self.t2)
562                result = "<{} t1:{}, t2:{}>".format(self.path(), t1_repr, t2_repr)
563        else:
564            result = "<{}>".format(self.path())
565        return result
566
567    def __setattr__(self, key, value):
568        # Setting up or down, will set the opposite link in this linked list.
569        if key in UP_DOWN and value is not None:
570            self.__dict__[key] = value
571            opposite_key = UP_DOWN[key]
572            value.__dict__[opposite_key] = self
573        else:
574            self.__dict__[key] = value
575
576    @property
577    def repetition(self):
578        return self.additional['repetition']
579
580    def auto_generate_child_rel(self, klass, param, param2=None):
581        """
582        Auto-populate self.child_rel1 and self.child_rel2.
583        This requires self.down to be another valid DiffLevel object.
584        :param klass: A ChildRelationship subclass describing the kind of parent-child relationship,
585                      e.g. DictRelationship.
586        :param param: A ChildRelationship subclass-dependent parameter describing how to get from parent to child,
587                      e.g. the key in a dict
588        """
589        if self.down.t1 is not notpresent:
590            self.t1_child_rel = ChildRelationship.create(
591                klass=klass, parent=self.t1, child=self.down.t1, param=param)
592        if self.down.t2 is not notpresent:
593            self.t2_child_rel = ChildRelationship.create(
594                klass=klass, parent=self.t2, child=self.down.t2, param=param if param2 is None else param2)
595
596    @property
597    def all_up(self):
598        """
599        Get the root object of this comparison.
600        (This is a convenient wrapper for following the up attribute as often as you can.)
601        :rtype: DiffLevel
602        """
603        level = self
604        while level.up:
605            level = level.up
606        return level
607
608    @property
609    def all_down(self):
610        """
611        Get the leaf object of this comparison.
612        (This is a convenient wrapper for following the down attribute as often as you can.)
613        :rtype: DiffLevel
614        """
615        level = self
616        while level.down:
617            level = level.down
618        return level
619
620    @staticmethod
621    def _format_result(root, result):
622        return None if result is None else "{}{}".format(root, result)
623
624    def path(self, root="root", force=None, get_parent_too=False, use_t2=False, output_format='str'):
625        """
626        A python syntax string describing how to descend to this level, assuming the top level object is called root.
627        Returns None if the path is not representable as a string.
628        This might be the case for example if there are sets involved (because then there's not path at all) or because
629        custom objects used as dictionary keys (then there is a path but it's not representable).
630        Example: root['ingredients'][0]
631        Note: We will follow the left side of the comparison branch, i.e. using the t1's to build the path.
632        Using t1 or t2 should make no difference at all, except for the last step of a child-added/removed relationship.
633        If it does in any other case, your comparison path is corrupt.
634
635        **Parameters**
636
637        :param root: The result string shall start with this var name
638        :param force: Bends the meaning of "no string representation".
639                      If None:
640                        Will strictly return Python-parsable expressions. The result those yield will compare
641                        equal to the objects in question.
642                      If 'yes':
643                        Will return a path including '(unrepresentable)' in place of non string-representable parts.
644                      If 'fake':
645                        Will try to produce an output optimized for readability.
646                        This will pretend all iterables are subscriptable, for example.
647        :param output_format: The format of the output. The options are 'str' which is the default and produces a
648                              string representation of the path or 'list' to produce a list of keys and attributes
649                              that produce the path.
650        """
651        # TODO: We could optimize this by building on top of self.up's path if it is cached there
652        cache_key = "{}{}{}{}".format(force, get_parent_too, use_t2, output_format)
653        if cache_key in self._path:
654            cached = self._path[cache_key]
655            if get_parent_too:
656                parent, param, result = cached
657                return (self._format_result(root, parent), param, self._format_result(root, result))
658            else:
659                return self._format_result(root, cached)
660
661        if output_format == 'str':
662            result = parent = param = ""
663        else:
664            result = []
665
666        level = self.all_up  # start at the root
667
668        # traverse all levels of this relationship
669        while level and level is not self:
670            # get this level's relationship object
671            if(use_t2):
672                next_rel = level.t2_child_rel
673            else:
674                next_rel = level.t1_child_rel or level.t2_child_rel  # next relationship object to get a formatted param from
675
676            # t1 and t2 both are empty
677            if next_rel is None:
678                break
679
680            # Build path for this level
681            if output_format == 'str':
682                item = next_rel.get_param_repr(force)
683                if item:
684                    parent = result
685                    param = next_rel.param
686                    result += item
687                else:
688                    # it seems this path is not representable as a string
689                    result = None
690                    break
691            elif output_format == 'list':
692                result.append(next_rel.param)
693
694            # Prepare processing next level
695            level = level.down
696
697        if output_format == 'str':
698            if get_parent_too:
699                self._path[cache_key] = (parent, param, result)
700                output = (self._format_result(root, parent), param, self._format_result(root, result))
701            else:
702                self._path[cache_key] = result
703                output = self._format_result(root, result)
704        else:
705            output = result
706        return output
707
708    def create_deeper(self,
709                      new_t1,
710                      new_t2,
711                      child_relationship_class,
712                      child_relationship_param=None,
713                      child_relationship_param2=None,
714                      report_type=None):
715        """
716        Start a new comparison level and correctly link it to this one.
717        :rtype: DiffLevel
718        :return: New level
719        """
720        level = self.all_down
721        result = DiffLevel(
722            new_t1, new_t2, down=None, up=level, report_type=report_type)
723        level.down = result
724        level.auto_generate_child_rel(
725            klass=child_relationship_class, param=child_relationship_param, param2=child_relationship_param2)
726        return result
727
728    def branch_deeper(self,
729                      new_t1,
730                      new_t2,
731                      child_relationship_class,
732                      child_relationship_param=None,
733                      child_relationship_param2=None,
734                      report_type=None):
735        """
736        Branch this comparison: Do not touch this comparison line, but create a new one with exactly the same content,
737        just one level deeper.
738        :rtype: DiffLevel
739        :return: New level in new comparison line
740        """
741        branch = self.copy()
742        return branch.create_deeper(new_t1, new_t2, child_relationship_class,
743                                    child_relationship_param, child_relationship_param2, report_type)
744
745    def copy(self):
746        """
747        Get a deep copy of this comparision line.
748        :return: The leaf ("downmost") object of the copy.
749        """
750        orig = self.all_up
751        result = copy(orig)  # copy top level
752
753        while orig is not None:
754            result.additional = copy(orig.additional)
755
756            if orig.down is not None:  # copy and create references to the following level
757                # copy following level
758                result.down = copy(orig.down)
759
760                if orig.t1_child_rel is not None:
761                    result.t1_child_rel = ChildRelationship.create(
762                        klass=orig.t1_child_rel.__class__,
763                        parent=result.t1,
764                        child=result.down.t1,
765                        param=orig.t1_child_rel.param)
766                if orig.t2_child_rel is not None:
767                    result.t2_child_rel = ChildRelationship.create(
768                        klass=orig.t2_child_rel.__class__,
769                        parent=result.t2,
770                        child=result.down.t2,
771                        param=orig.t2_child_rel.param)
772
773            # descend to next level
774            orig = orig.down
775            if result.down is not None:
776                result = result.down
777        return result
778
779
780class ChildRelationship:
781    """
782    Describes the relationship between a container object (the "parent") and the contained
783    "child" object.
784    """
785
786    # Format to a be used for representing param.
787    # E.g. for a dict, this turns a formatted param param "42" into "[42]".
788    param_repr_format = None
789
790    # This is a hook allowing subclasses to manipulate param strings.
791    # :param string: Input string
792    # :return: Manipulated string, as appropriate in this context.
793    quote_str = None
794
795    @staticmethod
796    def create(klass, parent, child, param=None):
797        if not issubclass(klass, ChildRelationship):
798            raise TypeError
799        return klass(parent, child, param)
800
801    def __init__(self, parent, child, param=None):
802        # The parent object of this relationship, e.g. a dict
803        self.parent = parent
804
805        # The child object of this relationship, e.g. a value in a dict
806        self.child = child
807
808        # A subclass-dependent parameter describing how to get from parent to child, e.g. the key in a dict
809        self.param = param
810
811    def __repr__(self):
812        name = "<{} parent:{}, child:{}, param:{}>"
813        parent = short_repr(self.parent)
814        child = short_repr(self.child)
815        param = short_repr(self.param)
816        return name.format(self.__class__.__name__, parent, child, param)
817
818    def get_param_repr(self, force=None):
819        """
820        Returns a formatted param python parsable string describing this relationship,
821        or None if the relationship is not representable as a string.
822        This string can be appended to the parent Name.
823        Subclasses representing a relationship that cannot be expressed as a string override this method to return None.
824        Examples: "[2]", ".attribute", "['mykey']"
825        :param force: Bends the meaning of "no string representation".
826              If None:
827                Will strictly return partials of Python-parsable expressions. The result those yield will compare
828                equal to the objects in question.
829              If 'yes':
830                Will return a formatted param including '(unrepresentable)' instead of the non string-representable part.
831
832        """
833        return self.stringify_param(force)
834
835    def stringify_param(self, force=None):
836        """
837        Convert param to a string. Return None if there is no string representation.
838        This is called by get_param_repr()
839        :param force: Bends the meaning of "no string representation".
840                      If None:
841                        Will strictly return Python-parsable expressions. The result those yield will compare
842                        equal to the objects in question.
843                      If 'yes':
844                        Will return '(unrepresentable)' instead of None if there is no string representation
845
846        TODO: stringify_param has issues with params that when converted to string via repr,
847        it is not straight forward to turn them back into the original object.
848        Although repr is meant to be able to reconstruct the original object but for complex objects, repr
849        often does not recreate the original object.
850        Perhaps we should log that the repr reconstruction failed so the user is aware.
851        """
852        param = self.param
853        if isinstance(param, strings):
854            result = param if self.quote_str is None else self.quote_str.format(param)
855        elif isinstance(param, tuple):  # Currently only for numpy ndarrays
856            result = ']['.join(map(repr, param))
857        else:
858            candidate = repr(param)
859            try:
860                resurrected = literal_eval_extended(candidate)
861                # Note: This will miss string-representable custom objects.
862                # However, the only alternative I can currently think of is using eval() which is inherently dangerous.
863            except (SyntaxError, ValueError) as err:
864                logger.error(
865                    f'stringify_param was not able to get a proper repr for "{param}". '
866                    "This object will be reported as None. Add instructions for this object to DeepDiff's "
867                    f"helper.literal_eval_extended to make it work properly: {err}")
868                result = None
869            else:
870                result = candidate if resurrected == param else None
871
872        if result:
873            result = ':' if self.param_repr_format is None else self.param_repr_format.format(result)
874
875        return result
876
877
878class DictRelationship(ChildRelationship):
879    param_repr_format = "[{}]"
880    quote_str = "'{}'"
881
882
883class NumpyArrayRelationship(ChildRelationship):
884    param_repr_format = "[{}]"
885    quote_str = None
886
887
888class SubscriptableIterableRelationship(DictRelationship):
889    pass
890
891
892class InaccessibleRelationship(ChildRelationship):
893    pass
894
895
896# there is no random access to set elements
897class SetRelationship(InaccessibleRelationship):
898    pass
899
900
901class NonSubscriptableIterableRelationship(InaccessibleRelationship):
902
903    param_repr_format = "[{}]"
904
905    def get_param_repr(self, force=None):
906        if force == 'yes':
907            result = "(unrepresentable)"
908        elif force == 'fake' and self.param:
909            result = self.stringify_param()
910        else:
911            result = None
912
913        return result
914
915
916class AttributeRelationship(ChildRelationship):
917    param_repr_format = ".{}"
918