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 serializers. You can override the
serialization process of a particular type as follows:
``jsons.set_serializer(custom_serializer, SomeClass)``
"""

View File

@@ -0,0 +1,8 @@
def default_complex_serializer(obj: complex, **_) -> dict:
"""
Serialize a complex as a dict.
:param obj: the complex.
:param _: not used.
:return: a ``dict``.
"""
return {'real': obj.real, 'imag': obj.imag}

View File

@@ -0,0 +1,16 @@
from datetime import date
from jsons._datetime_impl import to_str, RFC3339_DATE_PATTERN
def default_date_serializer(obj: date, **kwargs) -> str:
"""
Serialize the given date instance to a string. It uses the RFC3339
pattern. If date is a localtime, an offset is provided. If date is
in UTC, the result is suffixed with a 'Z'.
:param obj: the date instance that is to be serialized.
:param kwargs: not used.
:return: ``date`` as an RFC3339 string.
"""
return to_str(obj, False, kwargs['fork_inst'],
RFC3339_DATE_PATTERN)

View File

@@ -0,0 +1,22 @@
from datetime import datetime
from typing import Optional
from jsons._datetime_impl import to_str, RFC3339_DATETIME_PATTERN
def default_datetime_serializer(obj: datetime,
*,
strip_microseconds: Optional[bool] = False,
**kwargs) -> str:
"""
Serialize the given datetime instance to a string. It uses the RFC3339
pattern. If datetime is a localtime, an offset is provided. If datetime is
in UTC, the result is suffixed with a 'Z'.
:param obj: the datetime instance that is to be serialized.
:param strip_microseconds: determines whether microseconds should be
omitted.
:param kwargs: not used.
:return: ``datetime`` as an RFC3339 string.
"""
return to_str(obj, strip_microseconds, kwargs['fork_inst'],
RFC3339_DATETIME_PATTERN)

View File

@@ -0,0 +1,11 @@
from decimal import Decimal
def default_decimal_serializer(obj: Decimal, **kwargs) -> str:
"""
Serialize a Decimal.
:param obj: an instance of a Decimal.
:param kwargs: any keyword arguments.
:return: ``obj`` serialized as a string.
"""
return str(obj)

View File

@@ -0,0 +1,85 @@
from typing import Callable, Dict, Optional, Tuple
from jsons._common_impl import JSON_KEYS
from jsons._dump_impl import dump
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 _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 _is_valid_json_key(key: object) -> bool:
return any(issubclass(type(key), json_key) for json_key in JSON_KEYS)

View File

@@ -0,0 +1,18 @@
from enum import EnumMeta
def default_enum_serializer(obj: EnumMeta,
*,
use_enum_name: bool = True,
**_) -> str:
"""
Serialize the given obj. By default, the name of the enum element is
returned.
:param obj: an instance of an enum.
:param use_enum_name: determines whether the name or the value should be
used for serialization.
:param _: not used.
:return: ``obj`` serialized as a string.
"""
attr = 'name' if use_enum_name else 'value'
return getattr(obj, attr)

View File

@@ -0,0 +1,81 @@
from collections.abc import Iterable
from multiprocessing import Process
from typing import Tuple, Optional
from typish import get_args, get_type
from jsons._dump_impl import dump
from jsons._multitasking import multi_task
from jsons.exceptions import SerializationError
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)
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 _get_subclasses(obj: Iterable, cls: type = None) -> Tuple[type, ...]:
subclasses = (None,) * len(obj)
if cls:
args = get_args(cls)
if len(args) == 1:
# E.g. List[int]
subclasses = args * len(obj)
elif len(args) > 1:
# E.g. Tuple[int, str, str]
subclasses = args
if len(subclasses) != len(obj):
msg = ('Not enough generic types ({}) in {}, expected {} to match '
'the iterable of length {}'
.format(len(subclasses), cls, len(obj), len(obj)))
raise SerializationError(msg)
return subclasses
def _do_dump(obj_cls_tuple: Tuple[object, type], *args, **kwargs):
kwargs_ = {**kwargs}
kwargs_['tasks'] = 1
return dump(*obj_cls_tuple, *args, **kwargs_)
def _determine_cls(obj: Iterable, cls: Optional[type]) -> Optional[type]:
cls_ = cls
if not cls and hasattr(obj, '__getitem__') and len(obj) > 0:
obj_with_only_one_elem = obj.__getitem__(slice(0, 1))
cls_ = get_type(obj_with_only_one_elem)
return cls_

View File

@@ -0,0 +1,48 @@
from typing import Optional
from typish import get_args
from jsons import get_serializer
from jsons._common_impl import StateHolder
from jsons._dump_impl import dump
def default_list_serializer(
obj: list,
cls: type = None,
*,
strict: bool = False,
fork_inst: Optional[type] = StateHolder,
**kwargs) -> list:
"""
Serialize the given ``obj`` to a list of serialized objects.
:param obj: the list that is to be serialized.
:param cls: the (subscripted) type of the list.
: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 may be given to the serialization
process.
:return: a list of which all elements are serialized.
"""
if not obj:
return []
kwargs_ = {**kwargs, 'strict': strict}
# The meta kwarg store_cls is filtered out, because an iterable should have
# its own -meta attribute.
kwargs_.pop('_store_cls', None)
inner_type = None
serializer = dump
cls_args = get_args(cls)
if cls_args:
inner_type = cls_args[0]
serializer = get_serializer(inner_type, fork_inst)
elif strict:
inner_type = type(obj[0])
serializer = get_serializer(inner_type, fork_inst)
return [serializer(elem, cls=inner_type, fork_inst=fork_inst, **kwargs_) for elem in obj]

View File

@@ -0,0 +1,334 @@
import inspect
from datetime import datetime, timezone
from inspect import isfunction
from typing import Optional, Callable, Union, MutableSequence, Tuple, Dict
from typish import get_type, get_mro
from jsons import get_serializer, announce_class
from jsons._cache import cached
from jsons._common_impl import get_class_name, META_ATTR, StateHolder
from jsons._compatibility_impl import get_type_hints
from jsons._datetime_impl import to_str
from jsons.classes import JsonSerializable
from jsons.classes.verbosity import Verbosity
from jsons.exceptions import SerializationError
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
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
def _add_dumped_elem(
result: dict,
attr_name: str,
dumped_elem: object,
strip_nulls: bool,
key_transformer: Optional[Callable[[str], str]]):
if not (strip_nulls and dumped_elem is None):
if key_transformer:
attr_name = key_transformer(attr_name)
result[attr_name] = dumped_elem
def _normalize_strip_attr(strip_attr) -> tuple:
# Make sure that strip_attr is always a tuple.
strip_attr = strip_attr or tuple()
if (not isinstance(strip_attr, MutableSequence)
and not isinstance(strip_attr, tuple)):
strip_attr = (strip_attr,)
return strip_attr
@cached
def _get_attributes_from_class(
cls: type,
strip_privates: bool,
strip_properties: bool,
strip_class_variables: bool,
strip_attr: tuple,
strict: bool) -> Dict[str, Optional[type]]:
# Get the attributes that are known in the class.
attributes_and_types = _get_attributes_and_types(cls, strict)
return _filter_attributes(cls, attributes_and_types, strip_privates,
strip_properties, strip_class_variables,
strip_attr)
def _get_attributes_from_object(
obj: object,
strip_privates: bool,
strip_properties: bool,
strip_class_variables: bool,
strip_attr: tuple,
strict: bool) -> Dict[str, Optional[type]]:
# Get the attributes that are known in the object.
cls = obj.__class__
attributes_and_types = _get_attributes_and_types(cls, strict)
attributes = {attr: attributes_and_types.get(attr, None)
for attr in dir(obj)}
return _filter_attributes(cls, attributes, strip_privates,
strip_properties, strip_class_variables,
strip_attr)
@cached
def _get_attributes_and_types(cls: type,
strict: bool) -> Dict[str, Optional[type]]:
if '__slots__' in cls.__dict__:
attributes = {attr: None for attr in cls.__slots__}
elif hasattr(cls, '__annotations__'):
attributes = get_type_hints(cls)
elif strict:
hints = get_type_hints(cls.__init__)
attributes = {k: hints[k] for k in hints if k != 'self'}
else:
attributes = {}
# Add properties and class variables.
props, class_vars = _get_class_props(cls)
for elem in props + class_vars:
attributes[elem] = None
return attributes
def _filter_attributes(
cls: type,
attributes: Dict[str, Optional[type]],
strip_privates: bool,
strip_properties: bool,
strip_class_variables: bool,
strip_attr: tuple) -> Dict[str, Optional[type]]:
# Filter the given attributes with the given preferences.
strip_attr = strip_attr + _ABC_ATTRS
excluded_elems = dir(JsonSerializable)
props, other_cls_vars = _get_class_props(cls)
return {attr: type_ for attr, type_ in attributes.items()
if not attr.startswith('__')
and not (strip_privates and attr.startswith('_'))
and not (strip_properties and attr in props)
and not (strip_class_variables and attr in other_cls_vars)
and attr not in strip_attr
and attr != 'json'
and not inspect.ismethod(getattr(cls, attr, None))
and not isfunction(getattr(cls, attr, None))
and attr not in excluded_elems
and not _is_innerclass(attr, cls)}
@cached
def _get_class_props(cls: type) -> Tuple[list, list]:
props = []
other_cls_vars = []
for n, v in _get_complete_class_dict(cls).items():
list_to_append = props if isinstance(v, property) else other_cls_vars
list_to_append.append(n)
return props, other_cls_vars
def _get_complete_class_dict(cls: type) -> dict:
cls_dict = {}
# Loop reversed so values of sub-classes override those of super-classes.
for cls_or_elder in reversed(get_mro(cls)):
cls_dict.update(cls_or_elder.__dict__)
return cls_dict
def _get_dict_with_meta(
obj: dict,
cls_name: str,
verbose: Verbosity,
fork_inst: type) -> dict:
# This function will add a -meta section to the given obj (provided that
# the given obj has -cls attributes for all children).
if verbose is Verbosity.WITH_NOTHING:
return obj
obj[META_ATTR] = {}
if Verbosity.WITH_CLASS_INFO in verbose:
collection_of_types = {}
_fill_collection_of_types(obj, cls_name, '/', collection_of_types)
collection_of_types['/'] = cls_name
obj[META_ATTR]['classes'] = collection_of_types
if Verbosity.WITH_DUMP_TIME in verbose:
dump_time = to_str(datetime.now(tz=timezone.utc), True, fork_inst)
obj[META_ATTR]['dump_time'] = dump_time
return obj
def _fill_collection_of_types(
obj_: dict,
cls_name_: Optional[str],
prefix: str,
collection_of_types_: dict) -> str:
# This function loops through obj_ to fill collection_of_types_ with the
# class names. All of the -cls attributes are removed in the process.
cls_name_ = _get_class_name_and_strip_cls(cls_name_, obj_)
for attr in obj_:
if attr != META_ATTR and isinstance(obj_[attr], dict):
attr_class = _fill_collection_of_types(obj_[attr],
None,
prefix + attr + '/',
collection_of_types_)
collection_of_types_[prefix + attr] = attr_class
return cls_name_
def _get_class_name_and_strip_cls(cls_name: Optional[str], obj: dict) -> str:
result = cls_name
if not cls_name and '-cls' in obj:
result = obj['-cls']
if '-cls' in obj:
del obj['-cls']
return result
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
@cached
def _is_innerclass(attr: str, cls: type) -> bool:
attr_obj = getattr(cls, attr, None)
return (isinstance(attr_obj, type)
and inspect.getsource(attr_obj) in inspect.getsource(cls))
_ABC_ATTRS = ('_abc_registry', '_abc_cache', '_abc_negative_cache',
'_abc_negative_cache_version', '_abc_impl')

View File

@@ -0,0 +1,15 @@
from pathlib import PurePath
def default_path_serializer(obj: PurePath, **kwargs) -> str:
"""
Serialize a ``pathlib.PurePath`` object to a ``str``, Posix-style.
Posix-style strings are used as they can be used to create ``pathlib.Path``
objects on both Posix and Windows systems, but Windows-style strings can
only be used to create valid ``pathlib.Path`` objects on Windows.
:param obj: the path to serialize.
:param kwargs: not used.
:return: a ``str``.
"""
return obj.as_posix()

View File

@@ -0,0 +1,36 @@
from typing import Optional, Any, NewType
from jsons.exceptions import SerializationError
def default_primitive_serializer(obj: object,
cls: Optional[type] = None,
**kwargs) -> object:
"""
Serialize a primitive; simply return the given ``obj``.
:param obj: the primitive.
:param cls: the type of ``obj``.
:return: ``obj``.
"""
result = obj
cls_ = cls
if _is_newtype(cls):
cls_ = cls.__supertype__
if cls_ and obj is not None and not isinstance(obj, cls_):
try:
result = cls_(obj)
except ValueError as err:
raise SerializationError('Could not cast "{}" into "{}"'
.format(obj, cls_.__name__)) from err
return result
def _is_newtype(cls: Any) -> bool:
try:
# isinstance(cls, NewType) only works as of Python3.10.
result = isinstance(cls, NewType)
except TypeError:
result = hasattr(cls, '__supertype__')
return result

View File

@@ -0,0 +1,15 @@
from datetime import date
from jsons._datetime_impl import to_str, RFC3339_TIME_PATTERN
def default_time_serializer(obj: date, **kwargs) -> str:
"""
Serialize the given time instance to a string. It uses the RFC3339
pattern.
:param obj: the time instance that is to be serialized.
:param kwargs: not used.
:return: ``time`` as an RFC3339 string.
"""
return to_str(obj, False, kwargs['fork_inst'],
RFC3339_TIME_PATTERN)

View File

@@ -0,0 +1,12 @@
from datetime import timedelta
def default_timedelta_serializer(obj: timedelta, **kwargs) -> float:
"""
Serialize the given timedelta instance to a float holding the total
seconds.
:param obj: the timedelta instance that is to be serialized.
:param kwargs: not used.
:return: ``timedelta`` as a float.
"""
return obj.total_seconds()

View File

@@ -0,0 +1,19 @@
from datetime import timezone
from jsons._dump_impl import dump
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
}

View File

@@ -0,0 +1,41 @@
from typing import Union, Optional, Tuple
from typish import get_args
from jsons._compatibility_impl import tuple_with_ellipsis
from jsons._dump_impl import dump
from jsons.serializers.default_iterable import default_iterable_serializer
def default_tuple_serializer(obj: tuple,
cls: Optional[type] = None,
**kwargs) -> Union[list, dict]:
"""
Serialize the given ``obj`` to a list of serialized objects.
:param obj: the tuple that is to be serialized.
:param cls: the type of the ``obj``.
:param kwargs: any keyword arguments that may be given to the serialization
process.
:return: a list of which all elements are serialized.
"""
if hasattr(obj, '_fields'):
return default_namedtuple_serializer(obj, **kwargs)
cls_ = cls
if cls and tuple_with_ellipsis(cls):
cls_ = Tuple[(get_args(cls)[0],) * len(obj)]
return default_iterable_serializer(obj, cls_, **kwargs)
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

View File

@@ -0,0 +1,37 @@
from typing import Union
from jsons._common_impl import get_class_name, NoneType
from jsons._compatibility_impl import get_union_params
from jsons._dump_impl import dump
from jsons.exceptions import JsonsError, SerializationError
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)

View File

@@ -0,0 +1,12 @@
from uuid import UUID
def default_uuid_serializer(obj: UUID, **kwargs) -> str:
"""
Serialize the given obj. By default, it is serialized as specified in RFC 4122.
e.g. '12345678-1234-1234-1234-123456789abc'
:param obj: an instance of an uuid.UUID.
:param kwargs: any keyword arguments.
:return: ``obj`` serialized as a string.
"""
return str(obj)

View File

@@ -0,0 +1,15 @@
from typing import Dict
try:
from zoneinfo import ZoneInfo
def default_zone_info_serializer(obj: ZoneInfo, *_, **__) -> Dict[str, str]:
"""
Serialize a ZoneInfo object.
:return: a serialized ZoneInfo instance.
"""
return {'key': obj.key}
except ImportError:
default_zone_info_serializer = None