128 lines
3.9 KiB
Python
128 lines
3.9 KiB
Python
# Copyright 2024 Volvo Car Corporation
|
|
# Licensed under Apache 2.0.
|
|
|
|
"""Module for various helper functions."""
|
|
import json
|
|
import collections
|
|
from pathlib import Path
|
|
from subprocess import getoutput
|
|
|
|
|
|
def get_repo_root():
|
|
""" Return absolute path to repository where script is executed, regardless
|
|
of the current script's location.
|
|
|
|
Returns:
|
|
path (str): Absolute, canonical path to the top-level repository.
|
|
if not a git repository, returns current working dir
|
|
|
|
"""
|
|
try:
|
|
root = Path(getoutput('git rev-parse --show-toplevel')).resolve()
|
|
except (FileNotFoundError, OSError):
|
|
root = Path.cwd().resolve()
|
|
return str(root)
|
|
|
|
|
|
def create_dir(path: Path):
|
|
"""If the directory for a given directory path does not exist, create it.
|
|
Including parent directories.
|
|
|
|
Args:
|
|
path (Path): Path to directory.
|
|
Returns:
|
|
path (Path): Path to directory.
|
|
"""
|
|
if not path.is_dir():
|
|
path.mkdir(parents=True)
|
|
return path
|
|
|
|
|
|
def merge_dicts(dict1, dict2, handle_collision=lambda a, b: a, merge_recursively=False):
|
|
"""Merge two dicts.
|
|
|
|
Args:
|
|
dict1 (dict): dict to merge
|
|
dict2 (dict): dict to merge
|
|
handle_collision (function(arg1, arg2)): function to resolve key collisions,
|
|
default keeps original value in dict1
|
|
merge_recursively (bool): if set to True it merges nested dicts recursively with handle_collision
|
|
resolving collisions with non-dict types.
|
|
Returns:
|
|
dict: the result of the two merged dicts
|
|
"""
|
|
result = dict1.copy()
|
|
for key, value in dict2.items():
|
|
if key not in result:
|
|
result.update({key: value})
|
|
elif isinstance(result[key], dict) and \
|
|
isinstance(value, dict) and \
|
|
merge_recursively:
|
|
result[key] = merge_dicts(result[key], value, handle_collision, merge_recursively)
|
|
else:
|
|
result[key] = handle_collision(result[key], value)
|
|
return result
|
|
|
|
|
|
def deep_dict_update(base, add):
|
|
"""Recursively update a dict that may contain sub-dicts.
|
|
|
|
Args:
|
|
base (dict): The base dict will be updated with the contents
|
|
of the add dict
|
|
add (dict): This dict will be added to the base dict
|
|
|
|
Returns:
|
|
dict: the updated base dict is returned
|
|
|
|
"""
|
|
for key, value in add.items():
|
|
if key not in base:
|
|
base[key] = value
|
|
elif isinstance(value, dict):
|
|
deep_dict_update(base[key], value)
|
|
return base
|
|
|
|
|
|
def deep_json_update(json_file, dict_to_merge):
|
|
""" Recursively update a json file with the content of a dict.
|
|
|
|
Args:
|
|
json_file (path): json file.
|
|
dict_to_merge (dict): Dictionary that will be merged into json file, overwriting values and adding keys.
|
|
"""
|
|
with open(json_file, 'r', encoding="utf-8") as fle:
|
|
json_dict = json.load(fle)
|
|
merged_dict = merge_dicts(
|
|
json_dict,
|
|
dict_to_merge,
|
|
handle_collision=lambda a, b: b,
|
|
merge_recursively=True
|
|
)
|
|
with open(json_file, 'w', encoding="utf-8") as fle:
|
|
json.dump(merged_dict, fle, indent=2)
|
|
|
|
|
|
def recursive_default_dict():
|
|
"""Returns recursively defined default dict. This allows people to insert
|
|
arbitrarily complex nested data into the dict without getting KeyErrors.
|
|
|
|
Returns:
|
|
defaultdict(self): A new defaultdict instance, recursively defined.
|
|
"""
|
|
return collections.defaultdict(recursive_default_dict)
|
|
|
|
|
|
def to_normal_dict(weird_dict):
|
|
"""Converts nested dict to normal, suitable for YAML/JSON dumping.
|
|
|
|
Args:
|
|
weird_dict (dict): Any dict-like item that can be converted to a dict.
|
|
Returns:
|
|
dict(nested): An identical nested dict structure, but using real dicts.
|
|
"""
|
|
for key, value in weird_dict.items():
|
|
if isinstance(value, dict):
|
|
weird_dict[key] = to_normal_dict(value)
|
|
return dict(weird_dict)
|