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