1414
1515from datetime import datetime as dt
1616from enum import Enum
17- from typing import Any , Dict
17+ from typing import Any
1818from urllib .parse import urlsplit , urlunsplit
1919
2020from okta .constants import DATETIME_FORMAT , EPOCH_DAY , EPOCH_MONTH , EPOCH_YEAR
@@ -85,18 +85,30 @@ def convert_absolute_url_into_relative_url(absolute_url):
8585# These replace the external flatdict dependency
8686
8787
88- def flatten_dict (d : Dict [str , Any ], parent_key : str = '' , delimiter : str = '::' ) -> Dict [str , Any ]:
88+ def flatten_dict (
89+ d : dict [str , Any ],
90+ parent_key : str = '' ,
91+ delimiter : str = '::' ,
92+ _depth : int = 0 ,
93+ max_depth : int = 100
94+ ) -> dict [str , Any ]:
8995 """
9096 Flatten a nested dictionary into a single-level dictionary.
9197
9298 Args:
9399 d: The dictionary to flatten
94100 parent_key: The base key to prepend to all keys (used in recursion)
95101 delimiter: The delimiter to use when joining keys (default '::' to avoid collision with snake_case)
102+ _depth: Internal recursion depth counter (do not set manually)
103+ max_depth: Maximum allowed nesting depth (default 100)
96104
97105 Returns:
98106 A flattened dictionary with delimited keys
99107
108+ Raises:
109+ TypeError: If d is not a dictionary
110+ ValueError: If nesting depth exceeds max_depth
111+
100112 Examples:
101113 >>> flatten_dict({'a': {'b': 1, 'c': 2}})
102114 {'a::b': 1, 'a::c': 2}
@@ -107,17 +119,26 @@ def flatten_dict(d: Dict[str, Any], parent_key: str = '', delimiter: str = '::')
107119 >>> flatten_dict({'user_name': {'first_name': 'John'}})
108120 {'user_name::first_name': 'John'}
109121 """
110- items = []
122+ if not isinstance (d , dict ):
123+ raise TypeError (f"flatten_dict expects dict, got { type (d ).__name__ } " )
124+
125+ if _depth > max_depth :
126+ raise ValueError (
127+ f"Dictionary nesting depth exceeds maximum allowed depth of { max_depth } . "
128+ f"This may indicate a circular reference or malformed configuration."
129+ )
130+
131+ result = {}
111132 for key , value in d .items ():
112133 new_key = f"{ parent_key } { delimiter } { key } " if parent_key else key
113134 if isinstance (value , dict ):
114- items . extend (flatten_dict (value , new_key , delimiter ). items ( ))
135+ result . update (flatten_dict (value , new_key , delimiter , _depth + 1 , max_depth ))
115136 else :
116- items . append (( new_key , value ))
117- return dict ( items )
137+ result [ new_key ] = value
138+ return result
118139
119140
120- def unflatten_dict (d : Dict [str , Any ], delimiter : str = '::' ) -> Dict [str , Any ]:
141+ def unflatten_dict (d : dict [str , Any ], delimiter : str = '::' ) -> dict [str , Any ]:
121142 """
122143 Unflatten a dictionary with delimited keys into a nested structure.
123144
@@ -128,62 +149,119 @@ def unflatten_dict(d: Dict[str, Any], delimiter: str = '::') -> Dict[str, Any]:
128149 Returns:
129150 A nested dictionary
130151
152+ Raises:
153+ TypeError: If d is not a dictionary
154+ ValueError: If there are conflicting keys (key is both a leaf value and a nested dict)
155+
131156 Examples:
132- >>> unflatten_dict({'a_b ': 1, 'a_c ': 2})
157+ >>> unflatten_dict({'a::b ': 1, 'a::c ': 2})
133158 {'a': {'b': 1, 'c': 2}}
134159
135- >>> unflatten_dict({'x_y_z ': 3})
160+ >>> unflatten_dict({'x::y::z ': 3})
136161 {'x': {'y': {'z': 3}}}
162+
163+ >>> unflatten_dict({'a_b': 1, 'a_c': 2}, delimiter='_')
164+ {'a': {'b': 1, 'c': 2}}
137165 """
138- result : Dict [str , Any ] = {}
166+ if not isinstance (d , dict ):
167+ raise TypeError (f"unflatten_dict expects dict, got { type (d ).__name__ } " )
168+
169+ result : dict [str , Any ] = {}
139170 for key , value in d .items ():
140171 parts = key .split (delimiter )
141172 current = result
142- for part in parts [:- 1 ]:
173+ for i , part in enumerate ( parts [:- 1 ]) :
143174 if part not in current :
144175 current [part ] = {}
176+ elif not isinstance (current [part ], dict ):
177+ # Conflict: trying to traverse into a leaf value
178+ conflicting_key = delimiter .join (parts [:i + 1 ])
179+ raise ValueError (
180+ f"Key conflict in unflatten_dict: '{ conflicting_key } ' is both "
181+ f"a leaf value and a nested dictionary"
182+ )
145183 current = current [part ]
184+
185+ # Check final key conflict
186+ if parts [- 1 ] in current and isinstance (current [parts [- 1 ]], dict ):
187+ raise ValueError (
188+ f"Key conflict in unflatten_dict: '{ key } ' would overwrite "
189+ f"existing nested dictionary"
190+ )
191+
146192 current [parts [- 1 ]] = value
147193 return result
148194
149195
150- def deep_merge (base : Dict [str , Any ], updates : Dict [str , Any ]) -> Dict [str , Any ]:
196+ def deep_merge (
197+ base : dict [str , Any ],
198+ updates : dict [str , Any ],
199+ _depth : int = 0 ,
200+ max_depth : int = 100
201+ ) -> dict [str , Any ]:
151202 """
152203 Deep merge two dictionaries, with updates overriding base values.
153204
154205 Args:
155206 base: The base dictionary
156207 updates: Dictionary with values to merge/override
208+ _depth: Internal recursion depth counter (do not set manually)
209+ max_depth: Maximum allowed nesting depth (default 100)
157210
158211 Returns:
159212 A new dictionary with merged values
160213
214+ Raises:
215+ TypeError: If base or updates is not a dictionary
216+ ValueError: If nesting depth exceeds max_depth
217+
161218 Examples:
162219 >>> deep_merge({'a': 1, 'b': 2}, {'b': 3, 'c': 4})
163220 {'a': 1, 'b': 3, 'c': 4}
164221
165222 >>> deep_merge({'a': {'b': 1}}, {'a': {'c': 2}})
166223 {'a': {'b': 1, 'c': 2}}
167224 """
225+ if not isinstance (base , dict ):
226+ raise TypeError (f"deep_merge base expects dict, got { type (base ).__name__ } " )
227+ if not isinstance (updates , dict ):
228+ raise TypeError (f"deep_merge updates expects dict, got { type (updates ).__name__ } " )
229+
230+ if _depth > max_depth :
231+ raise ValueError (
232+ f"Dictionary nesting depth exceeds maximum allowed depth of { max_depth } . "
233+ f"This may indicate a circular reference or malformed configuration."
234+ )
235+
168236 result = base .copy ()
169237 for key , value in updates .items ():
170238 if key in result and isinstance (result [key ], dict ) and isinstance (value , dict ):
171- result [key ] = deep_merge (result [key ], value )
239+ result [key ] = deep_merge (result [key ], value , _depth + 1 , max_depth )
172240 else :
173241 result [key ] = value
174242 return result
175243
176244
177- def remove_empty_values (d : Dict [str , Any ]) -> Dict [str , Any ]:
245+ def remove_empty_values (
246+ d : dict [str , Any ],
247+ _depth : int = 0 ,
248+ max_depth : int = 100
249+ ) -> dict [str , Any ]:
178250 """
179251 Recursively remove empty string values from a nested dictionary.
180252
181253 Args:
182254 d: The dictionary to clean
255+ _depth: Internal recursion depth counter (do not set manually)
256+ max_depth: Maximum allowed nesting depth (default 100)
183257
184258 Returns:
185259 A new dictionary with empty strings removed
186260
261+ Raises:
262+ TypeError: If d is not a dictionary
263+ ValueError: If nesting depth exceeds max_depth
264+
187265 Examples:
188266 >>> remove_empty_values({'a': '', 'b': 'value'})
189267 {'b': 'value'}
@@ -194,10 +272,19 @@ def remove_empty_values(d: Dict[str, Any]) -> Dict[str, Any]:
194272 Note:
195273 Only removes empty strings (""), not other falsy values like None, 0, False, []
196274 """
275+ if not isinstance (d , dict ):
276+ raise TypeError (f"remove_empty_values expects dict, got { type (d ).__name__ } " )
277+
278+ if _depth > max_depth :
279+ raise ValueError (
280+ f"Dictionary nesting depth exceeds maximum allowed depth of { max_depth } . "
281+ f"This may indicate a circular reference or malformed configuration."
282+ )
283+
197284 result = {}
198285 for key , value in d .items ():
199286 if isinstance (value , dict ):
200- nested = remove_empty_values (value )
287+ nested = remove_empty_values (value , _depth + 1 , max_depth )
201288 if nested : # Only add if nested dict is not empty after cleaning
202289 result [key ] = nested
203290 elif value != "" :
0 commit comments