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_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
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
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
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))
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'])
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
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]
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)
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})