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=[], invalid_values=[],
430               required_values=[], fail_on_empty=False, *args, **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 or if none of its values can be found in the
434    invalid_values list or if all of the values in the required_values list
435    can be found in var_value.
436
437    The caller may only specify one of these 3 arguments: valid_values,
438    invalid_values, required_values.
439
440    Description of argument(s):
441    var_value                       The value being validated.
442    valid_values                    A list of valid values.  Each element in
443                                    the var_value list must be equal to one of
444                                    these values to be considered valid.
445    invalid_values                  A list of invalid values.  If any element
446                                    in var_value is equal to any of the values
447                                    in this argument, var_value is considered
448                                    invalid.
449    required_values                 Every value in required_values must be
450                                    found in var_value.  Otherwise, var_value
451                                    is considered invalid.
452    fail_on_empty                   Indicates that an empty list for the
453                                    variable value should be considered an
454                                    error.
455    """
456
457    error_message = ""
458
459    # Validate this function's arguments.
460    len_valid_values = len(valid_values)
461    len_invalid_values = len(invalid_values)
462    len_required_values = len(required_values)
463    if (len_valid_values + len_invalid_values + len_required_values) > 1:
464        error_message += "Programmer error - You must provide only one of the"
465        error_message += " following: valid_values, invalid_values,"
466        error_message += " required_values.\n"
467        error_message += gp.sprint_var(invalid_values)
468        error_message += gp.sprint_var(valid_values)
469        error_message += gp.sprint_var(required_values)
470        return process_error_message(error_message)
471
472    if type(var_value) is not list:
473        var_name = get_var_name(*args, **kwargs)
474        error_message = valid_type(var_value, list, var_name=var_name)
475        if error_message:
476            return process_error_message(error_message)
477
478    if fail_on_empty and len(var_value) == 0:
479        var_name = get_var_name(*args, **kwargs)
480        error_message += "Invalid empty list:\n"
481        error_message += gp.sprint_varx(var_name, var_value, gp.show_type())
482        return process_error_message(error_message)
483
484    if len(required_values):
485        found_error = 0
486        display_required_values = list(required_values)
487        for ix in range(0, len(required_values)):
488            if required_values[ix] not in var_value:
489                found_error = 1
490                display_required_values[ix] = \
491                    str(display_required_values[ix]) + "*"
492        if found_error:
493            var_name = get_var_name(*args, **kwargs)
494            error_message += "The following list is invalid:\n"
495            error_message += gp.sprint_varx(var_name, var_value,
496                                            gp.blank() | gp.show_type())
497            error_message += "\n"
498            error_message += "Because some of the values in the "
499            error_message += "required_values list are not present (see"
500            error_message += " entries marked with \"*\"):\n"
501            error_message += "\n"
502            error_message += gp.sprint_varx('required_values',
503                                            display_required_values,
504                                            gp.blank() | gp.show_type())
505            error_message += "\n"
506
507        return process_error_message(error_message)
508
509    if len(invalid_values):
510        found_error = 0
511        display_var_value = list(var_value)
512        for ix in range(0, len(var_value)):
513            if var_value[ix] in invalid_values:
514                found_error = 1
515                display_var_value[ix] = str(var_value[ix]) + "*"
516
517        if found_error:
518            var_name = get_var_name(*args, **kwargs)
519            error_message += "The following list is invalid (see entries"
520            error_message += " marked with \"*\"):\n"
521            error_message += gp.sprint_varx(var_name, display_var_value,
522                                            gp.blank() | gp.show_type())
523            error_message += "\n"
524            error_message += gp.sprint_var(invalid_values, gp.show_type())
525        return process_error_message(error_message)
526
527    found_error = 0
528    display_var_value = list(var_value)
529    for ix in range(0, len(var_value)):
530        if var_value[ix] not in valid_values:
531            found_error = 1
532            display_var_value[ix] = str(var_value[ix]) + "*"
533
534    if found_error:
535        var_name = get_var_name(*args, **kwargs)
536        error_message += "The following list is invalid (see entries marked"
537        error_message += " with \"*\"):\n"
538        error_message += gp.sprint_varx(var_name, display_var_value,
539                                        gp.blank() | gp.show_type())
540        error_message += "\n"
541        error_message += gp.sprint_var(valid_values, gp.show_type())
542        return process_error_message(error_message)
543
544    return process_error_message(error_message)
545
546
547def valid_dict(var_value, required_keys=[], *args, **kwargs):
548    r"""
549    The variable value is valid if it is a dictionary containing all of the
550    required keys.
551
552    Description of argument(s):
553    var_value                       The value being validated.
554    required_keys                   A list of keys which must be found in the
555                                    dictionary for it to be considered valid.
556    """
557
558    error_message = ""
559    missing_keys = list(set(required_keys) - set(var_value.keys()))
560    if len(missing_keys) > 0:
561        var_name = get_var_name(*args, **kwargs)
562        error_message += "The following dictionary is invalid because it is"
563        error_message += " missing required keys:\n"
564        error_message += gp.sprint_varx(var_name, var_value,
565                                        gp.blank() | gp.show_type())
566        error_message += "\n"
567        error_message += gp.sprint_var(missing_keys, gp.show_type())
568    return process_error_message(error_message)
569
570
571# Modify selected function docstrings by adding headers/footers.
572
573func_names = [
574    "valid_type", "valid_value", "valid_range", "valid_integer",
575    "valid_dir_path", "valid_file_path", "valid_path", "valid_list",
576    "valid_dict"
577]
578
579raw_doc_strings = {}
580
581for func_name in func_names:
582    cmd_buf = "raw_doc_strings['" + func_name + "'] = " + func_name
583    cmd_buf += ".__doc__"
584    exec(cmd_buf)
585    cmd_buf = func_name + ".__doc__ = docstring_header + " + func_name
586    cmd_buf += ".__doc__.rstrip(\" \\n\") + additional_args_docstring_footer"
587    exec(cmd_buf)
588