Skip to content

Commit 4537e9e

Browse files
Merge pull request #42 from networktocode/gfm-issue-41
Add "summary" method to Diff/DiffElement, fix an issue in DiffElement.dict()
2 parents cf15ab7 + 320e015 commit 4537e9e

File tree

4 files changed

+178
-54
lines changed

4 files changed

+178
-54
lines changed

diffsync/diff.py

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,20 @@ def order_children_default(cls, children: Mapping) -> Iterator["DiffElement"]:
9595
for child in children.values():
9696
yield child
9797

98+
def summary(self) -> Mapping[Text, int]:
99+
"""Build a dict summary of this Diff and its child DiffElements."""
100+
summary = {
101+
"create": 0,
102+
"update": 0,
103+
"delete": 0,
104+
"no-change": 0,
105+
}
106+
for child in self.get_children():
107+
child_summary = child.summary()
108+
for key in summary:
109+
summary[key] += child_summary[key]
110+
return summary
111+
98112
def str(self, indent: int = 0):
99113
"""Build a detailed string representation of this Diff and its child DiffElements."""
100114
margin = " " * indent
@@ -241,7 +255,7 @@ def get_attrs_diffs(self) -> Mapping[Text, Mapping[Text, Any]]:
241255
242256
Returns:
243257
dict: of the form `{"-": {key1: <value>, key2: ...}, "+": {key1: <value>, key2: ...}}`,
244-
where the `"-"` or `"+"` dicts may be empty.
258+
where the `"-"` or `"+"` dicts may be absent.
245259
"""
246260
if self.source_attrs is not None and self.dest_attrs is not None:
247261
return {
@@ -257,10 +271,10 @@ def get_attrs_diffs(self) -> Mapping[Text, Mapping[Text, Any]]:
257271
},
258272
}
259273
if self.source_attrs is None and self.dest_attrs is not None:
260-
return {"-": {key: self.dest_attrs[key] for key in self.get_attrs_keys()}, "+": {}}
274+
return {"-": {key: self.dest_attrs[key] for key in self.get_attrs_keys()}}
261275
if self.source_attrs is not None and self.dest_attrs is None:
262-
return {"-": {}, "+": {key: self.source_attrs[key] for key in self.get_attrs_keys()}}
263-
return {"-": {}, "+": {}}
276+
return {"+": {key: self.source_attrs[key] for key in self.get_attrs_keys()}}
277+
return {}
264278

265279
def add_child(self, element: "DiffElement"):
266280
"""Attach a child object of type DiffElement.
@@ -297,6 +311,23 @@ def has_diffs(self, include_children: bool = True) -> bool:
297311

298312
return False
299313

314+
def summary(self) -> Mapping[Text, int]:
315+
"""Build a summary of this DiffElement and its children."""
316+
summary = {
317+
"create": 0,
318+
"update": 0,
319+
"delete": 0,
320+
"no-change": 0,
321+
}
322+
if self.action:
323+
summary[self.action] += 1
324+
else:
325+
summary["no-change"] += 1
326+
child_summary = self.child_diff.summary()
327+
for key in summary:
328+
summary[key] += child_summary[key]
329+
return summary
330+
300331
def str(self, indent: int = 0):
301332
"""Build a detailed string representation of this DiffElement and its children."""
302333
margin = " " * indent
@@ -325,9 +356,9 @@ def dict(self) -> Mapping[Text, Mapping[Text, Any]]:
325356
"""Build a dictionary representation of this DiffElement and its children."""
326357
attrs_diffs = self.get_attrs_diffs()
327358
result = {}
328-
if attrs_diffs.get("-"):
359+
if "-" in attrs_diffs:
329360
result["-"] = attrs_diffs["-"]
330-
if attrs_diffs.get("+"):
361+
if "+" in attrs_diffs:
331362
result["+"] = attrs_diffs["+"]
332363
if self.child_diff.has_diffs():
333364
result.update(self.child_diff.dict())

tests/unit/conftest.py

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import pytest
2020

2121
from diffsync import DiffSync, DiffSyncModel
22-
from diffsync.diff import Diff
22+
from diffsync.diff import Diff, DiffElement
2323
from diffsync.exceptions import ObjectNotCreated, ObjectNotUpdated, ObjectNotDeleted
2424

2525

@@ -357,3 +357,70 @@ class TrackedDiff(Diff):
357357
def complete(self):
358358
"""Function called when the Diff has been fully constructed and populated with data."""
359359
self.is_complete = True
360+
361+
362+
@pytest.fixture
363+
def diff_with_children():
364+
"""Provide a Diff which has multiple children, some of which have children of their own."""
365+
diff = Diff()
366+
367+
# person_element_1 only exists in the source
368+
person_element_1 = DiffElement("person", "Jimbo", {"name": "Jimbo"})
369+
person_element_1.add_attrs(source={})
370+
diff.add(person_element_1)
371+
372+
# person_element_2 only exists in the dest
373+
person_element_2 = DiffElement("person", "Sully", {"name": "Sully"})
374+
person_element_2.add_attrs(dest={})
375+
diff.add(person_element_2)
376+
377+
# device_element has no diffs of its own, but has a child intf_element
378+
device_element = DiffElement("device", "device1", {"name": "device1"})
379+
diff.add(device_element)
380+
381+
# intf_element exists in both source and dest as a child of device_element, and has differing attrs
382+
intf_element = DiffElement("interface", "eth0", {"device_name": "device1", "name": "eth0"})
383+
source_attrs = {"interface_type": "ethernet", "description": "my interface"}
384+
dest_attrs = {"description": "your interface"}
385+
intf_element.add_attrs(source=source_attrs, dest=dest_attrs)
386+
device_element.add_child(intf_element)
387+
388+
# address_element exists in both source and dest but has no diffs
389+
address_element = DiffElement("address", "RTP", {"name": "RTP"})
390+
address_element.add_attrs(source={"state": "NC"}, dest={"state": "NC"})
391+
diff.add(address_element)
392+
393+
return diff
394+
395+
396+
@pytest.fixture()
397+
def diff_element_with_children():
398+
"""Construct a DiffElement that has some diffs of its own as well as a child diff with additional diffs."""
399+
# parent_element has differing "role" attribute, while "location" does not differ
400+
parent_element = DiffElement("device", "device1", {"name": "device1"})
401+
parent_element.add_attrs(source={"role": "switch", "location": "RTP"}, dest={"role": "router", "location": "RTP"})
402+
403+
# child_element_1 has differing "description" attribute, while "interface_type" is only present on one side
404+
child_element_1 = DiffElement("interface", "eth0", {"device_name": "device1", "name": "eth0"})
405+
source_attrs = {"interface_type": "ethernet", "description": "my interface"}
406+
dest_attrs = {"description": "your interface"}
407+
child_element_1.add_attrs(source=source_attrs, dest=dest_attrs)
408+
409+
# child_element_2 only exists on source, and has no attributes
410+
child_element_2 = DiffElement("interface", "lo0", {"device_name": "device1", "name": "lo0"})
411+
child_element_2.add_attrs(source={})
412+
413+
# child_element_3 only exists on dest, and has some attributes
414+
child_element_3 = DiffElement("interface", "lo1", {"device_name": "device1", "name": "lo1"})
415+
child_element_3.add_attrs(dest={"description": "Loopback 1"})
416+
417+
# child_element_4 is identical between source and dest
418+
child_element_4 = DiffElement("interface", "lo100", {"device_name": "device1", "name": "lo100"})
419+
child_element_4.add_attrs(source={"description": "Loopback 100"}, dest={"description": "Loopback 100"})
420+
421+
parent_element.add_child(child_element_1)
422+
parent_element.add_child(child_element_2)
423+
parent_element.add_child(child_element_3)
424+
parent_element.add_child(child_element_4)
425+
426+
return parent_element

tests/unit/test_diff.py

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ def test_diff_empty():
3131
assert list(diff.get_children()) == []
3232

3333

34+
def test_diff_summary_with_no_diffs():
35+
diff = Diff()
36+
37+
assert diff.summary() == {"create": 0, "update": 0, "delete": 0, "no-change": 0}
38+
39+
3440
def test_diff_str_with_no_diffs():
3541
diff = Diff()
3642

@@ -74,39 +80,40 @@ def test_diff_children():
7480
assert diff.has_diffs()
7581

7682

77-
def test_diff_str_with_diffs():
78-
diff = Diff()
79-
device_element = DiffElement("device", "device1", {"name": "device1"})
80-
diff.add(device_element)
81-
intf_element = DiffElement("interface", "eth0", {"device_name": "device1", "name": "eth0"})
82-
source_attrs = {"interface_type": "ethernet", "description": "my interface"}
83-
dest_attrs = {"description": "your interface"}
84-
intf_element.add_attrs(source=source_attrs, dest=dest_attrs)
85-
diff.add(intf_element)
83+
def test_diff_summary_with_diffs(diff_with_children):
84+
# Create person "Jimbo"
85+
# Delete person "Sully"
86+
# Update interface "device1_eth0"
87+
# No change to address "RTP" and device "device1"
88+
assert diff_with_children.summary() == {"create": 1, "update": 1, "delete": 1, "no-change": 2}
89+
8690

87-
# Since device_element has no diffs, we don't have any "device" entry in the diff string:
91+
def test_diff_str_with_diffs(diff_with_children):
92+
# Since the address element has no diffs, we don't have any "address" entry in the diff string:
8893
assert (
89-
diff.str()
94+
diff_with_children.str()
9095
== """\
91-
interface
92-
interface: eth0
93-
description source(my interface) dest(your interface)\
96+
person
97+
person: Jimbo MISSING in dest
98+
person: Sully MISSING in source
99+
device
100+
device: device1
101+
interface
102+
interface: eth0
103+
description source(my interface) dest(your interface)\
94104
"""
95105
)
96106

97107

98-
def test_diff_dict_with_diffs():
99-
diff = Diff()
100-
device_element = DiffElement("device", "device1", {"name": "device1"})
101-
diff.add(device_element)
102-
intf_element = DiffElement("interface", "eth0", {"device_name": "device1", "name": "eth0"})
103-
source_attrs = {"interface_type": "ethernet", "description": "my interface"}
104-
dest_attrs = {"description": "your interface"}
105-
intf_element.add_attrs(source=source_attrs, dest=dest_attrs)
106-
diff.add(intf_element)
107-
108-
assert diff.dict() == {
109-
"interface": {"eth0": {"-": {"description": "your interface"}, "+": {"description": "my interface"}}},
108+
def test_diff_dict_with_diffs(diff_with_children):
109+
# Since the address element has no diffs, we don't have any "address" entry in the diff dict:
110+
assert diff_with_children.dict() == {
111+
"device": {
112+
"device1": {
113+
"interface": {"eth0": {"+": {"description": "my interface"}, "-": {"description": "your interface"}}}
114+
}
115+
},
116+
"person": {"Jimbo": {"+": {}}, "Sully": {"-": {}}},
110117
}
111118

112119

tests/unit/test_diff_element.py

Lines changed: 40 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ def test_diff_element_empty():
4242
assert element2.dest_name == "D1"
4343

4444

45+
def test_diff_element_summary_with_no_diffs():
46+
element = DiffElement("interface", "eth0", {"device_name": "device1", "name": "eth0"})
47+
assert element.summary() == {"create": 0, "update": 0, "delete": 0, "no-change": 1}
48+
49+
4550
def test_diff_element_str_with_no_diffs():
4651
element = DiffElement("interface", "eth0", {"device_name": "device1", "name": "eth0"})
4752
assert element.str() == "interface: eth0 (no diffs)"
@@ -76,6 +81,14 @@ def test_diff_element_attrs():
7681
assert element.get_attrs_keys() == ["description"] # intersection of source_attrs.keys() and dest_attrs.keys()
7782

7883

84+
def test_diff_element_summary_with_diffs():
85+
element = DiffElement("interface", "eth0", {"device_name": "device1", "name": "eth0"})
86+
element.add_attrs(source={"interface_type": "ethernet", "description": "my interface"})
87+
assert element.summary() == {"create": 1, "update": 0, "delete": 0, "no-change": 0}
88+
element.add_attrs(dest={"description": "your interface"})
89+
assert element.summary() == {"create": 0, "update": 1, "delete": 0, "no-change": 0}
90+
91+
7992
def test_diff_element_str_with_diffs():
8093
element = DiffElement("interface", "eth0", {"device_name": "device1", "name": "eth0"})
8194
element.add_attrs(source={"interface_type": "ethernet", "description": "my interface"})
@@ -98,6 +111,14 @@ def test_diff_element_dict_with_diffs():
98111
assert element.dict() == {"-": {"description": "your interface"}, "+": {"description": "my interface"}}
99112

100113

114+
def test_diff_element_dict_with_diffs_no_attrs():
115+
element = DiffElement("interface", "eth0", {"device_name": "device1", "name": "eth0"})
116+
element.add_attrs(source={})
117+
assert element.dict() == {"+": {}}
118+
element.add_attrs(dest={})
119+
assert element.dict() == {"+": {}, "-": {}}
120+
121+
101122
def test_diff_element_children():
102123
"""Test the basic functionality of the DiffElement class when storing and retrieving child elements."""
103124
child_element = DiffElement("interface", "eth0", {"device_name": "device1", "name": "eth0"})
@@ -118,38 +139,36 @@ def test_diff_element_children():
118139
assert not parent_element.has_diffs(include_children=False)
119140

120141

121-
def test_diff_element_str_with_child_diffs():
122-
parent_element = DiffElement("device", "device1", {"name": "device1"})
123-
parent_element.add_attrs(source={"role": "switch"}, dest={"role": "router"})
124-
child_element = DiffElement("interface", "eth0", {"device_name": "device1", "name": "eth0"})
125-
source_attrs = {"interface_type": "ethernet", "description": "my interface"}
126-
dest_attrs = {"description": "your interface"}
127-
child_element.add_attrs(source=source_attrs, dest=dest_attrs)
128-
parent_element.add_child(child_element)
142+
def test_diff_element_summary_with_child_diffs(diff_element_with_children):
143+
# create interface "lo0"
144+
# delete interface "lo1"
145+
# update device "device1" and interface "eth0"
146+
# no change to interface "lo100"
147+
assert diff_element_with_children.summary() == {"create": 1, "update": 2, "delete": 1, "no-change": 1}
148+
129149

150+
def test_diff_element_str_with_child_diffs(diff_element_with_children):
130151
assert (
131-
parent_element.str()
152+
diff_element_with_children.str()
132153
== """\
133154
device: device1
134155
role source(switch) dest(router)
135156
interface
136157
interface: eth0
137-
description source(my interface) dest(your interface)\
158+
description source(my interface) dest(your interface)
159+
interface: lo0 MISSING in dest
160+
interface: lo1 MISSING in source\
138161
"""
139162
)
140163

141164

142-
def test_diff_element_dict_with_child_diffs():
143-
parent_element = DiffElement("device", "device1", {"name": "device1"})
144-
parent_element.add_attrs(source={"role": "switch"}, dest={"role": "router"})
145-
child_element = DiffElement("interface", "eth0", {"device_name": "device1", "name": "eth0"})
146-
source_attrs = {"interface_type": "ethernet", "description": "my interface"}
147-
dest_attrs = {"description": "your interface"}
148-
child_element.add_attrs(source=source_attrs, dest=dest_attrs)
149-
parent_element.add_child(child_element)
150-
151-
assert parent_element.dict() == {
165+
def test_diff_element_dict_with_child_diffs(diff_element_with_children):
166+
assert diff_element_with_children.dict() == {
152167
"-": {"role": "router"},
153168
"+": {"role": "switch"},
154-
"interface": {"eth0": {"-": {"description": "your interface"}, "+": {"description": "my interface"}}},
169+
"interface": {
170+
"eth0": {"-": {"description": "your interface"}, "+": {"description": "my interface"}},
171+
"lo0": {"+": {}},
172+
"lo1": {"-": {"description": "Loopback 1"}},
173+
},
155174
}

0 commit comments

Comments
 (0)