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