from typing import Callable, Optional, Union from typish import get_args from jsons._common_impl import NoneType from jsons._compatibility_impl import (get_type_hints, get_union_params, tuple_with_ellipsis) from jsons._load_impl import load from jsons.exceptions import UnfulfilledArgumentError 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_) 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__'): # It is important to use get_type_hints so that forward references get resolved, # rather than access __annotations__ directly field_types = get_type_hints(cls) 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