Source code for ablator.config.utils
from collections import abc
import copy
import hashlib
import json
from functools import reduce
import typing as ty
[docs]def flatten_nested_dict(
_dict: dict, expand_list=True, seperator="."
) -> dict[str, ty.Any]:
"""
Flattens a nested dictionary, expanding lists and tuples if specified.
Parameters
----------
_dict : dict
The input dictionary to be flattened.
expand_list : bool, optional
Whether to expand lists and tuples in the dictionary, by default ``True``.
seperator : str, optional
The separator used for joining the keys, by default ``"."``.
Returns
-------
dict[str, ty.Any]
The flattened dictionary.
Examples
--------
>>> nested_dict = {"a": {"b": 1, "c": {"d": 2}}, "e": [3, 4]}
>>> flatten_nested_dict(nested_dict)
{'a.b': 1, 'a.c.d': 2, 'e.0': 3, 'e.1': 4}
"""
flatten_dict = copy.deepcopy(_dict)
for k, v in _dict.items():
_gen: ty.Optional[abc.Iterable] = None
if isinstance(v, dict):
_gen = v.items()
if isinstance(v, (list, tuple)) and expand_list:
_gen = enumerate(v)
if _gen is not None:
del flatten_dict[k]
for _k, _v in _gen:
flatten_dict[f"{k}{seperator}{_k}"] = _v
if len(flatten_dict) != len(_dict):
return flatten_nested_dict(flatten_dict)
return flatten_dict
[docs]def dict_hash(*dictionaries: list[dict[str, ty.Any]], hash_len=4):
"""
Calculates the MD5 hash of one or more dictionaries.
Parameters
----------
*dictionaries : list[dict[str, ty.Any]]
One or more dictionaries to calculate the hash for.
hash_len : int, optional
The length of the hash to return, by default 4.
Returns
-------
str
The MD5 hash of the dictionaries.
Examples
--------
>>> dict1 = {"a": 1, "b": 2}
>>> dict2 = {"c": 3, "d": 4}
>>> dict_hash(dict1, dict2)
'6d75e6'
"""
concat_dictionaries = [
copy.deepcopy(_) if isinstance(_, dict) else copy.deepcopy(_).__dict__
for _ in dictionaries
]
dictionary = reduce(lambda a, b: {**a, **b}, concat_dictionaries)
dhash = hashlib.md5()
# We need to sort arguments so {'a': 1, 'b': 2} is
# the same as {'b': 2, 'a': 1}
dictionary = flatten_nested_dict(dictionary)
_dict = {}
for k, v in dictionary.items():
if not isinstance(v, (bool, str, int, float, type(None))):
v = getattr(v, "__name__", str(v))
_dict[k] = v
encoded = json.dumps(_dict, sort_keys=True).encode()
dhash.update(encoded)
return dhash.hexdigest()[:hash_len]