Coverage for /Users/mac/Documents/Work/peachOps/yaml-config-manager/src/yamleaf/utils.py: 55%

80 statements  

« prev     ^ index     » next       coverage.py v7.9.2, created at 2025-07-03 20:01 +0000

1""" 

2Utility functions for configuration management. 

3""" 

4 

5from typing import Any, Dict, List, Union 

6 

7 

8def get_nested_value(data: Dict[str, Any], key_path: str, default: Any = None) -> Any: 

9 """ 

10 Get a nested value using dot notation. 

11 

12 Args: 

13 data: Dictionary to search in 

14 key_path: Dot-separated path to the value 

15 default: Default value if key not found 

16 

17 Returns: 

18 The value at the specified path or default 

19 """ 

20 if not key_path: 

21 return data 

22 

23 keys = key_path.split('.') 

24 current = data 

25 

26 try: 

27 for key in keys: 

28 # Handle list indices 

29 if isinstance(current, list) and key.isdigit(): 

30 current = current[int(key)] 

31 elif isinstance(current, dict) and key in current: 

32 current = current[key] 

33 else: 

34 return default 

35 return current 

36 except (KeyError, TypeError, IndexError, ValueError): 

37 return default 

38 

39 

40def set_nested_value(data: Dict[str, Any], key_path: str, value: Any) -> None: 

41 """ 

42 Set a nested value using dot notation. 

43 

44 Args: 

45 data: Dictionary to modify 

46 key_path: Dot-separated path to the value 

47 value: Value to set 

48 """ 

49 if not key_path: 

50 return 

51 

52 keys = key_path.split('.') 

53 current = data 

54 

55 for key in keys[:-1]: 

56 if key not in current: 

57 current[key] = {} 

58 elif not isinstance(current[key], dict): 

59 # If the key exists but isn't a dict, replace it 

60 current[key] = {} 

61 current = current[key] 

62 

63 current[keys[-1]] = value 

64 

65 

66def delete_nested_value(data: Dict[str, Any], key_path: str) -> bool: 

67 """ 

68 Delete a nested value using dot notation. 

69 

70 Args: 

71 data: Dictionary to modify 

72 key_path: Dot-separated path to the value to delete 

73 

74 Returns: 

75 True if value was deleted, False if key didn't exist 

76 """ 

77 if not key_path: 

78 return False 

79 

80 keys = key_path.split('.') 

81 current = data 

82 

83 try: 

84 for key in keys[:-1]: 

85 current = current[key] 

86 

87 if keys[-1] in current: 

88 del current[keys[-1]] 

89 return True 

90 return False 

91 except (KeyError, TypeError): 

92 return False 

93 

94 

95def flatten_dict(data: Dict[str, Any], parent_key: str = '', sep: str = '.') -> Dict[str, Any]: 

96 """ 

97 Flatten a nested dictionary using dot notation. 

98 

99 Args: 

100 data: Dictionary to flatten 

101 parent_key: Parent key prefix 

102 sep: Separator for keys 

103 

104 Returns: 

105 Flattened dictionary 

106 """ 

107 items = [] 

108 for key, value in data.items(): 

109 new_key = f"{parent_key}{sep}{key}" if parent_key else key 

110 if isinstance(value, dict): 

111 items.extend(flatten_dict(value, new_key, sep=sep).items()) 

112 elif isinstance(value, list): 

113 for i, item in enumerate(value): 

114 if isinstance(item, dict): 

115 items.extend(flatten_dict(item, f"{new_key}[{i}]", sep=sep).items()) 

116 else: 

117 items.append((f"{new_key}[{i}]", item)) 

118 else: 

119 items.append((new_key, value)) 

120 return dict(items) 

121 

122 

123def unflatten_dict(data: Dict[str, Any], sep: str = '.') -> Dict[str, Any]: 

124 """ 

125 Unflatten a dictionary from dot notation back to nested structure. 

126 

127 Args: 

128 data: Flattened dictionary 

129 sep: Separator used in keys 

130 

131 Returns: 

132 Nested dictionary 

133 """ 

134 result = {} 

135 for key, value in data.items(): 

136 set_nested_value(result, key, value) 

137 return result 

138 

139 

140def merge_configs(base: Dict[str, Any], override: Dict[str, Any]) -> Dict[str, Any]: 

141 """ 

142 Deep merge two configuration dictionaries. 

143 

144 Args: 

145 base: Base configuration 

146 override: Configuration to merge in 

147 

148 Returns: 

149 Merged configuration 

150 """ 

151 result = base.copy() 

152 

153 for key, value in override.items(): 

154 if key in result and isinstance(result[key], dict) and isinstance(value, dict): 

155 result[key] = merge_configs(result[key], value) 

156 else: 

157 result[key] = value 

158 

159 return result 

160 

161 

162def validate_config_structure(config: Dict[str, Any], schema: Dict[str, type]) -> List[str]: 

163 """ 

164 Validate configuration structure against a schema. 

165 

166 Args: 

167 config: Configuration to validate 

168 schema: Schema dictionary with expected types 

169 

170 Returns: 

171 List of validation errors 

172 """ 

173 errors = [] 

174 

175 for key, expected_type in schema.items(): 

176 if key not in config: 

177 errors.append(f"Missing required key: {key}") 

178 elif not isinstance(config[key], expected_type): 

179 errors.append(f"Key '{key}' expected {expected_type.__name__}, got {type(config[key]).__name__}") 

180 

181 return errors 

182 

183 

184def sanitize_key(key: str) -> str: 

185 """ 

186 Sanitize a configuration key to ensure it's valid. 

187 

188 Args: 

189 key: Key to sanitize 

190 

191 Returns: 

192 Sanitized key 

193 """ 

194 # Remove invalid characters and normalize 

195 sanitized = ''.join(c for c in key if c.isalnum() or c in '._-') 

196 

197 # Ensure it doesn't start with a number 

198 if sanitized and sanitized[0].isdigit(): 

199 sanitized = f"_{sanitized}" 

200 

201 return sanitized