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(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 list: 266 267 list: 268 list[0]: Current Limit State: No Active Power Limit 269 list[1]: Exception actions: Hard Power Off & Log Event to SEL 270 list[2]: Power Limit: 0 Watts 271 list[3]: Correction time: 0 milliseconds 272 list[4]: Sampling period: 0 seconds 273 274 And the following call in python: 275 276 power_limit = key_value_outbuf_to_dict(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 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 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(list[0]) - len(list[0].lstrip()) 358 sub_list = [] 359 for entry in 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] = 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] = 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 create_field_desc_regex(line): 442 443 r""" 444 Create a field descriptor regular expression based on the input line and return it. 445 446 This function is designed for use by the list_to_report function (defined below). 447 448 Example: 449 450 Given the following input line: 451 452 -------- ------------ ------------------ ------------------------ 453 454 This function will return this regular expression: 455 456 (.{8}) (.{12}) (.{18}) (.{24}) 457 458 This means that other report lines interpreted using the regular expression are expected to have: 459 - An 8 character field 460 - 3 spaces 461 - A 12 character field 462 - One space 463 - An 18 character field 464 - One space 465 - A 24 character field 466 467 Description of argument(s): 468 line A line consisting of dashes to represent fields and spaces to delimit 469 fields. 470 """ 471 472 # Split the line into a descriptors list. Example: 473 # descriptors: 474 # descriptors[0]: -------- 475 # descriptors[1]: 476 # descriptors[2]: 477 # descriptors[3]: ------------ 478 # descriptors[4]: ------------------ 479 # descriptors[5]: ------------------------ 480 descriptors = line.split(" ") 481 482 # Create regexes list. Example: 483 # regexes: 484 # regexes[0]: (.{8}) 485 # regexes[1]: 486 # regexes[2]: 487 # regexes[3]: (.{12}) 488 # regexes[4]: (.{18}) 489 # regexes[5]: (.{24}) 490 regexes = [] 491 for descriptor in descriptors: 492 if descriptor == "": 493 regexes.append("") 494 else: 495 regexes.append("(.{" + str(len(descriptor)) + "})") 496 497 # Join the regexes list into a regex string. 498 field_desc_regex = ' '.join(regexes) 499 500 return field_desc_regex 501 502 503def list_to_report(report_list, 504 to_lower=1, 505 field_delim=None): 506 r""" 507 Convert a list containing report text lines to a report "object" and return it. 508 509 The first entry in report_list must be a header line consisting of column names delimited by white space. 510 No column name may contain white space. The remaining report_list entries should contain tabular data 511 which corresponds to the column names. 512 513 A report object is a list where each entry is a dictionary whose keys are the field names from the first 514 entry in report_list. 515 516 Example: 517 Given the following report_list as input: 518 519 rl: 520 rl[0]: Filesystem 1K-blocks Used Available Use% Mounted on 521 rl[1]: dev 247120 0 247120 0% /dev 522 rl[2]: tmpfs 248408 79792 168616 32% /run 523 524 This function will return a list of dictionaries as shown below: 525 526 df_report: 527 df_report[0]: 528 [filesystem]: dev 529 [1k-blocks]: 247120 530 [used]: 0 531 [available]: 247120 532 [use%]: 0% 533 [mounted]: /dev 534 df_report[1]: 535 [filesystem]: dev 536 [1k-blocks]: 247120 537 [used]: 0 538 [available]: 247120 539 [use%]: 0% 540 [mounted]: /dev 541 542 Notice that because "Mounted on" contains a space, "on" would be considered the 7th field. In this case, 543 there is never any data in field 7 so things work out nicely. A caller could do some pre-processing if 544 desired (e.g. change "Mounted on" to "Mounted_on"). 545 546 Example 2: 547 548 If the 2nd line of report data is a series of dashes and spaces as in the following example, that line 549 will serve to delineate columns. 550 551 The 2nd line of data is like this: 552 ID status size tool,clientid,userid 553 -------- ------------ ------------------ ------------------------ 554 20000001 in progress 0x7D0 ,, 555 556 Description of argument(s): 557 report_list A list where each entry is one line of output from a report. The first 558 entry must be a header line which contains column names. Column names 559 may not contain spaces. 560 to_lower Change the resulting key names to lower case. 561 field_delim Indicates that there are field delimiters in report_list entries (which 562 should be removed). 563 """ 564 565 if len(report_list) <= 1: 566 # If we don't have at least a descriptor line and one line of data, return an empty array. 567 return [] 568 569 if field_delim is not None: 570 report_list = [re.sub("\\|", "", line) for line in report_list] 571 572 header_line = report_list[0] 573 if to_lower: 574 header_line = header_line.lower() 575 576 field_desc_regex = "" 577 if re.match(r"^-[ -]*$", report_list[1]): 578 # We have a field descriptor line (as shown in example 2 above). 579 field_desc_regex = create_field_desc_regex(report_list[1]) 580 field_desc_len = len(report_list[1]) 581 pad_format_string = "%-" + str(field_desc_len) + "s" 582 # The field descriptor line has served its purpose. Deleting it. 583 del report_list[1] 584 585 # Process the header line by creating a list of column names. 586 if field_desc_regex == "": 587 columns = header_line.split() 588 else: 589 # Pad the line with spaces on the right to facilitate processing with field_desc_regex. 590 header_line = pad_format_string % header_line 591 columns = list(map(str.strip, 592 re.findall(field_desc_regex, header_line)[0])) 593 594 report_obj = [] 595 for report_line in report_list[1:]: 596 if field_desc_regex == "": 597 line = report_line.split() 598 else: 599 # Pad the line with spaces on the right to facilitate processing with field_desc_regex. 600 report_line = pad_format_string % report_line 601 line = list(map(str.strip, 602 re.findall(field_desc_regex, report_line)[0])) 603 try: 604 line_dict = collections.OrderedDict(zip(columns, line)) 605 except AttributeError: 606 line_dict = DotDict(zip(columns, line)) 607 report_obj.append(line_dict) 608 609 return report_obj 610 611 612def outbuf_to_report(out_buf, 613 **args): 614 r""" 615 Convert a text buffer containing report lines to a report "object" and return it. 616 617 Refer to list_to_report (above) for more details. 618 619 Example: 620 621 Given the following out_buf: 622 623 Filesystem 1K-blocks Used Available Use% Mounted on 624 dev 247120 0 247120 0% /dev 625 tmpfs 248408 79792 168616 32% /run 626 627 This function will return a list of dictionaries as shown below: 628 629 df_report: 630 df_report[0]: 631 [filesystem]: dev 632 [1k-blocks]: 247120 633 [used]: 0 634 [available]: 247120 635 [use%]: 0% 636 [mounted]: /dev 637 df_report[1]: 638 [filesystem]: dev 639 [1k-blocks]: 247120 640 [used]: 0 641 [available]: 247120 642 [use%]: 0% 643 [mounted]: /dev 644 645 Other possible uses: 646 - Process the output of a ps command. 647 - Process the output of an ls command (the caller would need to supply column names) 648 649 Description of argument(s): 650 out_buf A text report. The first line must be a header line which contains 651 column names. Column names may not contain spaces. 652 **args Arguments to be interpreted by list_to_report. (See docstring of 653 list_to_report function for details). 654 """ 655 656 report_list = list(filter(None, out_buf.split("\n"))) 657 return list_to_report(report_list, **args) 658 659 660def nested_get(key_name, structure): 661 r""" 662 Return a list of all values from the nested structure that have the given key name. 663 664 Example: 665 666 Given a dictionary structure named "personnel" with the following contents: 667 668 personnel: 669 [manager]: 670 [last_name]: Doe 671 [first_name]: John 672 [accountant]: 673 [last_name]: Smith 674 [first_name]: Will 675 676 The following code... 677 678 last_names = nested_get('last_name', personnel) 679 print_var(last_names) 680 681 Would result in the following data returned: 682 683 last_names: 684 last_names[0]: Doe 685 last_names[1]: Smith 686 687 Description of argument(s): 688 key_name The key name (e.g. 'last_name'). 689 structure Any nested combination of lists or dictionaries (e.g. a dictionary, a 690 dictionary of dictionaries, a list of dictionaries, etc.). This function 691 will locate the given key at any level within the structure and include 692 its value in the returned list. 693 """ 694 695 result = [] 696 if type(structure) is list: 697 for entry in structure: 698 result += nested_get(key_name, entry) 699 return result 700 elif gp.is_dict(structure): 701 for key, value in structure.items(): 702 result += nested_get(key_name, value) 703 if key == key_name: 704 result.append(value) 705 706 return result 707 708 709def match_struct(structure, match_dict, regex=False): 710 r""" 711 Return True or False to indicate whether the structure matches the match dictionary. 712 713 Example: 714 715 Given a dictionary structure named "personnel" with the following contents: 716 717 personnel: 718 [manager]: 719 [last_name]: Doe 720 [first_name]: John 721 [accountant]: 722 [last_name]: Smith 723 [first_name]: Will 724 725 The following call would return True. 726 727 match_struct(personnel, {'last_name': '^Doe$'}, regex=True) 728 729 Whereas the following call would return False. 730 731 match_struct(personnel, {'last_name': 'Johnson'}, regex=True) 732 733 Description of argument(s): 734 structure Any nested combination of lists or dictionaries. See the prolog of 735 get_nested() for details. 736 match_dict Each key/value pair in match_dict must exist somewhere in the structure 737 for the structure to be considered a match. A match value of None is 738 considered a special case where the structure would be considered a match 739 only if the key in question is found nowhere in the structure. 740 regex Indicates whether the values in the match_dict should be interpreted as 741 regular expressions. 742 """ 743 744 # The structure must match for each match_dict entry to be considered a match. Therefore, any failure 745 # to match is grounds for returning False. 746 for match_key, match_value in match_dict.items(): 747 struct_key_values = nested_get(match_key, structure) 748 if match_value is None: 749 # Handle this as special case. 750 if len(struct_key_values) != 0: 751 return False 752 else: 753 if len(struct_key_values) == 0: 754 return False 755 if regex: 756 matches = [x for x in struct_key_values 757 if re.search(match_value, str(x))] 758 if not matches: 759 return False 760 elif match_value not in struct_key_values: 761 return False 762 763 return True 764 765 766def filter_struct(structure, filter_dict, regex=False, invert=False): 767 r""" 768 Filter the structure by removing any entries that do NOT contain the keys/values specified in filter_dict 769 and return the result. 770 771 The selection process is directed only at the first-level entries of the structure. 772 773 Example: 774 775 Given a dictionary named "properties" that has the following structure: 776 777 properties: 778 [/redfish/v1/Systems/system/Processors]: 779 [Members]: 780 [0]: 781 [@odata.id]: /redfish/v1/Systems/system/Processors/cpu0 782 [1]: 783 [@odata.id]: /redfish/v1/Systems/system/Processors/cpu1 784 [/redfish/v1/Systems/system/Processors/cpu0]: 785 [Status]: 786 [State]: Enabled 787 [Health]: OK 788 [/redfish/v1/Systems/system/Processors/cpu1]: 789 [Status]: 790 [State]: Enabled 791 [Health]: Bad 792 793 The following call: 794 795 properties = filter_struct(properties, "[('Health', 'OK')]") 796 797 Would return a new properties dictionary that looks like this: 798 799 properties: 800 [/redfish/v1/Systems/system/Processors/cpu0]: 801 [Status]: 802 [State]: Enabled 803 [Health]: OK 804 805 Note that the first item in the original properties directory had no key anywhere in the structure named 806 "Health". Therefore, that item failed to make the cut. The next item did have a key named "Health" 807 whose value was "OK" so it was included in the new structure. The third item had a key named "Health" 808 but its value was not "OK" so it also failed to make the cut. 809 810 Description of argument(s): 811 structure Any nested combination of lists or dictionaries. See the prolog of 812 get_nested() for details. 813 filter_dict For each key/value pair in filter_dict, each entry in structure must 814 contain the same key/value pair at some level. A filter_dict value of 815 None is treated as a special case. Taking the example shown above, 816 [('State', None)] would mean that the result should only contain records 817 that have no State key at all. 818 regex Indicates whether the values in the filter_dict should be interpreted as 819 regular expressions. 820 invert Invert the results. Instead of including only matching entries in the 821 results, include only NON-matching entries in the results. 822 """ 823 824 # Convert filter_dict from a string containing a python object definition to an actual python object (if 825 # warranted). 826 filter_dict = fa.source_to_object(filter_dict) 827 828 # Determine whether structure is a list or a dictionary and process accordingly. The result returned 829 # will be of the same type as the structure. 830 if type(structure) is list: 831 result = [] 832 for element in structure: 833 if match_struct(element, filter_dict, regex) != invert: 834 result.append(element) 835 else: 836 try: 837 result = collections.OrderedDict() 838 except AttributeError: 839 result = DotDict() 840 for struct_key, struct_value in structure.items(): 841 if match_struct(struct_value, filter_dict, regex) != invert: 842 result[struct_key] = struct_value 843 844 return result 845