diff --git a/AUTHORS.md b/AUTHORS.md index 6d22b4e0..66fa04b7 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -89,3 +89,4 @@ Authors in order of the timeline of their contributions: - [akshat62](https://github.com/akshat62) for adding wildcard/glob pattern support for `exclude_paths` and `include_paths`. - [mgorny](https://github.com/mgorny) for adding missing files to sdist and removing obsolete `MANIFEST.in`. - [Sanjays2402](https://github.com/Sanjays2402) for fixing missing type changes between equal-comparing list items (e.g. `[2]` vs `[2.0]`). +- [ChrisJr404](https://github.com/ChrisJr404) for adding `t1_name`/`t2_name` to rename the tree view's `t1`/`t2` labels. diff --git a/CHANGELOG.md b/CHANGELOG.md index 9282c421..3387397b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # DeepDiff Change log - Unreleased + - Added `t1_name` and `t2_name` parameters to rename the `t1`/`t2` labels shown in the tree view's string representation, e.g. `` (issue #531) - Fixed missing type changes between equal-comparing items inside ordered iterables, e.g. `DeepDiff([2], [2.0])` now reports the `int` → `float` change like `DeepDiff(2, 2.0)` and `DeepDiff({'a': 2}, {'a': 2.0})` already do (issue #605). - v9-1-0 diff --git a/deepdiff/diff.py b/deepdiff/diff.py index 4c7a4e5c..87147fcf 100755 --- a/deepdiff/diff.py +++ b/deepdiff/diff.py @@ -201,6 +201,8 @@ def __init__(self, progress_logger: Callable[[str], None]=logger.info, report_repetition: bool=False, significant_digits: Optional[int]=None, + t1_name: str="t1", + t2_name: str="t2", threshold_to_diff_deeper: float = 0.33, truncate_datetime: Optional[str]=None, use_enum_value: bool=False, @@ -223,6 +225,7 @@ def __init__(self, "number_format_notation, exclude_paths, include_paths, exclude_types, exclude_regex_paths, ignore_type_in_groups, " "ignore_string_type_changes, ignore_numeric_type_changes, ignore_type_subclasses, ignore_uuid_types, truncate_datetime, " "ignore_private_variables, ignore_nan_inequality, number_to_string_func, verbose_level, " + "t1_name, t2_name, " "view, hasher, hashes, max_passes, max_diffs, zip_ordered_iterables, " "cutoff_distance_for_pairs, cutoff_intersection_for_pairs, log_frequency_in_sec, cache_size, " "cache_tuning_sample_size, get_deep_distance, group_by, group_by_sort_key, cache_purge_level, log_stacktrace," @@ -233,6 +236,10 @@ def __init__(self, if _parameters: self.__dict__.update(_parameters) + # Backward compatibility: t1_name/t2_name may be absent from an + # externally-supplied _parameters dict, so fall back to the defaults. + self.t1_name = _parameters.get('t1_name', t1_name) + self.t2_name = _parameters.get('t2_name', t2_name) else: self.custom_operators = custom_operators or [] self.ignore_order = ignore_order @@ -309,6 +316,8 @@ def _group_by_sort_key(x): self.verbose_level = verbose_level else: raise ValueError(VERBOSE_LEVEL_RANGE_MSG) + self.t1_name = t1_name + self.t2_name = t2_name if cache_purge_level not in {0, 1, 2}: raise ValueError(PURGE_LEVEL_RANGE_MSG) self.view = view @@ -400,7 +409,8 @@ def _group_by_sort_key(x): self.t2 = t2 try: - root = DiffLevel(t1, t2, verbose_level=self.verbose_level) + root = DiffLevel(t1, t2, verbose_level=self.verbose_level, + t1_name=self.t1_name, t2_name=self.t2_name) # _original_type is only used to pass the original type of the data. Currently only used for numpy arrays. # The reason is that we convert the numpy array to python list and then later for distance calculations # we convert only the the last dimension of it into numpy arrays. diff --git a/deepdiff/docstrings/authors.rst b/deepdiff/docstrings/authors.rst index a216c78b..ef76dd47 100644 --- a/deepdiff/docstrings/authors.rst +++ b/deepdiff/docstrings/authors.rst @@ -130,6 +130,7 @@ Authors in order of the timeline of their contributions: - `akshat62 `__ for adding Fraction numeric support. - `akshat62 `__ for adding wildcard/glob pattern support for ``exclude_paths`` and ``include_paths``. - `mgorny `__ for adding missing files to sdist and removing obsolete ``MANIFEST.in``. +- `ChrisJr404 `__ for adding ``t1_name``/``t2_name`` to rename the tree view's ``t1``/``t2`` labels. .. _Sep Dehpour (Seperman): http://www.zepworks.com diff --git a/deepdiff/docstrings/diff_doc.rst b/deepdiff/docstrings/diff_doc.rst index 5ab9d60e..bc8b2a6d 100644 --- a/deepdiff/docstrings/diff_doc.rst +++ b/deepdiff/docstrings/diff_doc.rst @@ -199,6 +199,12 @@ significant_digits : int >= 0, default=None truncate_datetime: string, default = None :ref:`truncate_datetime_label` can take value one of 'second', 'minute', 'hour', 'day' and truncate with this value datetime objects before hashing it +t1_name: string, default = "t1" + The label used for the first object when a level of the tree view is represented as a string. For example, passing ``t1_name="before"`` turns ```` into ````. This only affects the ``repr`` of tree view levels; it has no effect on the text view or the comparison itself. + +t2_name: string, default = "t2" + The label used for the second object when a level of the tree view is represented as a string. See ``t1_name`` above. + threshold_to_diff_deeper: float, default = 0.33 :ref:`threshold_to_diff_deeper_label` is a number between 0 and 1. When comparing dictionaries that have a small intersection of keys, we will report the dictionary as a new_value instead of reporting individual keys changed. If you set it to zero, you get the same results as DeepDiff 7.0.1 and earlier, which means this feature is disabled. The new default is 0.33 which means if less that one third of keys between dictionaries intersect, report it as a new object. diff --git a/deepdiff/docstrings/view.rst b/deepdiff/docstrings/view.rst index f3797b52..37b05604 100644 --- a/deepdiff/docstrings/view.rst +++ b/deepdiff/docstrings/view.rst @@ -110,6 +110,15 @@ Value of an item has changed (Tree View) >>> changed.up +Custom labels for the compared objects (Tree View) + The ``t1`` and ``t2`` labels shown in the string representation can be renamed via the ``t1_name`` and ``t2_name`` parameters. This is handy when the two objects have meaningful roles such as "before" and "after". + + >>> t1 = {1:1, 2:2, 3:3} + >>> t2 = {1:1, 2:4, 3:3} + >>> ddiff = DeepDiff(t1, t2, view='tree', t1_name="before", t2_name="after") + >>> ddiff + {'values_changed': []} + List difference (Tree View) >>> t1 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":[1, 2, 3, 4]}} >>> t2 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":[1, 2]}} diff --git a/deepdiff/model.py b/deepdiff/model.py index a540ebfb..b77c0574 100644 --- a/deepdiff/model.py +++ b/deepdiff/model.py @@ -533,7 +533,9 @@ def __init__(self, child_rel1: Optional['ChildRelationship'] = None, child_rel2: Optional['ChildRelationship'] = None, additional: Optional[Dict[str, Any]] = None, - verbose_level: int = 1) -> None: + verbose_level: int = 1, + t1_name: str = "t1", + t2_name: str = "t2") -> None: """ :param child_rel1: Either: - An existing ChildRelationship object describing the "down" relationship for t1; or @@ -589,6 +591,11 @@ def __init__(self, self.verbose_level = verbose_level + # Labels used for t1 and t2 when this level is represented as a string. + # Customizable so the tree view repr can use meaningful names, e.g. "before"/"after". + self.t1_name = t1_name + self.t2_name = t2_name + def __repr__(self) -> str: if self.verbose_level: from deepdiff.summarize import summarize @@ -599,7 +606,8 @@ def __repr__(self) -> str: else: t1_repr = summarize(self.t1, max_length=35) t2_repr = summarize(self.t2, max_length=35) - result = "<{} t1:{}, t2:{}>".format(self.path(), t1_repr, t2_repr) + result = "<{} {}:{}, {}:{}>".format( + self.path(), self.t1_name, t1_repr, self.t2_name, t2_repr) else: result = "<{}>".format(self.path()) return result @@ -792,7 +800,8 @@ def create_deeper(self, """ level = self.all_down result = DiffLevel( - new_t1, new_t2, down=None, up=level, report_type=report_type, verbose_level=self.verbose_level) + new_t1, new_t2, down=None, up=level, report_type=report_type, verbose_level=self.verbose_level, + t1_name=self.t1_name, t2_name=self.t2_name) level.down = result level.auto_generate_child_rel( klass=child_relationship_class, param=child_relationship_param, param2=child_relationship_param2) diff --git a/tests/test_model.py b/tests/test_model.py index 383ff81e..72cfb577 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -268,6 +268,24 @@ def test_repr_very_long(self): self.lowest.verbose_level = level assert item_repr == '' + def test_repr_custom_t1_t2_names(self): + node = DiffLevel(2, 3, t1_name="before", t2_name="after") + assert repr(node) == "" + + def test_custom_t1_t2_names_propagate_to_children(self): + diff = DeepDiff({'a': {'b': 1}}, {'a': {'b': 2}}, view='tree', + t1_name='expected', t2_name='actual') + level = diff['values_changed'][0] + assert repr(level) == "" + + def test_default_t1_t2_names_unchanged(self): + diff = DeepDiff({'a': 1}, {'a': 2}, view='tree') + assert repr(diff['values_changed'][0]) == "" + + def test_custom_t1_t2_names_do_not_affect_text_view(self): + diff = DeepDiff({'a': 1}, {'a': 2}, t1_name='before', t2_name='after') + assert diff == {'values_changed': {"root['a']": {'new_value': 2, 'old_value': 1}}} + def test_repetition_attribute_and_repr(self): t1 = [1, 1] t2 = [1]