added more hinting to pattern validation messages

This commit is contained in:
PatchOfScotland
2023-03-31 12:51:12 +02:00
parent cea7b9f010
commit 6b532e8a70
3 changed files with 163 additions and 60 deletions

View File

@ -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

View File

@ -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

View File

@ -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: "