1#!/usr/bin/env python 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 one 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 and upper: 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=[], var_name=None): 571 r""" 572 The variable value is valid if it is a dictionary containing all of the required keys. 573 574 Description of argument(s): 575 var_value The value being validated. 576 required_keys A list of keys which must be found in the dictionary for it to be 577 considered valid. 578 """ 579 580 error_message = "" 581 missing_keys = list(set(required_keys) - set(var_value.keys())) 582 if len(missing_keys) > 0: 583 var_name = get_var_name(var_name) 584 error_message += "The following dictionary is invalid because it is" 585 error_message += " missing required keys:\n" 586 error_message += gp.sprint_varx(var_name, var_value, 587 gp.blank() | gp.show_type()) 588 error_message += "\n" 589 error_message += gp.sprint_var(missing_keys, gp.show_type()) 590 return process_error_message(error_message) 591 592 593def valid_program(var_value, var_name=None): 594 r""" 595 The variable value is valid if it contains the name of a program which can be located using the "which" 596 command. 597 598 Description of argument(s): 599 var_value The value being validated. 600 """ 601 602 error_message = "" 603 rc, out_buf = gc.shell_cmd("which " + var_value, quiet=1, show_err=0, 604 ignore_err=1) 605 if rc: 606 var_name = get_var_name(var_name) 607 error_message += "The following required program could not be found" 608 error_message += " using the $PATH environment variable:\n" 609 error_message += gp.sprint_varx(var_name, var_value, gp.blank()) 610 PATH = os.environ.get("PATH", "").split(":") 611 error_message += "\n" 612 error_message += gp.sprint_var(PATH) 613 return process_error_message(error_message) 614 615 616def valid_length(var_value, min_length=None, max_length=None, var_name=None): 617 r""" 618 The variable value is valid if it is an object (e.g. list, dictionary) whose length is within the 619 specified range. 620 621 Description of argument(s): 622 var_value The value being validated. 623 min_length The minimum length of the object. If not None, the length of var_value 624 must be greater than or equal to min_length. 625 max_length The maximum length of the object. If not None, the length of var_value 626 must be less than or equal to min_length. 627 """ 628 629 error_message = "" 630 length = len(var_value) 631 error_message = valid_range(length, min_length, max_length) 632 if error_message: 633 var_name = get_var_name(var_name) 634 error_message = "The length of the following object is not within the" 635 error_message += " expected range:\n" 636 error_message += gp.sprint_vars(min_length, max_length) 637 error_message += gp.sprint_var(length) 638 error_message += gp.sprint_varx(var_name, var_value, gp.blank()) 639 error_message += "\n" 640 return process_error_message(error_message) 641 642 return process_error_message(error_message) 643 644 645# Modify selected function docstrings by adding headers/footers. 646 647func_names = [ 648 "valid_type", "valid_value", "valid_range", "valid_integer", 649 "valid_dir_path", "valid_file_path", "valid_path", "valid_list", 650 "valid_dict", "valid_program", "valid_length", "valid_float", 651 "valid_date_time" 652] 653 654raw_doc_strings = {} 655 656for func_name in func_names: 657 cmd_buf = "raw_doc_strings['" + func_name + "'] = " + func_name 658 cmd_buf += ".__doc__" 659 exec(cmd_buf) 660 cmd_buf = func_name + ".__doc__ = docstring_header + " + func_name 661 cmd_buf += ".__doc__.rstrip(\" \\n\") + additional_args_docstring_footer" 662 exec(cmd_buf) 663