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