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
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-03 20:01 +0000
1"""
2Utility functions for configuration management.
3"""
5from typing import Any, Dict, List, Union
8def get_nested_value(data: Dict[str, Any], key_path: str, default: Any = None) -> Any:
9 """
10 Get a nested value using dot notation.
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
17 Returns:
18 The value at the specified path or default
19 """
20 if not key_path:
21 return data
23 keys = key_path.split('.')
24 current = data
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
40def set_nested_value(data: Dict[str, Any], key_path: str, value: Any) -> None:
41 """
42 Set a nested value using dot notation.
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
52 keys = key_path.split('.')
53 current = data
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]
63 current[keys[-1]] = value
66def delete_nested_value(data: Dict[str, Any], key_path: str) -> bool:
67 """
68 Delete a nested value using dot notation.
70 Args:
71 data: Dictionary to modify
72 key_path: Dot-separated path to the value to delete
74 Returns:
75 True if value was deleted, False if key didn't exist
76 """
77 if not key_path:
78 return False
80 keys = key_path.split('.')
81 current = data
83 try:
84 for key in keys[:-1]:
85 current = current[key]
87 if keys[-1] in current:
88 del current[keys[-1]]
89 return True
90 return False
91 except (KeyError, TypeError):
92 return False
95def flatten_dict(data: Dict[str, Any], parent_key: str = '', sep: str = '.') -> Dict[str, Any]:
96 """
97 Flatten a nested dictionary using dot notation.
99 Args:
100 data: Dictionary to flatten
101 parent_key: Parent key prefix
102 sep: Separator for keys
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)
123def unflatten_dict(data: Dict[str, Any], sep: str = '.') -> Dict[str, Any]:
124 """
125 Unflatten a dictionary from dot notation back to nested structure.
127 Args:
128 data: Flattened dictionary
129 sep: Separator used in keys
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
140def merge_configs(base: Dict[str, Any], override: Dict[str, Any]) -> Dict[str, Any]:
141 """
142 Deep merge two configuration dictionaries.
144 Args:
145 base: Base configuration
146 override: Configuration to merge in
148 Returns:
149 Merged configuration
150 """
151 result = base.copy()
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
159 return result
162def validate_config_structure(config: Dict[str, Any], schema: Dict[str, type]) -> List[str]:
163 """
164 Validate configuration structure against a schema.
166 Args:
167 config: Configuration to validate
168 schema: Schema dictionary with expected types
170 Returns:
171 List of validation errors
172 """
173 errors = []
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__}")
181 return errors
184def sanitize_key(key: str) -> str:
185 """
186 Sanitize a configuration key to ensure it's valid.
188 Args:
189 key: Key to sanitize
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 '._-')
197 # Ensure it doesn't start with a number
198 if sanitized and sanitized[0].isdigit():
199 sanitized = f"_{sanitized}"
201 return sanitized