Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

In-place patch/revert #92

Merged
merged 2 commits into from
Oct 10, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
27 changes: 22 additions & 5 deletions dictdiffer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,9 +245,19 @@ def check(key):
yield CHANGE, dotted_node, (deepcopy(first), deepcopy(second))


def patch(diff_result, destination):
"""Patch the diff result to the old dictionary."""
destination = deepcopy(destination)
def patch(diff_result, destination, in_place=False):
"""Patch the diff result to the destination dictionary.

:param diff_result: Changes returned by ``diff``.
:param destination: Structure to apply the changes to.
:param in_place: By default, destination dictionary is deep copied
before applying the patch, and the copy is returned.
Setting ``in_place=True`` means that patch will apply
the changes directly to and return the destination
structure.
"""
if not in_place:
destination = deepcopy(destination)

def add(node, changes):
for key, value in changes:
Expand Down Expand Up @@ -330,7 +340,7 @@ def change(node, changes):
yield swappers[action](node, change)


def revert(diff_result, destination):
def revert(diff_result, destination, in_place=False):
"""Call swap function to revert patched dictionary object.

Usage example:
Expand All @@ -341,5 +351,12 @@ def revert(diff_result, destination):
>>> revert(diff(first, second), second)
{'a': 'b'}

:param diff_result: Changes returned by ``diff``.
:param destination: Structure to apply the changes to.
:param in_place: By default, destination dictionary is deep
copied before being reverted, and the copy
is returned. Setting ``in_place=True`` means
that revert will apply the changes directly to
and return the destination structure.
"""
return patch(swap(diff_result), destination)
return patch(swap(diff_result), destination, in_place)
2 changes: 1 addition & 1 deletion dictdiffer/merge.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class Merger(object):
def __init__(self,
lca, first, second, actions,
path_limits=[], additional_info=None):
"""Initialization of Merger object.
"""Initialize the Merger object.

:param lca: latest common ancestor of the two diverging data structures
:param first: first data structure
Expand Down
12 changes: 6 additions & 6 deletions dictdiffer/resolve.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class UnresolvedConflictsException(Exception):
"""

def __init__(self, unresolved_conflicts):
"""Initialization of UnresolvedConflictsException.
"""Initialize the UnresolvedConflictsException.

:param unresolved_conflicts: list of unresolved conflicts.
dictdiffer.conflict.Conflict objects.
Expand All @@ -31,11 +31,11 @@ def __init__(self, unresolved_conflicts):
self.content = unresolved_conflicts

def __repr__(self):
"""String representation."""
"""Return the object representation."""
return self.message

def __str__(self):
"""String representation."""
"""Return the string representation."""
return self.message


Expand All @@ -56,7 +56,7 @@ class Resolver(object):
"""

def __init__(self, actions, additional_info=None):
"""Initialization of the Resolver.
"""Initialize the Resolver.

:param action: dict object containing the necessary resolution
functions
Expand Down Expand Up @@ -97,7 +97,7 @@ def _consecutive_slices(self, iterable):
return (iterable[:i] for i in reversed(range(1, len(iterable)+1)))

def resolve_conflicts(self, first_patches, second_patches, conflicts):
"""Method to present the given conflicts to the actions.
"""Convert the given conflicts to the actions.

The method, will map the conflicts to an actions based on the path of
the conflict. In case that the resolution attempt is not successful, it
Expand Down Expand Up @@ -134,7 +134,7 @@ def resolve_conflicts(self, first_patches, second_patches, conflicts):
raise UnresolvedConflictsException(self.unresolved_conflicts)

def manual_resolve_conflicts(self, picks):
"""Method to manually resolve conflicts.
"""Resolve manually the conflicts.

This method resolves conflicts that could not be resolved in an
automatic way. The picks parameter utilized the *take* attribute of the
Expand Down
6 changes: 3 additions & 3 deletions dictdiffer/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@


class WildcardDict(dict):
"""A dictionary that provides special wildcard keys.
"""Provide possibility to use special wildcard keys to access values.

Those wildcards are:
*: wildcard for everything that follows
Expand All @@ -34,7 +34,7 @@ class WildcardDict(dict):
"""

def __init__(self, values=None):
"""A dictionary that provides special wildcard keys.
"""Set lookup key indices.

:param values: a dictionary
"""
Expand Down Expand Up @@ -130,7 +130,7 @@ def __init__(self, path_limits=[], final_key=None):
containing[self.final_key] = True

def path_is_limit(self, key_path):
"""Querie the PathLimit object if the given key_path is a limit.
"""Query the PathLimit object if the given key_path is a limit.

>>> pl = PathLimit( [('foo', 'bar')] , final_key='!@#$%FINAL')
>>> pl.path_is_limit( ('foo', 'bar') )
Expand Down
12 changes: 12 additions & 0 deletions tests/test_dictdiffer.py
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,18 @@ def test_dict_combined_key_type(self):
assert second == patch(first_patch, first)
assert first_patch[0] == list(diff(first, second))[0]

def test_in_place_patch_and_revert(self):
first = {'a': 1}
second = {'a': 2}
changes = list(diff(first, second))
patched_copy = patch(changes, first)
assert first != patched_copy
reverted_in_place = revert(changes, patched_copy, in_place=True)
assert first == reverted_in_place
assert patched_copy == reverted_in_place
patched_in_place = patch(changes, first, in_place=True)
assert first == patched_in_place


class SwapperTests(unittest.TestCase):
def test_addition(self):
Expand Down