88import re
99from typing import Mapping , List , Union
1010
11+ import jmespath
12+
1113
1214def jmespath_value_parser (path : str ):
1315 """
@@ -31,15 +33,15 @@ def jmespath_value_parser(path: str):
3133 path_suffix = path .split ("." )[- 1 ]
3234
3335 if regex_match_ref_key :
36+ reference_key = regex_match_ref_key .group ()
3437 if regex_ref_key .search (path_suffix ):
3538 # [$peerAddress$,prefixesReceived] --> [prefixesReceived]
36- reference_key = regex_match_ref_key .group ()
3739 return path .replace (reference_key , "" )
3840
3941 # result[0].$vrfs$.default... --> result[0].vrfs.default....
40- regex_normalized_value = re .search (r"\$.*\$" , regex_match_ref_key . group () )
42+ regex_normalized_value = re .search (r"\$.*\$" , reference_key )
4143 if regex_normalized_value :
42- normalized_value = regex_match_ref_key . group () .split ("$" )[1 ]
44+ normalized_value = reference_key .split ("$" )[1 ]
4345 return path .replace (regex_normalized_value .group (), normalized_value )
4446 return path
4547
@@ -65,9 +67,9 @@ def jmespath_refkey_parser(path: str):
6567 splitted_jmespath [number ] = regex_match_anchor .group ().replace ("$" , "" )
6668
6769 if regex_match_anchor and not element .startswith ("[" ) and not element .endswith ("]" ):
68- splitted_jmespath = splitted_jmespath [: number + 1 ]
70+ splitted_jmespath = splitted_jmespath [:number ]
6971
70- return "." .join (splitted_jmespath )
72+ return "." .join (splitted_jmespath ) or "@"
7173
7274
7375def associate_key_of_my_value (paths : str , wanted_value : List ) -> List :
@@ -85,13 +87,19 @@ def associate_key_of_my_value(paths: str, wanted_value: List) -> List:
8587
8688 final_list = []
8789
88- for items in wanted_value :
89- if len (items ) != len (my_key_value_list ):
90- raise ValueError ("Key's value len != from value len" )
90+ if not all (isinstance (item , list ) for item in wanted_value ) and len (my_key_value_list ) == 1 :
91+ for item in wanted_value :
92+ temp_dict = {my_key_value_list [0 ]: item }
93+ final_list .append (temp_dict )
94+
95+ else :
96+ for items in wanted_value :
97+ if len (items ) != len (my_key_value_list ):
98+ raise ValueError ("Key's value len != from value len" )
9199
92- temp_dict = {my_key_value_list [my_index ]: my_value for my_index , my_value in enumerate (items )}
100+ temp_dict = {my_key_value_list [my_index ]: my_value for my_index , my_value in enumerate (items )}
93101
94- final_list .append (temp_dict )
102+ final_list .append (temp_dict )
95103
96104 return final_list
97105
@@ -120,3 +128,40 @@ def keys_values_zipper(list_of_reference_keys: List, wanted_value_with_key: List
120128 final_result .append ({my_key : wanted_value_with_key [my_index ]})
121129
122130 return final_result
131+
132+
133+ def multi_reference_keys (jmspath , data ):
134+ """Build a list of concatenated reference keys.
135+
136+ Args:
137+ jmspath: "$*$.peers.$*$.*.ipv4.[accepted_prefixes]"
138+ data: tests/mock/napalm_get_bgp_neighbors/multi_vrf.json
139+
140+ Returns:
141+ ["global.10.1.0.0", "global.10.2.0.0", "global.10.64.207.255", "global.7.7.7.7", "vpn.10.1.0.0", "vpn.10.2.0.0"]
142+ """
143+ ref_key_regex = re .compile (r"\$.*?\$" )
144+ mapping = []
145+ split_path = jmspath .split ("." )
146+
147+ ref_key_index = - 1 # -1 as the starting value, so it will match split path list indexes
148+ for index , element in enumerate (split_path ):
149+ if ref_key_regex .search (element ):
150+ ref_key_index += 1
151+ key_path = (
152+ "." .join (split_path [:index ]).replace ("$" , "" ) or "@"
153+ ) # @ is for top keys, as they are stripped with "*"
154+ flat_path = f"{ key_path } { ' | []' * key_path .count ('*' )} " # | [] to flatten the data, nesting level is eq to "*" count
155+ sub_data = jmespath .search (flat_path , data ) # extract sub-data with up to the ref key
156+ if isinstance (sub_data , dict ):
157+ keys = list (sub_data .keys ())
158+ elif isinstance (sub_data , list ):
159+ keys = []
160+ for parent , children in zip (
161+ mapping [ref_key_index - 1 ], sub_data
162+ ): # refer to previous keys as they are already present in mapping
163+ keys .extend (f"{ parent } .{ child } " for child in children .keys ()) # concatenate keys
164+ else :
165+ raise ValueError ("Ref key anchor must return either a dict or a list." )
166+ mapping .append (keys )
167+ return mapping [- 1 ] # return last element as it has all previous ref_keys concatenated.
0 commit comments