Skip to content

Commit e46b79f

Browse files
authored
Merge pull request #2 from networktocode-llc/lvrfrc87
Create utils for library refactoring
2 parents 74158fb + e9d5f2b commit e46b79f

15 files changed

+478
-292
lines changed

netcompare/check_type.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ class CheckType:
99

1010
def __init__(self, *args):
1111
"""Check Type init method."""
12-
pass
1312

1413
@staticmethod
1514
def init(*args):

netcompare/evaluator.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
"""Diff evaluator."""
22
import re
33
import sys
4-
from deepdiff import DeepDiff
54
from collections import defaultdict
6-
from collections.abc import Mapping as DictMapping
75
from functools import partial
8-
from typing import Mapping, List
6+
from typing import Mapping, List, Dict
7+
from deepdiff import DeepDiff
98

109

1110
sys.path.append(".")
1211

1312

14-
def diff_generator(pre_result: Mapping, post_result: Mapping) -> Mapping:
13+
def diff_generator(pre_result: Mapping, post_result: Mapping) -> Dict:
1514
"""
1615
Generates diff between pre and post data based on check definition.
1716
@@ -139,7 +138,7 @@ def group_value(tree_list: List, value: Mapping) -> Mapping:
139138
return value
140139

141140

142-
def dict_merger(original_dict: List, merged_dict: Mapping):
141+
def dict_merger(original_dict: Mapping, merged_dict: Mapping):
143142
"""
144143
Merge dictionaries to build final result.
145144
@@ -153,7 +152,7 @@ def dict_merger(original_dict: List, merged_dict: Mapping):
153152
{'10.17.254.2': {'state': {'new_value': 'Up', 'old_value': 'Idle'}}}
154153
"""
155154
for key in merged_dict.keys():
156-
if key in original_dict and isinstance(original_dict[key], dict) and isinstance(merged_dict[key], DictMapping):
155+
if key in original_dict and isinstance(original_dict[key], dict) and isinstance(merged_dict[key], dict):
157156
dict_merger(original_dict[key], merged_dict[key])
158157
else:
159158
original_dict[key] = merged_dict[key]

netcompare/runner.py

Lines changed: 31 additions & 256 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
#!/ur/bin/env python3
22
import re
33
import jmespath
4-
from typing import Mapping, List, Generator, Union
4+
from typing import Mapping, List, Union
5+
from .utils.jmspath_parsers import jmspath_value_parser, jmspath_refkey_parser
6+
from .utils.filter_parsers import exclude_filter
7+
from .utils.refkey import keys_cleaner, keys_values_zipper, associate_key_of_my_value
8+
from .utils.flatten import flatten_list
59

610

711
def extract_values_from_output(value: Mapping, path: Mapping, exclude: List) -> Union[Mapping, List, int, str, bool]:
@@ -18,276 +22,47 @@ def extract_values_from_output(value: Mapping, path: Mapping, exclude: List) ->
1822
"default": {
1923
"peerList": [
2024
{ ...
21-
exclude: [...]
25+
exclude: ["interfaceStatistics", "interfaceCounters"]
2226
23-
TODO: This function should be able to return a list, or a Dict, or a Integer, or a Boolean or a String
2427
Return:
2528
[{'7.7.7.7': {'prefixesReceived': 101}}, {'10.1.0.0': {'prefixesReceived': 120}}, ...
2629
"""
27-
30+
# Get the wanted values to be evaluated if jmspath expression is defined, otherwise
31+
# use the entire output if jmespath is not defined in check. This cover the "raw" diff type.
32+
if path and not exclude:
33+
wanted_value = jmespath.search(jmspath_value_parser(path), value)
34+
35+
elif path and exclude:
36+
wanted_value = jmespath.search(jmspath_value_parser(path), value)
37+
exclude_filter(wanted_value, exclude)
38+
elif not path and exclude:
39+
exclude_filter(value, exclude)
40+
return value
41+
42+
# data type check
2843
if path:
29-
found_values = jmespath.search(jmspath_value_parser(path), value)
30-
else:
31-
found_values = value
32-
33-
if exclude:
34-
my_value_exclude_cleaner(found_values, exclude)
35-
my_meaningful_values = found_values
36-
else:
37-
my_meaningful_values = get_meaningful_values(path, found_values)
38-
39-
if path and re.search(r"\$.*\$", path):
40-
wanted_reference_keys = jmespath.search(jmspath_refkey_parser(path), value)
41-
list_of_reference_keys = keys_cleaner(wanted_reference_keys)
42-
return keys_values_zipper(list_of_reference_keys, my_meaningful_values)
43-
else:
44-
return my_meaningful_values
45-
46-
47-
def jmspath_value_parser(path):
48-
"""
49-
Get the JMSPath value path from 'path'.
50-
51-
Args:
52-
path: "result[0].vrfs.default.peerList[*].[$peerAddress$,prefixesReceived]"
53-
Return:
54-
"result[0].vrfs.default.peerList[*].[prefixesReceived]"
55-
"""
56-
regex_match_value = re.search(r"\$.*\$\.|\$.*\$,|,\$.*\$", path)
57-
58-
if regex_match_value:
59-
# $peers$. --> peers
60-
regex_normalized_value = re.search(r"\$.*\$", regex_match_value.group())
61-
if regex_normalized_value:
62-
normalized_value = regex_match_value.group().split("$")[1]
63-
value_path = path.replace(regex_normalized_value.group(), normalized_value)
64-
else:
65-
value_path = path
66-
67-
return value_path
68-
69-
70-
def jmspath_refkey_parser(path):
71-
"""
72-
Get the JMSPath reference key path from 'path'.
73-
74-
Args:
75-
path: "result[0].vrfs.default.peerList[*].[$peerAddress$,prefixesReceived]"
76-
Return:
77-
"result[0].vrfs.default.peerList[*].[$peerAddress$]"
78-
"""
79-
splitted_jmspath = path.split(".")
80-
81-
for n, i in enumerate(splitted_jmspath):
82-
regex_match_anchor = re.search(r"\$.*\$", i)
83-
84-
if regex_match_anchor:
85-
splitted_jmspath[n] = regex_match_anchor.group().replace("$", "")
86-
87-
if regex_match_anchor and not i.startswith("[") and not i.endswith("]"):
88-
splitted_jmspath = splitted_jmspath[: n + 1]
89-
90-
return ".".join(splitted_jmspath)
91-
44+
if not any(isinstance(i, list) for i in wanted_value):
45+
return wanted_value
9246

93-
def get_meaningful_values(path, found_values):
94-
if path:
95-
# check if list of lists
96-
if not any(isinstance(i, list) for i in found_values):
97-
raise TypeError(
98-
"Catching value must be defined as list in jmespath expression i.e. result[*].state -> result[*].[state]. You have {}'.".format(
99-
path
100-
)
101-
)
102-
for element in found_values:
47+
for element in wanted_value:
10348
for item in element:
10449
if isinstance(item, dict):
10550
raise TypeError(
10651
'Must be list of lists i.e. [["Idle", 75759616], ["Idle", 75759620]]. You have {}\'.'.format(
107-
found_values
52+
wanted_value
10853
)
10954
)
11055
elif isinstance(item, list):
111-
found_values = flatten_list(found_values)
56+
wanted_value = flatten_list(wanted_value)
11257
break
11358

114-
my_meaningful_values = associate_key_of_my_value(jmspath_value_parser(path), found_values)
59+
paired_key_value = associate_key_of_my_value(jmspath_value_parser(path), wanted_value)
11560
else:
116-
my_meaningful_values = found_values
117-
return my_meaningful_values
118-
119-
120-
def my_value_exclude_cleaner(data: Mapping, exclude: List):
121-
"""
122-
Recusively look through all dict keys and pop out the one defined in "exclude".
123-
124-
Update in place existing dictionary. Look into unit test for example.
125-
126-
Args:
127-
data: {
128-
"interfaces": {
129-
"Management1": {
130-
"name": "Management1",
131-
"interfaceStatus": "connected",
132-
"autoNegotiate": "success",
133-
"interfaceStatistics": {
134-
"inBitsRate": 3403.4362520883615,
135-
"inPktsRate": 3.7424095978179257,
136-
"outBitsRate": 16249.69114419833,
137-
"updateInterval": 300,
138-
"outPktsRate": 2.1111866059750692
139-
},...
140-
exclude: ["interfaceStatistics", "interfaceCounters"]
141-
"""
142-
if isinstance(data, dict):
143-
for exclude_element in exclude:
144-
try:
145-
data.pop(exclude_element)
146-
except KeyError:
147-
pass
148-
149-
for key in data:
150-
if isinstance(data[key], dict) or isinstance(data[key], list):
151-
my_value_exclude_cleaner(data[key], exclude)
152-
153-
elif isinstance(data, list):
154-
for element in data:
155-
if isinstance(element, dict) or isinstance(element, list):
156-
my_value_exclude_cleaner(element, exclude)
157-
158-
159-
def flatten_list(my_list: List) -> List:
160-
"""
161-
Flatten a multi level nested list and returns a list of lists.
162-
163-
Args:
164-
my_list: nested list to be flattened.
165-
166-
Return:
167-
[[-1, 0], [-1, 0], [-1, 0], ...]
61+
paired_key_value = value
16862

169-
Example:
170-
>>> my_list = [[[[-1, 0], [-1, 0]]]]
171-
>>> flatten_list(my_list)
172-
[[-1, 0], [-1, 0]]
173-
"""
174-
if not isinstance(my_list, list):
175-
raise ValueError(f"Argument provided must be a list. You passed a {type(my_list)}")
176-
if is_flat_list(my_list):
177-
return my_list
178-
return list(iter_flatten_list(my_list))
179-
180-
181-
def iter_flatten_list(my_list: List) -> Generator[List, None, None]:
182-
"""Recursively yield all flat lists within a given list."""
183-
if is_flat_list(my_list):
184-
yield my_list
185-
else:
186-
for item in my_list:
187-
yield from iter_flatten_list(item)
188-
189-
190-
def is_flat_list(obj: List) -> bool:
191-
"""Return True is obj is a list that does not contain any lists as its first order elements."""
192-
return isinstance(obj, list) and not any(isinstance(i, list) for i in obj)
193-
194-
195-
def associate_key_of_my_value(paths: Mapping, found_values: List) -> List:
196-
"""
197-
Associate each key defined in path to every value found in output.
198-
199-
Args:
200-
paths: {"path": "global.peers.*.[is_enabled,is_up]"}
201-
found_values: [[True, False], [True, False], [True, False], [True, False]]
202-
203-
Return:
204-
[{'is_enabled': True, 'is_up': False}, ...
205-
206-
Example:
207-
>>> from runner import associate_key_of_my_value
208-
>>> path = {"path": "global.peers.*.[is_enabled,is_up]"}
209-
>>> found_values = [[True, False], [True, False], [True, False], [True, False]]
210-
{'is_enabled': True, 'is_up': False}, {'is_enabled': True, 'is_up': False}, ...
211-
"""
212-
213-
# global.peers.*.[is_enabled,is_up] / result.[*].state
214-
find_the_key_of_my_values = paths.split(".")[-1]
215-
216-
# [is_enabled,is_up]
217-
if find_the_key_of_my_values.startswith("[") and find_the_key_of_my_values.endswith("]"):
218-
# ['is_enabled', 'is_up']
219-
my_key_value_list = find_the_key_of_my_values.strip("[]").split(",")
220-
# state
63+
if path and re.search(r"\$.*\$", path):
64+
wanted_reference_keys = jmespath.search(jmspath_refkey_parser(path), value)
65+
list_of_reference_keys = keys_cleaner(wanted_reference_keys)
66+
return keys_values_zipper(list_of_reference_keys, paired_key_value)
22167
else:
222-
my_key_value_list = [find_the_key_of_my_values]
223-
224-
final_list = list()
225-
226-
for items in found_values:
227-
temp_dict = dict()
228-
229-
if len(items) != len(my_key_value_list):
230-
raise ValueError("Key's value len != from value len")
231-
232-
for my_index, my_value in enumerate(items):
233-
temp_dict.update({my_key_value_list[my_index]: my_value})
234-
final_list.append(temp_dict)
235-
236-
return final_list
237-
238-
239-
def keys_cleaner(wanted_reference_keys: Mapping) -> list:
240-
"""
241-
Get every required reference key from output.
242-
243-
Args:
244-
wanted_reference_keys: {'10.1.0.0': {'address_family': {'ipv4': ...
245-
246-
Return:
247-
['10.1.0.0', '10.2.0.0', '10.64.207.255', '7.7.7.7']
248-
249-
Example:
250-
>>> from runner import keys_cleaner
251-
>>> wanted_reference_keys = {'10.1.0.0': {'address_family': 'ipv4'}}
252-
>>> keys_cleaner(wanted_reference_keys)
253-
['10.1.0.0', '10.2.0.0', '10.64.207.255', '7.7.7.7']
254-
"""
255-
if isinstance(wanted_reference_keys, list):
256-
return wanted_reference_keys
257-
258-
elif isinstance(wanted_reference_keys, dict):
259-
my_keys_list = list()
260-
261-
for key in wanted_reference_keys.keys():
262-
my_keys_list.append(key)
263-
264-
return my_keys_list
265-
266-
267-
def keys_values_zipper(list_of_reference_keys: List, found_values_with_key: List) -> List:
268-
"""
269-
Build dictionary zipping keys with relative values.
270-
271-
Args:
272-
list_of_reference_keys: ['10.1.0.0', '10.2.0.0', '10.64.207.255', '7.7.7.7']
273-
found_values_with_key: [{'is_enabled': True, 'is_up': False}, ...
274-
275-
Return:
276-
[{'10.1.0.0': {'is_enabled': True, 'is_up': False}}, , ...
277-
278-
Example:
279-
>>> from runner import keys_values_zipper
280-
>>> list_of_reference_keys = ['10.1.0.0']
281-
>>> found_values_with_key = [{'is_enabled': True, 'is_up': False}]
282-
>>> keys_values_zipper(list_of_reference_keys, found_values_with_key)
283-
[{'10.1.0.0': {'is_enabled': True, 'is_up': False}}]
284-
"""
285-
final_result = list()
286-
287-
if len(list_of_reference_keys) != len(found_values_with_key):
288-
raise ValueError("Keys len != from Values len")
289-
290-
for my_index, my_key in enumerate(list_of_reference_keys):
291-
final_result.append({my_key: found_values_with_key[my_index]})
292-
293-
return final_result
68+
return paired_key_value

netcompare/utils/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)