#! /usr/bin/env python3.10
import json
from shared.logging import setup_logger
logger = setup_logger(__name__.split('.')[-1])
[docs]
def remove_spaces_and_set_case(key: str, case='lower') -> str:
"""
Normalize a string key by converting all characters to lowercase
and removing all spaces and underscores.
:param key: The string key to be normalized.
:type key: str
:return: The normalized key.
:rtype: str
Usage::
normalized_key = normalize_key('Hello_World')
print(normalized_key) # Output: 'helloworld'
"""
assert case in ['lower', 'l', 'u', 'upper'], "Case must be either 'lower' or 'upper'"
translation_table = str.maketrans('', '', ' _')
if case in ['lower', 'l']:
return key.lower().translate(translation_table)
else:
return key.upper().translate(translation_table)
[docs]
def recursively_normalize_dict_keys(d: dict) -> dict:
"""
Normalize all keys in a dictionary. If a value is a dictionary,
normalize its keys recursively.
:param d: The dictionary whose keys are to be normalized.
:type d: dict
:return: A new dictionary with normalized keys.
:rtype: dict
Usage::
data = {
'Hello World': 1,
'Good_Day': {
'Inner_Key': 2
}
}
normalized_data = normalize_dict(data)
print(normalized_data) # Output: {'helloworld': 1, 'goodday': {'innerkey': 2}}
"""
return {
remove_spaces_and_set_case(key): recursively_normalize_dict_keys(value) if isinstance(value,
dict) else value
for key, value in d.items()
}
[docs]
def validate_and_correct_dictionary(dict_to_verify: dict, default_config: dict, log=None) -> tuple:
"""
Verifies that the given JSON config contains all the required keys as per the default config.
If not, adds the missing keys with the default values.
Returns a log of warnings and errors in case of missing or unknown keys.
:param dict_to_verify: The JSON configuration to verify.
:type dict_to_verify: dict
:param default_config: The default configuration used for verification.
:type default_config: dict
:param log: A list of log messages. Default is an empty list.
:type log: list, optional
:return: A tuple of the verified JSON configuration and the log of warnings/errors.
:rtype: tuple
Usage::
json_config = {
"key1": "value1",
"key2": {
"subkey1": "subvalue1"
}
}
default_config = {
"key1": "value1",
"key2": {
"subkey1": "subvalue1",
"subkey2": "subvalue2"
},
"key3": "value3"
}
verified_config, log = verify_configuration_keys(json_config, default_config)
# verified_config will contain all keys from default_config, and log will contain warning/error messages
"""
if log is None:
log = []
if not dict_to_verify:
dict_to_verify = {}
for key, value in default_config.items():
if key not in dict_to_verify:
dict_to_verify[key] = value
log.append(f'Warning: Missing key "{key}" has been added with default value "{value}"')
elif isinstance(value, dict):
if isinstance(dict_to_verify[key], dict):
dict_to_verify[key], log = validate_and_correct_dictionary(dict_to_verify[key], value, log)
else:
log.append(f'Error: Key "{key}" should contain a dictionary but found "{type(dict_to_verify[key])}"')
else:
if not isinstance(dict_to_verify[key], type(value)):
log.append(
f'Error: Key "{key}" should be of type "{type(value)}" but found "{type(dict_to_verify[key])}"')
for key in dict_to_verify.keys():
if key not in default_config:
log.append(f'Error: Unknown key "{key}" found in config')
return dict_to_verify, log
[docs]
def load_json_file(filename):
"""
Loads a JSON file.
:param filename: The path to the JSON file.
:type filename: str
:return: The content of the JSON file.
:rtype: dict
"""
assert filename.endswith(".json") or filename.endswith(".JSON"), "File must be a JSON file"
with open(filename, 'r') as f:
cleaned_json = remove_json_comments(f.read())
return json.loads(cleaned_json)
[docs]
def save_json_file(filename, data):
"""
Saves a JSON file.
:param filename: The path to the JSON file.
:param data: The data to be saved.
:type filename: str
:type data: dict
"""
assert filename.endswith(".json") or filename.endswith(".JSON"), "File must be a JSON file"
with open(filename, 'w') as f:
json.dump(data, f, indent=4)
[docs]
def parse_and_identify_type(s):
# Initialize the type as None
identified_type = None
# Check if the string is an integer
if s.isdigit() or (s.startswith('-') and s[1:].isdigit()):
value = int(s)
identified_type = 'int'
print(f"{s} is an integer.")
# Check if the string is a float
elif (s.count('.') == 1 and
(s.replace('.', '', 1).isdigit() or
(s.startswith('-') and s[1:].replace('.', '', 1).isdigit()))):
value = float(s)
identified_type = 'float'
print(f"{s} is a float.")
else:
value = s
identified_type = 'str'
print(f"{s} is a regular string.")
# Return both the value and its identified type
return value, identified_type