1
0
Fork 0
bthlabs-jsonrpc/packages/bthlabs-jsonrpc-core/bthlabs_jsonrpc_core/serializer.py

182 lines
5.0 KiB
Python

# -*- coding: utf-8 -*-
# django-jsonrpc-core | (c) 2022-present Tomek Wójcik | MIT License
import datetime
import decimal
import typing
import uuid
from bthlabs_jsonrpc_core.exceptions import JSONRPCSerializerError
class JSONRPCSerializer:
"""
Serializer for JSONRPC responses.
This class is responsible for making the respones JSON-serializable.
Sequence types are all converted to lists. Dict-like types are all
converted to plain dicts. Simple types (``bool``, ``float``, ``int`` and
``str``) and ``None`` are returned as they are.
Datetime values are converted to strings using the ISO format. ``UUID`` and
``Decimal`` values are explicitly coerced to strings.
For values of other types, the serializer will try to invoke their
``to_rpc()`` method. If that fails, the serializer will raise
:py:exc:`JSONRPCSerializerError`.
Example:
.. code-block:: python
spam = ['eggs', {'spam': False}, Decimal('42.0')]
serializer = JSONRPCSerializer(spam)
print(serializer.data)
Example with ``to_rpc()``:
.. code-block:: python
class Spam:
def to_rpc(self):
return {
'spam': True
}
spam = ['eggs', Spam(), Decimal('42.0')]
serializer = JSONRPCSerializer(spam)
print(serializer.data)
"""
# Datetime types
DATETIME_TYPES = (datetime.date, datetime.datetime, datetime.time)
# Sequence types
SEQUENCE_TYPES = (set,)
# Simple types
SIMPLE_TYPES = (bool, float, int, str)
# Types that can be coerced to string
STRING_COERCIBLE_TYPES = (uuid.UUID, decimal.Decimal)
def __init__(self, data):
self._data = data
def is_simple_value(self, value: typing.Any) -> bool:
"""
Returns ``True`` if *value* is a simple value.
:meta private:
"""
value_type = type(value)
return (
value is None or value_type in self.SIMPLE_TYPES
)
def is_datetime_value(self, value: typing.Any) -> bool:
"""
Returns ``True`` if *value* is a datetime value.
:meta private:
"""
return type(value) in self.DATETIME_TYPES
def is_sequence_value(self, value: typing.Any) -> bool:
"""
Returns ``True`` if *value* is a sequence value.
:meta private:
"""
return any((
isinstance(value, typing.Sequence),
isinstance(value, typing.Generator),
type(value) in self.SEQUENCE_TYPES,
))
def is_dict_value(self, value: typing.Any) -> bool:
"""
Returns ``True`` if *value* is a simple value.
:meta private:
"""
return isinstance(value, typing.Dict)
def is_string_coercible_value(self, value: typing.Any) -> bool:
"""
Returns ``True`` if *value* is a coercible to string.
:meta private:
"""
return type(value) in self.STRING_COERCIBLE_TYPES
def serialize_datetime(self, value: typing.Any) -> typing.Any:
"""
Serializes a datetime value.
:meta private:
"""
return value.isoformat()
def serialize_sequence(self, value: typing.Any) -> typing.Any:
"""
Serializes a sequence value.
:meta private:
"""
return [self.serialize_value(item) for item in value]
def serialize_dict(self, value: typing.Any) -> typing.Any:
"""
Serializes a dict-like value.
:meta private:
"""
return {
key: self.serialize_value(item) for key, item in value.items()
}
def serialize_string_coercible(self, value: typing.Any) -> typing.Any:
"""
Serializes a string-coercible value.
:meta private:
"""
return str(value)
def serialize_value(self, value: typing.Any) -> typing.Any:
"""
Serializes *value* and returns the result.
:meta private:
"""
if isinstance(value, JSONRPCSerializer):
return value.data
elif self.is_simple_value(value):
return value
if self.is_datetime_value(value):
return self.serialize_datetime(value)
elif self.is_sequence_value(value):
return self.serialize_sequence(value)
elif self.is_dict_value(value):
return self.serialize_dict(value)
elif self.is_string_coercible_value(value):
return self.serialize_string_coercible(value)
elif hasattr(value, 'to_rpc'):
return self.serialize_value(value.to_rpc())
else:
raise JSONRPCSerializerError(
'Object of type {type} is not RPC serializable'.format(
type=type(value),
),
)
return value
@property
def data(self) -> typing.Any:
"""The serialized data."""
if not hasattr(self, '_serialized_data'):
self._serialized_data = self.serialize_value(self._data)
return self._serialized_data