comment here

This commit is contained in:
ton
2023-03-18 20:03:34 +07:00
commit 4553a0a589
14513 changed files with 2685043 additions and 0 deletions

View File

@@ -0,0 +1,6 @@
"""
This package contains default deserializers. You can override the
deserialization process of a particular type as follows:
``jsons.set_deserializer(custom_deserializer, SomeClass)``
"""

View File

@@ -0,0 +1,28 @@
from typing import Dict
from jsons._load_impl import load
from jsons.exceptions import DeserializationError
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])) from err
except DeserializationError as err:
raise AttributeError("Cannot deserialize {} to a complex number, "
"cannot cast value {} to float"
.format(obj, err.source)) from err

View File

@@ -0,0 +1,16 @@
from datetime import date
from jsons._datetime_impl import get_datetime_inst, RFC3339_DATE_PATTERN
def default_date_deserializer(obj: str,
cls: type = date,
**kwargs) -> date:
"""
Deserialize a string with an RFC3339 pattern to a date instance.
:param obj: the string that is to be deserialized.
:param cls: not used.
:param kwargs: not used.
:return: a ``datetime.date`` instance.
"""
return get_datetime_inst(obj, RFC3339_DATE_PATTERN).date()

View File

@@ -0,0 +1,24 @@
import datetime
import re
from jsons._datetime_impl import get_datetime_inst, RFC3339_DATETIME_PATTERN
def default_datetime_deserializer(obj: str,
cls: type = datetime,
**kwargs) -> datetime:
"""
Deserialize a string with an RFC3339 pattern to a datetime instance.
:param obj: the string that is to be deserialized.
:param cls: not used.
:param kwargs: not used.
:return: a ``datetime.datetime`` instance.
"""
pattern = RFC3339_DATETIME_PATTERN
if '.' in obj:
pattern += '.%f'
# strptime allows a fraction of length 6, so trip the rest (if exists).
regex_pattern = re.compile(r'(\.[0-9]+)')
frac = regex_pattern.search(obj).group()
obj = obj.replace(frac, frac[0:7])
return get_datetime_inst(obj, pattern)

View File

@@ -0,0 +1,16 @@
from decimal import Decimal
from typing import Optional, Union
def default_decimal_deserializer(obj: Union[str, float, int],
cls: Optional[type] = None,
**kwargs) -> Decimal:
"""
Deserialize a Decimal. Expects a string representation of a number, or
the number itself as a float or int.
:param obj: the string float or int that is to be deserialized.
:param cls: not used.
:param kwargs: any keyword arguments.
:return: the deserialized obj.
"""
return Decimal(obj)

View File

@@ -0,0 +1,32 @@
from collections import defaultdict
from typing import Optional, Callable, Dict
from typish import get_args
from jsons._load_impl import load
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)

View File

@@ -0,0 +1,87 @@
from typing import Callable, Optional, Tuple
from typish import get_args
from jsons._load_impl import load
from jsons.exceptions import DeserializationError
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.
"""
cls_args = get_args(cls)
obj_, keys_were_hashed = _load_hashed_keys(
obj, cls, cls_args, key_transformer=key_transformer, **kwargs)
return _deserialize(obj_, cls_args, key_transformer, keys_were_hashed, kwargs)
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 _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
}

View File

@@ -0,0 +1,32 @@
from enum import EnumMeta
from typing import Optional
def default_enum_deserializer(obj: str,
cls: EnumMeta,
*,
use_enum_name: Optional[bool] = None,
**kwargs) -> object:
"""
Deserialize an enum value to an enum instance. The serialized value can be
either the name or the key of an enum entry. If ``use_enum_name`` is set to
``True``, then the value *must* be the key of the enum entry. If
``use_enum_name`` is set to ``False``, the value *must* be the value of the
enum entry. By default, this deserializer tries both.
:param obj: the serialized enum.
:param cls: the enum class.
:param use_enum_name: determines whether the name or the value of an enum
element should be used.
:param kwargs: not used.
:return: the corresponding enum element instance.
"""
if use_enum_name:
result = cls[obj]
elif use_enum_name is False:
result = cls(obj)
else: # use_enum_name is None
try:
result = cls[obj]
except KeyError:
result = cls(obj) # May raise a ValueError (which is expected).
return result

View File

@@ -0,0 +1,30 @@
from collections.abc import Mapping, Iterable
from typing import Iterable as IterableType
from jsons._compatibility_impl import get_naked_class
from jsons.deserializers.default_list import default_list_deserializer
def default_iterable_deserializer(
obj: list,
cls: type,
**kwargs) -> Iterable:
"""
Deserialize a (JSON) list into an ``Iterable`` by deserializing all items
of that list. The given obj is assumed to be homogeneous; if the list has a
generic type (e.g. Set[datetime]) then it is assumed that all elements can
be deserialized to that type.
:param obj: The list that needs deserializing to an ``Iterable``.
:param cls: The type, optionally with a generic (e.g. Deque[str]).
:param kwargs: Any keyword arguments.
:return: A deserialized ``Iterable`` (e.g. ``set``) instance.
"""
cls_ = Mapping
if hasattr(cls, '__args__'):
cls_ = IterableType[cls.__args__]
list_ = default_list_deserializer(obj, cls_, **kwargs)
result = list_
naked_cls = get_naked_class(cls)
if not isinstance(result, naked_cls):
result = naked_cls(list_)
return result

View File

@@ -0,0 +1,69 @@
from multiprocessing import Process
from typing import Type
from typish import get_args
from jsons._common_impl import StateHolder
from jsons._load_impl import load
from jsons._multitasking import multi_task
from jsons.exceptions import JsonsError, DeserializationError
def default_list_deserializer(
obj: list,
cls: type = None,
*,
warn_on_fail: bool = False,
tasks: int = 1,
task_type: type = Process,
fork_inst: Type[StateHolder] = StateHolder,
**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 warn_on_fail: if ``True``, will warn upon any failure and continue.
:param tasks: the allowed number of tasks (threads or processes).
:param task_type: the type that is used for multitasking.
:param fork_inst: if given, it uses this fork of ``JsonSerializable``.
: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 = _do_load(obj, cls_, warn_on_fail, fork_inst, kwargs_)
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 _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, fork_inst=fork_inst, **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, 'element-not-deserialized')
else:
new_err = DeserializationError(new_msg, err.source, err.target)
raise new_err from err
return result

View File

@@ -0,0 +1,27 @@
from collections.abc import Mapping
from typing import Mapping as MappingType
from typish import get_args, get_origin
from jsons.deserializers.default_dict import default_dict_deserializer
def default_mapping_deserializer(obj: dict, cls: type, **kwargs) -> Mapping:
"""
Deserialize a (JSON) dict into a mapping by deserializing all items of that
dict.
:param obj: the dict that needs deserializing.
:param cls: the type, optionally with a generic (e.g. Set[str]).
:param kwargs: any keyword arguments.
:return: a deserialized set instance.
"""
cls_ = Mapping
cls_args = get_args(cls)
if cls_args:
cls_ = MappingType[cls_args]
dict_ = default_dict_deserializer(obj, cls_, **kwargs)
result = dict_
# Strip any generics from cls to allow for an instance check.
if not isinstance(result, get_origin(cls)):
result = cls(dict_)
return result

View File

@@ -0,0 +1,19 @@
from typing import Optional
from jsons.exceptions import DeserializationError
def default_nonetype_deserializer(obj: object,
cls: Optional[type] = None,
**kwargs) -> object:
"""
Deserialize a ``NoneType``.
:param obj: the value that is to be deserialized.
:param cls: not used.
:param kwargs: not used.
:return: ``obj``.
"""
if obj is not None:
raise DeserializationError('Cannot deserialize {} as NoneType'
.format(obj), source=obj, target=cls)
return obj

View File

@@ -0,0 +1,200 @@
import inspect
from typing import Optional, Callable, Tuple
from jsons._cache import cached
from jsons._common_impl import (
get_class_name,
META_ATTR,
get_cls_from_str,
determine_precedence,
can_match_with_none
)
from jsons._compatibility_impl import get_type_hints
from jsons._load_impl import load
from jsons.exceptions import SignatureMismatchError, UnfulfilledArgumentError
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``.
"""
obj, kwargs = _check_and_transform_keys(obj, key_transformer, **kwargs)
kwargs['strict'] = strict
constructor_args = _get_constructor_args(obj, cls, **kwargs)
remaining_attrs = _get_remaining_args(obj, cls, constructor_args,
strict, kwargs['fork_inst'])
instance = cls(**constructor_args)
_set_remaining_attrs(instance, remaining_attrs, **kwargs)
return instance
def _get_constructor_args(
obj,
cls,
meta_hints,
attr_getters=None,
**kwargs) -> dict:
# Loop through the signature of cls: the type we try to deserialize to. For
# every required parameter, we try to get the corresponding value from
# json_obj.
signature_parameters = _get_signature(cls)
hints = get_type_hints(cls.__init__, fallback_ns=cls.__module__)
attr_getters = dict(**(attr_getters or {}))
result = {}
for sig_key, sig in signature_parameters.items():
if sig_key != 'self':
key, value = _get_value_for_attr(obj=obj,
orig_cls=cls,
meta_hints=meta_hints,
attr_getters=attr_getters,
sig_key=sig_key,
cls=hints.get(sig_key, None),
sig=sig,
**kwargs)
if key:
result[key] = value
return result
@cached
def _get_signature(cls):
return inspect.signature(cls.__init__).parameters
def _get_value_for_attr(
obj,
cls,
orig_cls,
sig_key,
sig,
meta_hints,
attr_getters,
**kwargs):
# Find a value for the attribute (with signature sig_key).
if obj and sig_key in obj:
# This argument is in obj.
result = sig_key, _get_value_from_obj(obj, cls, sig, sig_key,
meta_hints, **kwargs)
elif sig_key in attr_getters:
# There exists an attr_getter for this argument.
attr_getter = attr_getters.pop(sig_key)
result = sig_key, attr_getter()
elif sig.default != inspect.Parameter.empty:
# There is a default value for this argument.
result = sig_key, sig.default
elif sig.kind in (inspect.Parameter.VAR_POSITIONAL,
inspect.Parameter.VAR_KEYWORD):
# This argument is either *args or **kwargs.
result = None, None
elif can_match_with_none(cls):
# It is fine that there is no value.
result = sig_key, None
else:
raise UnfulfilledArgumentError(
'No value found for "{}".'.format(sig_key), sig_key, obj, orig_cls)
return result
def _remove_prefix(prefix: str, s: str) -> str:
if s.startswith(prefix):
return s[len(prefix):] or '/' # Special case: map the empty string to '/'
return s
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 _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 _check_and_transform_keys(obj: dict,
key_transformer: Optional[Callable[[str], str]],
**kwargs) -> Tuple[dict, dict]:
if key_transformer:
obj = {key_transformer(key): obj[key] for key in obj}
kwargs = {
**kwargs,
'key_transformer': key_transformer
}
return obj, kwargs
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), unexpected_arg))
raise SignatureMismatchError(err_msg, unexpected_arg, obj, cls)
return remaining_attrs

View File

@@ -0,0 +1,12 @@
from pathlib import PurePath
def default_path_deserializer(obj: str, cls: type = PurePath, **kwargs) -> PurePath:
"""
Deserialize a string to a `pathlib.PurePath` object. Since ``pathlib``
implements ``PurePath``, no filename or existence checks are performed.
:param obj: the string to deserialize.
:param kwargs: not used.
:return: a ``str``.
"""
return cls(obj)

View File

@@ -0,0 +1,24 @@
from typing import Optional
from jsons.exceptions import DeserializationError
def default_primitive_deserializer(obj: object,
cls: Optional[type] = None,
**kwargs) -> object:
"""
Deserialize a primitive: it simply returns the given primitive.
:param obj: the value that is to be deserialized.
:param cls: not used.
:param kwargs: not used.
:return: ``obj``.
"""
result = obj
if obj is not None and not isinstance(obj, cls):
try:
result = cls(obj)
except ValueError as err:
raise DeserializationError(
'Could not cast "{}" into "{}"'.format(obj, cls.__name__),
obj, cls) from err
return result

View File

@@ -0,0 +1,27 @@
from datetime import datetime
from typing import Optional
from jsons._load_impl import load
from jsons.deserializers.default_primitive import default_primitive_deserializer
from jsons.exceptions import DeserializationError
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.
"""
target_is_str = cls is str and not kwargs.get('_inferred_cls')
if target_is_str:
return str(obj)
try:
result = load(obj, datetime, **kwargs)
except DeserializationError:
result = default_primitive_deserializer(obj, str)
return result

View File

@@ -0,0 +1,16 @@
from datetime import time
from jsons._datetime_impl import get_datetime_inst, RFC3339_TIME_PATTERN
def default_time_deserializer(obj: str,
cls: type = time,
**kwargs) -> time:
"""
Deserialize a string with an RFC3339 pattern to a time instance.
:param obj: the string that is to be deserialized.
:param cls: not used.
:param kwargs: not used.
:return: a ``datetime.time`` instance.
"""
return get_datetime_inst(obj, RFC3339_TIME_PATTERN).time()

View File

@@ -0,0 +1,14 @@
from datetime import timedelta
def default_timedelta_deserializer(obj: float,
cls: type = float,
**kwargs) -> timedelta:
"""
Deserialize a float to a timedelta instance.
:param obj: the float that is to be deserialized.
:param cls: not used.
:param kwargs: not used.
:return: a ``datetime.timedelta`` instance.
"""
return timedelta(seconds=obj)

View File

@@ -0,0 +1,16 @@
from datetime import timezone, timedelta
from jsons._load_impl import load
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'])

View File

@@ -0,0 +1,88 @@
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

View File

@@ -0,0 +1,30 @@
from typing import Union
from jsons._common_impl import get_class_name
from jsons._compatibility_impl import get_union_params
from jsons._load_impl import load
from jsons.exceptions import JsonsError, DeserializationError
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:
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).__name__, args_msg))
raise DeserializationError(err_msg, obj, cls)

View File

@@ -0,0 +1,16 @@
from typing import Optional
from uuid import UUID
def default_uuid_deserializer(obj: str,
cls: Optional[type] = None,
**kwargs) -> UUID:
"""
Deserialize a UUID. Expected format for string is specified in RFC 4122.
e.g. '12345678-1234-1234-1234-123456789abc'
:param obj: the string that is to be deserialized.
:param cls: not used.
:param kwargs: any keyword arguments.
:return: the deserialized obj.
"""
return UUID(obj)

View File

@@ -0,0 +1,14 @@
try:
from zoneinfo import ZoneInfo
def default_zone_info_deserializer(obj: ZoneInfo, *_, **__) -> ZoneInfo:
"""
Deserialize a ZoneInfo.
:param obj: a serialized ZoneInfo object.
:return: an instance of ZoneInfo.
"""
return ZoneInfo(obj['key'])
except ImportError:
default_zone_info_deserializer = None