1#!/usr/bin/env python3 2 3r""" 4This module provides validation functions like valid_value(), valid_integer(), etc. 5""" 6 7import os 8import gen_print as gp 9import gen_cmd as gc 10import func_args as fa 11import datetime 12 13exit_on_error = False 14 15 16def set_exit_on_error(value): 17 r""" 18 Set the exit_on_error value to either True or False. 19 20 If exit_on_error is set, validation functions like valid_value() will exit the program on error instead 21 of returning False. 22 23 Description of argument(s): 24 value Value to set global exit_on_error to. 25 """ 26 27 global exit_on_error 28 exit_on_error = value 29 30 31def get_var_name(var_name): 32 r""" 33 If var_name is not None, simply return its value. Otherwise, get the variable name of the first argument 34 used to call the validation function (e.g. valid, valid_integer, etc.) and return it. 35 36 This function is designed solely for use by other functions in this file. 37 38 Example: 39 40 A programmer codes this: 41 42 valid_value(last_name) 43 44 Which results in the following call stack: 45 46 valid_value(last_name) 47 -> get_var_name(var_name) 48 49 In this example, this function will return "last_name". 50 51 Example: 52 53 err_msg = valid_value(last_name, var_name="some_other_name") 54 55 Which results in the following call stack: 56 57 valid_value(var_value, var_name="some_other_name") 58 -> get_var_name(var_name) 59 60 In this example, this function will return "some_other_name". 61 62 Description of argument(s): 63 var_name The name of the variable. 64 """ 65 66 return var_name or gp.get_arg_name(0, 1, stack_frame_ix=3) 67 68 69def process_error_message(error_message): 70 r""" 71 Process the error_message in the manner described below. 72 73 This function is designed solely for use by other functions in this file. 74 75 NOTE: A blank error_message means that there is no error. 76 77 For the following explanations, assume the caller of this function is a function with the following 78 definition: 79 valid_value(var_value, valid_values=[], invalid_values=[], var_name=None): 80 81 If the user of valid_value() is assigning the valid_value() return value to a variable, 82 process_error_message() will simply return the error_message. This mode of usage is illustrated by the 83 following example: 84 85 error_message = valid_value(var1) 86 87 This mode is useful for callers who wish to validate a variable and then decide for themselves what to do 88 with the error_message (e.g. raise(error_message), BuiltIn().fail(error_message), etc.). 89 90 If the user of valid_value() is NOT assigning the valid_value() return value to a variable, 91 process_error_message() will behave as follows. 92 93 First, if error_message is non-blank, it will be printed to stderr via a call to 94 gp.print_error_report(error_message). 95 96 If exit_on_error is set: 97 - If the error_message is blank, simply return. 98 - If the error_message is non-blank, exit the program with a return code of 1. 99 100 If exit_on_error is NOT set: 101 - If the error_message is blank, return True. 102 - If the error_message is non-blank, return False. 103 104 Description of argument(s): 105 error_message An error message. 106 """ 107 108 # Determine whether the caller's caller is assigning the result to a variable. 109 l_value = gp.get_arg_name(None, -1, stack_frame_ix=3) 110 if l_value: 111 return error_message 112 113 if error_message == "": 114 if exit_on_error: 115 return 116 return True 117 118 gp.print_error_report(error_message, stack_frame_ix=4) 119 if exit_on_error: 120 exit(1) 121 return False 122 123 124# Note to programmers: All of the validation functions in this module should follow the same basic template: 125# def valid_value(var_value, var1, var2, varn, var_name=None): 126# 127# error_message = "" 128# if not valid: 129# var_name = get_var_name(var_name) 130# error_message += "The following variable is invalid because...:\n" 131# error_message += gp.sprint_varx(var_name, var_value, gp.blank()) 132# 133# return process_error_message(error_message) 134 135 136# The docstring header and footer will be added to each validation function's existing docstring. 137docstring_header = \ 138 r""" 139 Determine whether var_value is valid, construct an error_message and call 140 process_error_message(error_message). 141 142 See the process_error_message() function defined in this module for a description of how error messages 143 are processed. 144 """ 145 146additional_args_docstring_footer = \ 147 r""" 148 var_name The name of the variable whose value is passed in var_value. For the 149 general case, this argument is unnecessary as this function can figure 150 out the var_name. This is provided for Robot callers in which case, this 151 function lacks the ability to determine the variable name. 152 """ 153 154 155def valid_type(var_value, required_type, var_name=None): 156 r""" 157 The variable value is valid if it is of the required type. 158 159 Examples: 160 161 valid_type(var1, int) 162 163 valid_type(var1, (list, dict)) 164 165 Description of argument(s): 166 var_value The value being validated. 167 required_type A type or a tuple of types (e.g. str, int, etc.). 168 """ 169 170 error_message = "" 171 if type(required_type) is tuple: 172 if type(var_value) in required_type: 173 return process_error_message(error_message) 174 else: 175 if type(var_value) is required_type: 176 return process_error_message(error_message) 177 178 # If we get to this point, the validation has failed. 179 var_name = get_var_name(var_name) 180 error_message += "Invalid variable type:\n" 181 error_message += gp.sprint_varx(var_name, var_value, 182 gp.blank() | gp.show_type()) 183 error_message += "\n" 184 error_message += gp.sprint_var(required_type) 185 186 return process_error_message(error_message) 187 188 189def valid_value(var_value, valid_values=[], invalid_values=[], var_name=None): 190 191 r""" 192 The variable value is valid if it is either contained in the valid_values list or if it is NOT contained 193 in the invalid_values list. If the caller specifies nothing for either of these 2 arguments, 194 invalid_values will be initialized to ['', None]. This is a good way to fail on variables which contain 195 blank values. 196 197 It is illegal to specify both valid_values and invalid values. 198 199 Example: 200 201 var1 = '' 202 valid_value(var1) 203 204 This code would fail because var1 is blank and the default value for invalid_values is ['', None]. 205 206 Example: 207 var1 = 'yes' 208 valid_value(var1, valid_values=['yes', 'true']) 209 210 This code would pass. 211 212 Description of argument(s): 213 var_value The value being validated. 214 valid_values A list of valid values. The variable value must be equal to one of these 215 values to be considered valid. 216 invalid_values A list of invalid values. If the variable value is equal to any of 217 these, it is considered invalid. 218 """ 219 220 error_message = "" 221 222 # Validate this function's arguments. 223 len_valid_values = len(valid_values) 224 len_invalid_values = len(invalid_values) 225 if len_valid_values > 0 and len_invalid_values > 0: 226 error_message += "Programmer error - You must provide either an" 227 error_message += " invalid_values list or a valid_values" 228 error_message += " list but NOT both:\n" 229 error_message += gp.sprint_var(invalid_values) 230 error_message += gp.sprint_var(valid_values) 231 return process_error_message(error_message) 232 233 error_message = valid_type(valid_values, list, var_name='valid_values') 234 if error_message: 235 return process_error_message(error_message) 236 237 error_message = valid_type(invalid_values, list, var_name='invalid_values') 238 if error_message: 239 return process_error_message(error_message) 240 241 if len_valid_values > 0: 242 # Processing the valid_values list. 243 if var_value in valid_values: 244 return process_error_message(error_message) 245 var_name = get_var_name(var_name) 246 error_message += "Invalid variable value:\n" 247 error_message += gp.sprint_varx(var_name, var_value, 248 gp.blank() | gp.verbose() 249 | gp.show_type()) 250 error_message += "\n" 251 error_message += "It must be one of the following values:\n" 252 error_message += "\n" 253 error_message += gp.sprint_var(valid_values, 254 gp.blank() | gp.show_type()) 255 return process_error_message(error_message) 256 257 if len_invalid_values == 0: 258 # Assign default value. 259 invalid_values = ["", None] 260 261 # Assertion: We have an invalid_values list. Processing it now. 262 if var_value not in invalid_values: 263 return process_error_message(error_message) 264 265 var_name = get_var_name(var_name) 266 error_message += "Invalid variable value:\n" 267 error_message += gp.sprint_varx(var_name, var_value, 268 gp.blank() | gp.verbose() 269 | gp.show_type()) 270 error_message += "\n" 271 error_message += "It must NOT be any of the following values:\n" 272 error_message += "\n" 273 error_message += gp.sprint_var(invalid_values, 274 gp.blank() | gp.show_type()) 275 return process_error_message(error_message) 276 277 278def valid_range(var_value, lower=None, upper=None, var_name=None): 279 r""" 280 The variable value is valid if it is within the specified range. 281 282 This function can be used with any type of operands where they can have a greater than/less than 283 relationship to each other (e.g. int, float, str). 284 285 Description of argument(s): 286 var_value The value being validated. 287 lower The lower end of the range. If not None, the var_value must be greater 288 than or equal to lower. 289 upper The upper end of the range. If not None, the var_value must be less than 290 or equal to upper. 291 """ 292 293 error_message = "" 294 if lower is None and upper is None: 295 return process_error_message(error_message) 296 if lower is None and var_value <= upper: 297 return process_error_message(error_message) 298 if upper is None and var_value >= lower: 299 return process_error_message(error_message) 300 if lower is not None and upper is not None: 301 if lower > upper: 302 var_name = get_var_name(var_name) 303 error_message += "Programmer error - the lower value is greater" 304 error_message += " than the upper value:\n" 305 error_message += gp.sprint_vars(lower, upper, fmt=gp.show_type()) 306 return process_error_message(error_message) 307 if lower <= var_value <= upper: 308 return process_error_message(error_message) 309 310 var_name = get_var_name(var_name) 311 error_message += "The following variable is not within the expected" 312 error_message += " range:\n" 313 error_message += gp.sprint_varx(var_name, var_value, gp.show_type()) 314 error_message += "\n" 315 error_message += "range:\n" 316 error_message += gp.sprint_vars(lower, upper, fmt=gp.show_type(), indent=2) 317 return process_error_message(error_message) 318 319 320def valid_integer(var_value, lower=None, upper=None, var_name=None): 321 r""" 322 The variable value is valid if it is an integer or can be interpreted as an integer (e.g. 7, "7", etc.). 323 324 This function also calls valid_range to make sure the integer value is within the specified range (if 325 any). 326 327 Description of argument(s): 328 var_value The value being validated. 329 lower The lower end of the range. If not None, the var_value must be greater 330 than or equal to lower. 331 upper The upper end of the range. If not None, the var_value must be less than 332 or equal to upper. 333 """ 334 335 error_message = "" 336 var_name = get_var_name(var_name) 337 try: 338 var_value = int(str(var_value), 0) 339 except ValueError: 340 error_message += "Invalid integer value:\n" 341 error_message += gp.sprint_varx(var_name, var_value, 342 gp.blank() | gp.show_type()) 343 return process_error_message(error_message) 344 345 # Check the range (if any). 346 if lower: 347 lower = int(str(lower), 0) 348 if upper: 349 upper = int(str(upper), 0) 350 error_message = valid_range(var_value, lower, upper, var_name=var_name) 351 352 return process_error_message(error_message) 353 354 355def valid_float(var_value, lower=None, upper=None, var_name=None): 356 r""" 357 The variable value is valid if it is a floating point value or can be interpreted as a floating point 358 value (e.g. 7.5, "7.5", etc.). 359 360 This function also calls valid_range to make sure the float value is within the specified range (if any). 361 362 Description of argument(s): 363 var_value The value being validated. 364 lower The lower end of the range. If not None, the var_value must be greater 365 than or equal to lower. 366 upper The upper end of the range. If not None, the var_value must be less than 367 or equal to upper. 368 """ 369 370 error_message = "" 371 var_name = get_var_name(var_name) 372 try: 373 var_value = float(str(var_value)) 374 except ValueError: 375 error_message += "Invalid float value:\n" 376 error_message += gp.sprint_varx(var_name, var_value, 377 gp.blank() | gp.show_type()) 378 return process_error_message(error_message) 379 380 # Check the range (if any). 381 if lower: 382 lower = float(str(lower)) 383 if upper: 384 upper = float(str(upper)) 385 error_message = valid_range(var_value, lower, upper, var_name=var_name) 386 387 return process_error_message(error_message) 388 389 390def valid_date_time(var_value, var_name=None): 391 r""" 392 The variable value is valid if it can be interpreted as a date/time (e.g. "14:49:49.981", "tomorrow", 393 etc.) by the linux date command. 394 395 Description of argument(s): 396 var_value The value being validated. 397 """ 398 399 error_message = "" 400 rc, out_buf = gc.shell_cmd("date -d '" + str(var_value) + "'", quiet=1, show_err=0, ignore_err=1) 401 if rc: 402 var_name = get_var_name(var_name) 403 error_message += "Invalid date/time value:\n" 404 error_message += gp.sprint_varx(var_name, var_value, 405 gp.blank() | gp.show_type()) 406 return process_error_message(error_message) 407 408 return process_error_message(error_message) 409 410 411def valid_dir_path(var_value, var_name=None): 412 r""" 413 The variable value is valid if it contains the path of an existing directory. 414 415 Description of argument(s): 416 var_value The value being validated. 417 """ 418 419 error_message = "" 420 if not os.path.isdir(str(var_value)): 421 var_name = get_var_name(var_name) 422 error_message += "The following directory does not exist:\n" 423 error_message += gp.sprint_varx(var_name, var_value, gp.blank()) 424 425 return process_error_message(error_message) 426 427 428def valid_file_path(var_value, var_name=None): 429 r""" 430 The variable value is valid if it contains the path of an existing file. 431 432 Description of argument(s): 433 var_value The value being validated. 434 """ 435 436 error_message = "" 437 if not os.path.isfile(str(var_value)): 438 var_name = get_var_name(var_name) 439 error_message += "The following file does not exist:\n" 440 error_message += gp.sprint_varx(var_name, var_value, gp.blank()) 441 442 return process_error_message(error_message) 443 444 445def valid_path(var_value, var_name=None): 446 r""" 447 The variable value is valid if it contains the path of an existing file or directory. 448 449 Description of argument(s): 450 var_value The value being validated. 451 """ 452 453 error_message = "" 454 if not (os.path.isfile(str(var_value)) or os.path.isdir(str(var_value))): 455 var_name = get_var_name(var_name) 456 error_message += "Invalid path (file or directory does not exist):\n" 457 error_message += gp.sprint_varx(var_name, var_value, gp.blank()) 458 459 return process_error_message(error_message) 460 461 462def valid_list(var_value, valid_values=[], invalid_values=[], 463 required_values=[], fail_on_empty=False, var_name=None): 464 r""" 465 The variable value is valid if it is a list where each entry can be found in the valid_values list or if 466 none of its values can be found in the invalid_values list or if all of the values in the required_values 467 list can be found in var_value. 468 469 The caller may only specify one of these 3 arguments: valid_values, invalid_values, required_values. 470 471 Description of argument(s): 472 var_value The value being validated. 473 valid_values A list of valid values. Each element in the var_value list must be equal 474 to one of these values to be considered valid. 475 invalid_values A list of invalid values. If any element in var_value is equal to any of 476 the values in this argument, var_value is considered invalid. 477 required_values Every value in required_values must be found in var_value. Otherwise, 478 var_value is considered invalid. 479 fail_on_empty Indicates that an empty list for the variable value should be considered 480 an error. 481 """ 482 483 error_message = "" 484 485 # Validate this function's arguments. 486 if not (bool(len(valid_values)) ^ bool(len(invalid_values)) ^ bool(len(required_values))): 487 error_message += "Programmer error - You must provide only one of the" 488 error_message += " following: valid_values, invalid_values," 489 error_message += " required_values.\n" 490 error_message += gp.sprint_var(invalid_values, gp.show_type()) 491 error_message += gp.sprint_var(valid_values, gp.show_type()) 492 error_message += gp.sprint_var(required_values, gp.show_type()) 493 return process_error_message(error_message) 494 495 if type(var_value) is not list: 496 var_name = get_var_name(var_name) 497 error_message = valid_type(var_value, list, var_name=var_name) 498 if error_message: 499 return process_error_message(error_message) 500 501 if fail_on_empty and len(var_value) == 0: 502 var_name = get_var_name(var_name) 503 error_message += "Invalid empty list:\n" 504 error_message += gp.sprint_varx(var_name, var_value, gp.show_type()) 505 return process_error_message(error_message) 506 507 if len(required_values): 508 found_error = 0 509 display_required_values = list(required_values) 510 for ix in range(0, len(required_values)): 511 if required_values[ix] not in var_value: 512 found_error = 1 513 display_required_values[ix] = \ 514 str(display_required_values[ix]) + "*" 515 if found_error: 516 var_name = get_var_name(var_name) 517 error_message += "The following list is invalid:\n" 518 error_message += gp.sprint_varx(var_name, var_value, 519 gp.blank() | gp.show_type()) 520 error_message += "\n" 521 error_message += "Because some of the values in the " 522 error_message += "required_values list are not present (see" 523 error_message += " entries marked with \"*\"):\n" 524 error_message += "\n" 525 error_message += gp.sprint_varx('required_values', 526 display_required_values, 527 gp.blank() | gp.show_type()) 528 error_message += "\n" 529 530 return process_error_message(error_message) 531 532 if len(invalid_values): 533 found_error = 0 534 display_var_value = list(var_value) 535 for ix in range(0, len(var_value)): 536 if var_value[ix] in invalid_values: 537 found_error = 1 538 display_var_value[ix] = str(var_value[ix]) + "*" 539 540 if found_error: 541 var_name = get_var_name(var_name) 542 error_message += "The following list is invalid (see entries" 543 error_message += " marked with \"*\"):\n" 544 error_message += gp.sprint_varx(var_name, display_var_value, 545 gp.blank() | gp.show_type()) 546 error_message += "\n" 547 error_message += gp.sprint_var(invalid_values, gp.show_type()) 548 return process_error_message(error_message) 549 550 found_error = 0 551 display_var_value = list(var_value) 552 for ix in range(0, len(var_value)): 553 if var_value[ix] not in valid_values: 554 found_error = 1 555 display_var_value[ix] = str(var_value[ix]) + "*" 556 557 if found_error: 558 var_name = get_var_name(var_name) 559 error_message += "The following list is invalid (see entries marked" 560 error_message += " with \"*\"):\n" 561 error_message += gp.sprint_varx(var_name, display_var_value, 562 gp.blank() | gp.show_type()) 563 error_message += "\n" 564 error_message += gp.sprint_var(valid_values, gp.show_type()) 565 return process_error_message(error_message) 566 567 return process_error_message(error_message) 568 569 570def valid_dict(var_value, required_keys=[], valid_values={}, invalid_values={}, var_name=None): 571 r""" 572 The dictionary variable value is valid if it contains all required keys and each entry passes the 573 valid_value() call. 574 575 Examples: 576 person_record = {'last_name': 'Jones', 'first_name': 'John'} 577 valid_values = {'last_name': ['Doe', 'Jones', 'Johnson'], 'first_name': ['John', 'Mary']} 578 invalid_values = {'last_name': ['Manson', 'Hitler', 'Presley'], 'first_name': ['Mickey', 'Goofy']} 579 580 valid_dict(person_record, valid_values=valid_values) 581 valid_dict(person_record, invalid_values=invalid_values) 582 583 Description of argument(s): 584 var_value The value being validated. 585 required_keys A list of keys which must be found in the dictionary for it to be 586 considered valid. 587 valid_values A dictionary whose entries correspond to the entries in var_value. Each 588 value in valid_values is itself a valid_values list for the corresponding 589 value in var_value. For any var_value[key] to be considered valid, its 590 value must be found in valid_values[key]. 591 592 invalid_values A dictionary whose entries correspond to the entries in var_value. Each 593 value in invalid_values is itself an invalid_values list for the 594 corresponding value in var_value. For any var_value[key] to be 595 considered valid, its value must NOT be found in invalid_values[key]. 596 """ 597 598 error_message = "" 599 missing_keys = list(set(required_keys) - set(var_value.keys())) 600 if len(missing_keys) > 0: 601 var_name = get_var_name(var_name) 602 error_message += "The following dictionary is invalid because it is" 603 error_message += " missing required keys:\n" 604 error_message += gp.sprint_varx(var_name, var_value, gp.blank() | gp.show_type()) 605 error_message += "\n" 606 error_message += gp.sprint_var(missing_keys, gp.show_type()) 607 return process_error_message(error_message) 608 609 var_name = get_var_name(var_name) 610 if len(valid_values): 611 keys = valid_values.keys() 612 error_message = valid_dict(var_value, required_keys=keys, var_name=var_name) 613 if error_message: 614 return process_error_message(error_message) 615 for key, value in valid_values.items(): 616 key_name = " [" + key + "]" 617 sub_error_message = valid_value(var_value[key], valid_values=value, var_name=key_name) 618 if sub_error_message: 619 error_message += "The following dictionary is invalid because one of its entries is invalid:\n" 620 error_message += gp.sprint_varx(var_name, var_value, gp.blank() | gp.show_type()) 621 error_message += "\n" 622 error_message += sub_error_message 623 return process_error_message(error_message) 624 625 for key, value in invalid_values.items(): 626 if key not in var_value: 627 continue 628 key_name = " [" + key + "]" 629 sub_error_message = valid_value(var_value[key], invalid_values=value, var_name=key_name) 630 if sub_error_message: 631 error_message += "The following dictionary is invalid because one of its entries is invalid:\n" 632 error_message += gp.sprint_varx(var_name, var_value, gp.blank() | gp.show_type()) 633 error_message += "\n" 634 error_message += sub_error_message 635 return process_error_message(error_message) 636 637 return process_error_message(error_message) 638 639 640def valid_program(var_value, var_name=None): 641 r""" 642 The variable value is valid if it contains the name of a program which can be located using the "which" 643 command. 644 645 Description of argument(s): 646 var_value The value being validated. 647 """ 648 649 error_message = "" 650 rc, out_buf = gc.shell_cmd("which " + var_value, quiet=1, show_err=0, 651 ignore_err=1) 652 if rc: 653 var_name = get_var_name(var_name) 654 error_message += "The following required program could not be found" 655 error_message += " using the $PATH environment variable:\n" 656 error_message += gp.sprint_varx(var_name, var_value, gp.blank()) 657 PATH = os.environ.get("PATH", "").split(":") 658 error_message += "\n" 659 error_message += gp.sprint_var(PATH) 660 return process_error_message(error_message) 661 662 663def valid_length(var_value, min_length=None, max_length=None, var_name=None): 664 r""" 665 The variable value is valid if it is an object (e.g. list, dictionary) whose length is within the 666 specified range. 667 668 Description of argument(s): 669 var_value The value being validated. 670 min_length The minimum length of the object. If not None, the length of var_value 671 must be greater than or equal to min_length. 672 max_length The maximum length of the object. If not None, the length of var_value 673 must be less than or equal to min_length. 674 """ 675 676 error_message = "" 677 length = len(var_value) 678 error_message = valid_range(length, min_length, max_length) 679 if error_message: 680 var_name = get_var_name(var_name) 681 error_message = "The length of the following object is not within the" 682 error_message += " expected range:\n" 683 error_message += gp.sprint_vars(min_length, max_length) 684 error_message += gp.sprint_var(length) 685 error_message += gp.sprint_varx(var_name, var_value, gp.blank()) 686 error_message += "\n" 687 return process_error_message(error_message) 688 689 return process_error_message(error_message) 690 691 692# Modify selected function docstrings by adding headers/footers. 693 694func_names = [ 695 "valid_type", "valid_value", "valid_range", "valid_integer", 696 "valid_dir_path", "valid_file_path", "valid_path", "valid_list", 697 "valid_dict", "valid_program", "valid_length", "valid_float", 698 "valid_date_time" 699] 700 701raw_doc_strings = {} 702 703for func_name in func_names: 704 cmd_buf = "raw_doc_strings['" + func_name + "'] = " + func_name 705 cmd_buf += ".__doc__" 706 exec(cmd_buf) 707 cmd_buf = func_name + ".__doc__ = docstring_header + " + func_name 708 cmd_buf += ".__doc__.rstrip(\" \\n\") + additional_args_docstring_footer" 709 exec(cmd_buf) 710