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