blob: 53466dc71b18963c819f1d0b00e7c02cf4664d4d [file] [log] [blame]
#!/usr/bin/env python
r"""
This module provides validation functions like valid_value(), valid_integer(),
etc.
"""
import os
import gen_print as gp
import gen_cmd as gc
import func_args as fa
exit_on_error = False
def set_exit_on_error(value):
r"""
Set the exit_on_error value to either True or False.
If exit_on_error is set, validation functions like valid_value() will exit
the program on error instead of returning False.
Description of argument(s):
value Value to set global exit_on_error to.
"""
global exit_on_error
exit_on_error = value
def get_var_name(*args, **kwargs):
r"""
If args/kwargs contain a var_name, simply return its value. Otherwise,
get the variable name of the first argument used to call the validation
function (e.g. valid, valid_integer, etc.) and return it.
This function is designed solely for use by other functions in this file.
Example:
A programmer codes this:
valid_value(last_name)
Which results in the following call stack:
valid_value(last_name)
-> get_var_name(var_name)
In this example, this function will return "last_name".
Example:
err_msg = valid_value(last_name, var_name="some_other_name")
Which results in the following call stack:
valid_value(var_value, var_name="some_other_name")
-> get_var_name(var_name)
In this example, this function will return "some_other_name".
Description of argument(s):
var_name The name of the variable.
"""
var_name, args, kwargs = fa.pop_arg(*args, **kwargs)
if var_name:
return var_name
return gp.get_arg_name(0, 1, stack_frame_ix=3)
def process_error_message(error_message):
r"""
Process the error_message in the manner described below.
This function is designed solely for use by other functions in this file.
NOTE: A blank error_message means that there is no error.
For the following explanations, assume the caller of this function is a
function with the following definition:
valid_value(var_value, valid_values=[], invalid_values=[], *args,
**kwargs):
If the user of valid_value() is assigning the valid_value() return value
to a variable, process_error_message() will simply return the
error_message. This mode of usage is illustrated by the following example:
error_message = valid_value(var1)
This mode is useful for callers who wish to validate a variable and then
decide for themselves what to do with the error_message (e.g.
raise(error_message), BuiltIn().fail(error_message), etc.).
If the user of valid_value() is NOT assigning the valid_value() return
value to a variable, process_error_message() will behave as follows.
First, if error_message is non-blank, it will be printed to stderr via a
call to gp.print_error_report(error_message).
If exit_on_error is set:
- If the error_message is blank, simply return.
- If the error_message is non-blank, exit the program with a return code
of 1.
If exit_on_error is NOT set:
- If the error_message is blank, return True.
- If the error_message is non-blank, return False.
Description of argument(s):
error_message An error message.
"""
# Determine whether the caller's caller is assigning the result to a
# variable.
l_value = gp.get_arg_name(None, -1, stack_frame_ix=3)
if l_value:
return error_message
if error_message == "":
if exit_on_error:
return
return True
gp.print_error_report(error_message, stack_frame_ix=4)
if exit_on_error:
exit(1)
return False
# Note to programmers: All of the validation functions in this module should
# follow the same basic template:
# def valid_value(var_value, var1, var2, varn, *args, **kwargs):
#
# error_message = ""
# if not valid:
# var_name = get_var_name(*args, **kwargs)
# error_message += "The following variable is invalid because...:\n"
# error_message += gp.sprint_varx(var_name, var_value, gp.blank())
#
# return process_error_message(error_message)
# The docstring header and footer will be added to each validation function's
# existing docstring.
docstring_header = \
r"""
Determine whether var_value is valid, construct an error_message and call
process_error_message(error_message).
See the process_error_message() function defined in this module for a
description of how error messages are processed.
"""
additional_args_docstring_footer = \
r"""
args Additional positional arguments (described
below).
kwargs Additional keyword arguments (described
below).
Additional argument(s):
var_name The name of the variable whose value is
passed in var_value. For the general
case, this argument is unnecessary as this
function can figure out the var_name.
This is provided for Robot callers in
which case, this function lacks the
ability to determine the variable name.
"""
def valid_type(var_value, required_type, *args, **kwargs):
r"""
The variable value is valid if it is of the required type.
Examples:
valid_type(var1, int)
valid_type(var1, (list, dict))
Description of argument(s):
var_value The value being validated.
required_type A type or a tuple of types (e.g. str, int,
etc.).
"""
error_message = ""
if type(required_type) is tuple:
if type(var_value) in required_type:
return process_error_message(error_message)
else:
if type(var_value) is required_type:
return process_error_message(error_message)
# If we get to this point, the validation has failed.
var_name = get_var_name(*args, **kwargs)
error_message += "Invalid variable type:\n"
error_message += gp.sprint_varx(var_name, var_value,
gp.blank() | gp.show_type())
error_message += "\n"
error_message += gp.sprint_var(required_type)
return process_error_message(error_message)
def valid_value(var_value, valid_values=[], invalid_values=[], *args,
**kwargs):
r"""
The variable value is valid if it is either contained in the valid_values
list or if it is NOT contained in the invalid_values list. If the caller
specifies nothing for either of these 2 arguments, invalid_values will be
initialized to ['', None]. This is a good way to fail on variables which
contain blank values.
It is illegal to specify both valid_values and invalid values.
Example:
var1 = ''
valid_value(var1)
This code would fail because var1 is blank and the default value for
invalid_values is ['', None].
Example:
var1 = 'yes'
valid_value(var1, valid_values=['yes', 'true'])
This code would pass.
Description of argument(s):
var_value The value being validated.
valid_values A list of valid values. The variable
value must be equal to one of these values
to be considered valid.
invalid_values A list of invalid values. If the variable
value is equal to any of these, it is
considered invalid.
"""
error_message = ""
# Validate this function's arguments.
len_valid_values = len(valid_values)
len_invalid_values = len(invalid_values)
if len_valid_values > 0 and len_invalid_values > 0:
error_message += "Programmer error - You must provide either an"
error_message += " invalid_values list or a valid_values"
error_message += " list but NOT both:\n"
error_message += gp.sprint_var(invalid_values)
error_message += gp.sprint_var(valid_values)
return process_error_message(error_message)
if len_valid_values > 0:
# Processing the valid_values list.
if var_value in valid_values:
return process_error_message(error_message)
var_name = get_var_name(*args, **kwargs)
error_message += "Invalid variable value:\n"
error_message += gp.sprint_varx(var_name, var_value,
gp.blank() | gp.verbose()
| gp.show_type())
error_message += "\n"
error_message += "It must be one of the following values:\n"
error_message += "\n"
error_message += gp.sprint_var(valid_values,
gp.blank() | gp.show_type())
return process_error_message(error_message)
if len_invalid_values == 0:
# Assign default value.
invalid_values = ["", None]
# Assertion: We have an invalid_values list. Processing it now.
if var_value not in invalid_values:
return process_error_message(error_message)
var_name = get_var_name(*args, **kwargs)
error_message += "Invalid variable value:\n"
error_message += gp.sprint_varx(var_name, var_value,
gp.blank() | gp.verbose()
| gp.show_type())
error_message += "\n"
error_message += "It must NOT be one of the following values:\n"
error_message += "\n"
error_message += gp.sprint_var(invalid_values,
gp.blank() | gp.show_type())
return process_error_message(error_message)
def valid_range(var_value, lower=None, upper=None, *args, **kwargs):
r"""
The variable value is valid if it is within the specified range.
This function can be used with any type of operands where they can have a
greater than/less than relationship to each other (e.g. int, float, str).
Description of argument(s):
var_value The value being validated.
lower The lower end of the range. If not None,
the var_value must be greater than or
equal to lower.
upper The upper end of the range. If not None,
the var_value must be less than or equal
to upper.
"""
error_message = ""
if not lower and not upper:
return process_error_message(error_message)
if not lower and var_value <= upper:
return process_error_message(error_message)
if not upper and var_value >= lower:
return process_error_message(error_message)
if lower and upper:
if lower > upper:
var_name = get_var_name(*args, **kwargs)
error_message += "Programmer error - the lower value is greater"
error_message += " than the upper value:\n"
error_message += gp.sprint_vars(lower, upper, fmt=gp.show_type())
return process_error_message(error_message)
if lower <= var_value <= upper:
return process_error_message(error_message)
var_name = get_var_name(*args, **kwargs)
error_message += "The following variable is not within the expected"
error_message += " range:\n"
error_message += gp.sprint_varx(var_name, var_value, gp.show_type())
error_message += "\n"
error_message += "range:\n"
error_message += gp.sprint_vars(lower, upper, fmt=gp.show_type(), indent=2)
return process_error_message(error_message)
def valid_integer(var_value, lower=None, upper=None, *args, **kwargs):
r"""
The variable value is valid if it is an integer or can be interpreted as
an integer (e.g. 7, "7", etc.).
This function also calls valid_range to make sure the integer value is
within the specified range (if any).
Description of argument(s):
var_value The value being validated.
lower The lower end of the range. If not None,
the var_value must be greater than or
equal to lower.
upper The upper end of the range. If not None,
the var_value must be less than or equal
to upper.
"""
error_message = ""
var_name = get_var_name(*args, **kwargs)
try:
var_value = int(str(var_value), 0)
except ValueError:
error_message += "Invalid integer value:\n"
error_message += gp.sprint_varx(var_name, var_value,
gp.blank() | gp.show_type())
return process_error_message(error_message)
# Check the range (if any).
if lower:
lower = int(str(lower), 0)
if upper:
upper = int(str(upper), 0)
error_message = valid_range(var_value, lower, upper, var_name=var_name)
return process_error_message(error_message)
def valid_dir_path(var_value, *args, **kwargs):
r"""
The variable value is valid if it contains the path of an existing
directory.
Description of argument(s):
var_value The value being validated.
"""
error_message = ""
if not os.path.isdir(str(var_value)):
var_name = get_var_name(*args, **kwargs)
error_message += "The following directory does not exist:\n"
error_message += gp.sprint_varx(var_name, var_value, gp.blank())
return process_error_message(error_message)
def valid_file_path(var_value, *args, **kwargs):
r"""
The variable value is valid if it contains the path of an existing file.
Description of argument(s):
var_value The value being validated.
"""
error_message = ""
if not os.path.isfile(str(var_value)):
var_name = get_var_name(*args, **kwargs)
error_message += "The following file does not exist:\n"
error_message += gp.sprint_varx(var_name, var_value, gp.blank())
return process_error_message(error_message)
def valid_path(var_value, *args, **kwargs):
r"""
The variable value is valid if it contains the path of an existing file or
directory.
Description of argument(s):
var_value The value being validated.
"""
error_message = ""
if not (os.path.isfile(str(var_value)) or os.path.isdir(str(var_value))):
var_name = get_var_name(*args, **kwargs)
error_message += "Invalid path (file or directory does not exist):\n"
error_message += gp.sprint_varx(var_name, var_value, gp.blank())
return process_error_message(error_message)
def valid_list(var_value, valid_values=[], invalid_values=[],
required_values=[], fail_on_empty=False, *args, **kwargs):
r"""
The variable value is valid if it is a list where each entry can be found
in the valid_values list or if none of its values can be found in the
invalid_values list or if all of the values in the required_values list
can be found in var_value.
The caller may only specify one of these 3 arguments: valid_values,
invalid_values, required_values.
Description of argument(s):
var_value The value being validated.
valid_values A list of valid values. Each element in
the var_value list must be equal to one of
these values to be considered valid.
invalid_values A list of invalid values. If any element
in var_value is equal to any of the values
in this argument, var_value is considered
invalid.
required_values Every value in required_values must be
found in var_value. Otherwise, var_value
is considered invalid.
fail_on_empty Indicates that an empty list for the
variable value should be considered an
error.
"""
error_message = ""
# Validate this function's arguments.
len_valid_values = len(valid_values)
len_invalid_values = len(invalid_values)
len_required_values = len(required_values)
if (len_valid_values + len_invalid_values + len_required_values) > 1:
error_message += "Programmer error - You must provide only one of the"
error_message += " following: valid_values, invalid_values,"
error_message += " required_values.\n"
error_message += gp.sprint_var(invalid_values)
error_message += gp.sprint_var(valid_values)
error_message += gp.sprint_var(required_values)
return process_error_message(error_message)
if type(var_value) is not list:
var_name = get_var_name(*args, **kwargs)
error_message = valid_type(var_value, list, var_name=var_name)
if error_message:
return process_error_message(error_message)
if fail_on_empty and len(var_value) == 0:
var_name = get_var_name(*args, **kwargs)
error_message += "Invalid empty list:\n"
error_message += gp.sprint_varx(var_name, var_value, gp.show_type())
return process_error_message(error_message)
if len(required_values):
found_error = 0
display_required_values = list(required_values)
for ix in range(0, len(required_values)):
if required_values[ix] not in var_value:
found_error = 1
display_required_values[ix] = \
str(display_required_values[ix]) + "*"
if found_error:
var_name = get_var_name(*args, **kwargs)
error_message += "The following list is invalid:\n"
error_message += gp.sprint_varx(var_name, var_value,
gp.blank() | gp.show_type())
error_message += "\n"
error_message += "Because some of the values in the "
error_message += "required_values list are not present (see"
error_message += " entries marked with \"*\"):\n"
error_message += "\n"
error_message += gp.sprint_varx('required_values',
display_required_values,
gp.blank() | gp.show_type())
error_message += "\n"
return process_error_message(error_message)
if len(invalid_values):
found_error = 0
display_var_value = list(var_value)
for ix in range(0, len(var_value)):
if var_value[ix] in invalid_values:
found_error = 1
display_var_value[ix] = str(var_value[ix]) + "*"
if found_error:
var_name = get_var_name(*args, **kwargs)
error_message += "The following list is invalid (see entries"
error_message += " marked with \"*\"):\n"
error_message += gp.sprint_varx(var_name, display_var_value,
gp.blank() | gp.show_type())
error_message += "\n"
error_message += gp.sprint_var(invalid_values, gp.show_type())
return process_error_message(error_message)
found_error = 0
display_var_value = list(var_value)
for ix in range(0, len(var_value)):
if var_value[ix] not in valid_values:
found_error = 1
display_var_value[ix] = str(var_value[ix]) + "*"
if found_error:
var_name = get_var_name(*args, **kwargs)
error_message += "The following list is invalid (see entries marked"
error_message += " with \"*\"):\n"
error_message += gp.sprint_varx(var_name, display_var_value,
gp.blank() | gp.show_type())
error_message += "\n"
error_message += gp.sprint_var(valid_values, gp.show_type())
return process_error_message(error_message)
return process_error_message(error_message)
def valid_dict(var_value, required_keys=[], *args, **kwargs):
r"""
The variable value is valid if it is a dictionary containing all of the
required keys.
Description of argument(s):
var_value The value being validated.
required_keys A list of keys which must be found in the
dictionary for it to be considered valid.
"""
error_message = ""
missing_keys = list(set(required_keys) - set(var_value.keys()))
if len(missing_keys) > 0:
var_name = get_var_name(*args, **kwargs)
error_message += "The following dictionary is invalid because it is"
error_message += " missing required keys:\n"
error_message += gp.sprint_varx(var_name, var_value,
gp.blank() | gp.show_type())
error_message += "\n"
error_message += gp.sprint_var(missing_keys, gp.show_type())
return process_error_message(error_message)
def valid_program(var_value, *args, **kwargs):
r"""
The variable value is valid if it contains the name of a program which can
be located using the "which" command.
Description of argument(s):
var_value The value being validated.
"""
error_message = ""
rc, out_buf = gc.shell_cmd("which " + var_value, quiet=1, show_err=0,
ignore_err=1)
if rc:
var_name = get_var_name(*args, **kwargs)
error_message += "The following required program could not be found"
error_message += " using the $PATH environment variable:\n"
error_message += gp.sprint_varx(var_name, var_value, gp.blank())
PATH = os.environ.get("PATH", "").split(":")
error_message += "\n"
error_message += gp.sprint_var(PATH)
return process_error_message(error_message)
# Modify selected function docstrings by adding headers/footers.
func_names = [
"valid_type", "valid_value", "valid_range", "valid_integer",
"valid_dir_path", "valid_file_path", "valid_path", "valid_list",
"valid_dict", "valid_program"
]
raw_doc_strings = {}
for func_name in func_names:
cmd_buf = "raw_doc_strings['" + func_name + "'] = " + func_name
cmd_buf += ".__doc__"
exec(cmd_buf)
cmd_buf = func_name + ".__doc__ = docstring_header + " + func_name
cmd_buf += ".__doc__.rstrip(\" \\n\") + additional_args_docstring_footer"
exec(cmd_buf)