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