Made raise_for_structure provide better feedback on errors

The function now takes an argument corresponding to the key that is
being worked on. Also made the handling of dict validation more explicit
about keys and the reasoning around why it does what it does.
This commit is contained in:
Erik Thuning 2024-06-18 11:26:20 +02:00
parent 8de039a3f3
commit 11917608bb

@ -37,10 +37,10 @@ canonical_manifest = {
'description': str,
'created': lambda v: (isinstance(v, int)
or v is None
or f"Invalid type {type(v)}, int"),
or f"{type(v)}, expected int"),
'duration': lambda v: (isinstance(v, float)
or v is None
or f"Invalid type {type(v)}, float"),
or f"{type(v)}, expected float"),
'presenters': [str],
'courses': [{'designation': str,
'semester': str}],
@ -55,7 +55,7 @@ canonical_manifest = {
}
def raise_for_structure(data, structure):
def raise_for_structure(data, structure, key=''):
"""
Validate the structure of a json-like object (data) against an expected
format (structure), throwing an exception on invalid structure.
@ -67,39 +67,44 @@ def raise_for_structure(data, structure):
# Base case, validate that data type matches structure type
if not isinstance(data, structure):
raise ValueError(
f"Invalid type {type(data)} for {data}, expected {structure}")
f"Invalid type for {key}: {type(data)}, expected {structure}. "
f"Value was {data}.")
elif isinstance(structure, FunctionType):
# The expected type is complicated and defined by function. Call it.
result = structure(data)
if result != True:
raise ValueError(result)
raise ValueError(f"Invalid type for {key}: {result}. "
f"Value was {data}.")
elif isinstance(structure, list):
# List case, validate that data is list and recurse over items
if not isinstance(data, list):
raise ValueError(
f"Invalid type {type(data)} for {data}, expected list")
f"Invalid type for {key}: {type(data)}, expected list. "
f"Value was {data}.")
for c in data:
raise_for_structure(c, structure[0])
raise_for_structure(c, structure[0], f"{key} listitem")
elif isinstance(structure, dict):
# Dict case, validate that data is dict
# and recurse over keys and values
if not isinstance(data, dict):
raise ValueError(
f"Invalid type {type(data)} for {data}, expected dict")
for k in data:
if k not in structure:
f"Invalid type for {key}: {type(data)}, expected dict. "
f"Value was {data}.")
for k in data.keys():
if k not in structure.keys():
# Maybe the key in structure is a type?
# In that case there must only be one key in the structure.
str_key = list(structure.keys())[0]
if (len(structure.keys()) == 1
and isinstance(str_key, type)):
# Check that all keys in that structure are the right type
raise_for_structure(k, str_key)
raise_for_structure(k, str_key, f"{key} dictkey")
# Keep checking the values as normal
raise_for_structure(data[k], structure[str_key])
raise_for_structure(data[k], structure[str_key], k)
else:
raise KeyError(f"Unexpected key {k}")
else:
raise_for_structure(data[k], structure[k])
raise_for_structure(data[k], structure[k], k)
return True
def get_section(config, name):