@@ -172,18 +172,6 @@ def convert_to_type(value: Any) -> Any:
172172 return value
173173
174174
175- def remove_nones (d : dict [str , any ]) -> dict [str , any ]:
176- """Removes elements of the dict that are None values"""
177- new_d = d .copy ()
178- for k , v in d .items ():
179- if v is None :
180- new_d .pop (k )
181- continue
182- if isinstance (v , dict ):
183- new_d [k ] = remove_nones (v )
184- return new_d
185-
186-
187175def none_to_zero (d : dict [str , any ], key_match : str = "^.+$" ) -> dict [str , any ]:
188176 """Replaces None values in a dict with 0"""
189177 new_d = d .copy ()
@@ -197,21 +185,36 @@ def none_to_zero(d: dict[str, any], key_match: str = "^.+$") -> dict[str, any]:
197185 return new_d
198186
199187
200- def remove_empties (d : dict [str , any ]) -> dict [str , any ]:
188+ def remove_nones (d : Any ) -> Any :
189+ """Removes elements of the data that are None values"""
190+ return clean_data (d , remove_empty = False , remove_none = True )
191+
192+
193+ def clean_data (d : Any , remove_empty : bool = True , remove_none : bool = True ) -> Any :
201194 """Recursively removes empty lists and dicts and none from a dict"""
202195 # log.debug("Cleaning up %s", json_dump(d))
203- new_d = d .copy ()
204- for k , v in d .items ():
205- if isinstance (v , str ) and v == "" :
206- new_d .pop (k )
207- continue
208- if not isinstance (v , (list , dict )):
209- continue
210- if len (v ) == 0 :
211- new_d .pop (k )
212- elif isinstance (v , dict ):
213- new_d [k ] = remove_empties (v )
214- return new_d
196+ if not isinstance (d , (list , dict )):
197+ return d
198+
199+ if isinstance (d , list ):
200+ # Remove empty strings and nones
201+ if remove_empty :
202+ d = [elem for elem in d if not (isinstance (elem , str ) and elem == "" )]
203+ if remove_none :
204+ d = [elem for elem in d if elem is not None ]
205+ return [clean_data (elem , remove_empty , remove_none ) for elem in d ]
206+
207+ # Remove empty dict string values
208+ if remove_empty :
209+ new_d = {k : v for k , v in d .items () if not isinstance (v , str ) or v != "" }
210+ if remove_none :
211+ new_d = {k : v for k , v in d .items () if v is not None }
212+
213+ # Remove empty dict list or dict values
214+ new_d = {k : v for k , v in new_d .items () if not isinstance (v , (list , dict )) or len (v ) > 0 }
215+
216+ # Recurse
217+ return {k : clean_data (v , remove_empty , remove_none ) for k , v in new_d .items ()}
215218
216219
217220def sort_lists (data : Any , redact_tokens : bool = True ) -> Any :
@@ -278,23 +281,21 @@ def list_to_regexp(str_list: list[str]) -> str:
278281 return "(" + "|" .join (str_list ) + ")" if len (str_list ) > 0 else ""
279282
280283
281- def list_to_csv (
282- array : Union [None , str , int , float , list [str ], set [str ], tuple [str ]], separator : str = "," , check_for_separator : bool = False
283- ) -> Optional [str ]:
284+ def list_to_csv (array : Union [None , str , int , float , list [str ], set [str ], tuple [str ]], separator : str = "," , check_for_separator : bool = False ) -> Any :
284285 """Converts a list of strings to CSV"""
285286 if isinstance (array , str ):
286287 return csv_normalize (array , separator ) if " " in array else array
287288 if array is None :
288289 return None
289- if isinstance (array , (list , set , tuple )):
290+ if isinstance (array , (list , set , tuple )) and all ( isinstance ( e , str ) for e in array ) :
290291 if check_for_separator :
291292 # Don't convert to string if one array item contains the string separator
292293 s = separator .strip ()
293294 for item in array :
294295 if s in item :
295296 return array
296297 return separator .join ([v .strip () for v in array ])
297- return str ( array )
298+ return array
298299
299300
300301def csv_normalize (string : str , separator : str = "," ) -> str :
@@ -314,10 +315,10 @@ def union(list1: list[any], list2: list[any]) -> list[any]:
314315 return list1 + [value for value in list2 if value not in list1 ]
315316
316317
317- def difference (list1 : list [any ], list2 : list [any ]) -> list [any ]:
318+ def difference (list1 : list [Any ], list2 : list [Any ]) -> list [Any ]:
318319 """Computes difference of 2 lists"""
319320 # FIXME - This should be sets
320- return [ value for value in list1 if value not in list2 ]
321+ return list ( set ( list1 ) - set ( list2 ))
321322
322323
323324def quote (string : str , sep : str ) -> str :
@@ -578,15 +579,11 @@ def __prefix(value: Any) -> Any:
578579 return value
579580
580581
581- def filter_export (json_data : dict [str , any ], key_properties : list [str ], full : bool ) -> dict [str , any ]:
582+ def filter_export (json_data : dict [str , Any ], key_properties : list [str ], full : bool ) -> dict [str , Any ]:
582583 """Filters dict for export removing or prefixing non-key properties"""
583- new_json_data = json_data .copy ()
584- for k in json_data :
585- if k not in key_properties :
586- if full and k != "actions" :
587- new_json_data [f"_{ k } " ] = __prefix (new_json_data .pop (k ))
588- else :
589- new_json_data .pop (k )
584+ new_json_data = {k : json_data [k ] for k in key_properties if k in json_data }
585+ if full :
586+ new_json_data |= {f"_{ k } " : __prefix (v ) for k , v in json_data .items () if k not in key_properties }
590587 return new_json_data
591588
592589
@@ -726,15 +723,15 @@ def dict_remap_and_stringify(original_dict: dict[str, str], remapping: dict[str,
726723 return dict_stringify (dict_remap (original_dict , remapping ))
727724
728725
729- def list_to_dict (original_list : list [dict [str , any ]], key_field : str ) -> dict [str , any ]:
726+ def list_to_dict (original_list : list [dict [str , Any ]], key_field : str ) -> dict [str , any ]:
730727 """Converts a list to dict with list key_field as dict key"""
731728 converted_dict = {elem [key_field ]: elem for elem in original_list }
732729 for e in converted_dict .values ():
733730 e .pop (key_field )
734731 return converted_dict
735732
736733
737- def dict_to_list (original_dict : dict [str , any ], key_field : str , value_field : Optional [str ] = "value" ) -> list [str , any ]:
734+ def dict_to_list (original_dict : dict [str , Any ], key_field : str , value_field : Optional [str ] = "value" ) -> list [str , any ]:
738735 """Converts a dict to list adding dict key in list key_field"""
739736 if isinstance (original_dict , list ):
740737 return original_dict
@@ -794,20 +791,6 @@ def pretty_print_json(file: str) -> bool:
794791 return True
795792
796793
797- def order_keys (original_dict : dict [str , any ], * keys ) -> dict [str , any ]:
798- """Orders a dict keys in a chosen order, existings keys not in *keys are pushed to the end
799- :param dict[str, any] original_dict: Dict to order
800- :param str *keys: List of keys in desired order
801- :return: same dict with keys in desired order
802- """
803- ordered_dict = {}
804- for key in [k for k in keys if k in original_dict ]:
805- ordered_dict [key ] = original_dict [key ]
806- for key in [k for k in original_dict if k not in keys ]:
807- ordered_dict [key ] = original_dict [key ]
808- return ordered_dict
809-
810-
811794def flatten (original_dict : dict [str , any ]) -> dict [str , any ]:
812795 """Flattens a recursive dict into a flat one"""
813796 flat_dict = {}
@@ -825,3 +808,39 @@ def similar_strings(key1: str, key2: str, max_distance: int = 5) -> bool:
825808 return False
826809 max_distance = min (len (key1 ) // 2 , len (key2 ) // 2 , max_distance )
827810 return (len (key2 ) >= 7 and (re .match (key2 , key1 ))) or Levenshtein .distance (key1 , key2 , score_cutoff = 6 ) <= max_distance
811+
812+
813+ def sort_list_by_key (list_to_sort : list [dict [str , Any ]], key : str , priority_field : Optional [str ] = None ) -> list [dict [str , Any ]]:
814+ """Sorts a lits of dicts by a given key, exception for the priority field that would go first"""
815+ f_elem = None
816+ if priority_field :
817+ f_elem = next ((elem for elem in list_to_sort if priority_field in elem ), None )
818+ tmp_dict = {elem [key ]: elem for elem in list_to_sort if elem != f_elem }
819+ first_elem = [f_elem ] if f_elem else []
820+ return first_elem + list (dict (sorted (tmp_dict .items ())).values ())
821+
822+
823+ def order_keys (original_dict : dict [str , any ], * keys ) -> dict [str , any ]:
824+ """Orders a dict keys in a chosen order, existings keys not in *keys are pushed to the end
825+ :param dict[str, any] original_dict: Dict to order
826+ :param str *keys: List of keys in desired order
827+ :return: same dict with keys in desired order
828+ """
829+ ordered_dict = {}
830+ for key in [k for k in keys if k in original_dict ]:
831+ ordered_dict [key ] = original_dict [key ]
832+ for key in [k for k in original_dict if k not in keys ]:
833+ ordered_dict [key ] = original_dict [key ]
834+ return ordered_dict
835+
836+
837+ def order_dict (d : dict [str , Any ], key_order : list [str ]) -> dict [str , Any ]:
838+ """Orders keys of a dictionary in a given order"""
839+ new_d = {k : d [k ] for k in key_order if k in d }
840+ return new_d | {k : v for k , v in d .items () if k not in new_d }
841+
842+
843+ def order_list (l : list [str ], * key_order ) -> list [str ]:
844+ """Orders elements of a list in a given order"""
845+ new_l = [k for k in key_order if k in l ]
846+ return new_l + [k for k in l if k not in new_l ]
0 commit comments