def default_tuple_deserializer(obj: list,
                               cls: type = None,
                               *,
                               key_transformer: Optional[Callable[[str],
                                                                  str]] = None,
                               **kwargs) -> object:
    """
    Deserialize a (JSON) list into a tuple by deserializing all items of that
    list.
    :param obj: the tuple that needs deserializing.
    :param cls: the type optionally with a generic (e.g. Tuple[str, int]).
    :param kwargs: any keyword arguments.
    :return: a deserialized tuple instance.
    """
    if hasattr(cls, '_fields'):
        return default_namedtuple_deserializer(obj,
                                               cls,
                                               key_transformer=key_transformer,
                                               **kwargs)
    cls_args = get_args(cls)
    if cls_args:
        tuple_types = getattr(cls, '__tuple_params__', cls_args)
        if tuple_with_ellipsis(cls):
            tuple_types = [tuple_types[0]] * len(obj)
        list_ = [
            load(value, tuple_types[i], **kwargs)
            for i, value in enumerate(obj)
        ]
    else:
        list_ = [load(value, **kwargs) for i, value in enumerate(obj)]
    return tuple(list_)
Exemple #2
0
def default_dict_deserializer(obj: dict,
                              cls: type,
                              *,
                              key_transformer: Optional[Callable[[str],
                                                                 str]] = None,
                              **kwargs) -> dict:
    """
    Deserialize a dict by deserializing all instances of that dict.
    :param obj: the dict that needs deserializing.
    :param key_transformer: a function that transforms the keys to a different
    style (e.g. PascalCase).
    :param cls: not used.
    :param kwargs: any keyword arguments.
    :return: a deserialized dict instance.
    """
    key_tfr = key_transformer or (lambda key: key)
    cls_args = get_args(cls)
    kwargs_ = {**kwargs, 'key_transformer': key_transformer}
    if len(cls_args) == 2:
        cls_k, cls_v = cls_args
        kwargs_k = {**kwargs_, 'cls': cls_k}
        kwargs_v = {**kwargs_, 'cls': cls_v}
        res = {
            load(key_tfr(k), **kwargs_k): load(obj[k], **kwargs_v)
            for k in obj
        }
    else:
        res = {key_tfr(key): load(obj[key], **kwargs_) for key in obj}
    return res
Exemple #3
0
def default_list_deserializer(obj: list,
                              cls: type = None,
                              *,
                              tasks: int = 1,
                              task_type: type = Process,
                              **kwargs) -> list:
    """
    Deserialize a list by deserializing all items of that list.
    :param obj: the list that needs deserializing.
    :param cls: the type optionally with a generic (e.g. List[str]).
    :param tasks: the allowed number of tasks (threads or processes).
    :param task_type: the type that is used for multitasking.
    :param kwargs: any keyword arguments.
    :return: a deserialized list instance.
    """
    cls_ = None
    kwargs_ = {**kwargs}
    cls_args = get_args(cls)
    if cls_args:
        cls_ = cls_args[0]
        # Mark the cls as 'inferred' so that later it is known where cls came
        # from and the precedence of classes can be determined.
        kwargs_['_inferred_cls'] = True

    if tasks == 1:
        result = [load(elem, cls=cls_, tasks=1, **kwargs_) for elem in obj]
    elif tasks > 1:
        result = multi_task(load, obj, tasks, task_type, cls_, **kwargs_)
    else:
        raise JsonsError('Invalid number of tasks: {}'.format(tasks))
    return result
def transform(obj: object,
              cls: Type[T],
              *,
              mapper: Callable[[Dict[str, Any]], Dict[str, Any]] = None,
              dump_cls: type = None,
              dump_args: List[Any] = None,
              dump_kwargs: List[Dict[str, Any]] = None,
              **kwargs) -> T:
    """
    Transform the given ``obj`` to an instance of ``cls``.

    :param obj: the object that is to be transformed into a type of ``cls``.
    :param cls: the type that ``obj`` is to be transformed into.
    :param mapper: a callable that takes the dumped dict and returns a mapped
    dict right before it is loaded into ``cls``.
    :param dump_cls: the ``cls`` parameter that is given to ``dump``.
    :param dump_args: the ``args`` parameter that is given to ``dump``.
    :param dump_kwargs: the ``kwargs`` parameter that is given to ``dump``.
    :param kwargs: any keyword arguments that are given to ``load``.
    :return: an instance of ``cls``.
    """
    dump_args_ = dump_args or []
    dump_kwargs_ = dump_kwargs or {}
    dumped = dump(obj, dump_cls, *dump_args_, **dump_kwargs_)
    mapper_ = mapper or (lambda x: x)
    dumped_mapped = mapper_(dumped)
    return load(dumped_mapped, cls, **kwargs)
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
Exemple #6
0
def _load_hashed_keys(obj: dict, cls: type, cls_args: tuple,
                      **kwargs) -> Tuple[dict, bool]:
    # Load any hashed keys and return a copy of the given obj if any hashed
    # keys are unpacked.
    result = obj

    stored_keys = set(obj.get('-keys', set()))
    if stored_keys:
        # Apparently, there are stored hashed keys, we need to unpack them.
        if len(cls_args) != 2:
            raise DeserializationError(
                'A detailed type is needed for cls of '
                'the form Dict[<type>, <type>] to '
                'deserialize a dict with hashed keys.', obj, cls)
        result = {**obj}
        key_type = cls_args[0]
        for key in stored_keys:
            # Get the original (unhashed) key and load it.
            original_key = result['-keys'][key]
            loaded_key = load(original_key, cls=key_type, **kwargs)

            # Replace the hashed key by the loaded key entirely.
            result[loaded_key] = result[key]
            del result['-keys'][key]
            del result[key]

        del result['-keys']
    return result, len(stored_keys) > 0
Exemple #7
0
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 _set_remaining_attrs(instance,
                         remaining_attrs,
                         attr_getters,
                         **kwargs):
    # Set any remaining attributes on the newly created instance.
    attr_getters = attr_getters or {}
    for attr_name in remaining_attrs:
        annotations = get_type_hints(instance.__class__)
        attr_type = annotations.get(attr_name)

        if isinstance(remaining_attrs[attr_name], dict) \
                and '-keys' in remaining_attrs[attr_name] \
                and not attr_type:
            fork_inst = kwargs['fork_inst']
            fork_inst._warn('A dict with -keys was detected without a type '
                            'hint for attribute `{}`. This probably means '
                            'that you did not provide an annotation in your '
                            'class (ending up in __annotations__).'
                            .format(attr_name), 'hashed-keys-without-hint')
        attr_type = attr_type or type(remaining_attrs[attr_name])

        loaded_attr = load(remaining_attrs[attr_name], attr_type, **kwargs)
        try:
            setattr(instance, attr_name, loaded_attr)
        except AttributeError:
            pass  # This is raised when a @property does not have a setter.
    for attr_name, getter in attr_getters.items():
        setattr(instance, attr_name, getter())
def default_complex_deserializer(obj: Dict[str, float],
                                 cls: type = complex,
                                 **kwargs) -> complex:
    """
    Deserialize a dictionary with 'real' and 'imag' keys to a complex number.
    :param obj: the dict that is to be deserialized.
    :param cls: not used.
    :param kwargs: not used.
    :return: an instance of ``complex``.
    """
    try:
        clean_obj = load({
            'real': obj['real'],
            'imag': obj['imag']
        },
                         cls=Dict[str, float])
        return complex(clean_obj['real'], clean_obj['imag'])
    except KeyError as err:
        raise AttributeError("Cannot deserialize {} to a complex number, "
                             "does not contain key '{}'".format(
                                 obj, err.args[0]))
    except DeserializationError as err:
        raise AttributeError("Cannot deserialize {} to a complex number, "
                             "cannot cast value {} to float".format(
                                 obj, err.source))
Exemple #10
0
def _deserialize(obj: dict, cls_args: tuple, key_transformer: Callable[[str],
                                                                       str],
                 keys_were_hashed: bool, kwargs: dict) -> dict:
    key_transformer = key_transformer or (lambda key: key)
    key_func = key_transformer
    kwargs_ = {**kwargs, 'key_transformer': key_transformer}

    if len(cls_args) == 2:
        cls_k, cls_v = cls_args
        kwargs_['cls'] = cls_v
        if not keys_were_hashed:
            # In case of cls is something like Dict[<key>, <value>], we need to
            # ensure that the keys in the result are <key>. If the keys were
            # hashed though, they have been loaded already.
            kwargs_k = {**kwargs, 'cls': cls_k}
            key_func = lambda key: load(key_transformer(key), **kwargs_k)
    return {key_func(key): load(obj[key], **kwargs_) for key in obj}
 def load(cls: type, json_obj: object, **kwargs) -> object:
     """
     See ``jsons.load``.
     :param kwargs: the keyword args are passed on to the serializer
     function.
     :param json_obj: the object that is loaded into an instance of `cls`.
     :return: this instance in a JSON representation (dict).
     """
     return load(json_obj, cls, fork_inst=cls, **kwargs)
def default_timezone_deserializer(obj: dict,
                                  cls: type = timezone,
                                  **kwargs) -> timezone:
    """
    Deserialize a dict to a timezone instance.
    :param obj: the dict that is to be deserialized.
    :param cls: not used.
    :param kwargs: not used.
    :return: a ``datetime.timezone`` instance.
    """
    return timezone(load(obj['offset'], timedelta), obj['name'])
Exemple #13
0
def _fill(
        obj: list,
        cls: type,
        result: list,
        start: int,
        end: int,
        tasks: int,
        kwargs: dict):
    # Fill result with the loaded objects of obj within the range start - end.
    for i_ in range(start, end):
        loaded = load(obj[i_], cls, tasks=tasks, **kwargs)
        result[i_] = loaded
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
Exemple #15
0
def _set_remaining_attrs(instance,
                         remaining_attrs,
                         attr_getters=None,
                         **kwargs):
    # Set any remaining attributes on the newly created instance.
    attr_getters = attr_getters or {}
    for attr_name in remaining_attrs:
        loaded_attr = load(remaining_attrs[attr_name],
                           type(remaining_attrs[attr_name]), **kwargs)
        try:
            setattr(instance, attr_name, loaded_attr)
        except AttributeError:
            pass  # This is raised when a @property does not have a setter.
    for attr_name, getter in attr_getters.items():
        setattr(instance, attr_name, getter())
def default_list_deserializer(obj: list, cls: type = None, **kwargs) -> list:
    """
    Deserialize a list by deserializing all items of that list.
    :param obj: the list that needs deserializing.
    :param cls: the type optionally with a generic (e.g. List[str]).
    :param kwargs: any keyword arguments.
    :return: a deserialized list instance.
    """
    cls_ = None
    kwargs_ = {**kwargs}
    if cls and hasattr(cls, '__args__'):
        cls_ = cls.__args__[0]
        # Mark the cls as 'inferred' so that later it is known where cls came
        # from and the precedence of classes can be determined.
        kwargs_['_inferred_cls'] = True
    return [load(x, cls_, **kwargs_) for x in obj]
Exemple #17
0
def _do_load(obj: list, cls: type, warn_on_fail: bool,
             fork_inst: Type[StateHolder], kwargs) -> list:
    result = []
    for index, elem in enumerate(obj):
        try:
            result.append(load(elem, cls=cls, tasks=1, **kwargs))
        except DeserializationError as err:
            new_msg = ('Could not deserialize element at index %s. %s' %
                       (index, err.message))
            if warn_on_fail:
                fork_inst._warn(new_msg)
            else:
                new_err = DeserializationError(new_msg, err.source, err.target)
                raise new_err from err

    return result
def default_string_deserializer(obj: str,
                                cls: Optional[type] = None,
                                **kwargs) -> object:
    """
    Deserialize a string. If the given ``obj`` can be parsed to a date, a
    ``datetime`` instance is returned.
    :param obj: the string that is to be deserialized.
    :param cls: not used.
    :param kwargs: any keyword arguments.
    :return: the deserialized obj.
    """
    try:
        result = load(obj, datetime, **kwargs)
    except DeserializationError:
        result = default_primitive_deserializer(obj, str)
    return result
def _get_value_from_obj(obj, cls, sig, sig_key, meta_hints, **kwargs):
    # Obtain the value for the attribute with the given signature from the
    # given obj. Try to obtain the class of this attribute from the meta info
    # or from type hints.
    cls_key = '/{}'.format(sig_key)
    cls_str_from_meta = meta_hints.get(cls_key, None)
    new_hints = meta_hints
    cls_from_meta = None
    if cls_str_from_meta:
        cls_from_meta = get_cls_from_str(
            cls_str_from_meta, obj, kwargs['fork_inst'])
        # Rebuild the class hints: cls_key becomes the new root.
        new_hints = {
            _remove_prefix(cls_key, key): meta_hints[key]
            for key in meta_hints
        }
    cls_ = determine_precedence(cls=cls, cls_from_meta=cls_from_meta,
                                cls_from_type=None, inferred_cls=True)
    value = load(obj[sig_key], cls_, meta_hints=new_hints, **kwargs)
    return value
def default_dict_deserializer(obj: dict,
                              cls: type,
                              *,
                              key_transformer: Optional[Callable[[str],
                                                                 str]] = None,
                              **kwargs) -> dict:
    """
    Deserialize a dict by deserializing all instances of that dict.
    :param obj: the dict that needs deserializing.
    :param key_transformer: a function that transforms the keys to a different
    style (e.g. PascalCase).
    :param cls: not used.
    :param kwargs: any keyword arguments.
    :return: a deserialized dict instance.
    """
    key_transformer = key_transformer or (lambda key: key)
    kwargs_ = {**{'key_transformer': key_transformer}, **kwargs}
    if hasattr(cls, '__args__') and len(cls.__args__) > 1:
        sub_cls = cls.__args__[1]
        kwargs_['cls'] = sub_cls
    return {key_transformer(key): load(obj[key], **kwargs_) for key in obj}
def default_defaultdict_deserializer(obj: dict,
                                     cls: type,
                                     *,
                                     key_transformer: Optional[Callable[
                                         [str], str]] = None,
                                     **kwargs) -> dict:
    """
    Deserialize a defaultdict.
    :param obj: the dict that needs deserializing.
    :param key_transformer: a function that transforms the keys to a different
    style (e.g. PascalCase).
    :param cls: not used.
    :param kwargs: any keyword arguments.
    :return: a deserialized defaultdict instance.
    """
    args = get_args(cls)
    default_factory = None
    cls_ = Dict
    if args:
        key, value = get_args(cls)
        cls_ = Dict[key, value]
        default_factory = value
    loaded = load(obj, cls_, key_transformer=key_transformer, **kwargs)
    return defaultdict(default_factory, loaded)
Exemple #22
0
def _single_task(
        # Load the elements of the list in a single task.
        obj: list,
        cls: type,
        **kwargs):
    return [load(x, cls, tasks=1, **kwargs) for x in obj]
 def _wrapper(cls_, inst, **kwargs_):
     return load(inst, cls_, **{**kwargs_, **kwargs})