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