Coverage for nexusLIMS/utils/dicts.py: 100%

27 statements  

« prev     ^ index     » next       coverage.py v7.11.3, created at 2026-03-24 05:23 +0000

1"""Dictionary manipulation utilities for NexusLIMS.""" 

2 

3from typing import Any, Dict 

4 

5from benedict import benedict 

6 

7 

8def get_nested_dict_value_by_path(nest_dict, path): 

9 """ 

10 Get a nested dictionary value by path. 

11 

12 Get the value from within a nested dictionary structure by traversing into 

13 the dictionary as deep as that path found and returning that value. 

14 

15 Uses python-benedict for robust nested dictionary operations. 

16 

17 Parameters 

18 ---------- 

19 nest_dict : dict 

20 A dictionary of dictionaries that is to be queried 

21 path : tuple 

22 A tuple (or other iterable type) that specifies the subsequent keys 

23 needed to get to a a value within `nest_dict` 

24 

25 Returns 

26 ------- 

27 value : object or None 

28 The value at the path within the nested dictionary; if there's no 

29 value there, return None 

30 """ 

31 # Disable keypath_separator to avoid conflicts with keys containing special chars 

32 return benedict(nest_dict, keypath_separator=None).get(list(path)) 

33 

34 

35def set_nested_dict_value(nest_dict, path, value): 

36 """ 

37 Set a nested dictionary value by path. 

38 

39 Set a value within a nested dictionary structure by traversing into 

40 the dictionary as deep as that path found and changing it to `value`. 

41 

42 Uses python-benedict for robust nested dictionary operations. 

43 

44 Parameters 

45 ---------- 

46 nest_dict : dict 

47 A dictionary of dictionaries that is to be queried 

48 path : tuple 

49 A tuple (or other iterable type) that specifies the subsequent keys 

50 needed to get to a a value within `nest_dict` 

51 value : object 

52 The value which will be given to the path in the nested dictionary 

53 

54 Returns 

55 ------- 

56 value : object 

57 The value at the path within the nested dictionary 

58 """ 

59 # Disable keypath_separator to avoid conflicts with keys containing special chars 

60 b = benedict(nest_dict, keypath_separator=None) 

61 b[list(path)] = value # Updates in-place (benedict is dict subclass) 

62 

63 

64def try_getting_dict_value(dict_, key): 

65 """ 

66 Try to get a nested dictionary value. 

67 

68 This method will try to get a value from a dictionary (potentially 

69 nested) and fail silently if the value is not found, returning None. 

70 

71 Parameters 

72 ---------- 

73 dict_ : dict 

74 The dictionary from which to get a value 

75 key : str or tuple 

76 The key to query, or if an iterable container type (tuple, list, 

77 etc.) is given, the path into a nested dictionary to follow 

78 

79 Returns 

80 ------- 

81 val : object or None 

82 The value of the dictionary specified by `key`. If the dictionary 

83 does not have a key, returns None without raising an error 

84 """ 

85 try: 

86 if isinstance(key, str): 

87 return dict_[key] 

88 if hasattr(key, "__iter__"): 

89 return get_nested_dict_value_by_path(dict_, key) 

90 except (KeyError, TypeError): 

91 return None 

92 else: 

93 # we shouldn't reach this line, but always good to return a consistent 

94 # value just in case 

95 return None # pragma: no cover 

96 

97 

98def sort_dict(item): 

99 """Recursively sort a dictionary by keys.""" 

100 return { 

101 k: sort_dict(v) if isinstance(v, dict) else v 

102 for k, v in sorted(item.items(), key=lambda i: i[0].lower()) 

103 } 

104 

105 

106def remove_dtb_element(tree, path): 

107 """ 

108 Remove an element from a DictionaryTreeBrowser by setting it to None. 

109 

110 Helper method that sets a specific leaf of a DictionaryTreeBrowser to None. 

111 Use with :py:meth:`remove_dict_nones` to fully remove the desired DTB element after 

112 setting it to None (after converting DTB to dictionary). 

113 

114 Parameters 

115 ---------- 

116 tree : :py:class:`~hyperspy.misc.utils.DictionaryTreeBrowser` 

117 the ``DictionaryTreeBrowser`` object to remove the object from 

118 path : str 

119 period-delimited path to a DTB element 

120 

121 Returns 

122 ------- 

123 tree : :py:class:`~hyperspy.misc.utils.DictionaryTreeBrowser` 

124 """ 

125 tree.set_item(path, None) 

126 

127 return tree 

128 

129 

130def remove_dict_nones(dictionary: Dict[Any, Any]) -> Dict[Any, Any]: 

131 """ 

132 Delete keys with a value of ``None`` in a dictionary, recursively. 

133 

134 Taken from https://stackoverflow.com/a/4256027. 

135 

136 Parameters 

137 ---------- 

138 dictionary 

139 The dictionary, with keys that have None values removed 

140 

141 Returns 

142 ------- 

143 dict 

144 The same dictionary, but with "Nones" removed 

145 """ 

146 for key, value in list(dictionary.items()): 

147 if value is None: 

148 del dictionary[key] 

149 elif isinstance(value, dict): 

150 remove_dict_nones(value) 

151 return dictionary