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 if len_valid_values > 0: 234 # Processing the valid_values list. 235 if var_value in valid_values: 236 return process_error_message(error_message) 237 var_name = get_var_name(var_name) 238 error_message += "Invalid variable value:\n" 239 error_message += gp.sprint_varx(var_name, var_value, 240 gp.blank() | gp.verbose() 241 | gp.show_type()) 242 error_message += "\n" 243 error_message += "It must be one of the following values:\n" 244 error_message += "\n" 245 error_message += gp.sprint_var(valid_values, 246 gp.blank() | gp.show_type()) 247 return process_error_message(error_message) 248 249 if len_invalid_values == 0: 250 # Assign default value. 251 invalid_values = ["", None] 252 253 # Assertion: We have an invalid_values list. Processing it now. 254 if var_value not in invalid_values: 255 return process_error_message(error_message) 256 257 var_name = get_var_name(var_name) 258 error_message += "Invalid variable value:\n" 259 error_message += gp.sprint_varx(var_name, var_value, 260 gp.blank() | gp.verbose() 261 | gp.show_type()) 262 error_message += "\n" 263 error_message += "It must NOT be one of the following values:\n" 264 error_message += "\n" 265 error_message += gp.sprint_var(invalid_values, 266 gp.blank() | gp.show_type()) 267 return process_error_message(error_message) 268 269 270def valid_range(var_value, lower=None, upper=None, var_name=None): 271 r""" 272 The variable value is valid if it is within the specified range. 273 274 This function can be used with any type of operands where they can have a greater than/less than 275 relationship to each other (e.g. int, float, str). 276 277 Description of argument(s): 278 var_value The value being validated. 279 lower The lower end of the range. If not None, the var_value must be greater 280 than or equal to lower. 281 upper The upper end of the range. If not None, the var_value must be less than 282 or equal to upper. 283 """ 284 285 error_message = "" 286 if lower is None and upper is None: 287 return process_error_message(error_message) 288 if lower is None and var_value <= upper: 289 return process_error_message(error_message) 290 if upper is None and var_value >= lower: 291 return process_error_message(error_message) 292 if lower and upper: 293 if lower > upper: 294 var_name = get_var_name(var_name) 295 error_message += "Programmer error - the lower value is greater" 296 error_message += " than the upper value:\n" 297 error_message += gp.sprint_vars(lower, upper, fmt=gp.show_type()) 298 return process_error_message(error_message) 299 if lower <= var_value <= upper: 300 return process_error_message(error_message) 301 302 var_name = get_var_name(var_name) 303 error_message += "The following variable is not within the expected" 304 error_message += " range:\n" 305 error_message += gp.sprint_varx(var_name, var_value, gp.show_type()) 306 error_message += "\n" 307 error_message += "range:\n" 308 error_message += gp.sprint_vars(lower, upper, fmt=gp.show_type(), indent=2) 309 return process_error_message(error_message) 310 311 312def valid_integer(var_value, lower=None, upper=None, var_name=None): 313 r""" 314 The variable value is valid if it is an integer or can be interpreted as an integer (e.g. 7, "7", etc.). 315 316 This function also calls valid_range to make sure the integer value is within the specified range (if 317 any). 318 319 Description of argument(s): 320 var_value The value being validated. 321 lower The lower end of the range. If not None, the var_value must be greater 322 than or equal to lower. 323 upper The upper end of the range. If not None, the var_value must be less than 324 or equal to upper. 325 """ 326 327 error_message = "" 328 var_name = get_var_name(var_name) 329 try: 330 var_value = int(str(var_value), 0) 331 except ValueError: 332 error_message += "Invalid integer value:\n" 333 error_message += gp.sprint_varx(var_name, var_value, 334 gp.blank() | gp.show_type()) 335 return process_error_message(error_message) 336 337 # Check the range (if any). 338 if lower: 339 lower = int(str(lower), 0) 340 if upper: 341 upper = int(str(upper), 0) 342 error_message = valid_range(var_value, lower, upper, var_name=var_name) 343 344 return process_error_message(error_message) 345 346 347def valid_float(var_value, lower=None, upper=None, var_name=None): 348 r""" 349 The variable value is valid if it is a floating point value or can be interpreted as a floating point 350 value (e.g. 7.5, "7.5", etc.). 351 352 This function also calls valid_range to make sure the float value is within the specified range (if any). 353 354 Description of argument(s): 355 var_value The value being validated. 356 lower The lower end of the range. If not None, the var_value must be greater 357 than or equal to lower. 358 upper The upper end of the range. If not None, the var_value must be less than 359 or equal to upper. 360 """ 361 362 error_message = "" 363 var_name = get_var_name(var_name) 364 try: 365 var_value = float(str(var_value)) 366 except ValueError: 367 error_message += "Invalid float value:\n" 368 error_message += gp.sprint_varx(var_name, var_value, 369 gp.blank() | gp.show_type()) 370 return process_error_message(error_message) 371 372 # Check the range (if any). 373 if lower: 374 lower = float(str(lower)) 375 if upper: 376 upper = float(str(upper)) 377 error_message = valid_range(var_value, lower, upper, var_name=var_name) 378 379 return process_error_message(error_message) 380 381 382def valid_date_time(var_value, var_name=None): 383 r""" 384 The variable value is valid if it can be interpreted as a date/time (e.g. "14:49:49.981", "tomorrow", 385 etc.) by the linux date command. 386 387 Description of argument(s): 388 var_value The value being validated. 389 """ 390 391 error_message = "" 392 rc, out_buf = gc.shell_cmd("date -d '" + str(var_value) + "'", quiet=1, show_err=0, ignore_err=1) 393 if rc: 394 var_name = get_var_name(var_name) 395 error_message += "Invalid date/time value:\n" 396 error_message += gp.sprint_varx(var_name, var_value, 397 gp.blank() | gp.show_type()) 398 return process_error_message(error_message) 399 400 return process_error_message(error_message) 401 402 403def valid_dir_path(var_value, var_name=None): 404 r""" 405 The variable value is valid if it contains the path of an existing directory. 406 407 Description of argument(s): 408 var_value The value being validated. 409 """ 410 411 error_message = "" 412 if not os.path.isdir(str(var_value)): 413 var_name = get_var_name(var_name) 414 error_message += "The following directory does not exist:\n" 415 error_message += gp.sprint_varx(var_name, var_value, gp.blank()) 416 417 return process_error_message(error_message) 418 419 420def valid_file_path(var_value, var_name=None): 421 r""" 422 The variable value is valid if it contains the path of an existing file. 423 424 Description of argument(s): 425 var_value The value being validated. 426 """ 427 428 error_message = "" 429 if not os.path.isfile(str(var_value)): 430 var_name = get_var_name(var_name) 431 error_message += "The following file does not exist:\n" 432 error_message += gp.sprint_varx(var_name, var_value, gp.blank()) 433 434 return process_error_message(error_message) 435 436 437def valid_path(var_value, var_name=None): 438 r""" 439 The variable value is valid if it contains the path of an existing file or directory. 440 441 Description of argument(s): 442 var_value The value being validated. 443 """ 444 445 error_message = "" 446 if not (os.path.isfile(str(var_value)) or os.path.isdir(str(var_value))): 447 var_name = get_var_name(var_name) 448 error_message += "Invalid path (file or directory does not exist):\n" 449 error_message += gp.sprint_varx(var_name, var_value, gp.blank()) 450 451 return process_error_message(error_message) 452 453 454def valid_list(var_value, valid_values=[], invalid_values=[], 455 required_values=[], fail_on_empty=False, var_name=None): 456 r""" 457 The variable value is valid if it is a list where each entry can be found in the valid_values list or if 458 none of its values can be found in the invalid_values list or if all of the values in the required_values 459 list can be found in var_value. 460 461 The caller may only specify one of these 3 arguments: valid_values, invalid_values, required_values. 462 463 Description of argument(s): 464 var_value The value being validated. 465 valid_values A list of valid values. Each element in the var_value list must be equal 466 to one of these values to be considered valid. 467 invalid_values A list of invalid values. If any element in var_value is equal to any of 468 the values in this argument, var_value is considered invalid. 469 required_values Every value in required_values must be found in var_value. Otherwise, 470 var_value is considered invalid. 471 fail_on_empty Indicates that an empty list for the variable value should be considered 472 an error. 473 """ 474 475 error_message = "" 476 477 # Validate this function's arguments. 478 if not (bool(len(valid_values)) ^ bool(len(invalid_values)) ^ bool(len(required_values))): 479 error_message += "Programmer error - You must provide only one of the" 480 error_message += " following: valid_values, invalid_values," 481 error_message += " required_values.\n" 482 error_message += gp.sprint_var(invalid_values, gp.show_type()) 483 error_message += gp.sprint_var(valid_values, gp.show_type()) 484 error_message += gp.sprint_var(required_values, gp.show_type()) 485 return process_error_message(error_message) 486 487 if type(var_value) is not list: 488 var_name = get_var_name(var_name) 489 error_message = valid_type(var_value, list, var_name=var_name) 490 if error_message: 491 return process_error_message(error_message) 492 493 if fail_on_empty and len(var_value) == 0: 494 var_name = get_var_name(var_name) 495 error_message += "Invalid empty list:\n" 496 error_message += gp.sprint_varx(var_name, var_value, gp.show_type()) 497 return process_error_message(error_message) 498 499 if len(required_values): 500 found_error = 0 501 display_required_values = list(required_values) 502 for ix in range(0, len(required_values)): 503 if required_values[ix] not in var_value: 504 found_error = 1 505 display_required_values[ix] = \ 506 str(display_required_values[ix]) + "*" 507 if found_error: 508 var_name = get_var_name(var_name) 509 error_message += "The following list is invalid:\n" 510 error_message += gp.sprint_varx(var_name, var_value, 511 gp.blank() | gp.show_type()) 512 error_message += "\n" 513 error_message += "Because some of the values in the " 514 error_message += "required_values list are not present (see" 515 error_message += " entries marked with \"*\"):\n" 516 error_message += "\n" 517 error_message += gp.sprint_varx('required_values', 518 display_required_values, 519 gp.blank() | gp.show_type()) 520 error_message += "\n" 521 522 return process_error_message(error_message) 523 524 if len(invalid_values): 525 found_error = 0 526 display_var_value = list(var_value) 527 for ix in range(0, len(var_value)): 528 if var_value[ix] in invalid_values: 529 found_error = 1 530 display_var_value[ix] = str(var_value[ix]) + "*" 531 532 if found_error: 533 var_name = get_var_name(var_name) 534 error_message += "The following list is invalid (see entries" 535 error_message += " marked with \"*\"):\n" 536 error_message += gp.sprint_varx(var_name, display_var_value, 537 gp.blank() | gp.show_type()) 538 error_message += "\n" 539 error_message += gp.sprint_var(invalid_values, gp.show_type()) 540 return process_error_message(error_message) 541 542 found_error = 0 543 display_var_value = list(var_value) 544 for ix in range(0, len(var_value)): 545 if var_value[ix] not in valid_values: 546 found_error = 1 547 display_var_value[ix] = str(var_value[ix]) + "*" 548 549 if found_error: 550 var_name = get_var_name(var_name) 551 error_message += "The following list is invalid (see entries marked" 552 error_message += " with \"*\"):\n" 553 error_message += gp.sprint_varx(var_name, display_var_value, 554 gp.blank() | gp.show_type()) 555 error_message += "\n" 556 error_message += gp.sprint_var(valid_values, gp.show_type()) 557 return process_error_message(error_message) 558 559 return process_error_message(error_message) 560 561 562def valid_dict(var_value, required_keys=[], var_name=None): 563 r""" 564 The variable value is valid if it is a dictionary containing all of the required keys. 565 566 Description of argument(s): 567 var_value The value being validated. 568 required_keys A list of keys which must be found in the dictionary for it to be 569 considered valid. 570 """ 571 572 error_message = "" 573 missing_keys = list(set(required_keys) - set(var_value.keys())) 574 if len(missing_keys) > 0: 575 var_name = get_var_name(var_name) 576 error_message += "The following dictionary is invalid because it is" 577 error_message += " missing required keys:\n" 578 error_message += gp.sprint_varx(var_name, var_value, 579 gp.blank() | gp.show_type()) 580 error_message += "\n" 581 error_message += gp.sprint_var(missing_keys, gp.show_type()) 582 return process_error_message(error_message) 583 584 585def valid_program(var_value, var_name=None): 586 r""" 587 The variable value is valid if it contains the name of a program which can be located using the "which" 588 command. 589 590 Description of argument(s): 591 var_value The value being validated. 592 """ 593 594 error_message = "" 595 rc, out_buf = gc.shell_cmd("which " + var_value, quiet=1, show_err=0, 596 ignore_err=1) 597 if rc: 598 var_name = get_var_name(var_name) 599 error_message += "The following required program could not be found" 600 error_message += " using the $PATH environment variable:\n" 601 error_message += gp.sprint_varx(var_name, var_value, gp.blank()) 602 PATH = os.environ.get("PATH", "").split(":") 603 error_message += "\n" 604 error_message += gp.sprint_var(PATH) 605 return process_error_message(error_message) 606 607 608def valid_length(var_value, min_length=None, max_length=None, var_name=None): 609 r""" 610 The variable value is valid if it is an object (e.g. list, dictionary) whose length is within the 611 specified range. 612 613 Description of argument(s): 614 var_value The value being validated. 615 min_length The minimum length of the object. If not None, the length of var_value 616 must be greater than or equal to min_length. 617 max_length The maximum length of the object. If not None, the length of var_value 618 must be less than or equal to min_length. 619 """ 620 621 error_message = "" 622 length = len(var_value) 623 error_message = valid_range(length, min_length, max_length) 624 if error_message: 625 var_name = get_var_name(var_name) 626 error_message = "The length of the following object is not within the" 627 error_message += " expected range:\n" 628 error_message += gp.sprint_vars(min_length, max_length) 629 error_message += gp.sprint_var(length) 630 error_message += gp.sprint_varx(var_name, var_value, gp.blank()) 631 error_message += "\n" 632 return process_error_message(error_message) 633 634 return process_error_message(error_message) 635 636 637# Modify selected function docstrings by adding headers/footers. 638 639func_names = [ 640 "valid_type", "valid_value", "valid_range", "valid_integer", 641 "valid_dir_path", "valid_file_path", "valid_path", "valid_list", 642 "valid_dict", "valid_program", "valid_length", "valid_float", 643 "valid_date_time" 644] 645 646raw_doc_strings = {} 647 648for func_name in func_names: 649 cmd_buf = "raw_doc_strings['" + func_name + "'] = " + func_name 650 cmd_buf += ".__doc__" 651 exec(cmd_buf) 652 cmd_buf = func_name + ".__doc__ = docstring_header + " + func_name 653 cmd_buf += ".__doc__.rstrip(\" \\n\") + additional_args_docstring_footer" 654 exec(cmd_buf) 655