import copy import random from typing import Any, Callable, Dict, List, Optional, Tuple from models.openapi import Schema from utils.value_generator import generate_random_value def is_array_key(key: str) -> Tuple[bool, str]: if key.endswith("[]"): return True, key[:-2] return False, key def add_key( data: Dict[str, Any], element_key: str, element_reference: Any ) -> Tuple[Optional[Dict[str, Any]], Optional[str], Optional[Any]]: def add_key_action( current: Any, key: str, keys: List[str], reference: Any ) -> Tuple[bool, Any, Tuple[Optional[str], Any]]: if not isinstance(current, dict): return False, None, (None, None) new_key = "extra_key_666" # f"extra_key_{random.randint(1000, 9999)}" # disable random to check consistency while refatoring if not current: new_value = generate_random_value(None, reference) new_value[ new_key ] = 666 # type: ignore # random.randint(-512, 99999) # disable random to check consistency while refatoring if isinstance(new_value, dict): current.update(new_value) else: raise ValueError(f"Unexpected type: {new_value}") else: new_value = 666 # random.randint(-512, 99999) # disable random to check consistency while refatoring current[new_key] = new_value return True, current, (new_key, new_value) def recursive_traverse(current: Any, keys: List[str]) -> Tuple[bool, Any, Any]: if not keys: return add_key_action(current, element_key, [], element_reference) key = keys[0] remaining_keys = keys[1:] is_array, actual_key = is_array_key(key) if is_array: if actual_key not in current or not isinstance(current[actual_key], list): if not remaining_keys: return add_key_action( current, actual_key, remaining_keys, element_reference ) current[actual_key] = [{}] if not current[actual_key]: current[actual_key].append({}) success, value, extra = recursive_traverse( current[actual_key][0], remaining_keys ) if success: current[actual_key][0] = value return success, current, extra if actual_key not in current: if isinstance(current, dict): current[actual_key] = {} else: return add_key_action( current, actual_key, remaining_keys, element_reference ) success, value, extra = recursive_traverse(current[actual_key], remaining_keys) if success: current[actual_key] = value return success, current, extra new_data = copy.deepcopy(data) keys = element_key.split(".") success, value, extra = recursive_traverse(new_data, keys) if not success: return None, None, None if not isinstance(extra, tuple) or len(extra) != 2: return None, None, None new_key, new_value = extra return new_data, new_key, new_value def modify_key( data: Dict[str, Any], element_key: str, element_reference: Any ) -> Tuple[Optional[Dict[str, Any]], Optional[str]]: def modify_key_action( current: Any, key: str, keys: List[str], reference: Any ) -> Tuple[bool, Any, Optional[str]]: is_array, actual_key = is_array_key(key) if is_array: if ( not isinstance(current, dict) or actual_key not in current or not isinstance(current[actual_key], list) ): return False, None, None if not keys: # We're at the end of the path, and it's an array return False, None, None if not current[actual_key]: # Empty array return False, None, None # Move to the first element of the array success, value, new_key = modify_key_action( current[actual_key][0], keys[0], keys[1:], reference ) if success: current[actual_key][0] = value return success, current, new_key if not isinstance(current, dict): return False, None, None if not keys: # We're at the end of the path new_key = f"{actual_key}_666" # f"{key}_{random.randint(10000, 99999)}" # disable random to check consistency while refatoring if actual_key in current: current[new_key] = current.pop(actual_key) else: current[new_key] = generate_random_value(None, reference) return True, current, new_key # We're not at the end of the path if actual_key not in current: current[actual_key] = {} success, value, new_key = modify_key_action( current[actual_key], keys[0], keys[1:], reference ) if success: current[actual_key] = value return success, current, new_key new_data = copy.deepcopy(data) keys = element_key.split(".") success, value, new_key = modify_key_action( new_data, keys[0], keys[1:], element_reference ) if not success: return None, None return new_data, new_key def remove_key( data: Dict[str, Any], element_key: str ) -> Tuple[Optional[Dict[str, Any]], bool]: def remove_key_action( parent: Any, key: str, keys: List[str], reference: Any ) -> Tuple[bool, Any, bool]: is_array, actual_key = is_array_key(key) if is_array: if ( not isinstance(parent, dict) or actual_key not in parent or not isinstance(parent[actual_key], list) ): return False, parent, False if not parent[actual_key]: # Empty array return False, parent, False if not keys: # We're at the end of the path, and it's an array return False, parent, False # Apply to the first element of the array success, value, removed = remove_key_action( parent[actual_key][0], keys[0], keys[1:], reference ) if success: parent[actual_key][0] = value return success, parent, removed if not isinstance(parent, dict): return False, parent, False if not keys: # We're at the end of the path if actual_key in parent: del parent[actual_key] return True, parent, True return False, parent, False # We're not at the end of the path if actual_key not in parent: return False, parent, False success, value, removed = remove_key_action( parent[actual_key], keys[0], keys[1:], reference ) if success: parent[actual_key] = value return success, parent, removed new_data = copy.deepcopy(data) keys = element_key.split(".") success, value, removed = remove_key_action(new_data, keys[0], keys[1:], None) if not success: return new_data, False return new_data, removed def set_value( data: Dict[str, Any], element_key: str, element_reference: Schema, value_generator: Callable[[Any, Schema], Any], ) -> Tuple[Optional[Dict[str, Any]], Any]: def set_value_action( current: Any, keys: List[str], reference: Schema ) -> Tuple[bool, Any, Any]: if not keys: try: new_value = value_generator(current, reference) return True, new_value, new_value except ValueError: return False, current, None key = keys[0] remaining_keys = keys[1:] is_array, actual_key = is_array_key(key) if is_array: if not isinstance(current, dict) or actual_key not in current: current[actual_key] = [] if not current[actual_key]: current[actual_key].append({}) if not remaining_keys: try: new_value = value_generator(current[actual_key][0], reference) current[actual_key] = [new_value] return True, current, new_value except ValueError: return False, current, None success, value, new_value = set_value_action( current[actual_key][0], remaining_keys, reference ) if success: current[actual_key][0] = value return success, current, new_value if isinstance(current, list): if not current: current.append({}) try: index = int(actual_key) if 0 <= index < len(current): success, value, new_value = set_value_action( current[index], remaining_keys, reference ) if success: current[index] = value return success, current, new_value except ValueError: pass success, value, new_value = set_value_action( current[0], remaining_keys, reference ) if success: current[0] = value return success, current, new_value if not isinstance(current, dict): return False, current, None if actual_key not in current: if remaining_keys: current[actual_key] = {} else: current[actual_key] = None success, value, new_value = set_value_action( current[actual_key], remaining_keys, reference ) if success: current[actual_key] = value return success, current, new_value new_data = data.copy() keys = ( element_key.split(".") if not element_key.startswith("path_params.") else ["path_params", element_key[12:]] ) # some path parameter have dot in them try: success, value, new_value = set_value_action(new_data, keys, element_reference) if not success: return None, None return value, new_value except ValueError: return None, None