Skip to content

Commit

Permalink
Merge pull request #4 from grahamegee/tidy_up
Browse files Browse the repository at this point in the history
Tidy up
  • Loading branch information
grahamegee committed Oct 15, 2016
2 parents ff6e6b3 + d06812f commit ce14ca5
Show file tree
Hide file tree
Showing 8 changed files with 219 additions and 108 deletions.
28 changes: 16 additions & 12 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
language: python
python:
- "2.7"
# - "3.2"
- "3.3"
- "3.4"
- "3.5"
install:
- pip install .
- pip install coveralls
script:
nosetests --with-coverage --cover-package=diffr --cover-branches
after_success:
coveralls
- '2.7'
- '3.3'
- '3.4'
- '3.5'
install:
- pip install .
- pip install coveralls
script: nosetests --with-coverage --cover-package=diffr --cover-branches
after_success: coveralls
deploy:
provider: pypi
user: grahamegee
on:
tags: true
password:
secure: lwwQCLEIPb0IidkC9ztsj/c+PAvNQArR3xawAr8Vg8miUNJPk27sxYftkrt2wBXV3azi2ddI9N3AtTjDBtamG7TgOIvDVIKmmSYmUxmB9dSZBFI46kUI70sBp3cPb4FqAwervQRkD+G7nmcPj0Gt517J8X7crc4mS+Fec+1cQHFn6RvzzNO7Rxnwv2GWpZDJUVvBuGeC0EmiZj084mSyXYM/YKTslqefLBTVKAXZj/tCszY3OMTPufWpOeDUSO4bf2nnfZlygFmEZuiD/YbPEws+d4ZCMkYb4pLB4KYrGKoDMiPANlo1Snhb8jMM7AijrK9PyYHafl44EQI5mXR5GueSj5LDgokYZVAqUsp11v55o3zIKCeMuGUGazdqJ7qg+ATW6sAxeo1eujTIc8Q/X3lfEuKgsIcDxHQe7xImISGH3nnq0BJzT1KVRvvhM3tmedCG0Qg6nDHzRn9uB17SPFkUYnzTBilVG6wevrklAcK2Mbo8vwYMO5PslRBAsee68vy/QzLvqHC9yRql2vw2ccTt61aDYI0YAogWlxdr0g4AaWQuvV50sELIGrOq15cRLH4MkegkLdNVkDaVsc8vMDzNQscSYV1Jzyr/qBk0ObfEED7vMRccJVweXNp48s0t1pNHaHrsLxekrjhBqDSDBREjiVpAg1x+pBnYrUm3bQw=
90 changes: 59 additions & 31 deletions diffr/data_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,9 @@ def diffs_are_equal(diff_a, diff_b):
# somone is bound to try and use this library with an implementation of
# ordered set, I can only deal with the ones I know about.
if is_ordered(diff_a.type):
return diff_a.diffs == diff_b.diffs
return diff_a._diffs == diff_b._diffs
else:
return sequences_contain_same_items(diff_a.diffs, diff_b.diffs)
return sequences_contain_same_items(diff_a._diffs, diff_b._diffs)


def _indices_of_changed_items(diff_list, context_limit):
Expand Down Expand Up @@ -96,7 +96,7 @@ def context_slice(diff_list, context_limit):


def recursively_set_context_limit(diff, context_limit):
diff.context_limit = context_limit
diff._context_limit = context_limit
for diff_item in diff:
if type(diff_item) == DiffItem:
item = diff_item.item
Expand All @@ -119,22 +119,26 @@ class Diff(object):
It can also can be wrapped in a DiffItem as an item of a higher level Diff
ie the objects being diffed are nested.
:attribute type: The type of the objects being diffed
:attribute diffs: A list containing all of the DiffItems including an
unchanged ones.
:attribute depth: Indicates how deep this diff is in a nested diff.
Diffs are uniquely identified by the values of their attributes.
:property type: The type of the objects being diffed
:property depth: Indicates how deep this diff is in a nested diff.
'''
def __init__(self, obj_type, diffs, depth=0):
self.type = obj_type
self.diffs = tuple(diffs)
self._type = obj_type
self._diffs = tuple(diffs)
# flag used by __format__ and the DiffContext context manager
self.context_limit = None
self.depth = depth
self._indent = ' ' * depth
self.start = unchanged('{}('.format(self.type))
self.end = unchanged(')')
self._context_limit = None
self._depth = depth
self._indent = ' ' * self._depth
self._start = unchanged('{}('.format(self._type.__name__))
self._end = unchanged(')')

@property
def type(self):
return self._type

@property
def depth(self):
return self._depth

def _extract_context(self, context_block):
if hasattr(context_block[0], 'context') and context_block[0].context:
Expand All @@ -143,33 +147,33 @@ def _extract_context(self, context_block):
return (from_start, from_end, to_start, to_end)

def __len__(self):
return len(self.diffs)
return len(self._diffs)

def __bool__(self):
if self.diffs:
return any(d.state != unchanged for d in self.diffs)
if self._diffs:
return any(d.state != unchanged for d in self._diffs)
return False

def __nonzero__(self):
# python 2.7
return self.__bool__()

def __iter__(self):
return iter(self.diffs)
return iter(self._diffs)

def __getitem__(self, index):
cls = type(self)
if isinstance(index, slice):
return cls(self.type, self.diffs[index], self.depth)
return cls(self._type, self._diffs[index], self._depth)
elif isinstance(index, Integral):
return self.diffs[index]
return self._diffs[index]
else:
msg = '{.__name__} indices must be integers'
raise TypeError(msg.format(cls))

def __eq__(self, other):
eq = (
self.type == other.type,
self._type == other._type,
diffs_are_equal(self, other))
return all(eq)

Expand All @@ -194,25 +198,25 @@ def _make_context_banner(self, context_block):
insert('+'), insert(t_s), insert(t_e))

def __str__(self):
if not self.diffs:
return self.start + self.end
if not self._diffs:
return self._start + self._end

output = [self.start]
if self.context_limit is not None:
items_to_display = context_slice(self.diffs, self.context_limit)
output = [self._start]
if self._context_limit is not None:
items_to_display = context_slice(self._diffs, self._context_limit)
else:
items_to_display = [self.diffs]
items_to_display = [self._diffs]

for context_block in items_to_display:
banner = self._make_context_banner(context_block)
if banner:
output.append(banner)
if self.type is str:
if self._type is str:
self._make_string_diff_output(output, context_block)
else:
self._make_diff_output(output, context_block)

output.append(self._indent + self.end)
output.append(self._indent + self._end)
return '\n'.join(output)

def _make_string_diff_output(self, output, context_block):
Expand Down Expand Up @@ -272,6 +276,18 @@ def __eq__(self, other):
def __ne__(self, other):
return not self == other

def __format__(self, fmt_spec):
if fmt_spec.endswith('c'):
if self.state == changed:
context_limit = int(fmt_spec[:-1])
with adjusted_context_limit(self.item, context_limit):
formatted_string = str(self)
return formatted_string
raise ValueError(
'format specifier \'c\' can be only used on Diff instances')
else:
return str(self)


class MappingDiffItem(DiffItem):
'''
Expand Down Expand Up @@ -304,3 +320,15 @@ def __eq__(self, other):

def __ne__(self, other):
return not self == other

def __format__(self, fmt_spec):
if fmt_spec.endswith('c'):
if self.state == changed:
context_limit = int(fmt_spec[:-1])
with adjusted_context_limit(self.value, context_limit):
formatted_string = str(self)
return formatted_string
raise ValueError(
'format specifier \'c\' can only be used on Diff instances')
else:
return str(self)
10 changes: 5 additions & 5 deletions diffr/patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ def object_constructor(obj):
def patch_sequence(obj, diff):
patched = deepcopy(obj)
offset = 0
for diff_item in diff.diffs:
for diff_item in diff:
start, end, _, _ = diff_item.context
if diff_item.state is remove:
validate_removal(lambda: (obj[start], diff_item))
Expand Down Expand Up @@ -147,7 +147,7 @@ def patch_mapping(obj, diff):
# followed by a remove, as it stands this would cause patch to actually
# remove it completely!
patched = deepcopy(obj)
for map_item in diff.diffs:
for map_item in diff:
if map_item.state is remove:
validate_mapping_removal(
lambda: (map_item.value, patched[map_item.key]))
Expand Down Expand Up @@ -184,7 +184,7 @@ def patch_ordered_mapping(obj, diff):
# treated pretty much in the same way as a sequence.
patched_items = list(obj.items())
offset = 0
for i, diff_item in enumerate(diff.diffs):
for i, diff_item in enumerate(diff):
if diff_item.state is remove:
validate_removal(lambda: (patched_items[i + offset], diff_item))
patched_items = (
Expand Down Expand Up @@ -214,9 +214,9 @@ def patch_ordered_mapping(obj, diff):


def patch_set(obj, diff):
removals = set([di.item for di in diff.diffs if di.state is remove])
removals = set([di.item for di in diff if di.state is remove])
if removals.intersection(obj) != removals:
raise ValueError(
'Some items subject to removal do not exist in patch target')
inserts = set([di.item for di in diff.diffs if di.state is insert])
inserts = set([di.item for di in diff if di.state is insert])
return type(obj)(obj.difference(removals).union(inserts))
10 changes: 10 additions & 0 deletions docs/releases/1.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
### version 1.0
Support for recursively diffing and patching nested python data structures. The following types are supported:
* Subclasses of:
- Sequence
- Mapping
- Set
* OrderedDict
* named_tuple

A structured Diff object that can be programmatically inspected
14 changes: 14 additions & 0 deletions examples/formatted_output.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from diffr import diff


a = [0] * 5 + ['-' * 5 + 'a' + '-' * 5] + [0] * 5
b = [0] * 5 + ['-' * 5 + 'b' + '-' * 5] + [0] * 5
d = diff(a, b)
print('Print out the diff in full by default')
print(d)
print()
print('Reduce the context by formatting with context_limit = 3')
print('{:3c}'.format(d))
print()
print('Reduce the context further (context_limit = 1)')
print(format(d, '1c'))
62 changes: 31 additions & 31 deletions examples/programmatic_diff_inspection.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,59 +9,59 @@
# the displayed diff
print(d)
print('---------------------------------------------------------')

# the whole diff
print('---------------------------------------------------------')
print('can access the full diff\n')
print(''.join([str(i) for i in d.diffs]))
print('---------------------------------------------------------')

print(''.join([str(i) for i in d]))
print('---------------------------------------------------------')
print('diff item states\n')
# inspect diff item state
print('item {} at index {} is a removal: {}'.format(
str(d.diffs[0]),
0,
d.diffs[0].state == remove)
str(d[0]), 0, d[0].state == remove)
)
print('---------------------------------------------------------')

print('---------------------------------------------------------')
print('Breaking diffs up into managable chunks')
print('diff item context\n')
print(
'middle unchanged portion of diff = "{}"'.format(
''.join([str(i) for i in d.diffs[13:19]])
''.join([str(i) for i in d[13:19]])
)
)
print('---------------------------------------------------------')
print('use context attribute to slice the data structures\n')
a_start, _, b_start, _ = d.diffs[13].context
_, a_end, _, b_end = d.diffs[18].context
a_start, _, b_start, _ = d[13].context
_, a_end, _, b_end = d[18].context
print(
'a context slice: "{}", b context slice: "{}"'.format(
a[a_start:a_end], b[b_start:b_end]))
print('---------------------------------------------------------')
print('inspect context blocks\n')
cb1, cb2 = d.context_blocks
a1_start, a1_end, b1_start, b1_end = cb1.context
a2_start, a2_end, b2_start, b2_end = cb2.context
print(
'first context block: a = "{}", b = "{}"'.format(
a[a1_start: a1_end], b[b1_start: b1_end]
)
)
print(
'last context block: a = "{}", b = "{}"'.format(
a[a2_start: a2_end], b[b2_start: b2_end]
)
)
print('---------------------------------------------------------')
print('diff comparison\n')
a = {'a' : 3}
b = {'a' : 4}
a = {'a': 3}
b = {'a': 4}
d_nested = diff([1, a], [1, b])
d = diff(a, b)
print(d)
print(d_nested)
print(d == d_nested.diffs[1].item)
print('Item 1 of the nested diff == the diff: {}'.format(d == d_nested[1].item))
print('---------------------------------------------------------')
print('filter on inserts')
a = 'a' * 5 + 'b' * 5
b = 'b' * 5 + 'a' * 5
print(''.join([str(i) for i in diff(a, b) if i.state == insert]))
print('filter on removals')
print(''.join([str(i) for i in diff(a, b) if i.state == remove]))
print('---------------------------------------------------------')
print('Diff evaluates false if it\'s empty or if there are no changes')
empty_diff = diff([], [])
diff_with_no_changes = diff('abc', 'abc')
print('bool({}) == {}'.format(empty_diff, bool(empty_diff)))
print('bool({}) == {}'.format(diff_with_no_changes, bool(diff_with_no_changes)))
print('---------------------------------------------------------')
print('Inspecting diff properties')
a = {'a': 3}
b = {'a': 4}
d_nested = diff([1, a], [1, b])
print('Depth of outer diff is {}'.format(d_nested.depth))
print('Depth of inner diff is {}'.format(d_nested[1].item.depth))
print('---------------------------------------------------------')
print('Type of outer diff is {}'.format(d_nested.type))
print('Type of inner diff is {}'.format(d_nested[1].item.type))
Loading

0 comments on commit ce14ca5

Please sign in to comment.