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_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 dump(self, **kwargs) -> object: """ See ``jsons.dump``. :param kwargs: the keyword args are passed on to the serializer function. :return: this instance in a JSON representation (dict). """ return dump(self, fork_inst=self.__class__, **kwargs)
def default_dict_serializer(obj: dict, cls: Optional[type] = None, *, strict: bool = False, strip_nulls: bool = False, key_transformer: Optional[Callable[[str], str]] = None, types: Optional[Dict[str, type]] = None, **kwargs) -> dict: """ Serialize the given ``obj`` to a dict of serialized objects. :param obj: the dict that is to be serialized. :param cls: not used. :param strict: if ``True`` the serialization will raise upon any the failure of any attribute. Otherwise it continues with a warning. :param strict: a bool to determine if the serializer should be strict (i.e. only dumping stuff that is known to ``cls``). :param strip_nulls: if ``True`` the resulting dict will not contain null values. :param key_transformer: a function that will be applied to all keys in the resulting dict. :param types: a ``dict`` with attribute names (keys) and their types (values). :param kwargs: any keyword arguments that may be given to the serialization process. :return: a dict of which all elements are serialized. """ result = dict() types = types or dict() for key in obj: obj_ = obj[key] cls_ = types.get(key, None) # If key is not a valid json type, use the hash as key and store the # original key in a separate section. dict_and_key = _store_and_hash(result, key, key_transformer=key_transformer, strip_nulls=strip_nulls, strict=strict, types=types, **kwargs) if dict_and_key: result, key = dict_and_key dumped_elem = dump(obj_, cls=cls_, key_transformer=key_transformer, strip_nulls=strip_nulls, strict=strict, **kwargs) if not (strip_nulls and dumped_elem is None): if key_transformer: key = key_transformer(key) result[key] = dumped_elem return result
def default_timezone_serializer(obj: timezone, **kwargs) -> dict: """ Serialize the given timezone instance to a dict holding the total seconds. :param obj: the timezone instance that is to be serialized. :param kwargs: not used. :return: ``timezone`` as a dict. """ name = obj.tzname(None) offset = dump(obj.utcoffset(None), **kwargs) return {'name': name, 'offset': offset}
def default_dict_serializer(obj: dict, cls: Optional[type] = None, *, strict: bool = False, strip_nulls: bool = False, key_transformer: Optional[Callable[[str], str]] = None, **kwargs) -> dict: """ Serialize the given ``obj`` to a dict of serialized objects. :param obj: the dict that is to be serialized. :param cls: the type of ``obj``; ``obj`` is dumped as if of that type. :param strict: if ``True`` the serialization will raise upon any the failure of any attribute. Otherwise it continues with a warning. :param strip_nulls: if ``True`` the resulting dict will not contain null values. :param key_transformer: a function that will be applied to all keys in the resulting dict. :param kwargs: any keyword arguments that may be given to the serialization process. :return: a dict of which all elements are serialized. """ result = dict() fork_inst = kwargs['fork_inst'] for key in obj: dumped_elem = None try: dumped_elem = dump(obj[key], key_transformer=key_transformer, strip_nulls=strip_nulls, **kwargs) _store_cls_info(dumped_elem, key, obj, **kwargs) except RecursionDetectedError: fork_inst._warn( 'Recursive structure detected in attribute "{}" ' 'of object of type "{}", ignoring the attribute.'.format( key, get_class_name(cls))) except SerializationError as err: if strict: raise else: fork_inst._warn('Failed to dump attribute "{}" of object of ' 'type "{}". Reason: {}. Ignoring the ' 'attribute.'.format(key, get_class_name(cls), err.message)) break if not (strip_nulls and dumped_elem is None): if key_transformer: key = key_transformer(key) result[key] = dumped_elem return result
def default_iterable_serializer(obj: Iterable, **kwargs) -> list: """ Serialize the given ``obj`` to a list of serialized objects. :param obj: the iterable that is to be serialized. :param kwargs: any keyword arguments that may be given to the serialization process. :return: a list of which all elements are serialized. """ # The meta kwarg store_cls is filtered out, because an iterable should have # its own -meta attribute. kwargs_ = {key: kwargs[key] for key in kwargs if key != '_store_cls'} return [dump(elem, **kwargs_) for elem in obj]
def default_namedtuple_serializer(obj: tuple, **kwargs) -> dict: """ Serialize the given ``obj`` to a dict of serialized objects. :param obj: the named tuple that is to be serialized. :param kwargs: any keyword arguments that may be given to the serialization process. :return: a dict of which all elements are serialized. """ result = { field_name: dump(getattr(obj, field_name), **kwargs) for field_name in obj._fields } return result
def _store_and_hash(obj: dict, key: object, **kwargs) -> Optional[Tuple[dict, int]]: # Store the given key in the given dict under a special section if that # key is not a valid json key. Return a hash of that key. result = None if not any(issubclass(type(key), json_key) for json_key in JSON_KEYS): # Apparently key is not a valid json key value. Since it got into # a Python dict without crashing, it must be hashable. obj_ = {**obj} key_hash = hash(key) if '-keys' not in obj_: obj_['-keys'] = {} dumped_key = dump(key, **kwargs) obj_['-keys'][key_hash] = dumped_key result = (obj_, key_hash) return result
def _do_serialize( obj: object, cls: type, attributes: Dict[str, Optional[type]], kwargs: dict, key_transformer: Optional[Callable[[str], str]] = None, strip_nulls: bool = False, strip_privates: bool = False, strip_properties: bool = False, strip_class_variables: bool = False, strip_attr: Union[str, MutableSequence[str], Tuple[str]] = None, strict: bool = False, fork_inst: Optional[type] = StateHolder) -> Dict[str, object]: result = dict() for attr_name, cls_ in attributes.items(): attr = obj.__getattribute__(attr_name) dumped_elem = None try: dumped_elem = dump(attr, cls=cls_, key_transformer=key_transformer, strip_nulls=strip_nulls, strip_privates=strip_privates, strip_properties=strip_properties, strip_class_variables=strip_class_variables, strip_attr=strip_attr, **kwargs) _store_cls_info(dumped_elem, attr, kwargs) except RecursionDetectedError: fork_inst._warn( 'Recursive structure detected in attribute "{}" ' 'of object of type "{}", ignoring the attribute.'.format( attr_name, get_class_name(cls))) except SerializationError as err: if strict: raise else: fork_inst._warn('Failed to dump attribute "{}" of object of ' 'type "{}". Reason: {}. Ignoring the ' 'attribute.'.format(attr, get_class_name(cls), err.message)) break _add_dumped_elem(result, attr_name, dumped_elem, strip_nulls, key_transformer) return result
def _store_and_hash( obj: dict, key: object, **kwargs ) -> Optional[Tuple[dict, int]]: # Store the given key in the given dict under a special section if that # key is not a valid json key. Return a hash of that key. result = None if not _is_valid_json_key(key): # First try to dump the key, that might be enough already. dumped_key = dump(key, **kwargs) result = obj, dumped_key if not _is_valid_json_key(dumped_key): # Apparently, this was not enough; the key is still not "jsonable". key_hash = hash(key) obj_ = {**obj} obj_.setdefault('-keys', {}) obj_['-keys'][key_hash] = dumped_key result = obj_, key_hash return result
def default_iterable_serializer(obj: Iterable, cls: type = None, *, strict: bool = False, tasks: int = 1, task_type: type = Process, **kwargs) -> list: """ Serialize the given ``obj`` to a list of serialized objects. :param obj: the iterable that is to be serialized. :param cls: the (subscripted) type of the iterable. :param strict: a bool to determine if the serializer should be strict (i.e. only dumping stuff that is known to ``cls``). :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 that may be given to the serialization process. :return: a list of which all elements are serialized. """ # The meta kwarg store_cls is filtered out, because an iterable should have # its own -meta attribute. kwargs_ = {**kwargs, 'strict': strict} kwargs_.pop('_store_cls', None) if strict: cls_ = determine_cls(obj, cls) # cls_ = cls or get_type(obj) # Get the List[T] type from the instance. subclasses = _get_subclasses(obj, cls_) else: subclasses = _get_subclasses(obj, None) if tasks < 2: result = [ dump(elem, cls=subclasses[i], **kwargs_) for i, elem in enumerate(obj) ] else: zipped_objs = list(zip(obj, subclasses)) result = multi_task(do_dump, zipped_objs, tasks, task_type, **kwargs_) return result
def _do_dump(obj_cls_tuple: Tuple[object, type], *args, **kwargs): kwargs_ = {**kwargs} kwargs_['tasks'] = 1 return dump(*obj_cls_tuple, *args, **kwargs_)
def _wrapper(inst, **kwargs_): return dump(inst, **{**kwargs_, **kwargs})