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