Source code for mdg.tools.io

from typing import get_type_hints, Any, Tuple
from enum import Enum

DEFAULT_TYPES = [str, list, tuple, dict, bool, int, type(None)]


[docs] def obj_to_dict(obj: Any, base_dict={}) -> dict: """ Creates nested dictionaries from input object """ # Attributes to convert are specified in object definition type hints type_dict: dict = get_type_hints(type(obj)) output = {} # Loop through all type hints on object and convert for attr_name in type_dict.keys(): # Attribute must be set on instance if hasattr(obj, attr_name): value = getattr(obj, attr_name) # If the value is not a base type or enum then it's a class TODO: Actual check for class if type(value) not in DEFAULT_TYPES and not isinstance(value, Enum): # If the class is owned by this parent class then convert to a dict if attr_name in obj.Meta.owned_subobjects: value = obj_to_dict(value) # If the class is not owned by this parent class then convert to a reference else: value = getattr(value, value.Meta.id_field) # If value is a list then examine the type of the list and convert each element elif type(value) in [list, tuple]: new_value: Any = [] for element in value: # Check for base types as in non list mode above: TODO: Actual check for class if type(element) not in DEFAULT_TYPES and not isinstance(element, Enum): # Check for class ownership as above if attr_name in obj.Meta.owned_subobjects: new_value.append(obj_to_dict(element)) else: new_value.append(getattr(element, element.Meta.id_field)) # Just a base type so use the value else: new_value.append(element) value = new_value elif isinstance(value, Enum): value = value.name # Value has been created so set the dictionary item output[attr_name] = value return output
[docs] def dict_to_obj(input: dict, base_object_class, references: dict = {}) -> object: obj, refs = dict_to_obj_pass1(input, base_object_class) dict_to_obj_pass2(obj, refs) return obj
[docs] def dict_to_obj_pass2(obj: Any, references: dict) -> None: """ Pass 2 walks through objects and replaces attributes which are references with the actual class. """ type_dict: dict = get_type_hints(type(obj)) # Loop through all type hints on object for attr_name in type_dict.keys(): # Walk through all attributes of object if hasattr(obj, attr_name): value = getattr(obj, attr_name) obj_attr_type = type_dict[attr_name] if type(value) == list: # We have a list if its supposed to be a list of classes then examine if obj_attr_type.__args__[0] not in DEFAULT_TYPES: new_list = [] for item in value: # If item is supposed to be a class but is actually a default type then must be reference if type(item) in DEFAULT_TYPES: new_list.append(references[value]) else: dict_to_obj_pass2(item, references) new_list.append(item) setattr(obj, attr_name, new_list) elif obj_attr_type not in DEFAULT_TYPES: # If value is supposed to be a class but is actually a default type then must be reference if type(value) in DEFAULT_TYPES: setattr(obj, attr_name, references[value]) # If value is a class then parse it else: dict_to_obj_pass2(value, references)
[docs] def dict_to_obj_pass1(input: dict, base_object_class, references: dict = {}) -> Tuple[Any, dict]: """ Creates objects from input dictionary. Pass 1 keeps references and compiles a list of objects with ids used in pass 2. """ # Instantiate the requested class & get the type hints obj = base_object_class() type_dict = get_type_hints(type(obj)) for attr_name in type_dict.keys(): if attr_name in input.keys(): # The input has this item defined in the class so extract # dict_attr_type = type(input[attr_name]) # type_dict[attr_name].__origin__ TODO: use definition rather than input obj_attr_type = type_dict[attr_name] if type(input[attr_name]) != list: if obj_attr_type in DEFAULT_TYPES: # Value definition is standard type so just set the value setattr(obj, attr_name, input[attr_name]) elif type(input[attr_name]) == dict: # Not a standard type, represented as dict. Recurse to expand sub-object new_obj, new_refs = dict_to_obj_pass1(input[attr_name], type_dict[attr_name], references) setattr(obj, attr_name, new_obj) # Store object in reference dictionary and add new object references.update(new_refs) references[getattr(new_obj, new_obj.Meta.id_field)] = new_obj else: # If definition is class and dict value has string then must be a reference using id_field value from Meta setattr(obj, attr_name, input[attr_name]) else: # Input has a list of something, loop through each element and extract value = [] item_type = type_dict[attr_name].__args__[0] for item in input[attr_name]: if item_type in DEFAULT_TYPES: # List of standard type so just add the value value.append(item) else: # attr_class = type_dict[attr_name].__args__[0] if type(item) == dict: # Class definition has list of class instances and input has a dictionary so recurse to expand sub-object new_obj, new_refs = dict_to_obj_pass1(item, item_type) value.append(new_obj) # Store object in reference dictionary and add new object references.update(new_refs) references[getattr(new_obj, new_obj.Meta.id_field)] = new_obj else: # If definition is class and dict value has string then must be a reference using id_field value from Meta value.append(item) setattr(obj, attr_name, value) return obj, references