import logging
import pickle # noqa: S403
from abc import ABC, abstractmethod
from typing import Any, Optional
logger = logging.getLogger(__name__)
try:
import ujson as json # noqa: I900
except ImportError:
logger.debug("ujson module not found, using json")
import json # type: ignore[no-redef]
try:
import msgpack
except ImportError:
msgpack = None
logger.debug("msgpack not installed, MsgPackSerializer unavailable")
_NOT_SET = object()
class BaseSerializer(ABC):
DEFAULT_ENCODING: Optional[str] = "utf-8"
def __init__(self, *args, encoding=_NOT_SET, **kwargs):
self.encoding = self.DEFAULT_ENCODING if encoding is _NOT_SET else encoding
super().__init__(*args, **kwargs)
# TODO(PY38): Positional-only
@abstractmethod
def dumps(self, value: Any) -> Any:
"""Serialise the value to be stored in the backend."""
# TODO(PY38): Positional-only
@abstractmethod
def loads(self, value: Any) -> Any:
"""Decode the value retrieved from the backend."""
[docs]class NullSerializer(BaseSerializer):
"""
This serializer does nothing. Its only recommended to be used by
:class:`aiocache.SimpleMemoryCache` because for other backends it will
produce incompatible data unless you work only with str types because it
store data as is.
DISCLAIMER: Be careful with mutable types and memory storage. The following
behavior is considered normal (same as ``functools.lru_cache``)::
cache = Cache()
my_list = [1]
await cache.set("key", my_list)
my_list.append(2)
await cache.get("key") # Will return [1, 2]
"""
[docs] def dumps(self, value):
"""
Returns the same value
"""
return value
[docs] def loads(self, value):
"""
Returns the same value
"""
return value
[docs]class StringSerializer(BaseSerializer):
"""
Converts all input values to str. All return values are also str. Be
careful because this means that if you store an ``int(1)``, you will get
back '1'.
The transformation is done by just casting to str in the ``dumps`` method.
If you want to keep python types, use ``PickleSerializer``. ``JsonSerializer``
may also be useful to keep type of symple python types.
"""
[docs] def dumps(self, value):
"""
Serialize the received value casting it to str.
:param value: obj Anything support cast to str
:returns: str
"""
return str(value)
[docs] def loads(self, value):
"""
Returns value back without transformations
"""
return value
[docs]class PickleSerializer(BaseSerializer):
"""
Transform data to bytes using pickle.dumps and pickle.loads to retrieve it back.
"""
DEFAULT_ENCODING = None
def __init__(self, *args, protocol=pickle.DEFAULT_PROTOCOL, **kwargs):
super().__init__(*args, **kwargs)
self.protocol = protocol
[docs] def dumps(self, value):
"""
Serialize the received value using ``pickle.dumps``.
:param value: obj
:returns: bytes
"""
return pickle.dumps(value, protocol=self.protocol)
[docs] def loads(self, value):
"""
Deserialize value using ``pickle.loads``.
:param value: bytes
:returns: obj
"""
if value is None:
return None
return pickle.loads(value) # noqa: S301
[docs]class JsonSerializer(BaseSerializer):
"""
Transform data to json string with json.dumps and json.loads to retrieve it back. Check
https://docs.python.org/3/library/json.html#py-to-json-table for how types are converted.
ujson will be used by default if available. Be careful with differences between built in
json module and ujson:
- ujson dumps supports bytes while json doesn't
- ujson and json outputs may differ sometimes
"""
[docs] def dumps(self, value):
"""
Serialize the received value using ``json.dumps``.
:param value: dict
:returns: str
"""
return json.dumps(value)
[docs] def loads(self, value):
"""
Deserialize value using ``json.loads``.
:param value: str
:returns: output of ``json.loads``.
"""
if value is None:
return None
return json.loads(value)
[docs]class MsgPackSerializer(BaseSerializer):
"""
Transform data to bytes using msgpack.dumps and msgpack.loads to retrieve it back. You need
to have ``msgpack`` installed in order to be able to use this serializer.
:param encoding: str. Can be used to change encoding param for ``msg.loads`` method.
Default is utf-8.
:param use_list: bool. Can be used to change use_list param for ``msgpack.loads`` method.
Default is True.
"""
def __init__(self, *args, use_list=True, **kwargs):
if not msgpack:
raise RuntimeError("msgpack not installed, MsgPackSerializer unavailable")
self.use_list = use_list
super().__init__(*args, **kwargs)
[docs] def dumps(self, value):
"""
Serialize the received value using ``msgpack.dumps``.
:param value: obj
:returns: bytes
"""
return msgpack.dumps(value)
[docs] def loads(self, value):
"""
Deserialize value using ``msgpack.loads``.
:param value: bytes
:returns: obj
"""
raw = False if self.encoding == "utf-8" else True
if value is None:
return None
return msgpack.loads(value, raw=raw, use_list=self.use_list)