1#!/usr/bin/env python 2 3r""" 4Define variable manipulation functions. 5""" 6 7import os 8import re 9 10try: 11 from robot.utils import DotDict 12except ImportError: 13 pass 14 15import collections 16 17import gen_print as gp 18import gen_misc as gm 19import func_args as fa 20 21 22def create_var_dict(*args): 23 r""" 24 Create a dictionary whose keys/values are the arg names/arg values passed 25 to it and return it to the caller. 26 27 Note: The resulting dictionary will be ordered. 28 29 Description of argument(s): 30 *args An unlimited number of arguments to be processed. 31 32 Example use: 33 34 first_name = 'Steve' 35 last_name = 'Smith' 36 var_dict = create_var_dict(first_name, last_name) 37 38 gp.print_var(var_dict) 39 40 The print-out of the resulting var dictionary is: 41 var_dict: 42 var_dict[first_name]: Steve 43 var_dict[last_name]: Smith 44 """ 45 46 try: 47 result_dict = collections.OrderedDict() 48 except AttributeError: 49 result_dict = DotDict() 50 51 arg_num = 1 52 for arg in args: 53 arg_name = gp.get_arg_name(None, arg_num, stack_frame_ix=2) 54 result_dict[arg_name] = arg 55 arg_num += 1 56 57 return result_dict 58 59 60default_record_delim = ':' 61default_key_val_delim = '.' 62 63 64def join_dict(dict, 65 record_delim=default_record_delim, 66 key_val_delim=default_key_val_delim): 67 r""" 68 Join a dictionary's keys and values into a string and return the string. 69 70 Description of argument(s): 71 dict The dictionary whose keys and values are 72 to be joined. 73 record_delim The delimiter to be used to separate 74 dictionary pairs in the resulting string. 75 key_val_delim The delimiter to be used to separate keys 76 from values in the resulting string. 77 78 Example use: 79 80 gp.print_var(var_dict) 81 str1 = join_dict(var_dict) 82 gp.print_var(str1) 83 84 Program output. 85 var_dict: 86 var_dict[first_name]: Steve 87 var_dict[last_name]: Smith 88 str1: 89 first_name.Steve:last_name.Smith 90 """ 91 92 format_str = '%s' + key_val_delim + '%s' 93 return record_delim.join([format_str % (key, value) for (key, value) in 94 dict.items()]) 95 96 97def split_to_dict(string, 98 record_delim=default_record_delim, 99 key_val_delim=default_key_val_delim): 100 r""" 101 Split a string into a dictionary and return it. 102 103 This function is the complement to join_dict. 104 105 Description of argument(s): 106 string The string to be split into a dictionary. 107 The string must have the proper delimiters 108 in it. A string created by join_dict 109 would qualify. 110 record_delim The delimiter to be used to separate 111 dictionary pairs in the input string. 112 key_val_delim The delimiter to be used to separate 113 keys/values in the input string. 114 115 Example use: 116 117 gp.print_var(str1) 118 new_dict = split_to_dict(str1) 119 gp.print_var(new_dict) 120 121 122 Program output. 123 str1: 124 first_name.Steve:last_name.Smith 125 new_dict: 126 new_dict[first_name]: Steve 127 new_dict[last_name]: Smith 128 """ 129 130 try: 131 result_dict = collections.OrderedDict() 132 except AttributeError: 133 result_dict = DotDict() 134 135 raw_keys_values = string.split(record_delim) 136 for key_value in raw_keys_values: 137 key_value_list = key_value.split(key_val_delim) 138 try: 139 result_dict[key_value_list[0]] = key_value_list[1] 140 except IndexError: 141 result_dict[key_value_list[0]] = "" 142 143 return result_dict 144 145 146def create_file_path(file_name_dict, 147 dir_path="/tmp/", 148 file_suffix=""): 149 r""" 150 Create a file path using the given parameters and return it. 151 152 Description of argument(s): 153 file_name_dict A dictionary with keys/values which are to 154 appear as part of the file name. 155 dir_path The dir_path that is to appear as part of 156 the file name. 157 file_suffix A suffix to be included as part of the 158 file name. 159 """ 160 161 dir_path = gm.add_trailing_slash(dir_path) 162 return dir_path + join_dict(file_name_dict) + file_suffix 163 164 165def parse_file_path(file_path): 166 r""" 167 Parse a file path created by create_file_path and return the result as a 168 dictionary. 169 170 This function is the complement to create_file_path. 171 172 Description of argument(s): 173 file_path The file_path. 174 175 Example use: 176 gp.print_var(boot_results_file_path) 177 file_path_data = parse_file_path(boot_results_file_path) 178 gp.print_var(file_path_data) 179 180 Program output. 181 182 boot_results_file_path: 183 /tmp/pgm_name.obmc_boot_test:openbmc_nickname.beye6:master_pid.2039:boot_re 184 sults 185 file_path_data: 186 file_path_data[dir_path]: /tmp/ 187 file_path_data[pgm_name]: obmc_boot_test 188 file_path_data[openbmc_nickname]: beye6 189 file_path_data[master_pid]: 2039 190 file_path_data[boot_results]: 191 """ 192 193 try: 194 result_dict = collections.OrderedDict() 195 except AttributeError: 196 result_dict = DotDict() 197 198 dir_path = os.path.dirname(file_path) + os.sep 199 file_path = os.path.basename(file_path) 200 201 result_dict['dir_path'] = dir_path 202 203 result_dict.update(split_to_dict(file_path)) 204 205 return result_dict 206 207 208def parse_key_value(string, 209 delim=":", 210 strip=" ", 211 to_lower=1, 212 underscores=1): 213 r""" 214 Parse a key/value string and return as a key/value tuple. 215 216 This function is useful for parsing a line of program output or data that 217 is in the following form: 218 <key or variable name><delimiter><value> 219 220 An example of a key/value string would be as follows: 221 222 Current Limit State: No Active Power Limit 223 224 In the example shown, the delimiter is ":". The resulting key would be as 225 follows: 226 Current Limit State 227 228 Note: If one were to take the default values of to_lower=1 and 229 underscores=1, the resulting key would be as follows: 230 current_limit_state 231 232 The to_lower and underscores arguments are provided for those who wish to 233 have their key names have the look and feel of python variable names. 234 235 The resulting value for the example above would be as follows: 236 No Active Power Limit 237 238 Another example: 239 name=Mike 240 241 In this case, the delim would be "=", the key is "name" and the value is 242 "Mike". 243 244 Description of argument(s): 245 string The string to be parsed. 246 delim The delimiter which separates the key from 247 the value. 248 strip The characters (if any) to strip from the 249 beginning and end of both the key and the 250 value. 251 to_lower Change the key name to lower case. 252 underscores Change any blanks found in the key name to 253 underscores. 254 """ 255 256 pair = string.split(delim) 257 258 key = pair[0].strip(strip) 259 if len(pair) == 0: 260 value = "" 261 else: 262 value = delim.join(pair[1:]).strip(strip) 263 264 if to_lower: 265 key = key.lower() 266 if underscores: 267 key = re.sub(r" ", "_", key) 268 269 return key, value 270 271 272def key_value_list_to_dict(list, 273 process_indent=0, 274 **args): 275 r""" 276 Convert a list containing key/value strings or tuples to a dictionary and 277 return it. 278 279 See docstring of parse_key_value function for details on key/value strings. 280 281 Example usage: 282 283 For the following value of list: 284 285 list: 286 list[0]: Current Limit State: No Active Power Limit 287 list[1]: Exception actions: Hard Power Off & Log Event to SEL 288 list[2]: Power Limit: 0 Watts 289 list[3]: Correction time: 0 milliseconds 290 list[4]: Sampling period: 0 seconds 291 292 And the following call in python: 293 294 power_limit = key_value_outbuf_to_dict(list) 295 296 The resulting power_limit directory would look like this: 297 298 power_limit: 299 [current_limit_state]: No Active Power Limit 300 [exception_actions]: Hard Power Off & Log Event to SEL 301 [power_limit]: 0 Watts 302 [correction_time]: 0 milliseconds 303 [sampling_period]: 0 seconds 304 305 For the following list: 306 307 headers: 308 headers[0]: 309 headers[0][0]: content-length 310 headers[0][1]: 559 311 headers[1]: 312 headers[1][0]: x-xss-protection 313 headers[1][1]: 1; mode=block 314 315 And the following call in python: 316 317 headers_dict = key_value_list_to_dict(headers) 318 319 The resulting headers_dict would look like this: 320 321 headers_dict: 322 [content-length]: 559 323 [x-xss-protection]: 1; mode=block 324 325 Another example containing a sub-list (see process_indent description 326 below): 327 328 Provides Device SDRs : yes 329 Additional Device Support : 330 Sensor Device 331 SEL Device 332 FRU Inventory Device 333 Chassis Device 334 335 Note that the 2 qualifications for containing a sub-list are met: 1) 336 'Additional Device Support' has no value and 2) The entries below it are 337 indented. In this case those entries contain no delimiters (":") so they 338 will be processed as a list rather than as a dictionary. The result would 339 be as follows: 340 341 mc_info: 342 mc_info[provides_device_sdrs]: yes 343 mc_info[additional_device_support]: 344 mc_info[additional_device_support][0]: Sensor Device 345 mc_info[additional_device_support][1]: SEL Device 346 mc_info[additional_device_support][2]: FRU Inventory Device 347 mc_info[additional_device_support][3]: Chassis Device 348 349 Description of argument(s): 350 list A list of key/value strings. (See 351 docstring of parse_key_value function for 352 details). 353 process_indent This indicates that indented 354 sub-dictionaries and sub-lists are to be 355 processed as such. An entry may have a 356 sub-dict or sub-list if 1) It has no value 357 other than blank 2) There are entries 358 below it that are indented. Note that 359 process_indent is not allowed for a list 360 of tuples (vs. a list of key/value 361 strings). 362 **args Arguments to be interpreted by 363 parse_key_value. (See docstring of 364 parse_key_value function for details). 365 """ 366 367 try: 368 result_dict = collections.OrderedDict() 369 except AttributeError: 370 result_dict = DotDict() 371 372 if not process_indent: 373 for entry in list: 374 if type(entry) is tuple: 375 key, value = entry 376 else: 377 key, value = parse_key_value(entry, **args) 378 result_dict[key] = value 379 return result_dict 380 381 # Process list while paying heed to indentation. 382 delim = args.get("delim", ":") 383 # Initialize "parent_" indentation level variables. 384 parent_indent = len(list[0]) - len(list[0].lstrip()) 385 sub_list = [] 386 for entry in list: 387 key, value = parse_key_value(entry, **args) 388 389 indent = len(entry) - len(entry.lstrip()) 390 391 if indent > parent_indent and parent_value == "": 392 # This line is indented compared to the parent entry and the 393 # parent entry has no value. 394 # Append the entry to sub_list for later processing. 395 sub_list.append(str(entry)) 396 continue 397 398 # Process any outstanding sub_list and add it to 399 # result_dict[parent_key]. 400 if len(sub_list) > 0: 401 if any(delim in word for word in sub_list): 402 # If delim is found anywhere in the sub_list, we'll process 403 # as a sub-dictionary. 404 result_dict[parent_key] = key_value_list_to_dict(sub_list, 405 **args) 406 else: 407 result_dict[parent_key] = map(str.strip, sub_list) 408 del sub_list[:] 409 410 result_dict[key] = value 411 412 parent_key = key 413 parent_value = value 414 parent_indent = indent 415 416 # Any outstanding sub_list to be processed? 417 if len(sub_list) > 0: 418 if any(delim in word for word in sub_list): 419 # If delim is found anywhere in the sub_list, we'll process as a 420 # sub-dictionary. 421 result_dict[parent_key] = key_value_list_to_dict(sub_list, **args) 422 else: 423 result_dict[parent_key] = map(str.strip, sub_list) 424 425 return result_dict 426 427 428def key_value_outbuf_to_dict(out_buf, 429 **args): 430 r""" 431 Convert a buffer with a key/value string on each line to a dictionary and 432 return it. 433 434 Each line in the out_buf should end with a \n. 435 436 See docstring of parse_key_value function for details on key/value strings. 437 438 Example usage: 439 440 For the following value of out_buf: 441 442 Current Limit State: No Active Power Limit 443 Exception actions: Hard Power Off & Log Event to SEL 444 Power Limit: 0 Watts 445 Correction time: 0 milliseconds 446 Sampling period: 0 seconds 447 448 And the following call in python: 449 450 power_limit = key_value_outbuf_to_dict(out_buf) 451 452 The resulting power_limit directory would look like this: 453 454 power_limit: 455 [current_limit_state]: No Active Power Limit 456 [exception_actions]: Hard Power Off & Log Event to SEL 457 [power_limit]: 0 Watts 458 [correction_time]: 0 milliseconds 459 [sampling_period]: 0 seconds 460 461 Description of argument(s): 462 out_buf A buffer with a key/value string on each 463 line. (See docstring of parse_key_value 464 function for details). 465 **args Arguments to be interpreted by 466 parse_key_value. (See docstring of 467 parse_key_value function for details). 468 """ 469 470 # Create key_var_list and remove null entries. 471 key_var_list = list(filter(None, out_buf.split("\n"))) 472 return key_value_list_to_dict(key_var_list, **args) 473 474 475def create_field_desc_regex(line): 476 477 r""" 478 Create a field descriptor regular expression based on the input line and 479 return it. 480 481 This function is designed for use by the list_to_report function (defined 482 below). 483 484 Example: 485 486 Given the following input line: 487 488 -------- ------------ ------------------ ------------------------ 489 490 This function will return this regular expression: 491 492 (.{8}) (.{12}) (.{18}) (.{24}) 493 494 This means that other report lines interpreted using the regular 495 expression are expected to have: 496 - An 8 character field 497 - 3 spaces 498 - A 12 character field 499 - One space 500 - An 18 character field 501 - One space 502 - A 24 character field 503 504 Description of argument(s): 505 line A line consisting of dashes to represent 506 fields and spaces to delimit fields. 507 """ 508 509 # Split the line into a descriptors list. Example: 510 # descriptors: 511 # descriptors[0]: -------- 512 # descriptors[1]: 513 # descriptors[2]: 514 # descriptors[3]: ------------ 515 # descriptors[4]: ------------------ 516 # descriptors[5]: ------------------------ 517 descriptors = line.split(" ") 518 519 # Create regexes list. Example: 520 # regexes: 521 # regexes[0]: (.{8}) 522 # regexes[1]: 523 # regexes[2]: 524 # regexes[3]: (.{12}) 525 # regexes[4]: (.{18}) 526 # regexes[5]: (.{24}) 527 regexes = [] 528 for descriptor in descriptors: 529 if descriptor == "": 530 regexes.append("") 531 else: 532 regexes.append("(.{" + str(len(descriptor)) + "})") 533 534 # Join the regexes list into a regex string. 535 field_desc_regex = ' '.join(regexes) 536 537 return field_desc_regex 538 539 540def list_to_report(report_list, 541 to_lower=1, 542 field_delim=None): 543 r""" 544 Convert a list containing report text lines to a report "object" and 545 return it. 546 547 The first entry in report_list must be a header line consisting of column 548 names delimited by white space. No column name may contain white space. 549 The remaining report_list entries should contain tabular data which 550 corresponds to the column names. 551 552 A report object is a list where each entry is a dictionary whose keys are 553 the field names from the first entry in report_list. 554 555 Example: 556 Given the following report_list as input: 557 558 rl: 559 rl[0]: Filesystem 1K-blocks Used Available Use% Mounted on 560 rl[1]: dev 247120 0 247120 0% /dev 561 rl[2]: tmpfs 248408 79792 168616 32% /run 562 563 This function will return a list of dictionaries as shown below: 564 565 df_report: 566 df_report[0]: 567 [filesystem]: dev 568 [1k-blocks]: 247120 569 [used]: 0 570 [available]: 247120 571 [use%]: 0% 572 [mounted]: /dev 573 df_report[1]: 574 [filesystem]: dev 575 [1k-blocks]: 247120 576 [used]: 0 577 [available]: 247120 578 [use%]: 0% 579 [mounted]: /dev 580 581 Notice that because "Mounted on" contains a space, "on" would be 582 considered the 7th field. In this case, there is never any data in field 583 7 so things work out nicely. A caller could do some pre-processing if 584 desired (e.g. change "Mounted on" to "Mounted_on"). 585 586 Example 2: 587 588 If the 2nd line of report data is a series of dashes and spaces as in the 589 following example, that line will serve to delineate columns. 590 591 The 2nd line of data is like this: 592 ID status size 593 tool,clientid,userid 594 -------- ------------ ------------------ ------------------------ 595 20000001 in progress 0x7D0 ,, 596 597 Description of argument(s): 598 report_list A list where each entry is one line of 599 output from a report. The first entry 600 must be a header line which contains 601 column names. Column names may not 602 contain spaces. 603 to_lower Change the resulting key names to lower 604 case. 605 field_delim Indicates that there are field delimiters 606 in report_list entries (which should be 607 removed). 608 """ 609 610 if len(report_list) <= 1: 611 # If we don't have at least a descriptor line and one line of data, 612 # return an empty array. 613 return [] 614 615 if field_delim is not None: 616 report_list = [re.sub("\\|", "", line) for line in report_list] 617 618 header_line = report_list[0] 619 if to_lower: 620 header_line = header_line.lower() 621 622 field_desc_regex = "" 623 if re.match(r"^-[ -]*$", report_list[1]): 624 # We have a field descriptor line (as shown in example 2 above). 625 field_desc_regex = create_field_desc_regex(report_list[1]) 626 field_desc_len = len(report_list[1]) 627 pad_format_string = "%-" + str(field_desc_len) + "s" 628 # The field descriptor line has served its purpose. Deleting it. 629 del report_list[1] 630 631 # Process the header line by creating a list of column names. 632 if field_desc_regex == "": 633 columns = header_line.split() 634 else: 635 # Pad the line with spaces on the right to facilitate processing with 636 # field_desc_regex. 637 header_line = pad_format_string % header_line 638 columns = list(map(str.strip, 639 re.findall(field_desc_regex, header_line)[0])) 640 641 report_obj = [] 642 for report_line in report_list[1:]: 643 if field_desc_regex == "": 644 line = report_line.split() 645 else: 646 # Pad the line with spaces on the right to facilitate processing 647 # with field_desc_regex. 648 report_line = pad_format_string % report_line 649 line = list(map(str.strip, 650 re.findall(field_desc_regex, report_line)[0])) 651 try: 652 line_dict = collections.OrderedDict(zip(columns, line)) 653 except AttributeError: 654 line_dict = DotDict(zip(columns, line)) 655 report_obj.append(line_dict) 656 657 return report_obj 658 659 660def outbuf_to_report(out_buf, 661 **args): 662 r""" 663 Convert a text buffer containing report lines to a report "object" and 664 return it. 665 666 Refer to list_to_report (above) for more details. 667 668 Example: 669 670 Given the following out_buf: 671 672 Filesystem 1K-blocks Used Available Use% Mounted 673 on 674 dev 247120 0 247120 0% /dev 675 tmpfs 248408 79792 168616 32% /run 676 677 This function will return a list of dictionaries as shown below: 678 679 df_report: 680 df_report[0]: 681 [filesystem]: dev 682 [1k-blocks]: 247120 683 [used]: 0 684 [available]: 247120 685 [use%]: 0% 686 [mounted]: /dev 687 df_report[1]: 688 [filesystem]: dev 689 [1k-blocks]: 247120 690 [used]: 0 691 [available]: 247120 692 [use%]: 0% 693 [mounted]: /dev 694 695 Other possible uses: 696 - Process the output of a ps command. 697 - Process the output of an ls command (the caller would need to supply 698 column names) 699 700 Description of argument(s): 701 out_buf A text report. The first line must be a 702 header line which contains column names. 703 Column names may not contain spaces. 704 **args Arguments to be interpreted by 705 list_to_report. (See docstring of 706 list_to_report function for details). 707 """ 708 709 report_list = list(filter(None, out_buf.split("\n"))) 710 return list_to_report(report_list, **args) 711 712 713def nested_get(key_name, structure): 714 r""" 715 Return a list of all values from the nested structure that have the given 716 key name. 717 718 Example: 719 720 Given a dictionary structure named "personnel" with the following contents: 721 722 personnel: 723 [manager]: 724 [last_name]: Doe 725 [first_name]: John 726 [accountant]: 727 [last_name]: Smith 728 [first_name]: Will 729 730 The following code... 731 732 last_names = nested_get('last_name', personnel) 733 print_var(last_names) 734 735 Would result in the following data returned: 736 737 last_names: 738 last_names[0]: Doe 739 last_names[1]: Smith 740 741 Description of argument(s): 742 key_name The key name (e.g. 'last_name'). 743 structure Any nested combination of lists or 744 dictionaries (e.g. a dictionary, a 745 dictionary of dictionaries, a list of 746 dictionaries, etc.). This function will 747 locate the given key at any level within 748 the structure and include its value in the 749 returned list. 750 """ 751 752 result = [] 753 if type(structure) is list: 754 for entry in structure: 755 result += nested_get(key_name, entry) 756 return result 757 elif gp.is_dict(structure): 758 for key, value in structure.items(): 759 result += nested_get(key_name, value) 760 if key == key_name: 761 result.append(value) 762 763 return result 764 765 766def match_struct(structure, match_dict, regex=False): 767 r""" 768 Return True or False to indicate whether the structure matches the match 769 dictionary. 770 771 Example: 772 773 Given a dictionary structure named "personnel" with the following contents: 774 775 personnel: 776 [manager]: 777 [last_name]: Doe 778 [first_name]: John 779 [accountant]: 780 [last_name]: Smith 781 [first_name]: Will 782 783 The following call would return True. 784 785 match_struct(personnel, {'last_name': '^Doe$'}, regex=True) 786 787 Whereas the following call would return False. 788 789 match_struct(personnel, {'last_name': 'Johnson'}, regex=True) 790 791 Description of argument(s): 792 structure Any nested combination of lists or 793 dictionaries. See the prolog of 794 get_nested() for details. 795 match_dict Each key/value pair in match_dict must 796 exist somewhere in the structure for the 797 structure to be considered a match. A 798 match value of None is considered a 799 special case where the structure would be 800 considered a match only if the key in 801 question is found nowhere in the structure. 802 regex Indicates whether the values in the 803 match_dict should be interpreted as 804 regular expressions. 805 """ 806 807 # The structure must match for each match_dict entry to be considered a 808 # match. Therefore, any failure to match is grounds for returning False. 809 for match_key, match_value in match_dict.items(): 810 struct_key_values = nested_get(match_key, structure) 811 if match_value is None: 812 # Handle this as special case. 813 if len(struct_key_values) != 0: 814 return False 815 else: 816 if len(struct_key_values) == 0: 817 return False 818 if regex: 819 matches = [x for x in struct_key_values 820 if re.search(match_value, str(x))] 821 if not matches: 822 return False 823 elif match_value not in struct_key_values: 824 return False 825 826 return True 827 828 829def filter_struct(structure, filter_dict, regex=False, invert=False): 830 r""" 831 Filter the structure by removing any entries that do NOT contain the 832 keys/values specified in filter_dict and return the result. 833 834 The selection process is directed only at the first-level entries of the 835 structure. 836 837 Example: 838 839 Given a dictionary named "properties" that has the following structure: 840 841 properties: 842 [/redfish/v1/Systems/system/Processors]: 843 [Members]: 844 [0]: 845 [@odata.id]: 846 /redfish/v1/Systems/system/Processors/cpu0 847 [1]: 848 [@odata.id]: 849 /redfish/v1/Systems/system/Processors/cpu1 850 [/redfish/v1/Systems/system/Processors/cpu0]: 851 [Status]: 852 [State]: Enabled 853 [Health]: OK 854 [/redfish/v1/Systems/system/Processors/cpu1]: 855 [Status]: 856 [State]: Enabled 857 [Health]: Bad 858 859 The following call: 860 861 properties = filter_struct(properties, "[('Health', 'OK')]") 862 863 Would return a new properties dictionary that looks like this: 864 865 properties: 866 [/redfish/v1/Systems/system/Processors/cpu0]: 867 [Status]: 868 [State]: Enabled 869 [Health]: OK 870 871 Note that the first item in the original properties directory had no key 872 anywhere in the structure named "Health". Therefore, that item failed to 873 make the cut. The next item did have a key named "Health" whose value was 874 "OK" so it was included in the new structure. The third item had a key 875 named "Health" but its value was not "OK" so it also failed to make the 876 cut. 877 878 Description of argument(s): 879 structure Any nested combination of lists or 880 dictionaries. See the prolog of 881 get_nested() for details. 882 filter_dict For each key/value pair in filter_dict, 883 each entry in structure must contain the 884 same key/value pair at some level. A 885 filter_dict value of None is treated as a 886 special case. Taking the example shown 887 above, [('State', None)] would mean that 888 the result should only contain records 889 that have no State key at all. 890 regex Indicates whether the values in the 891 filter_dict should be interpreted as 892 regular expressions. 893 invert Invert the results. Instead of including 894 only matching entries in the results, 895 include only NON-matching entries in the 896 results. 897 """ 898 899 # Convert filter_dict from a string containing a python object definition 900 # to an actual python object (if warranted). 901 filter_dict = fa.source_to_object(filter_dict) 902 903 # Determine whether structure is a list or a dictionary and process 904 # accordingly. The result returned will be of the same type as the 905 # structure. 906 if type(structure) is list: 907 result = [] 908 for element in structure: 909 if match_struct(element, filter_dict, regex) != invert: 910 result.append(element) 911 else: 912 try: 913 result = collections.OrderedDict() 914 except AttributeError: 915 result = DotDict() 916 for struct_key, struct_value in structure.items(): 917 if match_struct(struct_value, filter_dict, regex) != invert: 918 result[struct_key] = struct_value 919 920 return result 921