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