def default_union_serializer(obj: object, cls: Union, **kwargs) -> object: """ Serialize an object to any matching type of the given union. The first successful serialization is returned. :param obj: The object that is to be serialized. :param cls: The Union type with a generic (e.g. Union[str, int]). :param kwargs: Any keyword arguments that are passed through the serialization process. :return: An object of the first type of the Union that could be serialized successfully. """ sub_types = get_union_params(cls) # Cater for Optional[...]/Union[None, ...] first to avoid blindly # string-ifying None in later serializers. if obj is None and NoneType in sub_types: return obj for sub_type in sub_types: try: return dump(obj, sub_type, **kwargs) except JsonsError: pass # Try the next one. else: args_msg = ', '.join( [get_class_name(cls_) for cls_ in get_union_params(cls)]) err_msg = ('Could not match the object of type "{}" to any type of ' 'the Union: {}'.format(type(obj), args_msg)) raise SerializationError(err_msg)
def default_union_deserializer(obj: object, cls: Union, **kwargs) -> object: """ Deserialize an object to any matching type of the given union. The first successful deserialization is returned. :param obj: The object that needs deserializing. :param cls: The Union type with a generic (e.g. Union[str, int]). :param kwargs: Any keyword arguments that are passed through the deserialization process. :return: An object of the first type of the Union that could be deserialized successfully. """ for sub_type in get_union_params(cls): try: return load(obj, sub_type, **kwargs) except JsonsError: pass # Try the next one. else: fork_inst = kwargs['fork_inst'] args_msg = ', '.join([ get_class_name(cls_, fork_inst=fork_inst) for cls_ in get_union_params(cls) ]) err_msg = ('Could not match the object of type "{}" to any type of ' 'the Union: {}'.format(str(cls), args_msg)) raise DeserializationError(err_msg, obj, cls)
def can_match_with_none(cls: type): # Return True if cls allows None; None is a valid value with the given cls. result = cls in (Any, object, None, NoneType) if not result: cls_name = get_class_name(cls).lower() result = 'union' in cls_name and NoneType in get_union_params(cls) return result
def default_namedtuple_deserializer(obj: Union[list, dict], cls: type, **kwargs) -> object: """ Deserialize a (JSON) list or dict into a named tuple by deserializing all items of that list/dict. :param obj: the tuple that needs deserializing. :param cls: the NamedTuple. :param kwargs: any keyword arguments. :return: a deserialized named tuple (i.e. an instance of a class). """ is_dict = isinstance(obj, dict) args = [] for index, field_name in enumerate(cls._fields): if index < len(obj): key = field_name if is_dict else index field = obj[key] else: field = cls._field_defaults.get(field_name, None) if field is None: hint = getattr(cls, '_field_types', {}).get(field_name) if type(None) not in (get_union_params(hint) or []): # The value 'None' is not permitted here. msg = ('No value present in {} for argument "{}"'.format( obj, field_name)) raise UnfulfilledArgumentError(msg, field_name, obj, cls) field_types = getattr(cls, '_field_types', None) cls_ = field_types.get(field_name) if field_types else None loaded_field = load(field, cls_, **kwargs) args.append(loaded_field) inst = cls(*args) return inst
def default_namedtuple_deserializer(obj: Union[list, dict], cls: type, *, key_transformer: Optional[Callable[ [str], str]] = None, **kwargs) -> object: """ Deserialize a (JSON) list or dict into a named tuple by deserializing all items of that list/dict. :param obj: the tuple that needs deserializing. :param cls: the NamedTuple. :param kwargs: any keyword arguments. :return: a deserialized named tuple (i.e. an instance of a class). """ is_dict = isinstance(obj, dict) key_tfr = key_transformer or (lambda key: key) if is_dict: tfm_obj = {key_tfr(k): v for k, v in obj.items()} args = [] for index, field_name in enumerate(cls._fields): if index < len(obj): if is_dict: field = tfm_obj[field_name] else: field = obj[index] else: field = cls._field_defaults.get(field_name, None) # _field_types has been deprecated in favor of __annotations__ in Python 3.8 if hasattr(cls, '__annotations__'): field_types = getattr(cls, '__annotations__', {}) else: field_types = getattr(cls, '_field_types', {}) if field is None: hint = field_types.get(field_name) if NoneType not in (get_union_params(hint) or []): # The value 'None' is not permitted here. msg = ('No value present in {} for argument "{}"'.format( obj, field_name)) raise UnfulfilledArgumentError(msg, field_name, obj, cls) cls_ = field_types.get(field_name) if field_types else None loaded_field = load(field, cls_, key_transformer=key_transformer, **kwargs) args.append(loaded_field) inst = cls(*args) return inst