added more hinting to pattern validation messages
This commit is contained in:
@ -59,7 +59,11 @@ class BasePattern:
|
|||||||
"""Validation check for 'name' variable from main constructor. Is
|
"""Validation check for 'name' variable from main constructor. Is
|
||||||
automatically called during initialisation. This does not need to be
|
automatically called during initialisation. This does not need to be
|
||||||
overridden by child classes."""
|
overridden by child classes."""
|
||||||
valid_string(name, VALID_PATTERN_NAME_CHARS)
|
valid_string(
|
||||||
|
name,
|
||||||
|
VALID_PATTERN_NAME_CHARS,
|
||||||
|
hint="BasePattern._is_valid_name.name"
|
||||||
|
)
|
||||||
|
|
||||||
def _is_valid_recipe(self, recipe:Any)->None:
|
def _is_valid_recipe(self, recipe:Any)->None:
|
||||||
"""Validation check for 'recipe' variable from main constructor. Must
|
"""Validation check for 'recipe' variable from main constructor. Must
|
||||||
|
@ -48,11 +48,18 @@ def check_type(variable:Any, expected_type:Type, alt_types:List[Type]=[],
|
|||||||
msg = f"Expected type(s) are '{type_list}', got {type(variable)}"
|
msg = f"Expected type(s) are '{type_list}', got {type(variable)}"
|
||||||
raise TypeError(msg)
|
raise TypeError(msg)
|
||||||
|
|
||||||
def check_callable(call:Any)->None:
|
def check_callable(call:Any, hint:str="")->None:
|
||||||
"""Checks if a given variable is a callable function. Raises TypeError if
|
"""Checks if a given variable is a callable function. Raises TypeError if
|
||||||
not."""
|
not."""
|
||||||
if not callable(call):
|
if not callable(call):
|
||||||
raise TypeError(f"Given object '{call}' is not a callable function")
|
if hint:
|
||||||
|
raise TypeError(
|
||||||
|
f"Given object '{call}' by '{hint}' is not a callable function"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise TypeError(
|
||||||
|
f"Given object '{call}' is not a callable function"
|
||||||
|
)
|
||||||
|
|
||||||
def check_implementation(child_func, parent_class):
|
def check_implementation(child_func, parent_class):
|
||||||
"""Checks if the given function has been overridden from the one inherited
|
"""Checks if the given function has been overridden from the one inherited
|
||||||
@ -84,68 +91,82 @@ def check_script(script:Any):
|
|||||||
for line in script:
|
for line in script:
|
||||||
check_type(line, str)
|
check_type(line, str)
|
||||||
|
|
||||||
def valid_string(variable:str, valid_chars:str, min_length:int=1)->None:
|
def valid_string(variable:str, valid_chars:str, min_length:int=1, hint:str=""
|
||||||
|
)->None:
|
||||||
"""Checks that all characters in a given string are present in a provided
|
"""Checks that all characters in a given string are present in a provided
|
||||||
list of characters. Will raise an ValueError if unexpected character is
|
list of characters. Will raise an ValueError if unexpected character is
|
||||||
encountered."""
|
encountered."""
|
||||||
check_type(variable, str)
|
check_type(variable, str, hint=hint)
|
||||||
check_type(valid_chars, str)
|
check_type(valid_chars, str, hint=hint)
|
||||||
|
|
||||||
# Check string is long enough
|
# Check string is long enough
|
||||||
if len(variable) < min_length:
|
if len(variable) < min_length:
|
||||||
raise ValueError (
|
if hint:
|
||||||
f"String '{variable}' is too short. Minimum length is {min_length}"
|
msg = f"String '{variable}' for '{hint}' is too short. Minimum " \
|
||||||
)
|
f"length is {min_length}"
|
||||||
|
else:
|
||||||
|
msg = f"String '{variable}' is too short. Minimum length is " \
|
||||||
|
f"{min_length}"
|
||||||
|
raise ValueError (msg)
|
||||||
|
|
||||||
# Check each char is acceptable
|
# Check each char is acceptable
|
||||||
for char in variable:
|
for char in variable:
|
||||||
if char not in valid_chars:
|
if char not in valid_chars:
|
||||||
raise ValueError(
|
if hint :
|
||||||
"Invalid character '%s'. Only valid characters are: "
|
msg = f"Invalid character '{char}' in '{hint}'. Only valid " \
|
||||||
"%s" % (char, valid_chars)
|
f"characters are: {valid_chars}"
|
||||||
)
|
else:
|
||||||
|
msg = f"Invalid character '{char}'. Only valid characters " \
|
||||||
|
f"are: {valid_chars}"
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
|
||||||
def valid_dict(variable:Dict[Any, Any], key_type:Type, value_type:Type,
|
def valid_dict(variable:Dict[Any, Any], key_type:Type, value_type:Type,
|
||||||
required_keys:List[Any]=[], optional_keys:List[Any]=[],
|
required_keys:List[Any]=[], optional_keys:List[Any]=[],
|
||||||
strict:bool=True, min_length:int=1)->None:
|
strict:bool=True, min_length:int=1, hint:str="")->None:
|
||||||
"""Checks that a given dictionary is valid. Key and Value types are
|
"""Checks that a given dictionary is valid. Key and Value types are
|
||||||
enforced, as are required and optional keys. Will raise ValueError,
|
enforced, as are required and optional keys. Will raise ValueError,
|
||||||
TypeError or KeyError depending on the problem encountered."""
|
TypeError or KeyError depending on the problem encountered."""
|
||||||
# Validate inputs
|
# Validate inputs
|
||||||
check_type(variable, Dict)
|
check_type(variable, Dict, hint=hint)
|
||||||
check_type(key_type, Type, alt_types=[_SpecialForm])
|
check_type(key_type, Type, alt_types=[_SpecialForm], hint=hint)
|
||||||
check_type(value_type, Type, alt_types=[_SpecialForm])
|
check_type(value_type, Type, alt_types=[_SpecialForm], hint=hint)
|
||||||
check_type(required_keys, list)
|
check_type(required_keys, list, hint=hint)
|
||||||
check_type(optional_keys, list)
|
check_type(optional_keys, list, hint=hint)
|
||||||
check_type(strict, bool)
|
check_type(strict, bool, hint=hint)
|
||||||
|
|
||||||
|
if hint:
|
||||||
|
hint = f"in '{hint}' "
|
||||||
|
|
||||||
# Check dict meets minimum length
|
# Check dict meets minimum length
|
||||||
if len(variable) < min_length:
|
if len(variable) < min_length:
|
||||||
raise ValueError(f"Dictionary '{variable}' is below minimum length of "
|
raise ValueError(
|
||||||
f"{min_length}")
|
f"Dictionary '{variable}' {hint}is below minimum length of "
|
||||||
|
f"{min_length}"
|
||||||
|
)
|
||||||
|
|
||||||
# Check key and value types
|
# Check key and value types
|
||||||
for k, v in variable.items():
|
for k, v in variable.items():
|
||||||
if key_type != Any and not isinstance(k, key_type):
|
if key_type != Any and not isinstance(k, key_type):
|
||||||
raise TypeError(f"Key {k} had unexpected type '{type(k)}' "
|
raise TypeError(f"Key {k} {hint}had unexpected type '{type(k)}' "
|
||||||
f"rather than expected '{key_type}' in dict '{variable}'")
|
f"rather than expected '{key_type}' in dict '{variable}'")
|
||||||
if value_type != Any and not isinstance(v, value_type):
|
if value_type != Any and not isinstance(v, value_type):
|
||||||
raise TypeError(f"Value {v} had unexpected type '{type(v)}' "
|
raise TypeError(f"Value {v} {hint}had unexpected type '{type(v)}' "
|
||||||
f"rather than expected '{value_type}' in dict '{variable}'")
|
f"rather than expected '{value_type}' in dict '{variable}'")
|
||||||
|
|
||||||
# Check all required keys present
|
# Check all required keys present
|
||||||
for rk in required_keys:
|
for rk in required_keys:
|
||||||
if rk not in variable.keys():
|
if rk not in variable.keys():
|
||||||
raise KeyError(f"Missing required key '{rk}' from dict "
|
raise KeyError(f"Missing required key '{rk}' from dict "
|
||||||
f"'{variable}'")
|
f"'{variable}' {hint}.")
|
||||||
|
|
||||||
# If strict checking, enforce that only required and optional keys are
|
# If strict checking, enforce that only required and optional keys are
|
||||||
# present
|
# present
|
||||||
if strict:
|
if strict:
|
||||||
for k in variable.keys():
|
for k in variable.keys():
|
||||||
if k not in required_keys and k not in optional_keys:
|
if k not in required_keys and k not in optional_keys:
|
||||||
raise ValueError(f"Unexpected key '{k}' should not be present "
|
raise ValueError(f"Unexpected key '{k}' {hint}should not be "
|
||||||
f"in dict '{variable}'")
|
f"present in dict '{variable}'")
|
||||||
|
|
||||||
def valid_list(variable:List[Any], entry_type:Type,
|
def valid_list(variable:List[Any], entry_type:Type,
|
||||||
alt_types:List[Type]=[], min_length:int=1, hint:str="")->None:
|
alt_types:List[Type]=[], min_length:int=1, hint:str="")->None:
|
||||||
@ -155,8 +176,13 @@ def valid_list(variable:List[Any], entry_type:Type,
|
|||||||
|
|
||||||
# Check length meets minimum
|
# Check length meets minimum
|
||||||
if len(variable) < min_length:
|
if len(variable) < min_length:
|
||||||
raise ValueError(f"List '{variable}' is too short. Should be at least "
|
if hint:
|
||||||
f"of length {min_length}")
|
msg = f"List '{variable}' is too short in {hint}. Should be at " \
|
||||||
|
f"least of length {min_length}"
|
||||||
|
else:
|
||||||
|
msg = f"List '{variable}' is too short. Should be at least " \
|
||||||
|
f"of length {min_length}"
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
# Check type of each value
|
# Check type of each value
|
||||||
for n, entry in enumerate(variable):
|
for n, entry in enumerate(variable):
|
||||||
@ -167,59 +193,92 @@ def valid_list(variable:List[Any], entry_type:Type,
|
|||||||
check_type(entry, entry_type, alt_types=alt_types)
|
check_type(entry, entry_type, alt_types=alt_types)
|
||||||
|
|
||||||
def valid_path(variable:str, allow_base:bool=False, extension:str="",
|
def valid_path(variable:str, allow_base:bool=False, extension:str="",
|
||||||
min_length:int=1):
|
min_length:int=1, hint:str=""):
|
||||||
"""Check that a given string expresses a valid path."""
|
"""Check that a given string expresses a valid path."""
|
||||||
valid_string(variable, VALID_PATH_CHARS, min_length=min_length)
|
valid_string(variable, VALID_PATH_CHARS, min_length=min_length, hint=hint)
|
||||||
|
|
||||||
# Check we aren't given a root path
|
# Check we aren't given a root path
|
||||||
if not allow_base and variable.startswith(sep):
|
if not allow_base and variable.startswith(sep):
|
||||||
raise ValueError(f"Cannot accept path '{variable}'. Must be relative.")
|
if hint:
|
||||||
|
msg = f"Cannot accept path '{variable}' in '{hint}'. Must be " \
|
||||||
|
"relative."
|
||||||
|
else:
|
||||||
|
msg = f"Cannot accept path '{variable}'. Must be relative."
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
# Check path contains a valid extension
|
# Check path contains a valid extension
|
||||||
if extension and not variable.endswith(extension):
|
if extension and not variable.endswith(extension):
|
||||||
raise ValueError(f"Path '{variable}' does not have required "
|
if hint:
|
||||||
f"extension '{extension}'.")
|
msg = f"Path '{variable}' in '{hint}' does not have required " \
|
||||||
|
f"extension '{extension}'."
|
||||||
|
else:
|
||||||
|
msg = f"Path '{variable}' does not have required extension " \
|
||||||
|
f"'{extension}'."
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
def valid_existing_file_path(variable:str, allow_base:bool=False,
|
def valid_existing_file_path(variable:str, allow_base:bool=False,
|
||||||
extension:str=""):
|
extension:str="", hint:str=""):
|
||||||
"""Check the given string is a path to an existing file."""
|
"""Check the given string is a path to an existing file."""
|
||||||
# Check that the string is a path
|
# Check that the string is a path
|
||||||
valid_path(variable, allow_base=allow_base, extension=extension)
|
valid_path(variable, allow_base=allow_base, extension=extension, hint=hint)
|
||||||
# Check the path exists
|
# Check the path exists
|
||||||
if not exists(variable):
|
if not exists(variable):
|
||||||
raise FileNotFoundError(
|
if hint:
|
||||||
f"Requested file path '{variable}' does not exist.")
|
msg = f"Requested file path '{variable}' in '{hint}' does not " \
|
||||||
|
"exist."
|
||||||
|
else:
|
||||||
|
msg = f"Requested file path '{variable}' does not exist."
|
||||||
|
raise FileNotFoundError(msg)
|
||||||
# Check it is a file
|
# Check it is a file
|
||||||
if not isfile(variable):
|
if not isfile(variable):
|
||||||
raise ValueError(
|
if hint:
|
||||||
f"Requested file '{variable}' is not a file.")
|
msg = f"Requested file '{variable}' in '{hint}' is not a file."
|
||||||
|
else:
|
||||||
|
msg = f"Requested file '{variable}' is not a file."
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
def valid_dir_path(variable:str, must_exist:bool=False, allow_base:bool=False
|
def valid_dir_path(variable:str, must_exist:bool=False, allow_base:bool=False,
|
||||||
)->None:
|
hint:str="")->None:
|
||||||
"""Check the given string is a valid directory path, either to an existing
|
"""Check the given string is a valid directory path, either to an existing
|
||||||
one or a location that could contain one."""
|
one or a location that could contain one."""
|
||||||
# Check that the string is a path
|
# Check that the string is a path
|
||||||
valid_path(variable, allow_base=allow_base, extension="")
|
valid_path(variable, allow_base=allow_base, extension="", hint=hint)
|
||||||
# Check the path exists
|
# Check the path exists
|
||||||
does_exist = exists(variable)
|
does_exist = exists(variable)
|
||||||
if must_exist and not does_exist:
|
if must_exist and not does_exist:
|
||||||
raise FileNotFoundError(
|
if hint:
|
||||||
f"Requested dir path '{variable}' does not exist.")
|
msg = f"Requested dir path '{variable}' in '{hint}' does not " \
|
||||||
|
"exist."
|
||||||
|
else:
|
||||||
|
msg = f"Requested dir path '{variable}' does not exist."
|
||||||
|
raise FileNotFoundError(msg)
|
||||||
# Check it is a directory
|
# Check it is a directory
|
||||||
if does_exist and not isdir(variable):
|
if does_exist and not isdir(variable):
|
||||||
raise ValueError(
|
if hint:
|
||||||
f"Requested dir '{variable}' is not a directory.")
|
msg = f"Requested dir '{variable}' in '{hint}' is not a directory."
|
||||||
|
else:
|
||||||
|
msg = f"Requested dir '{variable}' is not a directory."
|
||||||
|
raise ValueError()
|
||||||
|
|
||||||
def valid_non_existing_path(variable:str, allow_base:bool=False):
|
def valid_non_existing_path(variable:str, allow_base:bool=False, hint:str=""
|
||||||
|
)->None:
|
||||||
"""Check the given string is a path to something that does not exist."""
|
"""Check the given string is a path to something that does not exist."""
|
||||||
# Check that the string is a path
|
# Check that the string is a path
|
||||||
valid_path(variable, allow_base=allow_base, extension="")
|
valid_path(variable, allow_base=allow_base, extension="", hint=hint)
|
||||||
# Check the path does not exist
|
# Check the path does not exist
|
||||||
if exists(variable):
|
if exists(variable):
|
||||||
raise ValueError(f"Requested path '{variable}' already exists.")
|
if hint:
|
||||||
|
msg = f"Requested path '{variable}' in '{hint}' already exists."
|
||||||
|
else:
|
||||||
|
msg = f"Requested path '{variable}' already exists."
|
||||||
|
raise ValueError(msg)
|
||||||
# Check that any intermediate directories exist
|
# Check that any intermediate directories exist
|
||||||
if dirname(variable) and not exists(dirname(variable)):
|
if dirname(variable) and not exists(dirname(variable)):
|
||||||
raise ValueError(
|
if hint:
|
||||||
f"Route to requested path '{variable}' does not exist.")
|
msg = f"Route to requested path '{variable}' in '{hint}' does " \
|
||||||
|
"not exist."
|
||||||
|
else:
|
||||||
|
msg = f"Route to requested path '{variable}' does not exist."
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
# TODO add validation for requirement functions
|
# TODO add validation for requirement functions
|
@ -65,7 +65,12 @@ class FileEventPattern(BasePattern):
|
|||||||
def _is_valid_triggering_path(self, triggering_path:str)->None:
|
def _is_valid_triggering_path(self, triggering_path:str)->None:
|
||||||
"""Validation check for 'triggering_path' variable from main
|
"""Validation check for 'triggering_path' variable from main
|
||||||
constructor."""
|
constructor."""
|
||||||
valid_string(triggering_path, VALID_PATH_CHARS+'*', min_length=1)
|
valid_string(
|
||||||
|
triggering_path,
|
||||||
|
VALID_PATH_CHARS+'*',
|
||||||
|
min_length=1,
|
||||||
|
hint="FileEventPattern._is_valid_triggering_path.triggering_path"
|
||||||
|
)
|
||||||
if len(triggering_path) < 1:
|
if len(triggering_path) < 1:
|
||||||
raise ValueError (
|
raise ValueError (
|
||||||
f"triggiering path '{triggering_path}' is too short. "
|
f"triggiering path '{triggering_path}' is too short. "
|
||||||
@ -75,30 +80,65 @@ class FileEventPattern(BasePattern):
|
|||||||
def _is_valid_triggering_file(self, triggering_file:str)->None:
|
def _is_valid_triggering_file(self, triggering_file:str)->None:
|
||||||
"""Validation check for 'triggering_file' variable from main
|
"""Validation check for 'triggering_file' variable from main
|
||||||
constructor."""
|
constructor."""
|
||||||
valid_string(triggering_file, VALID_VARIABLE_NAME_CHARS)
|
valid_string(
|
||||||
|
triggering_file,
|
||||||
|
VALID_VARIABLE_NAME_CHARS,
|
||||||
|
hint="FileEventPattern._is_valid_triggering_file.triggering_file"
|
||||||
|
)
|
||||||
|
|
||||||
def _is_valid_recipe(self, recipe:str)->None:
|
def _is_valid_recipe(self, recipe:str)->None:
|
||||||
"""Validation check for 'recipe' variable from main constructor.
|
"""Validation check for 'recipe' variable from main constructor.
|
||||||
Called within parent BasePattern constructor."""
|
Called within parent BasePattern constructor."""
|
||||||
valid_string(recipe, VALID_RECIPE_NAME_CHARS)
|
valid_string(
|
||||||
|
recipe,
|
||||||
|
VALID_RECIPE_NAME_CHARS,
|
||||||
|
hint="FileEventPattern._is_valid_recipe.recipe"
|
||||||
|
)
|
||||||
|
|
||||||
def _is_valid_parameters(self, parameters:Dict[str,Any])->None:
|
def _is_valid_parameters(self, parameters:Dict[str,Any])->None:
|
||||||
"""Validation check for 'parameters' variable from main constructor.
|
"""Validation check for 'parameters' variable from main constructor.
|
||||||
Called within parent BasePattern constructor."""
|
Called within parent BasePattern constructor."""
|
||||||
valid_dict(parameters, str, Any, strict=False, min_length=0)
|
valid_dict(
|
||||||
|
parameters,
|
||||||
|
str,
|
||||||
|
Any,
|
||||||
|
strict=False,
|
||||||
|
min_length=0,
|
||||||
|
hint="FileEventPattern._is_valid_parameters.parameters"
|
||||||
|
)
|
||||||
for k in parameters.keys():
|
for k in parameters.keys():
|
||||||
valid_string(k, VALID_VARIABLE_NAME_CHARS)
|
valid_string(
|
||||||
|
k,
|
||||||
|
VALID_VARIABLE_NAME_CHARS,
|
||||||
|
hint=f"FileEventPattern._is_valid_parameters.parameters[{k}]"
|
||||||
|
)
|
||||||
|
|
||||||
def _is_valid_output(self, outputs:Dict[str,str])->None:
|
def _is_valid_output(self, outputs:Dict[str,str])->None:
|
||||||
"""Validation check for 'output' variable from main constructor.
|
"""Validation check for 'output' variable from main constructor.
|
||||||
Called within parent BasePattern constructor."""
|
Called within parent BasePattern constructor."""
|
||||||
valid_dict(outputs, str, str, strict=False, min_length=0)
|
valid_dict(
|
||||||
|
outputs,
|
||||||
|
str,
|
||||||
|
str,
|
||||||
|
strict=False,
|
||||||
|
min_length=0,
|
||||||
|
hint="FileEventPattern._is_valid_outputs.outputs"
|
||||||
|
)
|
||||||
for k in outputs.keys():
|
for k in outputs.keys():
|
||||||
valid_string(k, VALID_VARIABLE_NAME_CHARS)
|
valid_string(
|
||||||
|
k,
|
||||||
|
VALID_VARIABLE_NAME_CHARS,
|
||||||
|
hint=f"FileEventPattern._is_valid_outputs.outputs[{k}]"
|
||||||
|
)
|
||||||
|
|
||||||
def _is_valid_event_mask(self, event_mask)->None:
|
def _is_valid_event_mask(self, event_mask)->None:
|
||||||
"""Validation check for 'event_mask' variable from main constructor."""
|
"""Validation check for 'event_mask' variable from main constructor."""
|
||||||
valid_list(event_mask, str, min_length=1)
|
valid_list(
|
||||||
|
event_mask,
|
||||||
|
str,
|
||||||
|
min_length=1,
|
||||||
|
hint="FileEventPattern._is_valid_event_mask.event_mask"
|
||||||
|
)
|
||||||
for mask in event_mask:
|
for mask in event_mask:
|
||||||
if mask not in FILE_EVENTS:
|
if mask not in FILE_EVENTS:
|
||||||
raise ValueError(f"Invalid event mask '{mask}'. Valid are: "
|
raise ValueError(f"Invalid event mask '{mask}'. Valid are: "
|
||||||
|
Reference in New Issue
Block a user