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