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