Skip to content

Commit 9ed2e7c

Browse files
authored
Merge pull request #9 from networktocode-llc/lvrfrc87
WIP: Working on #5 and #6
2 parents e46b79f + 4e462f8 commit 9ed2e7c

16 files changed

+71
-201
lines changed

.travis.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ stages:
44
- "lint"
55
- "test"
66

7-
if: "type IN (pull_request)" # Add in "branch" as an option if desired for branch testing as well
7+
if: "type IN (pull_request)" # Add in "branch" as an option if desired for branch testing as well
88
language: "python"
99
services:
1010
- "docker"
@@ -30,7 +30,7 @@ jobs:
3030
- "pip install invoke toml"
3131
script:
3232
- "invoke black"
33-
- "invoke bandit" # Bandit fails to function on > Py3.8 https://github.com/PyCQA/bandit/issues/639
33+
- "invoke bandit" # Bandit fails to function on > Py3.8 https://github.com/PyCQA/bandit/issues/639
3434
# - "invoke pydocstyle"
3535
- "invoke flake8"
3636
- "invoke yamllint"

netcompare/check_type.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,13 @@ def init(*args):
1616
check_type = args[0]
1717
if check_type == "exact_match":
1818
return ExactMatchType(*args)
19-
elif check_type == "tolerance":
19+
if check_type == "tolerance":
2020
return ToleranceType(*args)
21-
else:
22-
raise NotImplementedError
21+
raise NotImplementedError
2322

2423
@staticmethod
2524
def extract_value_from_json_path(
26-
value: Mapping, path: Mapping, exclude: List = list()
25+
value: Mapping, path: Mapping, exclude: List = None
2726
) -> Union[Mapping, List, int, str, bool]:
2827
"""Return the value contained into a Mapping for a defined path."""
2928
return extract_values_from_output(value, path, exclude)

netcompare/evaluator.py

Lines changed: 6 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,7 @@
1111

1212

1313
def diff_generator(pre_result: Mapping, post_result: Mapping) -> Dict:
14-
"""
15-
Generates diff between pre and post data based on check definition.
16-
17-
Args:
18-
pre_result: pre data result.
19-
post_result: post data result.
20-
21-
Return:
22-
output: diff between pre and post data.
23-
24-
Example:
25-
>>> from evaluator import diff_generator
26-
>>> pre_data = {"result": [{"bgp_neigh": "10.17.254.2","state": "Idle"}]}
27-
>>> post_data = {"result": [{"bgp_neigh": "10.17.254.2","state": "Up"}]}
28-
>>> check_definition = {"check_type": "exact_match", "value_path": "result[*].[state]", "reference_key_path": "result[*].bgp_neigh"}
29-
>>> print(diff_generator(check_definition, post_data, check_definition))
30-
{'10.17.254.2': {'state': {'new_value': 'Up', 'old_value': 'Idle'}}}
31-
"""
14+
"""Generates diff between pre and post data based on check definition."""
3215
diff_result = DeepDiff(pre_result, post_result)
3316

3417
result = diff_result.get("values_changed", {})
@@ -45,38 +28,7 @@ def diff_generator(pre_result: Mapping, post_result: Mapping) -> Dict:
4528

4629

4730
def get_diff_iterables_items(diff_result: Mapping) -> Mapping:
48-
"""
49-
Return a dict with new and missing values where the values are in a list.
50-
51-
Args:
52-
diff_result: dict retruned from a DeepDiff compare.
53-
54-
Return:
55-
defaultdict
56-
57-
Example:
58-
>>> diff_result = {
59-
"iterable_item_added": {
60-
"root['Ethernet3'][1]": {
61-
"hostname": "ios-xrv-unittest",
62-
"port": "Gi0/0/0/0"
63-
},
64-
"root['Ethernet3'][2]": {
65-
"hostname": "ios-xrv-unittest",
66-
"port": "Gi0/0/0/1"
67-
}
68-
}
69-
}
70-
>>> get_diff_iterables_items(diff_result)
71-
defaultdict(functools.partial(<class 'collections.defaultdict'>, <class 'list'>),
72-
{"['Ethernet3']": defaultdict(list,
73-
{'new': [
74-
{'hostname': 'ios-xrv-unittest', 'port': 'Gi0/0/0/0'},
75-
{'hostname': 'ios-xrv-unittest', 'port': 'Gi0/0/0/1'}
76-
]}
77-
)}
78-
)
79-
"""
31+
"""Return a dict with new and missing values where the values are in a list."""
8032
# DeepDiff iterable_items are returned when the source data is a list
8133
# and provided in the format: "root['Ethernet3'][1]"
8234
# or more generically: root['KEY']['KEY']['KEY']...[numeric_index]
@@ -104,12 +56,10 @@ def get_diff_iterables_items(diff_result: Mapping) -> Mapping:
10456

10557
def fix_deepdiff_key_names(obj: Mapping) -> Mapping:
10658
"""Return a dict based on the provided dict object where the brackets and quotes are removed from the string."""
107-
# sample keys:
108-
# root[2]['10.64.207.255']['accepted_prefixes']
109-
# root['Ethernet1'][0]['port']
11059
pattern = r"'([A-Za-z0-9_\./\\-]*)'"
11160

112-
result = dict()
61+
result = {}
62+
# root[2]['10.64.207.255']['accepted_prefixes']
11363
for key, value in obj.items():
11464
key_parts = re.findall(pattern, key)
11565
partial_res = group_value(key_parts, value)
@@ -118,39 +68,14 @@ def fix_deepdiff_key_names(obj: Mapping) -> Mapping:
11868

11969

12070
def group_value(tree_list: List, value: Mapping) -> Mapping:
121-
"""
122-
Build dictionary based on value's key and reference key.
123-
124-
Args:
125-
tree_list: list of value's key and reference key
126-
value: value results
127-
128-
Return:
129-
value: Mapping of the changed values associated to reference key
130-
131-
Example:
132-
>>> tree_list = ['10.17.254.2', 'state']
133-
>>> value = {'new_value': 'Up', 'old_value': 'Idle'}
134-
{'10.17.254.2': {'state': {'new_value': 'Up', 'old_value': 'Idle'}}}
135-
"""
71+
"""Build dictionary based on value's key and reference key."""
13672
if tree_list:
13773
return {tree_list[0]: group_value(tree_list[1:], value)}
13874
return value
13975

14076

14177
def dict_merger(original_dict: Mapping, merged_dict: Mapping):
142-
"""
143-
Merge dictionaries to build final result.
144-
145-
Args:
146-
original_dict: empty dictionary to be merged
147-
merged_dict: dictionary containing returned key/value and associated reference key
148-
149-
Example:
150-
>>> original_dict = {}
151-
>>> merged_dict = {'10.17.254.2': {'state': {'new_value': 'Up', 'old_value': 'Idle'}}}
152-
{'10.17.254.2': {'state': {'new_value': 'Up', 'old_value': 'Idle'}}}
153-
"""
78+
"""Merge dictionaries to build final result."""
15479
for key in merged_dict.keys():
15580
if key in original_dict and isinstance(original_dict[key], dict) and isinstance(merged_dict[key], dict):
15681
dict_merger(original_dict[key], merged_dict[key])

netcompare/runner.py

Lines changed: 7 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,15 @@
1-
#!/ur/bin/env python3
1+
"""Library wrapper for output parsing."""
22
import re
3-
import jmespath
43
from typing import Mapping, List, Union
4+
import jmespath
55
from .utils.jmspath_parsers import jmspath_value_parser, jmspath_refkey_parser
66
from .utils.filter_parsers import exclude_filter
77
from .utils.refkey import keys_cleaner, keys_values_zipper, associate_key_of_my_value
88
from .utils.flatten import flatten_list
99

1010

1111
def extract_values_from_output(value: Mapping, path: Mapping, exclude: List) -> Union[Mapping, List, int, str, bool]:
12-
"""Return data from output depending on the check path. See unit text for complete example.
13-
14-
Args:
15-
path: "result[0].vrfs.default.peerList[*].[$peerAddress$,prefixesReceived]",
16-
value: {
17-
"jsonrpc": "2.0",
18-
"id": "EapiExplorer-1",
19-
"result": [
20-
{
21-
"vrfs": {
22-
"default": {
23-
"peerList": [
24-
{ ...
25-
exclude: ["interfaceStatistics", "interfaceCounters"]
26-
27-
Return:
28-
[{'7.7.7.7': {'prefixesReceived': 101}}, {'10.1.0.0': {'prefixesReceived': 120}}, ...
29-
"""
12+
"""Return data from output depending on the check path. See unit text for complete example."""
3013
# Get the wanted values to be evaluated if jmspath expression is defined, otherwise
3114
# use the entire output if jmespath is not defined in check. This cover the "raw" diff type.
3215
if path and not exclude:
@@ -48,11 +31,9 @@ def extract_values_from_output(value: Mapping, path: Mapping, exclude: List) ->
4831
for item in element:
4932
if isinstance(item, dict):
5033
raise TypeError(
51-
'Must be list of lists i.e. [["Idle", 75759616], ["Idle", 75759620]]. You have {}\'.'.format(
52-
wanted_value
53-
)
34+
f'Must be list of lists i.e. [["Idle", 75759616], ["Idle", 75759620]]. You have {wanted_value}\'.'
5435
)
55-
elif isinstance(item, list):
36+
if isinstance(item, list):
5637
wanted_value = flatten_list(wanted_value)
5738
break
5839

@@ -64,5 +45,5 @@ def extract_values_from_output(value: Mapping, path: Mapping, exclude: List) ->
6445
wanted_reference_keys = jmespath.search(jmspath_refkey_parser(path), value)
6546
list_of_reference_keys = keys_cleaner(wanted_reference_keys)
6647
return keys_values_zipper(list_of_reference_keys, paired_key_value)
67-
else:
68-
return paired_key_value
48+
49+
return paired_key_value

netcompare/utils/filter_parsers.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
"""Filtering parsing."""
12
from typing import Mapping, List
23

34

@@ -36,5 +37,5 @@ def exclude_filter(data: Mapping, exclude: List):
3637

3738
elif isinstance(data, list):
3839
for element in data:
39-
if isinstance(element, dict) or isinstance(element, list):
40+
if isinstance(element, (dict, list)):
4041
exclude_filter(element, exclude)

netcompare/utils/flatten.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
"""Flatten multi-nested list."""
12
from typing import List, Generator
23

34

netcompare/utils/jmspath_parsers.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
"""JMSPath expresion parsers."""
12
import re
23

34

@@ -19,7 +20,8 @@ def jmspath_value_parser(path: str):
1920
if regex_normalized_value:
2021
normalized_value = regex_match_value.group().split("$")[1]
2122
return path.replace(regex_normalized_value.group(), normalized_value)
22-
else: return path
23+
24+
return path
2325

2426

2527
def jmspath_refkey_parser(path: str):
@@ -33,13 +35,13 @@ def jmspath_refkey_parser(path: str):
3335
"""
3436
splitted_jmspath = path.split(".")
3537

36-
for n, i in enumerate(splitted_jmspath):
37-
regex_match_anchor = re.search(r"\$.*\$", i)
38+
for number, element in enumerate(splitted_jmspath):
39+
regex_match_anchor = re.search(r"\$.*\$", element)
3840

3941
if regex_match_anchor:
40-
splitted_jmspath[n] = regex_match_anchor.group().replace("$", "")
42+
splitted_jmspath[number] = regex_match_anchor.group().replace("$", "")
4143

42-
if regex_match_anchor and not i.startswith("[") and not i.endswith("]"):
43-
splitted_jmspath = splitted_jmspath[: n + 1]
44+
if regex_match_anchor and not element.startswith("[") and not element.endswith("]"):
45+
splitted_jmspath = splitted_jmspath[: number + 1]
4446

4547
return ".".join(splitted_jmspath)

netcompare/utils/refkey.py

Lines changed: 16 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,31 @@
1+
"""Reference key utilities."""
12
from typing import Mapping, List
23

34

45
def keys_cleaner(wanted_reference_keys: Mapping) -> List[Mapping]:
5-
"""
6-
Get every required reference key from output.
7-
8-
Args:
9-
wanted_reference_keys: {'10.1.0.0': {'address_family': {'ipv4': ...
10-
11-
Return:
12-
['10.1.0.0', '10.2.0.0', '10.64.207.255', '7.7.7.7']
13-
14-
Example:
15-
>>> from runner import keys_cleaner
16-
>>> wanted_reference_keys = {'10.1.0.0': {'address_family': 'ipv4'}}
17-
>>> keys_cleaner(wanted_reference_keys)
18-
['10.1.0.0', '10.2.0.0', '10.64.207.255', '7.7.7.7']
19-
"""
6+
"""Get every required reference key from output."""
207
if isinstance(wanted_reference_keys, list):
218
return wanted_reference_keys
229

23-
elif isinstance(wanted_reference_keys, dict):
24-
my_keys_list = list()
10+
if isinstance(wanted_reference_keys, dict):
11+
my_keys_list = []
2512

26-
if isinstance(wanted_reference_keys, dict):
13+
if isinstance(wanted_reference_keys, dict):
2714
for key in wanted_reference_keys.keys():
2815
my_keys_list.append(key)
2916
else:
30-
raise TypeError(f'Must be a dictionary. You have type:{type(wanted_reference_keys)} output:{wanted_reference_keys}\'.')
31-
32-
return my_keys_list
33-
17+
raise TypeError(
18+
f"Must be a dictionary. You have type:{type(wanted_reference_keys)} output:{wanted_reference_keys}'."
19+
)
3420

35-
def keys_values_zipper(list_of_reference_keys: List, wanted_value_with_key: List) -> List:
36-
"""
37-
Build dictionary zipping keys with relative values.
21+
return my_keys_list
3822

39-
Args:
40-
list_of_reference_keys: ['10.1.0.0', '10.2.0.0', '10.64.207.255', '7.7.7.7']
41-
wanted_value_with_key: [{'is_enabled': True, 'is_up': False}, ...
23+
return None
4224

43-
Return:
44-
[{'10.1.0.0': {'is_enabled': True, 'is_up': False}}, , ...
4525

46-
Example:
47-
>>> from runner import keys_values_zipper
48-
>>> list_of_reference_keys = ['10.1.0.0']
49-
>>> wanted_value_with_key = [{'is_enabled': True, 'is_up': False}]
50-
>>> keys_values_zipper(list_of_reference_keys, wanted_value_with_key)
51-
[{'10.1.0.0': {'is_enabled': True, 'is_up': False}}]
52-
"""
53-
final_result = list()
26+
def keys_values_zipper(list_of_reference_keys: List, wanted_value_with_key: List) -> List:
27+
"""Build dictionary zipping keys with relative values."""
28+
final_result = []
5429

5530
if len(list_of_reference_keys) != len(wanted_value_with_key):
5631
raise ValueError("Keys len != from Values len")
@@ -62,23 +37,7 @@ def keys_values_zipper(list_of_reference_keys: List, wanted_value_with_key: List
6237

6338

6439
def associate_key_of_my_value(paths: str, wanted_value: List) -> List:
65-
"""
66-
Associate each key defined in path to every value found in output.
67-
68-
Args:
69-
paths: {"path": "global.peers.*.[is_enabled,is_up]"}
70-
wanted_value: [[True, False], [True, False], [True, False], [True, False]]
71-
72-
Return:
73-
[{'is_enabled': True, 'is_up': False}, ...
74-
75-
Example:
76-
>>> from runner import associate_key_of_my_value
77-
>>> path = {"path": "global.peers.*.[is_enabled,is_up]"}
78-
>>> wanted_value = [[True, False], [True, False], [True, False], [True, False]]
79-
{'is_enabled': True, 'is_up': False}, {'is_enabled': True, 'is_up': False}, ...
80-
"""
81-
40+
"""Associate each key defined in path to every value found in output."""
8241
# global.peers.*.[is_enabled,is_up] / result.[*].state
8342
find_the_key_of_my_values = paths.split(".")[-1]
8443

@@ -90,10 +49,10 @@ def associate_key_of_my_value(paths: str, wanted_value: List) -> List:
9049
else:
9150
my_key_value_list = [find_the_key_of_my_values]
9251

93-
final_list = list()
52+
final_list = []
9453

9554
for items in wanted_value:
96-
temp_dict = dict()
55+
temp_dict = {}
9756

9857
if len(items) != len(my_key_value_list):
9958
raise ValueError("Key's value len != from value len")

0 commit comments

Comments
 (0)