def test_is_generic_unsupported_python(): with patch("mashumaro.meta.helpers.PY_36", False): with patch("mashumaro.meta.helpers.PY_37", False): with patch("mashumaro.meta.helpers.PY_38", False): with patch("mashumaro.meta.helpers.PY_39", False): with pytest.raises(NotImplementedError): is_generic(int)
def _unpack_field_value( self, fname, ftype, parent, value_name="value", metadata=MappingProxyType({}), ): overridden: typing.Optional[str] = None deserialize_option = metadata.get("deserialize") if deserialize_option is None: strategy = metadata.get("serialization_strategy") if isinstance(strategy, SerializationStrategy): deserialize_option = strategy.deserialize if deserialize_option is None: strategy = self.get_config().serialization_strategy.get(ftype) if isinstance(strategy, dict): deserialize_option = strategy.get("deserialize") elif isinstance(strategy, SerializationStrategy): deserialize_option = strategy.deserialize if callable(deserialize_option): setattr(self.cls, f"__{fname}_deserialize", deserialize_option) overridden = f"cls.__{fname}_deserialize({value_name})" if is_dataclass(ftype): return overridden or ( f"{type_name(ftype)}.from_dict({value_name}, " f"use_bytes, use_enum, use_datetime)") with suppress(TypeError): if issubclass(ftype, SerializableType): return (overridden or f"{type_name(ftype)}._deserialize({value_name})") origin_type = get_type_origin(ftype) if is_special_typing_primitive(origin_type): if origin_type is typing.Any: return overridden or value_name elif is_union(ftype): args = getattr(ftype, "__args__", ()) if len(args) == 2 and args[1] == NoneType: # it is Optional return self._unpack_field_value(fname, args[0], parent, metadata=metadata) else: method_name = self._add_unpack_union( fname, ftype, args, parent, metadata) return (f"cls.{method_name}({value_name}," f"use_bytes,use_enum,use_datetime)") elif origin_type is typing.AnyStr: raise UnserializableDataError( "AnyStr is not supported by mashumaro") elif is_type_var(ftype): raise UnserializableDataError( "TypeVars are not supported by mashumaro") else: raise UnserializableDataError( f"{ftype} as a field type is not supported by mashumaro") elif origin_type is int: return overridden or f"int({value_name})" elif origin_type is float: return overridden or f"float({value_name})" elif origin_type in (bool, NoneType): return overridden or value_name elif origin_type in (datetime.datetime, datetime.date, datetime.time): if overridden: return f"{value_name} if use_datetime else {overridden}" elif deserialize_option is not None: if deserialize_option == "ciso8601": self.ensure_module_imported("ciso8601") datetime_parser = "ciso8601.parse_datetime" elif deserialize_option == "pendulum": self.ensure_module_imported("pendulum") datetime_parser = "pendulum.parse" else: raise UnserializableField( fname, ftype, parent, f"Unsupported deserialization engine " f'"{deserialize_option}"', ) suffix = "" if origin_type is datetime.date: suffix = ".date()" elif origin_type is datetime.time: suffix = ".time()" return (f"{value_name} if use_datetime else " f"{datetime_parser}({value_name}){suffix}") return (f"{value_name} if use_datetime else " f"datetime.{origin_type.__name__}." f"fromisoformat({value_name})") elif origin_type is datetime.timedelta: return overridden or f"datetime.timedelta(seconds={value_name})" elif origin_type is datetime.timezone: return overridden or f"parse_timezone({value_name})" elif origin_type is uuid.UUID: return overridden or f"uuid.UUID({value_name})" elif origin_type is ipaddress.IPv4Address: return overridden or f"ipaddress.IPv4Address({value_name})" elif origin_type is ipaddress.IPv6Address: return overridden or f"ipaddress.IPv6Address({value_name})" elif origin_type is ipaddress.IPv4Network: return overridden or f"ipaddress.IPv4Network({value_name})" elif origin_type is ipaddress.IPv6Network: return overridden or f"ipaddress.IPv6Network({value_name})" elif origin_type is ipaddress.IPv4Interface: return overridden or f"ipaddress.IPv4Interface({value_name})" elif origin_type is ipaddress.IPv6Interface: return overridden or f"ipaddress.IPv6Interface({value_name})" elif origin_type is Decimal: return overridden or f"Decimal({value_name})" elif origin_type is Fraction: return overridden or f"Fraction({value_name})" elif issubclass(origin_type, typing.Collection) and not issubclass( origin_type, enum.Enum): args = getattr(ftype, "__args__", ()) def inner_expr(arg_num=0, v_name="value", v_type=None): if v_type: return self._unpack_field_value(fname, v_type, parent, v_name) else: return self._unpack_field_value(fname, args[arg_num], parent, v_name) if issubclass(origin_type, typing.ByteString): if origin_type is bytes: specific = f"decodebytes({value_name}.encode())" return (f"{value_name} if use_bytes else " f"{overridden or specific}") elif origin_type is bytearray: if overridden: overridden = ( f"bytearray({value_name}) if use_bytes else " f"{overridden}") specific = (f"bytearray({value_name} if use_bytes else " f"decodebytes({value_name}.encode()))") return overridden or specific elif issubclass(origin_type, str): return overridden or value_name elif issubclass(origin_type, typing.List): if is_generic(ftype): return (overridden or f"[{inner_expr()} for value in {value_name}]") elif ftype is list: raise UnserializableField(fname, ftype, parent, "Use typing.List[T] instead") elif issubclass(origin_type, typing.Deque): if is_generic(ftype): return (overridden or f"collections.deque([{inner_expr()} " f"for value in {value_name}])") elif ftype is collections.deque: raise UnserializableField(fname, ftype, parent, "Use typing.Deque[T] instead") elif issubclass(origin_type, typing.Tuple): if is_generic(ftype): return ( overridden or f"tuple([{inner_expr()} for value in {value_name}])") elif ftype is tuple: raise UnserializableField(fname, ftype, parent, "Use typing.Tuple[T] instead") elif issubclass(origin_type, typing.FrozenSet): if is_generic(ftype): return (overridden or f"frozenset([{inner_expr()} " f"for value in {value_name}])") elif ftype is frozenset: raise UnserializableField( fname, ftype, parent, "Use typing.FrozenSet[T] instead") elif issubclass(origin_type, typing.AbstractSet): if is_generic(ftype): return (overridden or f"set([{inner_expr()} for value in {value_name}])") elif ftype is set: raise UnserializableField(fname, ftype, parent, "Use typing.Set[T] instead") elif issubclass(origin_type, typing.ChainMap): if ftype is collections.ChainMap: raise UnserializableField( fname, ftype, parent, "Use typing.ChainMap[KT,VT] instead", ) elif is_generic(ftype): if is_dataclass(args[0]): raise UnserializableDataError( "ChainMaps with dataclasses as keys " "are not supported by mashumaro") else: return (overridden or f"collections.ChainMap(" f'*[{{{inner_expr(0,"key")}:{inner_expr(1)} ' f"for key, value in m.items()}} " f"for m in {value_name}])") elif PY_37_MIN and issubclass(origin_type, typing.OrderedDict): if ftype is collections.OrderedDict: raise UnserializableField( fname, ftype, parent, "Use typing.OrderedDict[KT,VT] instead", ) elif is_generic(ftype): if is_dataclass(args[0]): raise UnserializableDataError( "OrderedDict with dataclasses as keys " "are not supported by mashumaro") else: return (overridden or f"collections.OrderedDict(" f'{{{inner_expr(0,"key")}: {inner_expr(1)} ' f"for key, value in {value_name}.items()}})") elif issubclass(origin_type, typing.Counter): if ftype is collections.Counter: raise UnserializableField( fname, ftype, parent, "Use typing.Counter[KT] instead", ) elif is_generic(ftype): if is_dataclass(args[0]): raise UnserializableDataError( "Counter with dataclasses as keys " "are not supported by mashumaro") else: return (overridden or f"collections.Counter(" f'{{{inner_expr(0,"key")}: ' f"{inner_expr(1, v_type=int)} " f"for key, value in {value_name}.items()}})") elif issubclass(origin_type, typing.Mapping): if ftype is dict: raise UnserializableField( fname, ftype, parent, "Use typing.Dict[KT,VT] or Mapping[KT,VT] instead", ) elif is_generic(ftype): if is_dataclass(args[0]): raise UnserializableDataError( "Mappings with dataclasses as keys " "are not supported by mashumaro") else: return (overridden or f'{{{inner_expr(0,"key")}: {inner_expr(1)} ' f"for key, value in {value_name}.items()}}") elif issubclass(origin_type, typing.Sequence): if is_generic(ftype): return (overridden or f"[{inner_expr()} for value in {value_name}]") elif issubclass(origin_type, os.PathLike): if overridden: return overridden elif issubclass(origin_type, pathlib.PosixPath): return f"pathlib.PosixPath({value_name})" elif issubclass(origin_type, pathlib.WindowsPath): return f"pathlib.WindowsPath({value_name})" elif issubclass(origin_type, pathlib.Path): return f"pathlib.Path({value_name})" elif issubclass(origin_type, pathlib.PurePosixPath): return f"pathlib.PurePosixPath({value_name})" elif issubclass(origin_type, pathlib.PureWindowsPath): return f"pathlib.PureWindowsPath({value_name})" elif issubclass(origin_type, pathlib.PurePath): return f"pathlib.PurePath({value_name})" elif origin_type is os.PathLike: return f"pathlib.PurePath({value_name})" else: return f"{type_name(origin_type)}({value_name})" elif issubclass(origin_type, enum.Enum): specific = f"{type_name(origin_type)}({value_name})" return f"{value_name} if use_enum else {overridden or specific}" elif overridden: return overridden raise UnserializableField(fname, ftype, parent)
def _pack_value( self, fname, ftype, parent, value_name="value", metadata=MappingProxyType({}), ): overridden: typing.Optional[str] = None serialize_option = metadata.get("serialize") if serialize_option is None: strategy = metadata.get("serialization_strategy") if isinstance(strategy, SerializationStrategy): serialize_option = strategy.serialize if serialize_option is None: strategy = self.get_config().serialization_strategy.get(ftype) if isinstance(strategy, dict): serialize_option = strategy.get("serialize") elif isinstance(strategy, SerializationStrategy): serialize_option = strategy.serialize if callable(serialize_option): setattr( self.cls, f"__{fname}_serialize", staticmethod(serialize_option), ) overridden = f"self.__{fname}_serialize({value_name})" if is_dataclass(ftype): flags = self.get_to_dict_flags(ftype) return overridden or f"{value_name}.to_dict({flags})" with suppress(TypeError): if issubclass(ftype, SerializableType): return overridden or f"{value_name}._serialize()" origin_type = get_type_origin(ftype) if is_special_typing_primitive(origin_type): if origin_type is typing.Any: return overridden or value_name elif is_union(ftype): args = getattr(ftype, "__args__", ()) if len(args) == 2 and args[1] == NoneType: # it is Optional return self._pack_value(fname, args[0], parent, metadata=metadata) else: method_name = self._add_pack_union(fname, ftype, args, parent, metadata) return (f"self.{method_name}({value_name}," f"{self.get_to_dict_flags()})") elif origin_type is typing.AnyStr: raise UnserializableDataError( "AnyStr is not supported by mashumaro") elif is_type_var(ftype): raise UnserializableDataError( "TypeVars are not supported by mashumaro") else: raise UnserializableDataError( f"{ftype} as a field type is not supported by mashumaro") elif origin_type is int: return overridden or f"int({value_name})" elif origin_type is float: return overridden or f"float({value_name})" elif origin_type in (bool, NoneType): return overridden or value_name elif origin_type in (datetime.datetime, datetime.date, datetime.time): if overridden: return f"{value_name} if use_datetime else {overridden}" return ( f"{value_name} if use_datetime else {value_name}.isoformat()") elif origin_type is datetime.timedelta: return overridden or f"{value_name}.total_seconds()" elif origin_type is datetime.timezone: return overridden or f"{value_name}.tzname(None)" elif origin_type is uuid.UUID: return overridden or f"str({value_name})" elif origin_type in [ ipaddress.IPv4Address, ipaddress.IPv6Address, ipaddress.IPv4Network, ipaddress.IPv6Network, ipaddress.IPv4Interface, ipaddress.IPv6Interface, ]: return overridden or f"str({value_name})" elif origin_type is Decimal: return overridden or f"str({value_name})" elif origin_type is Fraction: return overridden or f"str({value_name})" elif issubclass(origin_type, typing.Collection) and not issubclass( origin_type, enum.Enum): args = getattr(ftype, "__args__", ()) def inner_expr(arg_num=0, v_name="value", v_type=None): if v_type: return self._pack_value(fname, v_type, parent, v_name) else: return self._pack_value(fname, args[arg_num], parent, v_name) if issubclass(origin_type, typing.ByteString): specific = f"encodebytes({value_name}).decode()" return ( f"{value_name} if use_bytes else {overridden or specific}") elif issubclass(origin_type, str): return overridden or value_name elif issubclass( origin_type, (typing.List, typing.Deque, typing.Tuple, typing.AbstractSet), ): if is_generic(ftype): return (overridden or f"[{inner_expr()} for value in {value_name}]") elif ftype is list: raise UnserializableField(fname, ftype, parent, "Use typing.List[T] instead") elif ftype is collections.deque: raise UnserializableField(fname, ftype, parent, "Use typing.Deque[T] instead") elif ftype is tuple: raise UnserializableField(fname, ftype, parent, "Use typing.Tuple[T] instead") elif ftype is set: raise UnserializableField(fname, ftype, parent, "Use typing.Set[T] instead") elif ftype is frozenset: raise UnserializableField( fname, ftype, parent, "Use typing.FrozenSet[T] instead") elif issubclass(origin_type, typing.ChainMap): if ftype is collections.ChainMap: raise UnserializableField( fname, ftype, parent, "Use typing.ChainMap[KT,VT] instead", ) elif is_generic(ftype): if is_dataclass(args[0]): raise UnserializableDataError( "ChainMaps with dataclasses as keys " "are not supported by mashumaro") else: return (overridden or f'[{{{inner_expr(0,"key")}:{inner_expr(1)} ' f"for key,value in m.items()}} " f"for m in value.maps]") elif PY_37_MIN and issubclass(origin_type, typing.OrderedDict): if ftype is collections.OrderedDict: raise UnserializableField( fname, ftype, parent, "Use typing.OrderedDict[KT,VT] instead", ) elif is_generic(ftype): if is_dataclass(args[0]): raise UnserializableDataError( "OrderedDict with dataclasses as keys " "are not supported by mashumaro") else: return (overridden or f'{{{inner_expr(0, "key")}: {inner_expr(1)} ' f"for key, value in {value_name}.items()}}") elif issubclass(origin_type, typing.Counter): if ftype is collections.Counter: raise UnserializableField( fname, ftype, parent, "Use typing.Counter[KT] instead", ) elif is_generic(ftype): if is_dataclass(args[0]): raise UnserializableDataError( "Counter with dataclasses as keys " "are not supported by mashumaro") else: return (overridden or f'{{{inner_expr(0, "key")}: ' f"{inner_expr(1, v_type=int)} " f"for key, value in {value_name}.items()}}") elif issubclass(origin_type, typing.Mapping): if ftype is dict: raise UnserializableField( fname, ftype, parent, "Use typing.Dict[KT,VT] or Mapping[KT,VT] instead", ) elif is_generic(ftype): if is_dataclass(args[0]): raise UnserializableDataError( "Mappings with dataclasses as keys " "are not supported by mashumaro") else: return (overridden or f'{{{inner_expr(0,"key")}: {inner_expr(1)} ' f"for key, value in {value_name}.items()}}") elif issubclass(origin_type, typing.Sequence): if is_generic(ftype): return (overridden or f"[{inner_expr()} for value in {value_name}]") elif issubclass(origin_type, os.PathLike): return overridden or f"{value_name}.__fspath__()" elif issubclass(origin_type, enum.Enum): specific = f"{value_name}.value" return f"{value_name} if use_enum else {overridden or specific}" elif overridden: return overridden raise UnserializableField(fname, ftype, parent)