blob: d1601ecf81979ce148864afdea0701da2c06e921 [file] [log] [blame]
Michael Walsh7423c012016-10-04 10:27:21 -05001#!/usr/bin/env python
2
3r"""
Michael Walsh018e25f2019-08-01 11:27:12 -05004This module provides validation functions like valid_value(), valid_integer(),
5etc.
Michael Walsh7423c012016-10-04 10:27:21 -05006"""
7
Michael Walsh78bdfdd2017-01-10 11:27:30 -06008import os
Michael Walsh7423c012016-10-04 10:27:21 -05009import gen_print as gp
Michael Walsh018e25f2019-08-01 11:27:12 -050010import func_args as fa
Michael Walsh7423c012016-10-04 10:27:21 -050011
Michael Walshe23b5ad2018-06-01 14:54:35 -050012exit_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
Michael Walsh018e25f2019-08-01 11:27:12 -050019 If exit_on_error is set, validation functions like valid_value() will exit
Michael Walshe23b5ad2018-06-01 14:54:35 -050020 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
Michael Walsh018e25f2019-08-01 11:27:12 -050030def get_var_name(*args, **kwargs):
Michael Walshe23b5ad2018-06-01 14:54:35 -050031 r"""
Michael Walsh018e25f2019-08-01 11:27:12 -050032 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.
Michael Walshe23b5ad2018-06-01 14:54:35 -050035
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)
Michael Walsh018e25f2019-08-01 11:27:12 -050047 -> get_var_name(var_name)
Michael Walshe23b5ad2018-06-01 14:54:35 -050048
49 In this example, this function will return "last_name".
50
51 Example:
52
Michael Walsh018e25f2019-08-01 11:27:12 -050053 err_msg = valid_value(last_name, var_name="some_other_name")
Michael Walshe23b5ad2018-06-01 14:54:35 -050054
55 Which results in the following call stack:
56
Michael Walsh018e25f2019-08-01 11:27:12 -050057 valid_value(var_value, var_name="some_other_name")
Michael Walshe23b5ad2018-06-01 14:54:35 -050058 -> 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
Michael Walsh018e25f2019-08-01 11:27:12 -050066 var_name, args, kwargs = fa.pop_arg(*args, **kwargs)
67 if var_name:
Michael Walshe23b5ad2018-06-01 14:54:35 -050068 return var_name
Michael Walsh018e25f2019-08-01 11:27:12 -050069 return gp.get_arg_name(0, 1, stack_frame_ix=3)
Michael Walshe23b5ad2018-06-01 14:54:35 -050070
71
72def process_error_message(error_message):
73 r"""
Michael Walsh018e25f2019-08-01 11:27:12 -050074 Process the error_message in the manner described below.
Michael Walshe23b5ad2018-06-01 14:54:35 -050075
Michael Walsh018e25f2019-08-01 11:27:12 -050076 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.
Michael Walshe23b5ad2018-06-01 14:54:35 -0500109
110 Description of argument(s):
111 error_message An error message.
112 """
113
Michael Walsh018e25f2019-08-01 11:27:12 -0500114 # 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
Michael Walshe23b5ad2018-06-01 14:54:35 -0500120 if error_message == "":
Michael Walsh018e25f2019-08-01 11:27:12 -0500121 if exit_on_error:
122 return
Michael Walshe23b5ad2018-06-01 14:54:35 -0500123 return True
124
Michael Walsh018e25f2019-08-01 11:27:12 -0500125 gp.print_error_report(error_message, stack_frame_ix=4)
Michael Walshe23b5ad2018-06-01 14:54:35 -0500126 if exit_on_error:
Michael Walsh10a4d982018-10-30 13:07:46 -0500127 exit(1)
Michael Walshe23b5ad2018-06-01 14:54:35 -0500128 return False
129
Michael Walsh7423c012016-10-04 10:27:21 -0500130
Michael Walsh018e25f2019-08-01 11:27:12 -0500131# 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)
Michael Walsh7423c012016-10-04 10:27:21 -0500142
Michael Walsh018e25f2019-08-01 11:27:12 -0500143
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.
Michael Walsh7423c012016-10-04 10:27:21 -0500153 """
154
Michael Walsh018e25f2019-08-01 11:27:12 -0500155additional_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
Michael Walshbec416d2016-11-10 08:54:52 -0600244 error_message = ""
Michael Walshbec416d2016-11-10 08:54:52 -0600245
Michael Walshe23b5ad2018-06-01 14:54:35 -0500246 # Validate this function's arguments.
Michael Walsh7423c012016-10-04 10:27:21 -0500247 len_valid_values = len(valid_values)
248 len_invalid_values = len(invalid_values)
249 if len_valid_values > 0 and len_invalid_values > 0:
Michael Walsh018e25f2019-08-01 11:27:12 -0500250 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)
Michael Walsh7423c012016-10-04 10:27:21 -0500256
257 if len_valid_values > 0:
258 # Processing the valid_values list.
259 if var_value in valid_values:
Michael Walsh018e25f2019-08-01 11:27:12 -0500260 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)
Michael Walsh7423c012016-10-04 10:27:21 -0500272
273 if len_invalid_values == 0:
Michael Walshbec416d2016-11-10 08:54:52 -0600274 # Assign default value.
Michael Walsh018e25f2019-08-01 11:27:12 -0500275 invalid_values = ["", None]
Michael Walsh7423c012016-10-04 10:27:21 -0500276
277 # Assertion: We have an invalid_values list. Processing it now.
278 if var_value not in invalid_values:
Michael Walsh018e25f2019-08-01 11:27:12 -0500279 return process_error_message(error_message)
Michael Walsh7423c012016-10-04 10:27:21 -0500280
Michael Walsh018e25f2019-08-01 11:27:12 -0500281 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())
Michael Walshe23b5ad2018-06-01 14:54:35 -0500291 return process_error_message(error_message)
Michael Walshbec416d2016-11-10 08:54:52 -0600292
Michael Walshbec416d2016-11-10 08:54:52 -0600293
Michael Walsh018e25f2019-08-01 11:27:12 -0500294def valid_range(var_value, lower=None, upper=None, *args, **kwargs):
Michael Walshbec416d2016-11-10 08:54:52 -0600295 r"""
Michael Walsh018e25f2019-08-01 11:27:12 -0500296 The variable value is valid if it is within the specified range.
Michael Walshbec416d2016-11-10 08:54:52 -0600297
Michael Walsh018e25f2019-08-01 11:27:12 -0500298 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):
Michael Walshbec416d2016-11-10 08:54:52 -0600302 var_value The value being validated.
Michael Walsh018e25f2019-08-01 11:27:12 -0500303 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.
Michael Walshbec416d2016-11-10 08:54:52 -0600309 """
310
Michael Walshbec416d2016-11-10 08:54:52 -0600311 error_message = ""
Michael Walsh018e25f2019-08-01 11:27:12 -0500312 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)
Michael Walshbec416d2016-11-10 08:54:52 -0600327
Michael Walsh018e25f2019-08-01 11:27:12 -0500328 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)
Michael Walshe23b5ad2018-06-01 14:54:35 -0500335 return process_error_message(error_message)
Michael Walsh7423c012016-10-04 10:27:21 -0500336
Michael Walsh78bdfdd2017-01-10 11:27:30 -0600337
Michael Walsh018e25f2019-08-01 11:27:12 -0500338def valid_integer(var_value, lower=None, upper=None, *args, **kwargs):
Michael Walsh78bdfdd2017-01-10 11:27:30 -0600339 r"""
Michael Walsh018e25f2019-08-01 11:27:12 -0500340 The variable value is valid if it is an integer or can be interpreted as
341 an integer (e.g. 7, "7", etc.).
Michael Walsh78bdfdd2017-01-10 11:27:30 -0600342
Michael Walsh018e25f2019-08-01 11:27:12 -0500343 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):
Michael Walsh78bdfdd2017-01-10 11:27:30 -0600347 var_value The value being validated.
Michael Walsh018e25f2019-08-01 11:27:12 -0500348 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.
Michael Walsh78bdfdd2017-01-10 11:27:30 -0600383 """
384
385 error_message = ""
386 if not os.path.isdir(str(var_value)):
Michael Walsh018e25f2019-08-01 11:27:12 -0500387 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)
Michael Walsh78bdfdd2017-01-10 11:27:30 -0600390
Michael Walshe23b5ad2018-06-01 14:54:35 -0500391 return process_error_message(error_message)
Michael Walsh78bdfdd2017-01-10 11:27:30 -0600392
Michael Walsh78bdfdd2017-01-10 11:27:30 -0600393
Michael Walsh018e25f2019-08-01 11:27:12 -0500394def valid_file_path(var_value, *args, **kwargs):
Michael Walsh78bdfdd2017-01-10 11:27:30 -0600395 r"""
Michael Walsh018e25f2019-08-01 11:27:12 -0500396 The variable value is valid if it contains the path of an existing file.
Michael Walsh78bdfdd2017-01-10 11:27:30 -0600397
Michael Walsh018e25f2019-08-01 11:27:12 -0500398 Description of argument(s):
Michael Walsh78bdfdd2017-01-10 11:27:30 -0600399 var_value The value being validated.
Michael Walsh78bdfdd2017-01-10 11:27:30 -0600400 """
401
402 error_message = ""
403 if not os.path.isfile(str(var_value)):
Michael Walsh018e25f2019-08-01 11:27:12 -0500404 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)
Michael Walsh78bdfdd2017-01-10 11:27:30 -0600407
Michael Walshe23b5ad2018-06-01 14:54:35 -0500408 return process_error_message(error_message)
Michael Walsh78bdfdd2017-01-10 11:27:30 -0600409
Michael Walsh78bdfdd2017-01-10 11:27:30 -0600410
Michael Walsh018e25f2019-08-01 11:27:12 -0500411def valid_path(var_value, *args, **kwargs):
Michael Walshe23b5ad2018-06-01 14:54:35 -0500412 r"""
Michael Walsh018e25f2019-08-01 11:27:12 -0500413 The variable value is valid if it contains the path of an existing file or
414 directory.
Michael Walshe23b5ad2018-06-01 14:54:35 -0500415
Michael Walsh018e25f2019-08-01 11:27:12 -0500416 Description of argument(s):
Michael Walshe23b5ad2018-06-01 14:54:35 -0500417 var_value The value being validated.
Michael Walshe23b5ad2018-06-01 14:54:35 -0500418 """
419
420 error_message = ""
421 if not (os.path.isfile(str(var_value)) or os.path.isdir(str(var_value))):
Michael Walsh018e25f2019-08-01 11:27:12 -0500422 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)
Michael Walshe23b5ad2018-06-01 14:54:35 -0500425
Michael Walshe23b5ad2018-06-01 14:54:35 -0500426 return process_error_message(error_message)
Michael Walsh2c687e92018-05-09 11:47:56 -0500427
428
Michael Walsh018e25f2019-08-01 11:27:12 -0500429def valid_list(var_value, valid_values=[], fail_on_empty=False, *args,
430 **kwargs):
Michael Walsh2c687e92018-05-09 11:47:56 -0500431 r"""
Michael Walsh018e25f2019-08-01 11:27:12 -0500432 The variable value is valid if it is a list where each entry can be found
433 in the valid_values list.
Michael Walsh2c687e92018-05-09 11:47:56 -0500434
Michael Walsh018e25f2019-08-01 11:27:12 -0500435 Description of argument(s):
436 var_value The value being validated.
Michael Walshca193992018-08-02 17:20:00 -0500437 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.
Michael Walsh018e25f2019-08-01 11:27:12 -0500440 fail_on_empty Indicates that an empty list for the
441 variable value should be considered an
442 error.
Michael Walshca193992018-08-02 17:20:00 -0500443 """
444
445 error_message = ""
Michael Walsh018e25f2019-08-01 11:27:12 -0500446
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)
Michael Walshca193992018-08-02 17:20:00 -0500458
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:
Michael Walsh018e25f2019-08-01 11:27:12 -0500467 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)
Michael Walshca193992018-08-02 17:20:00 -0500475
Michael Walshca193992018-08-02 17:20:00 -0500476 return process_error_message(error_message)
Michael Walsh7ac5fd82018-10-11 16:58:49 -0500477
478
Michael Walsh018e25f2019-08-01 11:27:12 -0500479def valid_dict(var_value, required_keys=[], *args, **kwargs):
Michael Walsh7ac5fd82018-10-11 16:58:49 -0500480 r"""
Michael Walsh018e25f2019-08-01 11:27:12 -0500481 The variable value is valid if it is a dictionary containing all of the
482 required keys.
Michael Walsh7ac5fd82018-10-11 16:58:49 -0500483
Michael Walsh018e25f2019-08-01 11:27:12 -0500484 Description of argument(s):
485 var_value The value being validated.
Michael Walsh7ac5fd82018-10-11 16:58:49 -0500486 required_keys A list of keys which must be found in the
487 dictionary for it to be considered valid.
Michael Walsh7ac5fd82018-10-11 16:58:49 -0500488 """
489
490 error_message = ""
Michael Walsh018e25f2019-08-01 11:27:12 -0500491 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())
Michael Walsh7ac5fd82018-10-11 16:58:49 -0500500 return process_error_message(error_message)
Michael Walsh018e25f2019-08-01 11:27:12 -0500501
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)