11#!/ur/bin/env python3
22import re
33import 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
711def 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
0 commit comments