Example #1
0
    def test_get_class_name_without__name__(self):
        class Meta(type):
            __name__ = None

        class C(metaclass=Meta):
            pass

        self.assertEqual('C', get_class_name(C))
        self.assertEqual('tests.test_common_impl.C',
                         get_class_name(C, fully_qualified=True))
Example #2
0
    def test_get_class_name_without__name__(self):
        class Meta(type):
            __name__ = None

        class C(metaclass=Meta):
            pass

        self.assertEqual('C', get_class_name(C))
        self.assertEqual('{}.C'.format(__name__),
                         get_class_name(C, fully_qualified=True))
Example #3
0
def _get_lizer(cls: type, lizers: Dict[str, callable],
               classes_lizers: list) -> callable:
    cls_name = get_class_name(cls, str.lower)
    lizer = lizers.get(cls_name, None)
    if not lizer:
        parents = get_parents(cls, classes_lizers)
        if parents:
            pname = get_class_name(parents[0], str.lower)
            lizer = lizers[pname]
    return lizer
Example #4
0
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 set_serializer(func: callable,
                   cls: type,
                   high_prio: bool = True,
                   fork_inst: type = StateHolder) -> None:
    """
    Set a serializer function for the given type. You may override the default
    behavior of ``jsons.load`` by setting a custom serializer.

    The ``func`` argument must take one argument (i.e. the object that is to be
    serialized) and also a ``kwargs`` parameter. For example:

    >>> def func(obj, **kwargs):
    ...    return dict()

    You may ask additional arguments between ``cls`` and ``kwargs``.

    :param func: the serializer function.
    :param cls: the type this serializer can handle.
    :param high_prio: determines the order in which is looked for the callable.
    :param fork_inst: if given, it uses this fork of ``JsonSerializable``.
    :return: None.
    """
    if cls:
        index = 0 if high_prio else len(fork_inst._classes_serializers)
        fork_inst._classes_serializers.insert(index, cls)
        cls_name = get_class_name(cls, fork_inst=fork_inst)
        fork_inst._serializers[cls_name.lower()] = func
    else:
        fork_inst._serializers['nonetype'] = func
Example #6
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)
Example #7
0
def fork(
        fork_inst: Type[T] = StateHolder,
        name: Optional[str] = None) -> Type[T]:
    """
    Fork from the given ``StateHolder`` to create a separate "branch" of
    serializers and deserializers.
    :param fork_inst: The ``StateHolder`` on which the new fork is based.
    :param name: The ``__name__`` of the new ``type``.
    :return: A "fork inst" that can be used to separately store
    (de)serializers from the regular ``StateHolder``.
    """
    fork_inst._fork_counter += 1
    if name:
        class_name = name
    else:
        class_name = '{}_fork{}'.format(
            get_class_name(fork_inst),
            fork_inst._fork_counter
        )
    result = type(class_name, (fork_inst,), {})
    result._classes_serializers = fork_inst._classes_serializers.copy()
    result._classes_deserializers = fork_inst._classes_deserializers.copy()
    result._serializers = fork_inst._serializers.copy()
    result._deserializers = fork_inst._deserializers.copy()
    result._fork_counter = 0
    return result
Example #8
0
def set_deserializer(
        func: callable,
        cls: Union[type, Sequence[type]],
        high_prio: bool = True,
        fork_inst: type = StateHolder) -> None:
    """
    Set a deserializer function for the given type. You may override the
    default behavior of ``jsons.dump`` by setting a custom deserializer.

    The ``func`` argument must take two arguments (i.e. the dict containing the
    serialized values and the type that the values should be deserialized into)
    and also a ``kwargs`` parameter. For example:

    >>> def func(dict_, cls, **kwargs):
    ...    return cls()

    You may ask additional arguments between ``cls`` and ``kwargs``.

    :param func: the deserializer function.
    :param cls: the type or sequence of types this serializer can handle.
    :param high_prio: determines the order in which is looked for the callable.
    :param fork_inst: if given, it uses this fork of ``JsonSerializable``.
    :return: None.
    """
    if isinstance(cls, Sequence):
        for cls_ in cls:
            set_deserializer(func, cls_, high_prio, fork_inst)
    elif cls:
        index = 0 if high_prio else len(fork_inst._classes_deserializers)
        fork_inst._classes_deserializers.insert(index, cls)
        cls_name = get_class_name(cls, fully_qualified=True)
        fork_inst._deserializers[cls_name.lower()] = func
    else:
        fork_inst._deserializers['nonetype'] = func
Example #9
0
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)
Example #10
0
def dump(obj: object,
         cls: Optional[type] = None,
         fork_inst: Optional[type] = _StateHolder,
         **kwargs) -> object:
    """
    Serialize the given ``obj`` to a JSON equivalent type (e.g. dict, list,
    int, ...).

    The way objects are serialized can be finetuned by setting serializer
    functions for the specific type using ``set_serializer``.

    You can also provide ``cls`` to specify that ``obj`` needs to be serialized
    as if it was of type ``cls`` (meaning to only take into account attributes
    from ``cls``). The type ``cls`` must have a ``__slots__`` defined. Any type
    will do, but in most cases you may want ``cls`` to be a base class of
    ``obj``.
    :param obj: a Python instance of any sort.
    :param cls: if given, ``obj`` will be dumped as if it is of type ``type``.
    :param fork_inst: if given, it uses this fork of ``JsonSerializable``.
    :param kwargs: the keyword args are passed on to the serializer function.
    :return: the serialized obj as a JSON type.
    """
    if cls and not hasattr(cls, '__slots__'):
        raise SerializationError('Invalid type: "{}". Only types that have a '
                                 '__slots__ defined are allowed when '
                                 'providing "cls".'.format(
                                     get_class_name(cls)))
    cls_ = cls or obj.__class__
    serializer = _get_serializer(cls_, fork_inst)
    kwargs_ = {'fork_inst': fork_inst, **kwargs}
    try:
        return serializer(obj, cls=cls, **kwargs_)
    except Exception as err:
        raise SerializationError(str(err))
Example #11
0
def _check_and_get_cls_and_meta_hints(
        json_obj: object, cls: type, fork_inst: type,
        inferred_cls: bool) -> Tuple[type, Optional[dict]]:
    # Check if json_obj is of a valid type and return the cls.
    if type(json_obj) not in VALID_TYPES:
        invalid_type = get_class_name(type(json_obj), fully_qualified=True)
        valid_types = [
            get_class_name(typ, fully_qualified=True) for typ in VALID_TYPES
        ]
        msg = ('Invalid type: "{}", only arguments of the following types are '
               'allowed: {}'.format(invalid_type, ", ".join(valid_types)))
        raise DeserializationError(msg, json_obj, cls)

    cls_from_meta, meta = get_cls_and_meta(json_obj, fork_inst)
    meta_hints = meta.get('classes', {}) if meta else {}
    return determine_precedence(cls, cls_from_meta, type(json_obj),
                                inferred_cls), meta_hints
Example #12
0
def _store_cls_info(result: object, original_obj: dict, kwargs):
    if kwargs.get('_store_cls', None) and isinstance(result, dict):
        cls = get_type(original_obj)
        if cls.__module__ == 'typing':
            cls_name = repr(cls)
        else:
            cls_name = get_class_name(cls, fully_qualified=True)
        result['-cls'] = cls_name
Example #13
0
def _get_lizer_by_parents(cls: type, lizers: Dict[str, callable],
                          classes_lizers: list, fork_inst: type) -> callable:
    result = None
    parents = _get_parents(cls, classes_lizers)
    if parents:
        pname = get_class_name(parents[0], str.lower, fully_qualified=True)
        result = lizers[pname]
    return result
def _check_for_none(json_obj: object, cls: type):
    # Check if the json_obj is None and whether or not that is fine.
    if json_obj is None and not can_match_with_none(cls):
        cls_name = get_class_name(cls).lower()
        raise DeserializationError(
            message='NoneType cannot be deserialized into {}'.format(cls_name),
            source=json_obj,
            target=cls)
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
Example #16
0
def _store_cls_info(result: object, attr: str, original_obj: dict, **kwargs):
    if isinstance(result, dict) and kwargs.get('_store_cls'):
        cls = get_type(original_obj[attr])
        if cls.__module__ == 'typing':
            cls_name = repr(cls)
        else:
            cls_name = get_class_name(cls,
                                      fully_qualified=True,
                                      fork_inst=kwargs['fork_inst'])
        result['-cls'] = cls_name
Example #17
0
def default_object_serializer(
        obj: object,
        key_transformer: Optional[Callable[[str], str]] = None,
        strip_nulls: bool = False,
        strip_privates: bool = False,
        strip_properties: bool = False,
        strip_class_variables: bool = False,
        verbose: Union[Verbosity, bool] = False,
        **kwargs) -> dict:
    """
    Serialize the given ``obj`` to a dict. All values within ``obj`` are also
    serialized. If ``key_transformer`` is given, it will be used to transform
    the casing (e.g. snake_case) to a different format (e.g. camelCase).
    :param obj: the object that is to be serialized.
    :param key_transformer: a function that will be applied to all keys in the
    resulting dict.
    :param strip_nulls: if ``True`` the resulting dict will not contain null
    values.
    :param strip_privates: if ``True`` the resulting dict will not contain
    private attributes (i.e. attributes that start with an underscore).
    :param strip_properties: if ``True`` the resulting dict will not contain
    values from @properties.
    :param strip_class_variables: if ``True`` the resulting dict will not
    contain values from class variables.
    :param verbose: if ``True`` the resulting dict will contain meta
    information (e.g. on how to deserialize).
    :param kwargs: any keyword arguments that are to be passed to the
    serializer functions.
    :return: a Python dict holding the values of ``obj``.
    """
    if obj is None:
        return obj
    cls = kwargs['cls'] or obj.__class__
    obj_dict = _get_dict_from_obj(obj, strip_privates, strip_properties,
                                  strip_class_variables, **kwargs)
    kwargs_ = {**kwargs, 'verbose': verbose}
    verbose = Verbosity.from_value(verbose)
    if Verbosity.WITH_CLASS_INFO in verbose:
        kwargs_['_store_cls'] = True
    result = default_dict_serializer(
        obj_dict,
        key_transformer=key_transformer,
        strip_nulls=strip_nulls,
        strip_privates=strip_privates,
        strip_properties=strip_properties,
        strip_class_variables=strip_class_variables,
        **kwargs_)
    cls_name = get_class_name(cls, fully_qualified=True,
                              fork_inst=kwargs['fork_inst'])
    if kwargs.get('_store_cls'):
        result['-cls'] = cls_name
    else:
        result = _get_dict_with_meta(result, cls_name, verbose,
                                     kwargs['fork_inst'])
    return result
def _check_and_get_cls_and_meta_hints(
        json_obj: object,
        cls: type,
        fork_inst: type) -> Tuple[type, Optional[dict]]:
    # Check if json_obj is of a valid type and return the cls.
    if type(json_obj) not in VALID_TYPES:
        invalid_type = get_class_name(type(json_obj), fork_inst=fork_inst)
        valid_types = [get_class_name(typ, fork_inst=fork_inst)
                       for typ in VALID_TYPES]
        msg = ('Invalid type: "{}", only arguments of the following types are '
               'allowed: {}'.format(invalid_type, ", ".join(valid_types)))
        raise DeserializationError(msg, json_obj, cls)
    if json_obj is None:
        raise DeserializationError('Cannot load None with strict=True',
                                   json_obj, cls)

    cls_from_meta, meta = _get_cls_and_meta(json_obj, fork_inst)
    meta_hints = meta.get('classes', {}) if meta else {}
    # Providing cls takes precedence over cls_from_meta.
    return (cls or cls_from_meta or type(json_obj)), meta_hints
Example #19
0
def _get_lizer(cls: type,
               lizers: Dict[str, callable],
               classes_lizers: list,
               fork_inst: type,
               recursive: bool = False) -> callable:
    cls_name = get_class_name(cls, str.lower, fully_qualified=True)
    lizer = (lizers.get(cls_name, None)
             or _get_lizer_by_parents(cls, lizers, classes_lizers, fork_inst))
    if not lizer and not recursive and hasattr(cls, '__supertype__'):
        return _get_lizer(cls.__supertype__, lizers, classes_lizers, fork_inst,
                          True)
    return lizer
Example #20
0
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()
    is_attrs_cls = getattr(cls, '__attrs_attrs__', None) is not None
    make_attributes_public = is_attrs_cls and not strip_privates
    for attr_name, cls_ in attributes.items():
        attr = getattr(obj, attr_name)
        attr_type = cls_ or type(attr)
        announce_class(attr_type, fork_inst=fork_inst)
        serializer = get_serializer(attr_type, fork_inst)
        try:
            dumped_elem = serializer(
                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 Exception as err:
            if strict:
                raise SerializationError(message=err.args[0]) from err
            else:
                fork_inst._warn(
                    'Failed to dump attribute "{}" of object of '
                    'type "{}". Reason: {}. Ignoring the '
                    'attribute.'.format(attr, get_class_name(cls),
                                        err.args[0]),
                    'attribute-not-serialized')
                break

        if make_attributes_public:
            attr_name = attr_name.lstrip('_')
        _add_dumped_elem(result, attr_name, dumped_elem, strip_nulls,
                         key_transformer)
    return result
Example #21
0
def announce_class(cls: type,
                   cls_name: Optional[str] = None,
                   fork_inst: type = StateHolder):
    """
    Announce the given cls to jsons to allow jsons to deserialize a verbose
    dump into that class.
    :param cls: the class that is to be announced.
    :param cls_name: a custom name for that class.
    :param fork_inst: if given, it uses this fork of ``JsonSerializable``.
    :return: None.
    """
    cls_name = cls_name or get_class_name(cls, fully_qualified=True)
    fork_inst._announced_classes[cls] = cls_name
    fork_inst._announced_classes[cls_name] = cls
Example #22
0
def _get_remaining_args(obj: dict, cls: type, constructor_args: dict,
                        strict: bool, fork_inst: type) -> dict:
    # Get the remaining args or raise if strict and the signature is unmatched.
    remaining_attrs = {
        attr_name: obj[attr_name]
        for attr_name in obj
        if attr_name not in constructor_args and attr_name != META_ATTR
    }
    if strict and remaining_attrs:
        unexpected_arg = list(remaining_attrs.keys())[0]
        err_msg = ('Type "{}" does not expect "{}"'.format(
            get_class_name(cls, fork_inst=fork_inst), unexpected_arg))
        raise SignatureMismatchError(err_msg, unexpected_arg, obj, cls)
    return remaining_attrs
Example #23
0
 def fork(cls, name: Optional[str] = None) -> type:
     """
     Create a 'fork' of ``JsonSerializable``: a new ``type`` with a separate
     configuration of serializers and deserializers.
     :param name: the ``__name__`` of the new ``type``.
     :return: a new ``type`` based on ``JsonSerializable``.
     """
     cls._fork_counter += 1
     class_name = name or '{}_fork{}'.format(get_class_name(cls),
                                             cls._fork_counter)
     result = type(class_name, (cls, ), {})
     result._classes_serializers = cls._classes_serializers.copy()
     result._classes_deserializers = cls._classes_deserializers.copy()
     result._serializers = cls._serializers.copy()
     result._deserializers = cls._deserializers.copy()
     result._fork_counter = 0
     return result
def set_validator(func: Callable[[type], bool],
                  cls: Union[type, Sequence[type]],
                  fork_inst: type = StateHolder) -> None:
    """
    Set a validator function for the given ``cls``. The function should accept
    an instance of the type it should validate and must return ``False`` or
    raise any exception in case of a validation failure.
    :param func: the function that takes an instance of type ``cls`` and
    returns a bool (``True`` if the validation was successful).
    :param cls: the type or types that ``func`` is able to validate.
    :param fork_inst: if given, it uses this fork of ``JsonSerializable``.
    :return: None.
    """
    if isinstance(cls, Sequence):
        for cls_ in cls:
            set_validator(func, cls_, fork_inst)
    cls_name = get_class_name(cls, fork_inst=fork_inst, fully_qualified=True)
    fork_inst._validators[cls_name.lower()] = func
    fork_inst._classes_validators.append(cls)
Example #25
0
def _do_load(json_obj: object, deserializer: callable, cls: type,
             initial: bool, **kwargs):
    cls_name = get_class_name(cls, fully_qualified=True)
    if deserializer is None:
        raise DeserializationError(
            'No deserializer for type "{}"'.format(cls_name), json_obj, cls)
    try:
        result = deserializer(json_obj, cls, **kwargs)
        validate(result, cls, kwargs['fork_inst'])
    except Exception as err:
        clear()
        if isinstance(err, JsonsError):
            raise
        message = 'Could not deserialize value "{}" into "{}". {}'.format(
            json_obj, cls_name, err)
        raise DeserializationError(message, json_obj, cls)
    else:
        if initial:
            # Clear all lru caches right before returning the initial call.
            clear()
        return result
Example #26
0
def default_object_deserializer(obj: dict,
                                cls: type,
                                key_transformer: Optional[Callable[
                                    [str], str]] = None,
                                strict: bool = False,
                                **kwargs) -> object:
    """
    Deserialize ``obj`` into an instance of type ``cls``. If ``obj`` contains
    keys with a certain case style (e.g. camelCase) that do not match the style
    of ``cls`` (e.g. snake_case), a key_transformer should be used (e.g.
    KEY_TRANSFORMER_SNAKECASE).
    :param obj: a serialized instance of ``cls``.
    :param cls: the type to which ``obj`` should be deserialized.
    :param key_transformer: a function that transforms the keys in order to
    match the attribute names of ``cls``.
    :param strict: deserialize in strict mode.
    :param kwargs: any keyword arguments that may be passed to the
    deserializers.
    :return: an instance of type ``cls``.
    """
    concat_kwargs = kwargs
    if key_transformer:
        obj = {key_transformer(key): obj[key] for key in obj}
        concat_kwargs = {**kwargs, 'key_transformer': key_transformer}
    concat_kwargs['strict'] = strict
    constructor_args = _get_constructor_args(obj, cls, **concat_kwargs)
    remaining_attrs = {
        attr_name: obj[attr_name]
        for attr_name in obj if attr_name not in constructor_args
    }
    if strict and remaining_attrs:
        unexpected_arg = list(remaining_attrs.keys())[0]
        err_msg = 'Type "{}" does not expect "{}"'.format(
            get_class_name(cls), unexpected_arg)
        raise SignatureMismatchError(err_msg, unexpected_arg, obj, cls)
    instance = cls(**constructor_args)
    _set_remaining_attrs(instance, remaining_attrs, **kwargs)
    return instance
Example #27
0
def default_object_serializer(obj: object,
                              cls: Optional[type] = None,
                              *,
                              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,
                              verbose: Union[Verbosity, bool] = False,
                              strict: bool = False,
                              fork_inst: Optional[type] = StateHolder,
                              **kwargs) -> Optional[dict]:
    """
    Serialize the given ``obj`` to a dict. All values within ``obj`` are also
    serialized. If ``key_transformer`` is given, it will be used to transform
    the casing (e.g. snake_case) to a different format (e.g. camelCase).
    :param obj: the object that is to be serialized.
    :param cls: the type of the object that is to be dumped.
    :param key_transformer: a function that will be applied to all keys in the
    resulting dict.
    :param strip_nulls: if ``True`` the resulting dict will not contain null
    values.
    :param strip_privates: if ``True`` the resulting dict will not contain
    private attributes (i.e. attributes that start with an underscore).
    :param strip_properties: if ``True`` the resulting dict will not contain
    values from @properties.
    :param strip_class_variables: if ``True`` the resulting dict will not
    contain values from class variables.
    :param strip_attr: can be a name or a collection of names of attributes
    that are not to be included in the dump.
    :param verbose: if ``True`` the resulting dict will contain meta
    information (e.g. on how to deserialize).
    :param strict: a bool to determine if the serializer should be strict
    (i.e. only dumping stuff that is known to ``cls``).
    :param fork_inst: if given, it uses this fork of ``JsonSerializable``.
    :param kwargs: any keyword arguments that are to be passed to the
    serializer functions.
    :return: a Python dict holding the values
    of ``obj``.
    """
    strip_attr = _normalize_strip_attr(strip_attr)
    if cls and strict:
        attributes = _get_attributes_from_class(cls, strip_privates,
                                                strip_properties,
                                                strip_class_variables,
                                                strip_attr, strict)
    else:
        attributes = _get_attributes_from_object(obj, strip_privates,
                                                 strip_properties,
                                                 strip_class_variables,
                                                 strip_attr, strict)
        cls = obj.__class__

    verbose = Verbosity.from_value(verbose)
    kwargs_ = {
        **kwargs,
        'fork_inst': fork_inst,
        'verbose': verbose,
        'strict': strict,
        # Set a flag in kwargs to temporarily store -cls:
        '_store_cls': Verbosity.WITH_CLASS_INFO in verbose
    }

    result = _do_serialize(obj=obj,
                           cls=cls,
                           attributes=attributes,
                           kwargs=kwargs_,
                           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,
                           strict=strict,
                           fork_inst=fork_inst)

    cls_name = get_class_name(cls, fully_qualified=True)
    if not kwargs.get('_store_cls'):
        result = _get_dict_with_meta(result, cls_name, verbose, fork_inst)
    return result
Example #28
0
def load(json_obj: object,
         cls: Optional[type] = None,
         strict: bool = False,
         fork_inst: Optional[type] = _StateHolder,
         attr_getters: Optional[Dict[str, Callable[[], object]]] = None,
         **kwargs) -> object:
    """
    Deserialize the given ``json_obj`` to an object of type ``cls``. If the
    contents of ``json_obj`` do not match the interface of ``cls``, a
    DeserializationError is raised.

    If ``json_obj`` contains a value that belongs to a custom class, there must
    be a type hint present for that value in ``cls`` to let this function know
    what type it should deserialize that value to.


    **Example**:

    >>> from typing import List
    >>> import jsons
    >>> class Person:
    ...     # No type hint required for name
    ...     def __init__(self, name):
    ...         self.name = name
    >>> class Family:
    ...     # Person is a custom class, use a type hint
    ...         def __init__(self, persons: List[Person]):
    ...             self.persons = persons
    >>> loaded = jsons.load({'persons': [{'name': 'John'}]}, Family)
    >>> loaded.persons[0].name
    'John'

    If no ``cls`` is given, a dict is simply returned, but contained values
    (e.g. serialized ``datetime`` values) are still deserialized.

    If `strict` mode is off and the type of `json_obj` exactly matches `cls`
    then `json_obj` is simply returned.

    :param json_obj: the dict that is to be deserialized.
    :param cls: a matching class of which an instance should be returned.
    :param strict: a bool to determine if the deserializer should be strict
    (i.e. fail on a partially deserialized `json_obj` or on `None`).
    :param fork_inst: if given, it uses this fork of ``JsonSerializable``.
    :param attr_getters: a ``dict`` that may hold callables that return values
    for certain attributes.
    :param kwargs: the keyword args are passed on to the deserializer function.
    :return: an instance of ``cls`` if given, a dict otherwise.
    """
    if not strict and (json_obj is None or type(json_obj) == cls):
        return json_obj
    if type(json_obj) not in VALID_TYPES:
        raise DeserializationError(
            'Invalid type: "{}", only arguments of the following types are '
            'allowed: {}'.format(
                get_class_name(type(json_obj)),
                ", ".join(get_class_name(typ) for typ in VALID_TYPES)),
            json_obj, cls)
    if json_obj is None:
        raise DeserializationError('Cannot load None with strict=True',
                                   json_obj, cls)
    cls = cls or type(json_obj)
    deserializer = _get_deserializer(cls, fork_inst)
    kwargs_ = {
        'strict': strict,
        'fork_inst': fork_inst,
        'attr_getters': attr_getters,
        **kwargs
    }
    try:
        return deserializer(json_obj, cls, **kwargs_)
    except Exception as err:
        if isinstance(err, JsonsError):
            raise
        raise DeserializationError(str(err), json_obj, cls)
Example #29
0
 def test_get_class_name_of_none(self):
     self.assertEqual('NoneType', get_class_name(None))
Example #30
0
def default_object_serializer(obj: object,
                              cls: Optional[type] = None,
                              *,
                              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,
                              verbose: Union[Verbosity, bool] = False,
                              **kwargs) -> dict:
    """
    Serialize the given ``obj`` to a dict. All values within ``obj`` are also
    serialized. If ``key_transformer`` is given, it will be used to transform
    the casing (e.g. snake_case) to a different format (e.g. camelCase).
    :param obj: the object that is to be serialized.
    :param cls: the type of the object that is to be dumped.
    :param key_transformer: a function that will be applied to all keys in the
    resulting dict.
    :param strip_nulls: if ``True`` the resulting dict will not contain null
    values.
    :param strip_privates: if ``True`` the resulting dict will not contain
    private attributes (i.e. attributes that start with an underscore).
    :param strip_properties: if ``True`` the resulting dict will not contain
    values from @properties.
    :param strip_class_variables: if ``True`` the resulting dict will not
    contain values from class variables.
    :param strip_attr: can be a name or a collection of names of attributes
    that are not to be included in the dump.
    dict will not contain attributes with
    :param verbose: if ``True`` the resulting dict will contain meta
    information (e.g. on how to deserialize).
    :param kwargs: any keyword arguments that are to be passed to the
    serializer functions.
    :return: a Python dict holding the values of ``obj``.
    """
    if obj is None:
        return obj
    strip_attr = strip_attr or []
    if (not isinstance(strip_attr, MutableSequence)
            and not isinstance(strip_attr, tuple)):
        strip_attr = (strip_attr, )
    cls = cls or obj.__class__
    obj_dict = _get_dict_from_obj(obj, cls, strip_privates, strip_properties,
                                  strip_class_variables, strip_attr, **kwargs)
    kwargs_ = {**kwargs, 'verbose': verbose}
    verbose = Verbosity.from_value(verbose)
    if Verbosity.WITH_CLASS_INFO in verbose:
        # Set a flag in kwargs to temporarily store -cls.
        kwargs_['_store_cls'] = True
    result = default_dict_serializer(
        obj_dict,
        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_)
    cls_name = get_class_name(cls,
                              fully_qualified=True,
                              fork_inst=kwargs['fork_inst'])
    if not kwargs.get('_store_cls'):
        result = _get_dict_with_meta(result, cls_name, verbose,
                                     kwargs['fork_inst'])
    return result