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=[], fail_on_empty=False, *args, 430 **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. 434 435 Description of argument(s): 436 var_value The value being validated. 437 valid_values A list of valid values. Each element in 438 the var_value list must be equal to one of 439 these values to be considered valid. 440 fail_on_empty Indicates that an empty list for the 441 variable value should be considered an 442 error. 443 """ 444 445 error_message = "" 446 447 if type(var_value) is not list: 448 var_name = get_var_name(*args, **kwargs) 449 error_message = valid_type(var_value, list, var_name=var_name) 450 if error_message: 451 return process_error_message(error_message) 452 453 if fail_on_empty and len(var_value) == 0: 454 var_name = get_var_name(*args, **kwargs) 455 error_message += "Invalid empty list:\n" 456 error_message += gp.sprint_varx(var_name, var_value, gp.show_type()) 457 return process_error_message(error_message) 458 459 found_error = 0 460 display_var_value = list(var_value) 461 for ix in range(0, len(var_value)): 462 if var_value[ix] not in valid_values: 463 found_error = 1 464 display_var_value[ix] = var_value[ix] + "*" 465 466 if found_error: 467 var_name = get_var_name(*args, **kwargs) 468 error_message += "The following list is invalid (see entries marked" 469 error_message += " with \"*\"):\n" 470 error_message += gp.sprint_varx(var_name, display_var_value, 471 gp.blank() | gp.show_type()) 472 error_message += "\n" 473 error_message += gp.sprint_var(valid_values | gp.show_type()) 474 return process_error_message(error_message) 475 476 return process_error_message(error_message) 477 478 479def valid_dict(var_value, required_keys=[], *args, **kwargs): 480 r""" 481 The variable value is valid if it is a dictionary containing all of the 482 required keys. 483 484 Description of argument(s): 485 var_value The value being validated. 486 required_keys A list of keys which must be found in the 487 dictionary for it to be considered valid. 488 """ 489 490 error_message = "" 491 missing_keys = list(set(required_keys) - set(var_value.keys())) 492 if len(missing_keys) > 0: 493 var_name = get_var_name(*args, **kwargs) 494 error_message += "The following dictionary is invalid because it is" 495 error_message += " missing required keys:\n" 496 error_message += gp.sprint_varx(var_name, var_value, 497 gp.blank() | gp.show_type()) 498 error_message += "\n" 499 error_message += gp.sprint_var(missing_keys | gp.show_type()) 500 return process_error_message(error_message) 501 502 503# Modify selected function docstrings by adding headers/footers. 504 505func_names = [ 506 "valid_type", "valid_value", "valid_range", "valid_integer", 507 "valid_dir_path", "valid_file_path", "valid_path", "valid_list", 508 "valid_dict" 509] 510 511raw_doc_strings = {} 512 513for func_name in func_names: 514 cmd_buf = "raw_doc_strings['" + func_name + "'] = " + func_name 515 cmd_buf += ".__doc__" 516 exec(cmd_buf) 517 cmd_buf = func_name + ".__doc__ = docstring_header + " + func_name 518 cmd_buf += ".__doc__.rstrip(\" \\n\") + additional_args_docstring_footer" 519 exec(cmd_buf) 520