def dataclass_from_dict(klass, d): """ Converts a dictionary based on a dataclass, into an instance of that dataclass. Recursively goes through lists, optionals, and dictionaries. """ if is_type_SpecificOptional(klass): # Type is optional, data is either None, or Any if not d: return None return dataclass_from_dict(get_args(klass)[0], d) elif is_type_Tuple(klass): # Type is tuple, can have multiple different types inside i = 0 klass_properties = [] for item in d: klass_properties.append(dataclass_from_dict(klass.__args__[i], item)) i = i + 1 return tuple(klass_properties) elif dataclasses.is_dataclass(klass): # Type is a dataclass, data is a dictionary fieldtypes = {f.name: f.type for f in dataclasses.fields(klass)} return klass(**{f: dataclass_from_dict(fieldtypes[f], d[f]) for f in d}) elif is_type_List(klass): # Type is a list, data is a list return [dataclass_from_dict(get_args(klass)[0], item) for item in d] elif issubclass(klass, bytes): # Type is bytes, data is a hex string return klass(hexstr_to_bytes(d)) elif klass.__name__ in unhashable_types: # Type is unhashable (bls type), so cast from hex string return klass.from_bytes(hexstr_to_bytes(d)) else: # Type is a primitive, cast with correct class return klass(d)
def function_to_parse_one_item(cls: Type[cls.__name__], f_type: Type): # type: ignore """ This function returns a function taking one argument `f: BinaryIO` that parses and returns a value of the given type. """ inner_type: Type if f_type is bool: return parse_bool if is_type_SpecificOptional(f_type): inner_type = get_args(f_type)[0] parse_inner_type_f = cls.function_to_parse_one_item(inner_type) return lambda f: parse_optional(f, parse_inner_type_f) if hasattr(f_type, "parse"): return f_type.parse if f_type == bytes: return parse_bytes if is_type_List(f_type): inner_type = get_args(f_type)[0] parse_inner_type_f = cls.function_to_parse_one_item(inner_type) return lambda f: parse_list(f, parse_inner_type_f) if is_type_Tuple(f_type): inner_types = get_args(f_type) list_parse_inner_type_f = [cls.function_to_parse_one_item(_) for _ in inner_types] return lambda f: parse_tuple(f, list_parse_inner_type_f) if hasattr(f_type, "from_bytes") and f_type.__name__ in size_hints: bytes_to_read = size_hints[f_type.__name__] return lambda f: parse_size_hints(f, f_type, bytes_to_read) if f_type is str: return parse_str raise NotImplementedError(f"Type {f_type} does not have parse")
def parse_one_item(cls: Type[cls.__name__], f_type: Type, f: BinaryIO): # type: ignore inner_type: Type if f_type is bool: bool_byte = f.read(1) assert bool_byte is not None and len(bool_byte) == 1 # Checks for EOF if bool_byte == bytes([0]): return False elif bool_byte == bytes([1]): return True else: raise ValueError("Bool byte must be 0 or 1") if is_type_SpecificOptional(f_type): inner_type = get_args(f_type)[0] is_present_bytes = f.read(1) assert is_present_bytes is not None and len(is_present_bytes) == 1 # Checks for EOF if is_present_bytes == bytes([0]): return None elif is_present_bytes == bytes([1]): return cls.parse_one_item(inner_type, f) # type: ignore else: raise ValueError("Optional must be 0 or 1") if hasattr(f_type, "parse"): return f_type.parse(f) if f_type == bytes: list_size_bytes = f.read(4) assert list_size_bytes is not None and len(list_size_bytes) == 4 # Checks for EOF list_size: uint32 = uint32(int.from_bytes(list_size_bytes, "big")) bytes_read = f.read(list_size) assert bytes_read is not None and len(bytes_read) == list_size return bytes_read if is_type_List(f_type): inner_type = get_args(f_type)[0] full_list: List[inner_type] = [] # type: ignore # wjb assert inner_type != get_args(List)[0] # type: ignore list_size_bytes = f.read(4) assert list_size_bytes is not None and len(list_size_bytes) == 4 # Checks for EOF list_size = uint32(int.from_bytes(list_size_bytes, "big")) for list_index in range(list_size): full_list.append(cls.parse_one_item(inner_type, f)) # type: ignore return full_list if is_type_Tuple(f_type): inner_types = get_args(f_type) full_list = [] for inner_type in inner_types: full_list.append(cls.parse_one_item(inner_type, f)) # type: ignore return tuple(full_list) if hasattr(f_type, "from_bytes") and f_type.__name__ in size_hints: bytes_to_read = size_hints[f_type.__name__] bytes_read = f.read(bytes_to_read) assert bytes_read is not None and len(bytes_read) == bytes_to_read return f_type.from_bytes(bytes_read) if f_type is str: str_size_bytes = f.read(4) assert str_size_bytes is not None and len(str_size_bytes) == 4 # Checks for EOF str_size: uint32 = uint32(int.from_bytes(str_size_bytes, "big")) str_read_bytes = f.read(str_size) assert str_read_bytes is not None and len(str_read_bytes) == str_size # Checks for EOF return bytes.decode(str_read_bytes, "utf-8") raise RuntimeError(f"Type {f_type} does not have parse")
def test_basic_list(self): a = [1, 2, 3] assert is_type_List(type(a)) assert is_type_List(List) assert is_type_List(List[int]) assert is_type_List(List[uint8]) assert is_type_List(list) assert not is_type_List(Tuple) # type: ignore assert not is_type_List(tuple) assert not is_type_List(dict)
def stream_one_item(self, f_type: Type, item, f: BinaryIO) -> None: inner_type: Type if is_type_SpecificOptional(f_type): inner_type = get_args(f_type)[0] if item is None: f.write(bytes([0])) else: f.write(bytes([1])) self.stream_one_item(inner_type, item, f) elif f_type == bytes: write_uint32(f, uint32(len(item))) f.write(item) elif hasattr(f_type, "stream"): item.stream(f) elif hasattr(f_type, "__bytes__"): f.write(bytes(item)) elif is_type_List(f_type): assert is_type_List(type(item)) write_uint32(f, uint32(len(item))) inner_type = get_args(f_type)[0] # wjb assert inner_type != get_args(List)[0] # type: ignore for element in item: self.stream_one_item(inner_type, element, f) elif is_type_Tuple(f_type): inner_types = get_args(f_type) assert len(item) == len(inner_types) for i in range(len(item)): self.stream_one_item(inner_types[i], item[i], f) elif f_type is str: str_bytes = item.encode("utf-8") write_uint32(f, uint32(len(str_bytes))) f.write(str_bytes) elif f_type is bool: f.write(int(item).to_bytes(1, "big")) else: raise NotImplementedError(f"can't stream {item}, {f_type}")
def test_not_lists(self): assert not is_type_List(Dict)